Skip to content
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

fix: re-enable table filter #9593

Closed
wants to merge 2 commits into from
Closed

fix: re-enable table filter #9593

wants to merge 2 commits into from

Conversation

ktmud
Copy link
Member

@ktmud ktmud commented Apr 20, 2020

CATEGORY

  • Bug Fix

SUMMARY

"Emit filter events" on table and deck.gl chart has been broken since the introduction of scoped filters. This PR tries to re-enable it by:

  1. Re-enable "emit filter events" in Table chart
  2. Treat charts with table_filter == TRUE as the same as FilterBox in many places.

Must wait for the accompanying superet-ui PR: apache-superset/superset-ui#344

Fixes #8273 #8683 .

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Clicking on non-metric table cells should trigger a filter update:

image

lightcyan is the selected filter. lightorange is the hover state.

Users can edit the scopes of a table chart with filter events in the dashboard's filter scope editor:

image

TEST PLAN

  1. Go to any dashboard with either a FilterBox or table chart with "Emit Filter Events" enabled.
  2. Make sure filters and filter event works, make sure you can still edit the dashboard and filter scope.

ADDITIONAL INFORMATION

REVIEWERS

@graceguo-supercat @kristw @etr2460 @williaster

@ktmud ktmud force-pushed the table-filter branch 5 times, most recently from 22e63fb to 416b620 Compare April 20, 2020 07:55
@codecov-io
Copy link

codecov-io commented Apr 20, 2020

Codecov Report

Merging #9593 into master will decrease coverage by 4.87%.
The diff coverage is 56.41%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #9593      +/-   ##
==========================================
- Coverage   70.49%   65.61%   -4.88%     
==========================================
  Files         574      575       +1     
  Lines       30073    30091      +18     
  Branches     3054     3066      +12     
==========================================
- Hits        21200    19745    -1455     
- Misses       8762    10160    +1398     
- Partials      111      186      +75     
Flag Coverage Δ
#cypress ?
#javascript 58.71% <56.41%> (-0.05%) ⬇️
#python 70.47% <ø> (ø)
Impacted Files Coverage Δ
...frontend/src/dashboard/reducers/getInitialState.js 0.00% <0.00%> (-68.54%) ⬇️
...t-frontend/src/dashboard/actions/dashboardState.js 30.66% <50.00%> (-28.67%) ⬇️
...src/dashboard/util/getFilterConfigsFromFormdata.js 60.00% <53.12%> (-28.47%) ⬇️
...erset-frontend/src/dashboard/util/isFilterChart.js 100.00% <100.00%> (ø)
superset-frontend/src/SqlLab/App.jsx 0.00% <0.00%> (-100.00%) ⬇️
superset-frontend/src/explore/App.jsx 0.00% <0.00%> (-100.00%) ⬇️
superset-frontend/src/SqlLab/index.jsx 0.00% <0.00%> (-100.00%) ⬇️
superset-frontend/src/dashboard/App.jsx 0.00% <0.00%> (-100.00%) ⬇️
superset-frontend/src/explore/index.jsx 0.00% <0.00%> (-100.00%) ⬇️
superset-frontend/src/dashboard/index.jsx 0.00% <0.00%> (-100.00%) ⬇️
... and 139 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2b957a2...37a5fb5. Read the comment docs.

// `all_columns` is from NOT GROUP BY mode (raw records)
const columns = groupby.concat(all_columns);
columns.forEach(column => {
configs.columns[column] = undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would null be better here? seems like that might match the filterbox empty value? to me that's more an explicit "undefined" versus undefined

Copy link
Member Author

@ktmud ktmud Apr 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, null means users specifically choose an empty value for the filter. undefined is more like the "data not yet available" default.

In FilterBox, the defaultValue is also undefined, not null:

Since there is no way to set a default filter for table charts, using undefined to indicate "not set" seems more appropriate to me.

}),
{},
),
};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel everyday in the internet there will someone claim one or another new theory...🤷‍♀️
I saw so many ppl object to his opinion. Did he offer any reason WHY he made claim?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce code is often very difficult to read and plain old for loops or forEach functions are almost always easier to understand.

