-
Notifications
You must be signed in to change notification settings - Fork 14.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Dashboard filter indicators #7908
[Feature] Dashboard filter indicators #7908
Conversation
Needs rebasing (build error fixed in master) |
89184f3
to
2e92ec9
Compare
Codecov Report
@@ Coverage Diff @@
## master #7908 +/- ##
==========================================
+ Coverage 65.57% 65.69% +0.11%
==========================================
Files 469 486 +17
Lines 22496 22804 +308
Branches 2446 2515 +69
==========================================
+ Hits 14752 14980 +228
- Misses 7624 7696 +72
- Partials 120 128 +8
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a bunch of comments, will definitely give another pass too
const { filters } = this.props; | ||
const currentFilteredNames = | ||
filterKey && filters[filterKey] ? Object.keys(filters[filterKey]) : []; | ||
const filter_immune_slices = this.props.dashboardInfo.metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelCase please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
filterKey && filters[filterKey] ? Object.keys(filters[filterKey]) : []; | ||
const filter_immune_slices = this.props.dashboardInfo.metadata | ||
.filter_immune_slices; | ||
const filter_immune_slice_fields = this.props.dashboardInfo.metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelCase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
return; | ||
} | ||
|
||
const filter_immune_slice_fields_names = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelCase
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
@@ -97,4 +106,14 @@ describe('dashboardState actions', () => { | |||
); | |||
}); | |||
}); | |||
|
|||
it('should dispatch removeSlice if a removed slice is a filter_box', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: should be 'should dispatch removeFilter
i think
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes!!
@@ -60,12 +61,26 @@ describe('Dashboard', () => { | |||
return wrapper; | |||
} | |||
|
|||
const overrideFilters = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe just a stylistic preference, but i tend to name constant fixtures like this as OVERRIDE_FILTERS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
const FILTER_COLORS_COUNT = 20; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe note this var in the css file (and vice versa) so that people know where to update it if something changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
/* eslint-disable camelcase */ | ||
import { TIME_RANGE } from '../../visualizations/FilterBox/FilterBox'; | ||
|
||
export default function getFilterConfigsFromFormdata(form_data = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
camelCase throughout please!
|
||
return filter_configs.reduce((result, config) => { | ||
/* eslint-disable no-param-reassign */ | ||
result[config.column] = config.vals; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd prefer to not disable the lint rule here and just make an additional const in this function if needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
export default function isEmptyValue(value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'd imagine there's a lodash function or something that already implements this, any reason why we chose to write our own here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks! Use lodash now.
const TIME_RANGE = '__time_range'; | ||
export const TIME_RANGE = '__time_range'; | ||
export const FILTER_LABELS = { | ||
[TIME_RANGE]: 'Time range', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be wrapped in t()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't translate values in map. the UI component which uses this map will translate the needed value.
ea70f42
to
568ea9c
Compare
ping @etr2460 I rebased onto master, added a few unit tests to increase coverage (you have to click the Codecov link to see updated stats), fixed most of comments. Please take a second look. Thanks. |
568ea9c
to
4f394b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a few more comments!
componentWillReceiveProps(nextProps) { | ||
const { inFocus = false } = nextProps; | ||
if (inFocus) { | ||
this.scrollToView(0); | ||
} | ||
} | ||
|
||
getLocationHash() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need this function anymore right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks. i added this instance method before, because I had difficulty to add unit test, which needs to mock window.location.hash. now i found a better solution here.
|
||
render() { | ||
const { indicators } = this.props; | ||
const tooltipList = indicators.map((indicator, index) => ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personal preference, but i find it easier to read if we just put this map in the return JSX as well. Co-locating all of it makes it easier to see the whole dom structure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
return Array.isArray(values) ? values.join(', ') : values; | ||
} | ||
|
||
export default function FilterIndicatorTooltip({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still not sure if this is needed?
...dashboardFilters, | ||
}; | ||
/* eslint-disable no-param-reassign */ | ||
delete updatedFilters[action.chartId]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
prefer: const { [action.chartId]: deletedItem, ...updatedFilters };
https://github.com/airbnb/javascript/blob/master/README.md#objects--rest-spread
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks! this problem bothered me for a long time. now i knew solution.
*/ | ||
.dashboard-filter-indicators-container { | ||
position: absolute; | ||
z-index: 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is a z index absolutely needed here? Have we ensured that 10 is appropriate compared to all other possible z indexes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
be honest, i don't know how to make ensured 10 is appropriate. Just by manual test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it, we should probably make an issue to manage the z-indexes better at some point. this is fine for now though
overflow: hidden; | ||
display: flex; | ||
background-color: #bababa; | ||
transition: all 0.3s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a big fan of transition all, can we explicitly call out what styles should be transitioned over?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed. only width attribute.
b318ea2
to
f0be2fc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just a couple final nits, but otherwise lgtm! I'm super excited to see this go out!
componentWillReceiveProps(nextProps) { | ||
const { inFocus = false } = nextProps; | ||
if (inFocus) { | ||
this.scrollToView(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't need an argument right? or you can remove the default arg on the function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
return ( | ||
<FilterTooltipWrapper | ||
tooltip={ | ||
<React.Fragment> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using react 16.8 now so this can be changed to <>
and the closing tag can be </>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I search the whole code base and found about 9 places used <React.Fragment>
but 0 place used <>
. Maybe consistency is more important, if this is only a syntax sugar?
* under the License. | ||
*/ | ||
/* eslint-disable no-param-reassign */ | ||
/* eslint-disable camelcase */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
*/ | ||
.dashboard-filter-indicators-container { | ||
position: absolute; | ||
z-index: 10; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it, we should probably make an issue to manage the z-indexes better at some point. this is fine for now though
f0be2fc
to
9ab3e38
Compare
Any updates? Very cool PR.... |
This will probably get merged today! |
YAY!!!!! |
9ab3e38
to
b02b9a9
Compare
CATEGORY
Choose one
SUMMARY
Implementation
filters
in redux state.Before this PR,
filters
state is a sub-state indashboardstate
:In order to support UI features and in-dashboard navigation functionality, I move
dashboardFilter
up to be root-level state in redux store, and it will contain more props about filter value and its layout information:Because
filters
prop is used by a lot of components in dashboard, I added a converter utility functionbuildActiveFilters
, which converts rawdashboardFilters
state into oldfilters
format so that other components can still usefilters
as before, and won't be affected by this big state change.buildActiveFilters
function will be triggered whenever dashboardFilters state is changed, and the converted results will be cached. All other components in dashboard will callgetActiveFilters
to get the cached filters state, and they don't need to run the converter function again and again.In this PR we introduced a new color palette for dashboard filters.
Each filter field is assigned a unique colorKey:
chartId_filter_column_name
. This colorKey is used by bother filter badge and filter indicator. Dashboard will pick a color from filter color palette based on colorKey and build a color map. During render time, each filter and indicators in the chart will lookup color code based on its colorKey. Similar tobuildActiveFilters
function, dashboardFilter state update will trigger re-build color map.Suppose A is root, B,C,D are top level tabs, F, G are charts under tabs, H is nested tab, M is chart under nested tab. For example, when user lands on M, how to navigate user to a filter component in another root level tab B?
Dashboard stores layout data in flattened tree structure: each parent node contains ids of its children, and every UI component (chart, header, row, column, tabs, etc) keeps all the parent component ids from root to itself (see #6945):
From this parents list it's very easy to find out to select which root tab or nested tab to a specific filter.
dashboardState
has adirectPathToChild
prop, which is the current state of dashboard, holding a path to one of the chart (or any UI component). When user clicks on a filter indicator, dashboard will update itsdirectPathToChild
to be target filter's path. Then dashboard will render the correct list of parent tab/tabs, and display filter chart.Filter indicators UI components
/dashboard/container/FilterIndicators.jsx
/dashboard/components/FilterIndicatorsContainer.jsx
: indicators organizer for each chart. Usually one chart will be applicable for one or more filters, but user can set dashboard metadata that a chart might be immune to all filters in the dashboard, or some filter fields. We only display indicators for applicable filters. The logic to show/group/hide applicable filters for each chart is here./dashboard/components/FilterIndicator.jsx
and/dashboard/components/FilterIndicatorGroup.jsx
: little colored indicator component for each filter, if chart having > 5 filters, will group them into a group./dashboard/components/FilterTooltipWrapper.jsx
: the basic usage ofreact-bootstrap Tooltip
components are not enough for our need. We allow user to click on tooltip (especially needed for indicator group) to navigate to actual filter chart. So I added customized wrapper component to trigger/hideOverlay
with some delay./dashboard/components/FilterIndicatorTooltip.jsx
: tooltip component for filter. it showscolumn name
and selected values.![Screen Shot 2019-07-26 at 2 53 04 PM](https://user-images.githubusercontent.com/27990562/61983458-47335a80-afb5-11e9-9118-f28961fc1666.png)
If the filter is applicable but not applied, we will show **Not filtered**. If the filter is not applicable to the chart, we will not show indicator.SCREENSHOTS OR ANIMATED GIF
Page landing:
Interact with filter indicator
Navigate from chart to filter
TEST PLAN
CI and manual test.
ADDITIONAL INFORMATION
REVIEWERS
@williaster @michellethomas @etr2460 @xtinec @mistercrunch @kenchendesign
@betodealmeida @khtruong