-
Notifications
You must be signed in to change notification settings - Fork 8.2k
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
Nested input controls #16407
Nested input controls #16407
Conversation
faf78f1
to
095fec6
Compare
💔 Build Failed |
@gchaps I need some help with better naming for the field |
Nathan and I looked at different wording for parent (one being nest) and feel that parent/child terminology works best in this case. Our suggestion is to modify the wording to "Parent control." Here is the recommendation: Parent control Options are based on the value of parent control. Disabled if parent is not set. |
💔 Build Failed |
💔 Build Failed |
💚 Build Succeeded |
this.disable('Control has not been initialized'); | ||
} | ||
|
||
async fetch() { |
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 throwing an Error here because we require the subclass to define fetch
, can we reflect that in the error message?
* | ||
* @param ancestors {array of Controls} | ||
*/ | ||
setAncestors(ancestors) { |
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.
The RangeControl
also extends from Control
, but it doesn't do anything with ancestors, why are we adding all of these 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.
I wanted to make the ancestor logic as generic as possible so I put it into the Control object. Maybe Range controls will be nestable in a future release.
export function getParentCandidates(controlParamsList, controlId, lineageMap) { | ||
return controlParamsList.filter((controlParams) => { | ||
// Ignore controls that do not have index pattern and field set | ||
if (!controlParams.indexPattern || !controlParams.fieldName) { |
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 this ignoring controlParams that haven't been "completed"? We have this same check elsewhere, can we generalize it?
const lineage = [rootControlParams.id]; | ||
const getLineage = (controlParams) => { | ||
if (_.has(controlParams, 'parent') && controlParams.parent !== '' && !lineage.includes(controlParams.parent)) { | ||
lineage.push(controlParams.parent); |
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.
If we stored the actual reference to the parent, could we skip the getControl
in the VisController?
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.
No. There are two sets of state.
- control parameters - these are stored on the visualization saved object
- control object - this just lives in heap and contains the current display state
getLineageMap only deals with control parameters
}, false); | ||
} | ||
|
||
getAncestorSignature() { |
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.
Why are we using string concatenation to determine object equality as opposed to using _.isEqual
?
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 needed some way to store a hash of the last query. Saving the values seemed like an easy way to create a hash that is easy to read and see what has changed. Also, the control objects contain lots of other state that I don't care about. Comparing objects would detect other changes that are not telling me that only the value of the control has changed.
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.
Couldn't we just skip the .join
then and compare the array of ancestor values?
} | ||
|
||
getAncestorSearchSource() { | ||
let previousAncestorSearchSource; |
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.
Why are we making all of the search sources inherit from their parent/grandparents/etc.? Don't we just want to be using the parent's value as the filter? That doesn't seem like it'd require walking up the parent hierarchy more than your direct parent.
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 need the entire ancestry chain to ensure nested controls only contain options for the entire chain of selected values.
For example control1 = state, control2 = city, control3 = street name. There is a Rome Georgia and a Rome Ohio. If you only filtered by the parent control then your street name control would get results for all cities with the name Rome. But if you filter by the entire ancestry chain then the street name control would only get results where the State is Georgia and the City is Rome.
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.
Gotcha, that makes sense why we'd need to add the filters for all ancestors, but I'm still not following why we need the search source inheritance.
if (this.hasUnsetAncestor()) { | ||
this.disable(`Disabled until '${this.ancestors[0].label}' is set.`); | ||
// Remove any existing filters for control. | ||
this.filterManager.findFilters().forEach((existingFilter) => { |
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.
If you add a parent and child filters, pin both of them and then remove the parent filter via the input control, it's removing the pinned filter.
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.
That is the intended behavior. The filter pill is owned by the Control Panel. When the parent is unset, then the child state can only be unknown since there is no way of getting a valid options list
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.
} | ||
|
||
const ancestorSignature = this.getAncestorSignature(); | ||
if (ancestorSignature === this.lastAncestorSignature) { |
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.
Even when it's using the timefilter value and time has changed?
💔 Build Failed |
jenkins, test this |
💚 Build Succeeded |
💚 Build Succeeded |
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.
Centralizing the searchSource logic made this much easier for me to follow.
The only other outstanding questions I had were in regard to the way that we're short-circuiting when the ancestor's signature hasn't changed.
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.
LGTM
💚 Build Succeeded |
}); | ||
} | ||
searchSource.filter(() => { | ||
const activeFilters = [].concat(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.
I think we try to use ES2016 syntax for that kind of things: const activeFilters = [...filters];
…cestors have changed
3404b62
to
1ffd53b
Compare
💚 Build Succeeded |
* add input to select parent control * move lineage logic into seperate file for easier testing * move parent candidate logic into seperate file * decouple control factory and control initialization * disable control if parent not set * inherite search source from ancestors to have them filter values * call fetch when query filter is updated for filter value changes * delete filters when control has ancestor without value * avoid fetching option list when ancestors have not changed * add functional tests for nested controls * remove unneeded file * fix jest tests, add another test case to functional tests, update 'Parent' field copy * add more jest test coverage * fix useTimeFilter functionallity * add better error message when fetch is not defined by subclass * pass filters array instead of search source hierarchy * only modify queryFilter when filters are submitted * compare value array instead of concatinated string to determine if ancestors have changed * use ES2016 syntax for array concatination
* add input to select parent control * move lineage logic into seperate file for easier testing * move parent candidate logic into seperate file * decouple control factory and control initialization * disable control if parent not set * inherite search source from ancestors to have them filter values * call fetch when query filter is updated for filter value changes * delete filters when control has ancestor without value * avoid fetching option list when ancestors have not changed * add functional tests for nested controls * remove unneeded file * fix jest tests, add another test case to functional tests, update 'Parent' field copy * add more jest test coverage * fix useTimeFilter functionallity * add better error message when fetch is not defined by subclass * pass filters array instead of search source hierarchy * only modify queryFilter when filters are submitted * compare value array instead of concatinated string to determine if ancestors have changed * use ES2016 syntax for array concatination
This PR adds the ability to nest dropdown controls. A nested control is only enabled when all ancestor controls have values. The selectable options of a nested control are filtered by the values of all ancestor controls.