Programs should favor simplicity and clarity, not succinctness.

This guy works for Google and is in charge of promoting new browser features such as Service Workers.

Copy link
Contributor

@nruhe nruhe Aug 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ktmud Realize this is old, but wanted to add my 2cents. Performance considerations aside, I find reduce to frequently be faster to write and more readable than for loops and forEach. The key reasons being that I can cognitively follow the steps in sequence by knowing the goal is to turn A into Z, which means somewhere along the chain, I must take that step to go from A to C. As soon as that becomes imperative, I lose the ability to follow it sequentially since variables, intermediary steps, etc. must be declared up front. This leads me to have to make assumptions about what the purpose of the code is to understand the context of what I'm looking at. In a similar vein, that's why I typically hate non-curried underscore / lodash, because it reverses the call order and makes you start at the innermost function call.

That being said, the real reason why we write code that way is to have the ability to write it to pause / resume execution at any point in the chain (i.e. generators).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, I find reduce harder to read also because I was not able to follow it sequentially. It kind of places the input and output in a very twisted flow:

input.reduce((intermediateOutput, input) => {
   return intermediateOutput
}, output);

Maybe I just didn't use it often enough, but every time I encounter a reduce function, I always have to think what the output look like and sometimes it could even be difficult to find where the output object was initialized.

For this specific case, the original code looks like this:

    const updatedLabels  = {
      ...configs.labels,
      ...Object.entries(TIME_FILTER_MAP).reduce(
        (map, [key, value]) => ({
          ...map,
          [value]: TIME_FILTER_LABELS[key],
        }),
        {},
      ),
    };
    configs.labels = updatedLabels;

Without reduce, it's just:

    Object.entries(TIME_FILTER_MAP).forEach(([key, column]) => {
      configs.labels[column] = TIME_FILTER_LABELS[key];
    });

Is reduce really faster to write and read?

@ktmud ktmud force-pushed the table-filter branch 5 times, most recently from 92b8f10 to 37a5fb5 Compare April 21, 2020 04:41

