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

[NEP-12629] Ability to use "OR" between filters (+ filter groups) #108

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from

Conversation

footnoise
Copy link

@footnoise footnoise commented Jan 17, 2022

Original ticket: https://jobready.atlassian.net/browse/NEP-12629

Working on new version which allows using OR and AND operators with filters.

image

So, before we had registered filters in a hash and it was represented in FilterBarStore like that:

{
  "some_filter_key1": {
    "uid" : "some_filter_key1",
    "value" : "",
    "name" : "Some Filter Key 1",
    ...
  }, {
    "some_filter_key2" : {
    "uid" : "some_filter_key2",
    "value" : "",
    "name" : "Some Filter Key 2",
    ...
  }
}

and if a User selected some filters, for example with the key some_filter_key1 we needed mark it somehow and don't allow user to select that filter again (it should be removed from dropdown) and to make it we should call method this.context.filterBarStore.getDisabled() and pass it as disabledFilters property into component.

The new logic works in completely different way, we still have original registered hash of filters (as I described above) but data and values we have in the same FilterBarStore but in activeFilters array. In this case I use groupKey and inputKey properties to manage values data. It's a double array, which has groupKey as ID of the first array and inputKey as ID for the second array.

So, that means we should hide filters from dropdown and we don't need to set property disabledFilters anymore.

@footnoise footnoise force-pushed the feature/NEP-12629 branch 3 times, most recently from bd08153 to 37b8d05 Compare January 20, 2022 22:44
@footnoise footnoise marked this pull request as ready for review January 24, 2022 22:22
Copy link
Member

@SyntheticDave SyntheticDave left a comment

Choose a reason for hiding this comment

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

Amazing work, @footnoise!
I've left some questions here mostly for my own understanding of the code. I primarily want to make sure:

  • Existing saved searches, quick filters, and links to filtered index pages (e.g. from reports) still work as previously (if I've read the code right, this should be the case)
  • We use inline styles as little as possible, so anything can be overwritten at the application level

bower.json Outdated
@@ -1,7 +1,7 @@
{
"name": "react-filterbar",
"homepage": "https://github.com/jobready/react-filterbar",
"version": "1.7.1",
"version": "2.1",
Copy link
Member

Choose a reason for hiding this comment

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

Odd that this was out of sync with the package file.

Copy link
Member

Choose a reason for hiding this comment

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

Would this be better represented by a major upgrade 3.0 since there may not exist backward compatibility with previous design?

@@ -0,0 +1,29 @@
React Components Structure
Copy link
Member

Choose a reason for hiding this comment

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

Good to see this here ✨

Comment on lines -21 to -23
<FilterList
disabledFilters={this.context.filterBarStore.getDisabled()}
/>
Copy link
Member

Choose a reason for hiding this comment

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

What was the purpose of this previously?

Copy link
Author

@footnoise footnoise Jan 31, 2022

Choose a reason for hiding this comment

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

Yeah @SyntheticDave sorry, potentially I should put that architectural changes in PR description, because it's a huge changes. Definitely will do it before moving PR into Release stage.

So, before we had registered filters in a hash and it was represented in FilterBarStore like that:

{
  "some_filter_key1": {
    "uid" : "some_filter_key1",
    "value" : "",
    "name" : "Some Filter Key 1",
    ...
  }, {
    "some_filter_key2" : {
    "uid" : "some_filter_key2",
    "value" : "",
    "name" : "Some Filter Key 2",
    ...
  }
}

and if a User selected some filters, for example with the key some_filter_key1 we needed mark it somehow and don't allow user to select that filter again (it should be removed from dropdown) and to make it we should call method this.context.filterBarStore.getDisabled() and pass it as disabledFilters property into component.

The new logic works in completely different way, we still have original registered hash of filters (as I described above) but data and values we have in the same FilterBarStore but in activeFilters array. In this case I use groupKey and inputKey properties to manage values data. It's a double array, which has groupKey as ID of the first array and inputKey as ID for the second array.

So, that means we should hide filters from dropdown and we don't need to set property disabledFilters anymore.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the explanation. I definitely like the change, but you're right that it's a big one.

Comment on lines 68 to 73
filters.push(
(<FilterGroup
key={ idx }
groupKey={ idx }
filters={ groupFilters }
/>)
Copy link
Member

Choose a reason for hiding this comment

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

So does this mean if there's no group specified, all filters get automatically added to a new first group?
If so, it seems like existing saved searches and existing links to filters will still work correctly? Would be amazing if so.

Copy link
Author

Choose a reason for hiding this comment

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

Well, yeah, that's a good point about SavedSearches. My thinking was that SavedSearches should be presented as is with OLD format (w/o groups) and should be converted to NEW format (w/ groups) when new react component loads it the first time and SavedSearch should be saved in NEW format after if user decides to save it.

Copy link
Member

Choose a reason for hiding this comment

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

We'll definitely want to make sure we don't break existing saved searches or links in the application that direct to filtered index pages (e.g. reports often link to an index page with the same filters as is used by that cell in the report), so we'll need to make sure this is backwards compatible with those.


if (filters.length === 0) {
filters = (<div>No Filters Enabled!</div>);
filters.push((
<div style={ { marginTop: 'auto', marginBottom: 'auto', padding: '10px'} }>
Copy link
Member

Choose a reason for hiding this comment

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

There's a bunch of inline styling here. Is it instead worth creating classes for this so they can be overridden at the application level if needed?

Copy link
Author

Choose a reason for hiding this comment

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

Yeah, currently thinking how to make it better and replace inline styles with reusable css.

Copy link
Member

Choose a reason for hiding this comment

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

Don't stress too much about it. If it's too big a job to do so, I'd create a tech debt ticket to add that in later, as inline styles should be okay in the short term.

@@ -24,7 +24,7 @@ export class DateInput extends React.Component {
}

onBlur() {
this.context.filterBarActor.updateFilter(this.props.filterUid, "value", this.state.value);
this.context.filterBarActor.updateFilter(this.props.groupKey, this.props.inputKey, this.state.value);
Copy link
Member

Choose a reason for hiding this comment

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

So inputKey used to just be "value"?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, groupKey and inputKey here are just IDs where exactly values should be set. Object filterBarActor has activeFIlters array with filters values which can be presented like that:

activeFIlters = [
   ["value1", "value2"],
   ["value3", "value4"]
]

and if we need to update value for example for filter2 ("value2" is located in 1st group) we should pass groupKey as 0 (first index in array) and inputKey as 1 (second index in array)

@@ -72,6 +72,7 @@ export class LazyMultiSelectInput extends React.Component {
} else {
filter.value = event.target.value.split(",");
}
this.context.filterBarActor.updateFilter(this.props.groupKey, this.props.inputKey, filter.value);
Copy link
Member

Choose a reason for hiding this comment

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

Do you know why this was not needed for this filter type previously?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, because react-component and filters had different architecture & structure before (see my first comment above) but now we need to set selected data to activeFilters array.

for (var filterUid in enabledFilters) {
this.disableFilter(filterUid);
}
this.activeFilters = []
Copy link
Member

Choose a reason for hiding this comment

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

So we don't need to call disable filter on each individually now?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, because architecture and data-structure changed.

}

updateFilter(groupKey, inputKey, value) {
//this.deactivateQuickFiltersBasedOnFilterValue(filterUid, propValue, this.activeQuickFilters());
Copy link
Member

Choose a reason for hiding this comment

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

Should this be removed?

Copy link
Author

Choose a reason for hiding this comment

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

Oh yes, definitely!

this.activeFilters[groupKey][inputKey].value = value;
}

addGroupFilter(groupKey, filterUid) {
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned elsewhere, changing the order of these params would allow you to pass groupKey optionally, then just check for undefined.

Copy link
Author

Choose a reason for hiding this comment

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

Yep, that's a good point! I will follow your suggestion 👌

Copy link
Member

@msxavi msxavi left a comment

Choose a reason for hiding this comment

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

This is massive and neat. Great effort @footnoise !

bower.json Outdated
@@ -1,7 +1,7 @@
{
"name": "react-filterbar",
"homepage": "https://github.com/jobready/react-filterbar",
"version": "1.7.1",
"version": "2.1",
Copy link
Member

Choose a reason for hiding this comment

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

Would this be better represented by a major upgrade 3.0 since there may not exist backward compatibility with previous design?

package.json Outdated
@@ -1,6 +1,6 @@
{
"name": "react-filterbar",
"version": "2.0",
"version": "2.1",
Copy link
Member

Choose a reason for hiding this comment

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

As mentioned above, major upgrade perhaps?

Copy link
Author

Choose a reason for hiding this comment

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

@msxavi I'm happy with any version actually and if you think 3.0 better reflects current changes and improvements we can change it 👌

this.filterBarStore.enableQuickFilter(quickFilterName, blockName);
this.enableFilter(filterName, value);
Copy link
Member

Choose a reason for hiding this comment

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

Do you still reference enableFilter() in this file?

Comment on lines +27 to +31
if (this.props.onBlur) {
this.props.onBlur();
} else {
this.context.filterBarActor.updateFilter(this.props.groupKey, this.props.inputKey, this.state.value);
}
Copy link
Member

Choose a reason for hiding this comment

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

I didn't quite follow this change. Why filter types are being affected by groups? Is there any reason where a Filter needs to know the group it belongs to?

Copy link
Author

@footnoise footnoise Feb 7, 2022

Choose a reason for hiding this comment

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

@msxavi yes it's a bit tricky and most complicated part of the code.
As I mentioned in PR descripute now component has new data strucure it two-sized array activeFilters in FilterBarStore.
It can be presented like this:

this.activeFilters = [
  [Filter1, Filter2], // <- Group1
  [Filter3], // <- Group2
  [Filter4, Filter5] // <- Group3
]

As you can see the external array presents groups and interal array presents filters inside each group.
Groups/Filters rendering based on that activeFitlers variable and to identify correct pathway to manage data-values we heed groupKey which represents group index in external array and we need inputKey which represents filter index in internal array. When we change values in filter component we should update data in Store and set updated values in activeFilters and specially in this case we should use groupKey and inputKey like this:

this.activeFilters[groupKey][inputKey] = newFilterValue;

@footnoise footnoise force-pushed the feature/NEP-12629 branch 2 times, most recently from 106b84b to 2b9579b Compare February 20, 2022 14:02
Copy link

@thoran thoran left a comment

Choose a reason for hiding this comment

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

I'm not Mr. React exactly, but apart from one file which Github refused to show me it looks fine.

Copy link

@thoran thoran left a comment

Choose a reason for hiding this comment

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

Much tidier. The comments are generally inconsequential.

filtersArr = filters;
} else {
filtersArr = Object.keys(filters).map(function (name) {
if (!Array.isArray(savedSearchFilters)) {
Copy link

Choose a reason for hiding this comment

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

For instance, why is this better than say if (savedSearchFilters instanceof Array)?

return {
uid: name
};
});
}

return new _FilterVerificator.FilterVerificator(this.filterBarStore.getFilters(), filtersArr).verify();
if (!Array.isArray(filters[0])) {
Copy link

Choose a reason for hiding this comment

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

Is this overly defensive?

return filters;
}
}, {
key: "verifySavedFilters",
Copy link

Choose a reason for hiding this comment

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

I see. You've moved the filter 'grooming' to a new method.

_iterator.e(err);
} finally {
_iterator.f();
if (Array.isArray(activeFilters[0])) {
Copy link

Choose a reason for hiding this comment

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

Might be slightly easier on the eye if it was if (!Array.isArray(activeFilters[0])) { then it would ...parseQueryVersion1... then ...parseQueryVersion2.

field, type, value = nil

if needle.is_a? Array
# Case 1: Filters with new format
Copy link

Choose a reason for hiding this comment

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

I like that these are sign-posted.

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

Successfully merging this pull request may close these issues.

6 participants