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

[ML] Add query bar to Anomaly Explorer for filtering of anomaly results for one or more influencers #31763

Conversation

alvarezmelissa87
Copy link
Contributor

@alvarezmelissa87 alvarezmelissa87 commented Feb 21, 2019

Summary

Related issue: #18262

Adds a query bar component to the Anomaly Explorer which allows filtering of anomaly results for one or more influencers. The query bar autocomplete functionality suggests influencer field names and values based on the selected job's influencers.

image

image

image

  • Add autocomplete filter bar component
  • Placeholder text is example of actual fieldName/fieldValue in given job
  • Placeholder text is updates on job selection change
  • Filter on a single influencer field name and value
  • Ability to filter on more than one influencer with an OR operator
  • Ability to filter on more than one influencer with an AND operator
  • Ability to specify wildcards in the filter value e.g. region: us-*
  • When filter active Overall swim lane is masked (unfiltered) label is added and tooltip still displays
  • When filter active Job ID swim lane is masked with clarifying label and tooltip still displays
  • Jump to View by that’s relevant to active filter
  • View by dropdown only contains relevant fields
  • Filter persistent on refresh/via url for copy/past
  • Retain filter when multiple jobs selected
  • All query bar components tested

Follow up PR:

  • Ability to add filter via icon in Anomalies table
  • Ability to remove filter via icon in Anomalies table
  • Add a filter from the Top Influencers list
  • Remove a filter from the Top Influencers list
  • Do not retain filter if job switched and filter no longer relevant
  • console warning for click outside
  • Overall swimlane click should retain filter in view by swimlane update
  • Remove warning icon from no influencers found message

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

- [ ] This was checked for breaking API changes and was labeled appropriately
- [ ] This includes a feature addition or change that requires a release note and was labeled appropriately

@elasticmachine
Copy link
Contributor

Pinging @elastic/ml-ui

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@alvarezmelissa87 alvarezmelissa87 force-pushed the ml-anomaly-explorer-autocomplete-filter branch from ba2f052 to e882a70 Compare February 26, 2019 00:11
@elasticmachine
Copy link
Contributor

💔 Build Failed

@alvarezmelissa87 alvarezmelissa87 changed the title WIP: [ML] Add query bar to Anomaly Explorer for filtering of anomaly results for one or more influencers [ML] Add query bar to Anomaly Explorer for filtering of anomaly results for one or more influencers Feb 26, 2019
@elasticmachine
Copy link
Contributor

💔 Build Failed

Copy link
Contributor

@walterra walterra left a comment

Choose a reason for hiding this comment

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

Overall this is great! Did a first round of review and added a few questions.