/**
* Check if a chart is one of the filter chart types, i.e., FilterBox
* and any charts with `table_filter = TRUE`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even the chart is not a table viz, it can still have table_filter = TRUE? this sounds very problematic. Does that mean any chart can be filter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. That was how table filter was originally implemented. I think it’s probably OK. Tableau also has this feature where you can filter/highlight by categorical values from linked charts.

Copy link

@graceguo-supercat graceguo-supercat Apr 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but in the code you only have getFilterConfigsFromTableChart and getFilterConfigsFromFilterBox right? So only 2 viz types are eligible to work as filter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now. But the design should account for generic cases, which is why I proposed to change “table_filter” to a more generic name. Current implementation also tightly couple viz type with the filters, which kind of goes against our goal of making visualizations more pluggable. But we can deal with that one at a time.

: getFilterConfigsFromFilterBox(form_data);

// if current chart has preselected filters, update it
if (filters) {
Copy link

@graceguo-supercat graceguo-supercat Apr 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function getFilterConfigsFromFormdata, is used when dashboard get loaded in browser, and when user apply a filter. By adding this code block, will user applied filter will always be overwritten by dashboard preselected filters?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I don’t think this is actually in use. It comes from my previous attempt with some larger refactoring, which didn’t work out in the end. Will clean this up.

@graceguo-supercat
Copy link

graceguo-supercat commented Apr 21, 2020

Users can edit the scopes of a table chart with filter events in the dashboard's filter scope editor

I am not sure you tested all filter related use cases. There are some pretty complicated logic for filter_box's and filter's scope:

  • add/remove a filter viz from a dashboard,
  • move a chart in/out of a tab with filter scope,
  • move filter to a different dashboard layout position,
  • filter indicators to show what values are applied and by which filter component.
  • more...

I want to make table filter as simple as possible, otherwise it's really hard to maintain the majority usage. I suggest:

  • Filter scope modal should not cover table filter, table filter should apply to all charts in dashboard (global scope only),
  • table filter should not be saved as default_filters in dashboard metadata
  • dashboard doesn't persist table filter's current state. for example, when you click share dashboard and share a shorten dashboard link, you will lose current table filter's selection.

No matter you want to make table filter fully work as filter_box filter or not, please add some unit tests and Cypress e2e tests for table filters, to show what functions that table filter will support.

@mistercrunch

@ktmud
Copy link
Member Author

ktmud commented Apr 21, 2020

I want to make table filter as simple as possible, otherwise it's really hard to maintain the majority usage.

I agree. Actually tried to do a bigger refactor to simplify some code pieces, but it ended up breaking many of the test cases you mentioned. Current solution is the simplest I could find that didn't break anything.

To be honest, I'm even not sure about the value of this feature. Under which use cases it is irreplaceable by a FilterBox? If the burden of maintaining it outweighs the benefits, I'm in favor of officially deprecating it and focusing on improving the filter box instead.

@ktmud
Copy link
Member Author

ktmud commented Apr 21, 2020

If we are to keep this feature, I'd prefer not to exclude the table filters from scopes configuration.

Currently there is no easy way to propagate the selected filter values to all charts without tapping into activeFilters defined in activeDashboardFilters.js. Separating it from the scopes management would mean to create yet another data structure that manages global filters. This further complicates the code and may confuse the users as in "why can't I specify scopes for my filter table?".

@graceguo-supercat
Copy link

graceguo-supercat commented Apr 21, 2020

I kind of do not want to support full filter functionalities on table viz, because table_filter is an attribute (possibly used by all charts?), and it can be changed at any time. When a filter table become a normal table (or vice versa), how do we keep track and update filter status (and filter scopes) of all its linked dashboards?

@ktmud
Copy link
Member Author

ktmud commented Apr 21, 2020

You do what you do with FilterBox charts. It'd be the same as adding or removing a FilterBox from the dashboard. We just need to make sure it doesn't throw errors when it tries to read/write default_filters or scopes in dashboard metadata.

@graceguo-supercat
Copy link

graceguo-supercat commented Apr 21, 2020

it doesn't throw errors is not good enough. Dashboard will persist some information in its metadata, default_filters, layout, filter scopes., etc into dashboards table. For example, a chart was a filter with filter but then it became non-filter chart...then how about those persisted metadata? For the long term it will cause data quality issue and many potential bugs.

@mistercrunch
Copy link
Member

Awesome! Good to see this feature!

Tagging @bolkedebruin who I know was blocked by this

@ktmud
Copy link
Member Author

ktmud commented Apr 21, 2020

it doesn't throw errors is not good enough. Dashboard will persist some information in its metadata, default_filters, layout, filter scopes., etc into dashboards table. For example, a chart was a filter with filter but then it became non-filter chart...then how about those persisted metadata? For the long term it will cause data quality issue and many potential bugs.

I mean, it's the same as removing a FilterBox from the dashboard. Do we do anything special right now for filter boxes when they are removed from the dashboard? My point is, there is no need for special treatment. If we think outdated metadata would be an issue, then it must be an issue for FilterBox, too.

It doesn't throw errors was referring to the case you described---when a filter chart is removed from dashboard but the dashboard still persists metadata related to it. IMO, as long as the initialization process can safely ignore the outdated metadata when it realizes the related slice ID is not a filter chart, it's OK to leave the small pieces of metadata in persistent storage, just in case users want to change the same slice to a filter chart again.

I'm OK with NOT merging this fix if we think there are too much trouble or a better solution down the line.

@mistercrunch , question for you and @bolkedebruin ,

Under which use cases [a filter table] is irreplaceable by a FilterBox?

It seems there is a significant risk to future maintenance cost if we reintroduce this feature. Kind of feel we should deprecate it for now until we have a better idea on how to make this more robust.

@graceguo-supercat
Copy link

graceguo-supercat commented Apr 21, 2020

  • when remove a filter_box from dashboard: Currently dashboard will detect this change in dashboardFilters reducer, and then it update filter scopes, default filters, etc. when user save dashboard changes, all metadata is updated.

  • when you use table filter in dashboard_1:

  1. dashboard_1 owner set default filters and filter scopes for table filter
  2. after some time, the table filter can be changed by owner or someone else, turn off its filter attribute, so it is a regular table, not a filter.
  3. dashboard_1 still use this table as filter, right? because when dashboard loads filters related metadata, for example, default_filters, this slice's id is a filter key.

My suggestion is to enable table (or chart) filter, for backward compatible, but its state should not be persist into dashboard metadata. For every page load, during initialization dashboard can add these temporary filters into dashboardFilters state. when user trigger filter events from chart, dashboardFilters will handle state changes, so that at this dashboard visit, these chart filters can work as filter. The difference from filter_box filter is:

  • these chart filters have global scope, (if you want to have a filter with respect to only some tabs/charts, you should create a filter_box)
  • when save dashboard, chart filters' state should not be saved into metadata.

@ktmud
Copy link
Member Author

ktmud commented Apr 21, 2020

If table_filter is turned off later, the chart will not be used as a filter, even if there is some lingering information about it in dashboard metadata. The initialization process should be able to just ignore that. (If it doesn't, then it needs a fix. I can add test cases to make sure it does.)

The user interactions makes sense. I agree some users may find it simpler if table filters are always global. Maybe I missed something, but implementation wise, this could actually make things more complicated. We'd need to check chart types when saving dashboard metadata, which doesn't seem easy to pass around for dashboardFilters.

Given that all charts will have a global scope by default, I wonder how much benefits it adds to make such a distinction.

@ktmud ktmud marked this pull request as draft April 21, 2020 23:07
@graceguo-supercat
Copy link

graceguo-supercat commented Apr 22, 2020

@mistercrunch @bolkedebruin Dashboard's filter_box filter had added a lot new features during last year (#8404, #7908, #8166, #7642). Want to hear your feedback about what functionalities should table filter offer?

@ktmud ktmud changed the title Re-enable table filter, fix #8273 #8683 fix: re-enable table filter Aug 7, 2020
@stale
Copy link

stale bot commented Oct 10, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. For admin, please label this issue .pinned to prevent stale bot from closing the issue.

@stale stale bot added the inactive Inactive for >= 30 days label Oct 10, 2020
@kkalyan
Copy link
Contributor

kkalyan commented Oct 14, 2020

can we merge this?

@stale stale bot removed the inactive Inactive for >= 30 days label Oct 14, 2020
@stale
Copy link

stale bot commented Dec 25, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. For admin, please label this issue .pinned to prevent stale bot from closing the issue.

@stale stale bot added the inactive Inactive for >= 30 days label Dec 25, 2020
@ktmud
Copy link
Member Author

ktmud commented Jan 6, 2021

Supersedes by #11814 and its followups.

@ktmud ktmud closed this Jan 6, 2021
@stale stale bot removed the inactive Inactive for >= 30 days label Jan 6, 2021
@nikoszagk
Copy link

Hi to everyone! I'm new in Superset and I want to use this feature. My question is simple. Where should I put this
"table_filter == TRUE". In which path? Thanks

@villebro
Copy link
Member

@nikoszagk this feature is not yet available, but is being actively worked on right now. We expect it to become available fairly soon (=within a few months)

@villebro
Copy link
Member

@nikoszagk here's an example of current work going on in this area: #13178

@nikoszagk
Copy link

nikoszagk commented Feb 17, 2021

Oh, this is a useful function and it would be great. Although, is there any chart table of that, I can click on specific data and filter some other charts?

@junlincc
Copy link
Member

@nikolagigic you can checkout progress in our public project board https://github.com/apache/superset/projects/27

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add checkbox beside each row of "table" Table filter not working
10 participants