x-pack/plugins/ml/public/explorer/explorer.js Outdated Show resolved Hide resolved
x-pack/plugins/ml/public/explorer/explorer.js Outdated Show resolved Hide resolved
x-pack/plugins/ml/public/explorer/explorer.js Show resolved Hide resolved
x-pack/plugins/ml/public/explorer/explorer.js Outdated Show resolved Hide resolved
.html(label => {
const showFilterContext = filterActive === true && label === 'Overall';
if (showFilterContext) {
return `${label} (unfiltered)`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need to use mlEscape here? Like

  return `${mlEscape(label)} (unfiltered)`;

Additionally, "(unfiltered)" needs to become a translated message.

Copy link
Member

Choose a reason for hiding this comment

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

yes, i think so

Copy link
Member

Choose a reason for hiding this comment

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

This conversation was marked as resolved, but the suggested change hasn't been made.
is there a reason why we don't need to use mlEscape here?

Copy link
Contributor Author

@alvarezmelissa87 alvarezmelissa87 Feb 28, 2019

Choose a reason for hiding this comment

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

My mistake - fixed with: a861b7d

const wrapper = shallow(<ClickOutside onClickOutside={() => {}} />);
expect(wrapper).toMatchSnapshot();
});

Copy link
Contributor

Choose a reason for hiding this comment

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

Nit suggestion: Would be great to have a test here that tests the actual click event.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@jgowdyelastic
Copy link
Member

I'm seeing this error in the browser's console when anomaly explorer loads:
image

x-pack/plugins/ml/public/explorer/explorer.js Outdated Show resolved Hide resolved
@@ -666,14 +715,21 @@ export const Explorer = injectI18n(injectObservablesAsProps(
{ viewByLoadedForTimeFormatted: formatHumanReadableDateTime(timerange.earliestMs) }
);
} else {
let tempInfluencersFilterQuery = influencersFilterQuery;
Copy link
Member

Choose a reason for hiding this comment

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

nit, this could be const tempInfluencersFilterQuery = (maskAll === true) ? undefined : influencersFilterQuery

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in: a861b7d

if (influencersFilterQuery.match_all && Object.keys(influencersFilterQuery.match_all).length === 0) {
this.props.appStateHandler(APP_STATE_ACTION.CLEAR_INFLUENCER_FILTER_SETTINGS);
const stateUpdate = {
...{
Copy link
Member

Choose a reason for hiding this comment

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

this spread operator isn't necessary

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 Fixed here: a861b7d

.html(label => {
const showFilterContext = filterActive === true && label === 'Overall';
if (showFilterContext) {
return `${label} (unfiltered)`;
Copy link
Member

Choose a reason for hiding this comment

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

This conversation was marked as resolved, but the suggested change hasn't been made.
is there a reason why we don't need to use mlEscape here?

return new Promise((resolve) => {
const influencers = [];
if (noInfluencersConfigured !== true) {
ml.getMultipleJobs(
Copy link
Member

Choose a reason for hiding this comment

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

mlJobService already has all the jobs loaded from the server so doesn't need to request them again.
the function selectedJobsHaveInfluencers (in this file) uses this list and so a new function called getInfluencers could be added here which is very similar to selectedJobsHaveInfluencers.
This would remove the need for getMultipleJobs in the ml lib.

Also, noInfluencersConfigured is already based on the results of selectedJobsHaveInfluencers and so isn't needed at all here or in getIndexPattern

IMO, an even better change would be to remove selectedJobsHaveInfluencers entirely from this file and add getInfluencers to mlJobService.
the places where selectedJobsHaveInfluencers is set could then be based on mlJobService.getInfluencers(selectedJobs).length > 0

Copy link
Contributor Author

@alvarezmelissa87 alvarezmelissa87 Feb 28, 2019

Choose a reason for hiding this comment

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

Really great thoughts here, @jgowdyelastic. I think removing getMultipleJobs is the right call here since I can essentially access the already loaded jobs in this file via the mlJobService as you mentioned.

I've gone ahead and created a getInfluencers function in this file (explorer_utils.js) that is similar to selectedJobsHaveInfluencers but returns an array of influencer name strings. This way I can use it similarly to how you mentioned - by grabbing all the influencers I need in getIndexPattern and also using noInfluencersConfigured: (getInfluencers(selectedJobs).length === 0) in lieu of selectedJobsHaveInfluencers

Since that's the only context selectedJobsHaveInfluencers is being used (and only in explorer.js) I've removed that function since it's no longer needed. This solution also means I can remove loadInfluencerFields from explorer_utils. Let me know what you think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated in: a861b7d

this.setState({ suggestions, isLoadingSuggestions: false });
} catch (e) {
console.error('Error while fetching suggestions', e);
this.setState({ isLoadingSuggestions: false, error: (e.message ? e.message : 'Error while fetching suggestions') });
Copy link
Member

Choose a reason for hiding this comment

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

should the errors displayed in the callout be translated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call - updated with localized default error messages. 👍

import { uniqueId } from 'lodash';
import { FilterBar } from './filter_bar';
import { EuiCallOut } from '@elastic/eui';
import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; // luceneStringToDsl
Copy link
Member

Choose a reason for hiding this comment

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

accidentally left in comment?

Copy link
Contributor Author

@alvarezmelissa87 alvarezmelissa87 Feb 28, 2019

Choose a reason for hiding this comment

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

Yep - missed it! Removed in: a861b7d

try {
const ast = fromKueryExpression(inputValue);
const isAndOperator = (ast.function === 'and');
const query = convertKueryToEsQuery(inputValue, indexPattern);
Copy link
Member

@jgowdyelastic jgowdyelastic Feb 27, 2019

Choose a reason for hiding this comment

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

kibana's own buildEsQuery function could be used here i believe:

const q = {
  query: inputValue,
  language: 'kuery',
};
const query = buildEsQuery(indexPattern, [q]);

also, indexPattern is undefined here as it should come from props not state, this made me realise that it's not needed at all and so null or undefined could be passed to convertKueryToEsQuery or buildEsQuery (if you decide to use it).
e.g.
const query = buildEsQuery(null, [q]);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've decided to keep as is since this conversion function as been used in APM as well so I think it's best to keep things consistent.

}

const suggestions = this.props.suggestions.map((suggestion, index) => {
const key = suggestion + '_' + index;
Copy link
Member

Choose a reason for hiding this comment

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

nit, a template literal could be used here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! 😄

}

render() {
if (!this.props.show || isEmpty(this.props.suggestions)) {
Copy link
Member

Choose a reason for hiding this comment

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

nit, this.props.suggestions.length === 0 could be used instead here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call - updated 👍

Copy link
Contributor

@cchaos cchaos left a comment

Choose a reason for hiding this comment

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

Thx for the SASS change

x-pack/plugins/ml/public/services/results_service.js Outdated Show resolved Hide resolved
@@ -27,6 +27,12 @@ export const ml = {
});
},

getMultipleJobs(jobIds) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like this new function might not be needed if https://github.com/elastic/kibana/pull/31763/files#r260769619 can be implemented.

Copy link
Contributor Author

@alvarezmelissa87 alvarezmelissa87 Feb 28, 2019

Choose a reason for hiding this comment

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

Removed in a861b7d

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@alvarezmelissa87 alvarezmelissa87 requested a review from a team as a code owner March 1, 2019 14:56
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@peteharverson peteharverson left a comment

Choose a reason for hiding this comment

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

Two quick thoughts:

  • the kibana query language is now being referred to as 'KQL' through the UI, so I wonder if the folder and filenames should be renamed at this stage from e.g. kuery_filter_bar to kql_filter_bar.
  • is the query bar component generic enough to be used outside the Anomaly Explorer view? If so, it might be better to move it to the x-pack/plugins/ml/public/components folder. In the future, it would be great for example to use this in the Data Visualizer view.

@alvarezmelissa87 alvarezmelissa87 force-pushed the ml-anomaly-explorer-autocomplete-filter branch from 2e091d6 to a83bfc8 Compare March 4, 2019 16:22
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@peteharverson peteharverson left a comment

Choose a reason for hiding this comment

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

Tested latest changes and LGTM. Happy for the additional work to go into follow-up PRs.

@alvarezmelissa87 alvarezmelissa87 merged commit 576c90a into elastic:master Mar 5, 2019
alvarezmelissa87 added a commit to alvarezmelissa87/kibana that referenced this pull request Mar 5, 2019
…ts for one or more influencers (elastic#31763)

* wip: add autocomplete kueryFilterBar.update endpoints with query.

* Create indexPattern on job change. Value is autocompleted

* Mask unrelated swimlanes on filter apply

* Only show relevant viewBy swimlane

* reload viewBy swimlane when view By change is relevant to filter

* handle various query types when building filteredFields list

* remove deprecated code from old filter bar

* Show error callout when invalid filter syntax

* remove dependency on deprecated import var

* remove all left over apm dependencies

* persist filter on refresh

* Create initial filter bar placeholder dynamically

* add description text when filter active

* switch to relevant viewBy fieldname on filter

* recalculate placeholder on job change

* Create tests for all components

* View by dropdown only contains relevant fields for filter

* fix localization message noInfluencers component

* Move indexPattern to state and viewBy option filtering to utils

* remove unnecessary setState when setting viewBy for filter

* Use preloaded jobs from mlJobService to get influencers

* Only filter viewByOptions if valid field

* use kql and move to components dir

* move filter bar to public/components/
@alvarezmelissa87 alvarezmelissa87 deleted the ml-anomaly-explorer-autocomplete-filter branch March 5, 2019 21:15
alvarezmelissa87 added a commit that referenced this pull request Mar 5, 2019
…ts for one or more influencers (#31763) (#32518)

* wip: add autocomplete kueryFilterBar.update endpoints with query.

* Create indexPattern on job change. Value is autocompleted

* Mask unrelated swimlanes on filter apply

* Only show relevant viewBy swimlane

* reload viewBy swimlane when view By change is relevant to filter

* handle various query types when building filteredFields list

* remove deprecated code from old filter bar

* Show error callout when invalid filter syntax

* remove dependency on deprecated import var

* remove all left over apm dependencies

* persist filter on refresh

* Create initial filter bar placeholder dynamically

* add description text when filter active

* switch to relevant viewBy fieldname on filter

* recalculate placeholder on job change

* Create tests for all components

* View by dropdown only contains relevant fields for filter

* fix localization message noInfluencers component

* Move indexPattern to state and viewBy option filtering to utils

* remove unnecessary setState when setting viewBy for filter

* Use preloaded jobs from mlJobService to get influencers

* Only filter viewByOptions if valid field

* use kql and move to components dir

* move filter bar to public/components/
@walterra
Copy link
Contributor

walterra commented Mar 6, 2019

LGTM!

@jgowdyelastic
Copy link
Member

LGTM

@sophiec20 sophiec20 added the Feature:Anomaly Detection ML anomaly detection label Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants