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

[Security Solution][Detections] EQL Validation #77493

Merged
merged 40 commits into from
Oct 2, 2020

Conversation

rylnd
Copy link
Contributor

@rylnd rylnd commented Sep 15, 2020

Summary

This adds two main pieces for EQL Rules:

  • An API endpoint for making an EQL validation request
  • UI hooks & components to query this endpoint and display errors

While the endpoint may soon be replaced by an EQL search strategy (discussion ongoing), the UI and its behaviors should not change, so I'm hoping to get this merged in the interest of earlier feedback from product.

Outstanding Issues:

  • This PR reports mapping_exception errors (as would be caused by inaccessible indexes) as validation errors. While this information is useful to the rule writer, it would be more appropriate to move this validation upstream to the index patterns combobox. Once [Security Solution] Options to select index patterns #77192 is merged, we should have the data necessary to perform that validation on index patterns.

Empty input
Detections_-_Kibana
Input with Errors
Detections_-_Kibana
Input with Errors Expanded

Detections_-_Kibana

Example validation errors

// mismatched quotation mark
// "process where process.name == 'regsvr32.exe"
"line 1:31: token recognition error at: ''regsvr32.exe'"

// unquoted value
// process where process.name == regsvr32.exe
// verification_exception
"line 1:31: Unknown column [regsvr32.exe]"

// invalid sequence, only one event filter
// "sequence\n  [ process where process.name == 'regsvr32.exe' ]\n "
"line 3:2: mismatched input '<EOF>' expecting '['"

// invalid sequence, missing "where"
// "sequence\n  [ process.name == 'regsvr32.exe' ]\n"
"line 2:12: mismatched input '.' expecting 'where'"

// missing quote in stringContains
// "sequence\n  [ process where process.name == 'regsvr32.exe' ]\n  [ file where stringContains(file.name, scrobj.dll') ] "
"line 3:52: token recognition error at: '') ] '"

Checklist

For maintainers

This is mostly boilerplate with some rough parameter definitions; the
actual implementation of the validation is going to live in our
validateEql function.

A few tests are failing as the mocks haven't yet been implemented, I
need to see the shape of the responses first.
* Performs an EQL search
* filters out non-parsing errors, and returns what remains in the
  response
* Adds mocks for empty EQL responses (we don't yet have a need for
  mocked data, but we will when we unit-test validateEql)
* Adds EQL Validation response schema,mocks,tests
* Adds frontend function to call our validation endpoint
* Adds hook, useEqlValidation, to call the above function and return
  state
* Adds labels/help text for EQL Query bar
* EqlQueryBar consumes useEqlValidation and marks the field as invalid,
  but does not yet report errors.
This causes a broader error that results in a 400 response; we can (and
do) handle the case of a blank query in the form itself.
It doesn't add any information for the user, and it currently looks bad
when combined with validation errors.
* Fixes issue where old errors were persisted after the user had made
  modifications
These include errors related to index fields and mappings.
We're concerned with validation errors; the source of those errors is an
implementation detail of these functions.
This more closely resembles the new Eui Markdown editor, which places
errors and doc links in a footer.
These were broken by our use of useAppToasts and the EUI theme.
Decode doesn't do any additional processing, so we can use t.TypeOf here
(the default for buildRouteValidation).
Without `ignore: [400]` the ES client will log errors and then throw
them. We can catch the error, but the log is undesirable.

This updates the query to use the ignore parameter, along with updating
the validation logic to work with the updated response.

Adds some mocks and tests around these responses and helpers, since
these will exist independent of the validation implementation.
These include errors for inaccessible indexes, which should be useful to
the rule writer in writing their EQL query.
@@ -64,6 +65,12 @@ export interface TotalValue {
relation: string;
}

export interface BaseHit<T> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These types are cribbed from #77419, FYI. It doesn't look like they'll conflict but we'll see 🤞

@rylnd rylnd marked this pull request as ready for review September 22, 2020 22:29
Since EQL rules actually validate against the relevant indexes, changing
said indexes should invalidate/revalidate the query.

With the form lib, this is beautifully simple :)
Index corresponds to the value from the index field; now that our EQL
validation is performed by the form we have no need for it here.
@rylnd
Copy link
Contributor Author

rylnd commented Oct 1, 2020

@spong @peluja1012 I've updated the form such that:

  • validations are now performed automatically as you type (not just on blur of the textarea)
  • validation invocations are debounced (at 300ms) to prevent unnecessary calls
  • the query field is invalidated if indexes change

RE the ability to submit while validations are pending: this is still possible on this branch, but after talking with @sebelga this is a general bug with the form lib that should be addressed in #78588. I haven't yet verified those fixes, but I will do so tomorrow.

I think another round of 👀 is warranted (and appreciated!), please let me know if this baby's good to 🚢 !

*
* @returns A debounced async function that resolves on the next invocation
*/
export const debounceAsync = <Args extends unknown[], Result extends Promise<unknown>>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! 👍

@spong
Copy link
Member

spong commented Oct 1, 2020

@spong @peluja1012 I've updated the form such that:

  • validations are now performed automatically as you type (not just on blur of the textarea)
  • validation invocations are debounced (at 300ms) to prevent unnecessary calls
  • the query field is invalidated if indexes change

RE the ability to submit while validations are pending: this is still possible on this branch, but after talking with @sebelga this is a general bug with the form lib that should be addressed in #78588. I haven't yet verified those fixes, but I will do so tomorrow.

I think another round of 👀 is warranted (and appreciated!), please let me know if this baby's good to 🚢 !

Sounds good to me @rylnd -- thanks for the added research here! 🙂 Depending on how things test during FF with the 300ms debounce we may have to revert to validating onBlur, but I'm good moving forward with these changes. We have a fall-through for submitting while validating (rule will fail to run with appropriate error), so 👍 for waiting for the proper fix from #78588.

Will pull this down later today and give it a second test drive so we can 🚢 🚢 🚢 ! Thanks @rylnd!

Copy link
Contributor

@peluja1012 peluja1012 left a comment

Choose a reason for hiding this comment

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

Pulled down the branch and played around with EQL validation. Debounced validation works great, @rylnd. Thanks!

rylnd added 2 commits October 1, 2020 18:59
With our new async validation, a user can quickly navigate away from the
Definition tab before the validation has completed, resulting in the
form being invalidated. Any subsequent user actions cause the form to
correct itself, but until I can find a better solution here this really
just gives the validation time to complete and sidesteps the issue.
@rylnd rylnd requested a review from a team as a code owner October 2, 2020 03:15
 Conflicts:
	x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/schema.tsx
	x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/translations.tsx
@kibanamachine
Copy link
Contributor

💛 Build succeeded, but was flaky


Test Failures

Chrome UI Functional Tests.test/functional/apps/discover/_field_data·js.discover app discover tab field data search php should show the correct hit count

Link to Jenkins

Standard Out

Failed Tests Reporter:
  - Test has failed 1 times on tracked branches: https://github.com/elastic/kibana/issues/78689

[00:00:00]       │
[00:00:00]         └-: discover app
[00:00:00]           └-> "before all" hook
[00:00:00]           └-> "before all" hook
[00:15:19]           └-: discover tab
[00:15:19]             └-> "before all" hook
[00:15:19]             └-> "before all" hook
[00:15:19]               │ info [logstash_functional] Loading "mappings.json"
[00:15:19]               │ info [logstash_functional] Loading "data.json.gz"
[00:15:19]               │ info [logstash_functional] Skipped restore for existing index "logstash-2015.09.22"
[00:15:19]               │ info [logstash_functional] Skipped restore for existing index "logstash-2015.09.20"
[00:15:19]               │ info [logstash_functional] Skipped restore for existing index "logstash-2015.09.21"
[00:15:21]               │ info [discover] Loading "mappings.json"
[00:15:21]               │ info [discover] Loading "data.json.gz"
[00:15:21]               │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana] creating index, cause [api], templates [], shards [1]/[1]
[00:15:21]               │ info [discover] Created index ".kibana"
[00:15:21]               │ debg [discover] ".kibana" settings {"index":{"number_of_replicas":"1","number_of_shards":"1"}}
[00:15:21]               │ info [discover] Indexed 2 docs into ".kibana"
[00:15:21]               │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana/RG0dEKdRTpmZyDOEakTFBQ] update_mapping [_doc]
[00:15:21]               │ debg Migrating saved objects
[00:15:21]               │ proc [kibana]   log   [17:10:08.459] [info][savedobjects-service] Creating index .kibana_2.
[00:15:21]               │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana_2] creating index, cause [api], templates [], shards [1]/[1]
[00:15:21]               │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] updating number_of_replicas to [0] for indices [.kibana_2]
[00:15:21]               │ proc [kibana]   log   [17:10:08.516] [info][savedobjects-service] Reindexing .kibana to .kibana_1
[00:15:21]               │ info [o.e.c.m.MetadataCreateIndexService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana_1] creating index, cause [api], templates [], shards [1]/[1]
[00:15:21]               │ info [o.e.c.r.a.AllocationService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] updating number_of_replicas to [0] for indices [.kibana_1]
[00:15:21]               │ info [o.e.t.LoggingTaskListener] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] 6569 finished with response BulkByScrollResponse[took=17.9ms,timed_out=false,sliceId=null,updated=0,created=2,deleted=0,batches=1,versionConflicts=0,noops=0,retries=0,throttledUntil=0s,bulk_failures=[],search_failures=[]]
[00:15:21]               │ info [o.e.c.m.MetadataDeleteIndexService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana/RG0dEKdRTpmZyDOEakTFBQ] deleting index
[00:15:21]               │ proc [kibana]   log   [17:10:08.849] [info][savedobjects-service] Migrating .kibana_1 saved objects to .kibana_2
[00:15:21]               │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana_2/U6FF501mQ761J6-zVp0DmA] update_mapping [_doc]
[00:15:21]               │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana_2/U6FF501mQ761J6-zVp0DmA] update_mapping [_doc]
[00:15:21]               │ proc [kibana]   log   [17:10:08.921] [info][savedobjects-service] Pointing alias .kibana to .kibana_2.
[00:15:21]               │ proc [kibana]   log   [17:10:08.952] [info][savedobjects-service] Finished in 496ms.
[00:15:21]               │ debg applying update to kibana config: {"accessibility:disableAnimations":true,"dateFormat:tz":"UTC"}
[00:15:21]               │ info [o.e.c.m.MetadataMappingService] [kibana-ci-immutable-debian-tests-xxl-1601656565133987837] [.kibana_2/U6FF501mQ761J6-zVp0DmA] update_mapping [_doc]
[00:15:23]               │ debg replacing kibana config doc: {"defaultIndex":"logstash-*"}
[00:15:24]               │ debg navigating to discover url: http://localhost:61161/app/discover#/
[00:15:24]               │ debg navigate to: http://localhost:61161/app/discover#/
[00:15:24]               │ debg browser[INFO] http://localhost:61161/app/discover?_t=1601658611523#/ 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:15:24]               │
[00:15:24]               │ debg browser[INFO] http://localhost:61161/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:15:24]               │ debg ... sleep(700) start
[00:15:25]               │ debg ... sleep(700) end
[00:15:25]               │ debg returned from get, calling refresh
[00:15:25]               │ debg browser[INFO] http://localhost:61161/app/discover?_t=1601658611523#/ 341 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-P5polb1UreUSOe5V/Pv7tc+yeZuJXiOi/3fqhGsU7BE='), or a nonce ('nonce-...') is required to enable inline execution.
[00:15:25]               │
[00:15:25]               │ debg browser[INFO] http://localhost:61161/bootstrap.js 42:19 "^ A single error about an inline script not firing due to content security policy is expected!"
[00:15:25]               │ debg currentUrl = http://localhost:61161/app/discover#/
[00:15:25]               │          appUrl = http://localhost:61161/app/discover#/
[00:15:25]               │ debg TestSubjects.find(kibanaChrome)
[00:15:25]               │ debg Find.findByCssSelector('[data-test-subj="kibanaChrome"]') with timeout=60000
[00:15:26]               │ debg ... sleep(501) start
[00:15:26]               │ debg browser[INFO] http://localhost:61161/36944/bundles/core/core.entry.js 26:218785 "Detected an unhandled Promise rejection.
[00:15:26]               │      Error: Saved field \"@timestamp\" is invalid for use with the \"Date Histogram\" aggregation. Please select a new field."
[00:15:26]               │ERROR browser[SEVERE] http://localhost:61161/36944/bundles/plugin/data/data.plugin.js 5:524969 Uncaught Error: Saved field "@timestamp" is invalid for use with t…istogram" aggregation. Please select a new field.
[00:15:26]               │ debg ... sleep(501) end
[00:15:26]               │ debg in navigateTo url = http://localhost:61161/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(columns:!(_source),filters:!(),index:%27logstash-*%27,interval:auto,query:(language:kuery,query:%27%27),sort:!())
[00:15:26]               │ debg --- retry.try error: URL changed, waiting for it to settle
[00:15:27]               │ debg ... sleep(501) start
[00:15:27]               │ debg ... sleep(501) end
[00:15:27]               │ debg in navigateTo url = http://localhost:61161/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))&_a=(columns:!(_source),filters:!(),index:%27logstash-*%27,interval:auto,query:(language:kuery,query:%27%27),sort:!())
[00:15:27]               │ debg TestSubjects.exists(statusPageContainer)
[00:15:27]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="statusPageContainer"]') with timeout=2500
[00:15:30]               │ debg --- retry.tryForTime error: [data-test-subj="statusPageContainer"] is not displayed
[00:15:31]               │ debg Setting absolute range to Sep 19, 2015 @ 06:31:44.000 to Sep 23, 2015 @ 18:31:44.000
[00:15:31]               │ debg TestSubjects.exists(superDatePickerToggleQuickMenuButton)
[00:15:31]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="superDatePickerToggleQuickMenuButton"]') with timeout=20000
[00:15:31]               │ debg TestSubjects.exists(superDatePickerShowDatesButton)
[00:15:31]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="superDatePickerShowDatesButton"]') with timeout=2500
[00:15:31]               │ debg TestSubjects.click(superDatePickerShowDatesButton)
[00:15:31]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerShowDatesButton"]') with timeout=10000
[00:15:31]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerShowDatesButton"]') with timeout=10000
[00:15:31]               │ debg TestSubjects.exists(superDatePickerstartDatePopoverButton)
[00:15:31]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="superDatePickerstartDatePopoverButton"]') with timeout=2500
[00:15:31]               │ debg TestSubjects.click(superDatePickerendDatePopoverButton)
[00:15:31]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerendDatePopoverButton"]') with timeout=10000
[00:15:31]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerendDatePopoverButton"]') with timeout=10000
[00:15:31]               │ debg Find.findByCssSelector('div.euiPopover__panel-isOpen') with timeout=10000
[00:15:31]               │ debg TestSubjects.click(superDatePickerAbsoluteTab)
[00:15:31]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerAbsoluteTab"]') with timeout=10000
[00:15:31]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerAbsoluteTab"]') with timeout=10000
[00:15:31]               │ debg TestSubjects.click(superDatePickerAbsoluteDateInput)
[00:15:31]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:31]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:31]               │ debg TestSubjects.setValue(superDatePickerAbsoluteDateInput, Sep 23, 2015 @ 18:31:44.000)
[00:15:31]               │ debg TestSubjects.click(superDatePickerAbsoluteDateInput)
[00:15:31]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:31]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:32]               │ debg TestSubjects.click(superDatePickerstartDatePopoverButton)
[00:15:32]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerstartDatePopoverButton"]') with timeout=10000
[00:15:32]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerstartDatePopoverButton"]') with timeout=10000
[00:15:32]               │ debg Find.waitForElementStale with timeout=10000
[00:15:32]               │ debg Find.findByCssSelector('div.euiPopover__panel-isOpen') with timeout=10000
[00:15:32]               │ debg TestSubjects.click(superDatePickerAbsoluteTab)
[00:15:32]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerAbsoluteTab"]') with timeout=10000
[00:15:32]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerAbsoluteTab"]') with timeout=10000
[00:15:32]               │ debg TestSubjects.click(superDatePickerAbsoluteDateInput)
[00:15:32]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:32]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:32]               │ debg TestSubjects.setValue(superDatePickerAbsoluteDateInput, Sep 19, 2015 @ 06:31:44.000)
[00:15:32]               │ debg TestSubjects.click(superDatePickerAbsoluteDateInput)
[00:15:32]               │ debg Find.clickByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:32]               │ debg Find.findByCssSelector('[data-test-subj="superDatePickerAbsoluteDateInput"]') with timeout=10000
[00:15:33]               │ debg TestSubjects.exists(superDatePickerApplyTimeButton)
[00:15:33]               │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="superDatePickerApplyTimeButton"]') with timeout=2500
[00:15:35]               │ debg --- retry.tryForTime error: [data-test-subj="superDatePickerApplyTimeButton"] is not displayed
[00:15:36]               │ debg TestSubjects.click(querySubmitButton)
[00:15:36]               │ debg Find.clickByCssSelector('[data-test-subj="querySubmitButton"]') with timeout=10000
[00:15:36]               │ debg Find.findByCssSelector('[data-test-subj="querySubmitButton"]') with timeout=10000
[00:15:36]               │ debg Find.waitForElementStale with timeout=10000
[00:15:36]               │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:15:36]               │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:15:36]             └-: field data
[00:15:36]               └-> "before all" hook
[00:15:36]               └-> search php should show the correct hit count
[00:15:36]                 └-> "before each" hook: global before each
[00:15:36]                 │ debg QueryBar.setQuery(php)
[00:15:36]                 │ debg TestSubjects.click(queryInput)
[00:15:36]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:36]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:15:36]                 │ debg TestSubjects.find(queryInput)
[00:15:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:36]                 │ debg QueryBar.submitQuery
[00:15:36]                 │ debg TestSubjects.click(queryInput)
[00:15:36]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:36]                 │ debg isGlobalLoadingIndicatorVisible
[00:15:36]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:15:36]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:15:38]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:15:38]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:15:38]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:15:38]                 │ debg isGlobalLoadingIndicatorVisible
[00:15:38]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:15:38]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:15:40]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:15:40]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:15:40]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:15:41]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:15:41]                 │ debg TestSubjects.find(discoverQueryHits)
[00:15:41]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:15:51]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:15:51]                 │      Wait timed out after 10005ms
[00:15:51]                 │ debg QueryBar.setQuery(php)
[00:15:51]                 │ debg TestSubjects.click(queryInput)
[00:15:51]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:51]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:51]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:15:51]                 │ debg TestSubjects.find(queryInput)
[00:15:51]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:51]                 │ debg QueryBar.submitQuery
[00:15:51]                 │ debg TestSubjects.click(queryInput)
[00:15:51]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:51]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:15:51]                 │ debg isGlobalLoadingIndicatorVisible
[00:15:51]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:15:51]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:15:53]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:15:53]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:15:53]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:15:53]                 │ debg isGlobalLoadingIndicatorVisible
[00:15:53]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:15:53]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:15:55]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:15:56]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:15:56]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:15:56]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:15:56]                 │ debg TestSubjects.find(discoverQueryHits)
[00:15:56]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:16:06]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:16:06]                 │      Wait timed out after 10018ms
[00:16:06]                 │ debg QueryBar.setQuery(php)
[00:16:06]                 │ debg TestSubjects.click(queryInput)
[00:16:06]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:06]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:06]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:16:06]                 │ debg TestSubjects.find(queryInput)
[00:16:06]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:06]                 │ debg QueryBar.submitQuery
[00:16:06]                 │ debg TestSubjects.click(queryInput)
[00:16:06]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:06]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:06]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:06]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:06]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:08]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:08]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:08]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:08]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:08]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:08]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:10]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:10]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:10]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:10]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:16:10]                 │ debg TestSubjects.find(discoverQueryHits)
[00:16:10]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:16:20]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:16:20]                 │      Wait timed out after 10052ms
[00:16:21]                 │ debg QueryBar.setQuery(php)
[00:16:21]                 │ debg TestSubjects.click(queryInput)
[00:16:21]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:21]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:21]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:16:21]                 │ debg TestSubjects.find(queryInput)
[00:16:21]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:21]                 │ debg QueryBar.submitQuery
[00:16:21]                 │ debg TestSubjects.click(queryInput)
[00:16:21]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:21]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:21]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:21]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:21]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:23]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:23]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:23]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:23]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:23]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:23]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:25]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:25]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:25]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:25]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:16:25]                 │ debg TestSubjects.find(discoverQueryHits)
[00:16:25]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:16:35]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:16:35]                 │      Wait timed out after 10048ms
[00:16:36]                 │ debg QueryBar.setQuery(php)
[00:16:36]                 │ debg TestSubjects.click(queryInput)
[00:16:36]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:36]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:16:36]                 │ debg TestSubjects.find(queryInput)
[00:16:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:36]                 │ debg QueryBar.submitQuery
[00:16:36]                 │ debg TestSubjects.click(queryInput)
[00:16:36]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:36]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:36]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:36]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:38]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:38]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:38]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:38]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:38]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:38]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:40]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:40]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:40]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:40]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:16:40]                 │ debg TestSubjects.find(discoverQueryHits)
[00:16:40]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:16:50]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:16:50]                 │      Wait timed out after 10016ms
[00:16:51]                 │ debg QueryBar.setQuery(php)
[00:16:51]                 │ debg TestSubjects.click(queryInput)
[00:16:51]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:51]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:51]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:16:51]                 │ debg TestSubjects.find(queryInput)
[00:16:51]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:51]                 │ debg QueryBar.submitQuery
[00:16:51]                 │ debg TestSubjects.click(queryInput)
[00:16:51]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:51]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:16:51]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:51]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:51]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:53]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:53]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:53]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:53]                 │ debg isGlobalLoadingIndicatorVisible
[00:16:53]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:16:53]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:16:55]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:16:55]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:16:55]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:16:55]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:16:55]                 │ debg TestSubjects.find(discoverQueryHits)
[00:16:55]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:17:05]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:17:05]                 │      Wait timed out after 10019ms
[00:17:06]                 │ debg QueryBar.setQuery(php)
[00:17:06]                 │ debg TestSubjects.click(queryInput)
[00:17:06]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:06]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:06]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:17:06]                 │ debg TestSubjects.find(queryInput)
[00:17:06]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:06]                 │ debg QueryBar.submitQuery
[00:17:06]                 │ debg TestSubjects.click(queryInput)
[00:17:06]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:06]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:06]                 │ debg isGlobalLoadingIndicatorVisible
[00:17:06]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:17:06]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:17:07]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:17:08]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:17:08]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:17:08]                 │ debg isGlobalLoadingIndicatorVisible
[00:17:08]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:17:08]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:17:09]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:17:10]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:17:10]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:17:10]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:17:10]                 │ debg TestSubjects.find(discoverQueryHits)
[00:17:10]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:17:20]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:17:20]                 │      Wait timed out after 10052ms
[00:17:21]                 │ debg QueryBar.setQuery(php)
[00:17:21]                 │ debg TestSubjects.click(queryInput)
[00:17:21]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:21]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:21]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:17:21]                 │ debg TestSubjects.find(queryInput)
[00:17:21]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:21]                 │ debg QueryBar.submitQuery
[00:17:21]                 │ debg TestSubjects.click(queryInput)
[00:17:21]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:21]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:21]                 │ debg isGlobalLoadingIndicatorVisible
[00:17:21]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:17:21]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:17:22]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:17:23]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:17:23]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:17:23]                 │ debg isGlobalLoadingIndicatorVisible
[00:17:23]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:17:23]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:17:24]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:17:25]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:17:25]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:17:25]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:17:25]                 │ debg TestSubjects.find(discoverQueryHits)
[00:17:25]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:17:35]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:17:35]                 │      Wait timed out after 10045ms
[00:17:35]                 │ debg QueryBar.setQuery(php)
[00:17:35]                 │ debg TestSubjects.click(queryInput)
[00:17:35]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:35]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:36]                 │ debg TestSubjects.getAttribute(queryInput, value)
[00:17:36]                 │ debg TestSubjects.find(queryInput)
[00:17:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:36]                 │ debg QueryBar.submitQuery
[00:17:36]                 │ debg TestSubjects.click(queryInput)
[00:17:36]                 │ debg Find.clickByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:36]                 │ debg Find.findByCssSelector('[data-test-subj="queryInput"]') with timeout=10000
[00:17:36]                 │ debg isGlobalLoadingIndicatorVisible
[00:17:36]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:17:36]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:17:37]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:17:38]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:17:38]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:17:38]                 │ debg isGlobalLoadingIndicatorVisible
[00:17:38]                 │ debg TestSubjects.exists(globalLoadingIndicator)
[00:17:38]                 │ debg Find.existsByDisplayedByCssSelector('[data-test-subj="globalLoadingIndicator"]') with timeout=1500
[00:17:39]                 │ debg --- retry.tryForTime error: [data-test-subj="globalLoadingIndicator"] is not displayed
[00:17:40]                 │ debg TestSubjects.exists(globalLoadingIndicator-hidden)
[00:17:40]                 │ debg Find.existsByCssSelector('[data-test-subj="globalLoadingIndicator-hidden"]') with timeout=100000
[00:17:40]                 │ debg TestSubjects.getVisibleText(discoverQueryHits)
[00:17:40]                 │ debg TestSubjects.find(discoverQueryHits)
[00:17:40]                 │ debg Find.findByCssSelector('[data-test-subj="discoverQueryHits"]') with timeout=10000
[00:17:50]                 │ debg --- retry.try error: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:17:50]                 │      Wait timed out after 10016ms
[00:17:50]                 │ info Taking screenshot "/dev/shm/workspace/parallel/16/kibana/test/functional/screenshots/failure/discover app discover tab field data search php should show the correct hit count.png"
[00:17:50]                 │ info Current URL is: http://localhost:61161/app/discover#/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:%272015-09-19T06:31:44.000Z%27,to:%272015-09-23T18:31:44.000Z%27))&_a=(columns:!(_source),filters:!(),index:%27logstash-*%27,interval:auto,query:(language:kuery,query:php),sort:!())
[00:17:51]                 │ info Saving page source to: /dev/shm/workspace/parallel/16/kibana/test/functional/failure_debug/html/discover app discover tab field data search php should show the correct hit count.html
[00:17:51]                 └- ✖ fail: discover app discover tab field data search php should show the correct hit count
[00:17:51]                 │      Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
[00:17:51]                 │ Wait timed out after 10016ms
[00:17:51]                 │     at /dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/webdriver.js:842:17
[00:17:51]                 │     at process._tickCallback (internal/process/next_tick.js:68:7)
[00:17:51]                 │       at onFailure (test/common/services/retry/retry_for_success.ts:28:9)
[00:17:51]                 │       at retryForSuccess (test/common/services/retry/retry_for_success.ts:68:13)
[00:17:51]                 │ 
[00:17:51]                 │ 

Stack Trace

Error: retry.try timeout: TimeoutError: Waiting for element to be located By(css selector, [data-test-subj="discoverQueryHits"])
Wait timed out after 10016ms
    at /dev/shm/workspace/kibana/node_modules/selenium-webdriver/lib/webdriver.js:842:17
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at onFailure (test/common/services/retry/retry_for_success.ts:28:9)
    at retryForSuccess (test/common/services/retry/retry_for_success.ts:68:13)

Metrics [docs]

@kbn/optimizer bundle module count

id before after diff
securitySolution 1989 1996 +7

async chunks size

id before after diff
securitySolution 10.3MB 10.3MB +17.0KB

distributable file count

id before after diff
default 45824 45829 +5

page load bundle size

id before after diff
core 660.6KB 660.6KB +45.0B
securitySolution 585.9KB 586.1KB +194.0B
total +239.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@rylnd rylnd merged commit 7cfdeae into elastic:master Oct 2, 2020
@rylnd rylnd deleted the eql_validation branch October 2, 2020 18:19
rylnd added a commit to rylnd/kibana that referenced this pull request Oct 2, 2020
* WIP: Adding new route for EQL Validation

This is mostly boilerplate with some rough parameter definitions; the
actual implementation of the validation is going to live in our
validateEql function.

A few tests are failing as the mocks haven't yet been implemented, I
need to see the shape of the responses first.

* Cherry-pick Marshall's EQL types

* Implements actual EQL validation

* Performs an EQL search
* filters out non-parsing errors, and returns what remains in the
  response
* Adds mocks for empty EQL responses (we don't yet have a need for
  mocked data, but we will when we unit-test validateEql)

* Adds validation calls to the EQL form input

* Adds EQL Validation response schema,mocks,tests
* Adds frontend function to call our validation endpoint
* Adds hook, useEqlValidation, to call the above function and return
  state
* Adds labels/help text for EQL Query bar
* EqlQueryBar consumes useEqlValidation and marks the field as invalid,
  but does not yet report errors.

* Do not call the validation API if query is not present

This causes a broader error that results in a 400 response; we can (and
do) handle the case of a blank query in the form itself.

* Remove EQL Help Text

It doesn't add any information for the user, and it currently looks bad
when combined with validation errors.

* Flesh out and use our popover for displaying validation errors

* Fixes issue where old errors were persisted after the user had made
  modifications

* Include verification_exception errors as validation errors

These include errors related to index fields and mappings.

* Generalize our validation helpers

We're concerned with validation errors; the source of those errors is an
implementation detail of these functions.

* Move error popover and EQL reference link to footer

This more closely resembles the new Eui Markdown editor, which places
errors and doc links in a footer.

* Fix jest tests following additional prop

* Add icon for EQL Rule card

* Fixes existing EqlQueryBar tests

These were broken by our use of useAppToasts and the EUI theme.

* Add unit tests around error rendering on EQL Query Bar

* Add tests for ErrorPopover

* Remove unused schema type

Decode doesn't do any additional processing, so we can use t.TypeOf here
(the default for buildRouteValidation).

* Remove duplicated header

* Use ignore parameter to prevent EQL validations from logging errors

Without `ignore: [400]` the ES client will log errors and then throw
them. We can catch the error, but the log is undesirable.

This updates the query to use the ignore parameter, along with updating
the validation logic to work with the updated response.

Adds some mocks and tests around these responses and helpers, since
these will exist independent of the validation implementation.

* Include mapping_exceptions during EQL query validation

These include errors for inaccessible indexes, which should be useful to
the rule writer in writing their EQL query.

* Display toast messages for non-validation messages

* fix type errors

This type was renamed.

* Do not request data in our validation request

By not having the cluster retrieve/send any data, this should saves us
a few CPU cycles.

* Move EQL validation to an async form validator

Rather than invoking a custom validation hook (useEqlValidation) at custom times (onBlur) in our EqlQueryBar
component, we can instead move this functionality to a form validation
function and have it be invoked automatically by our form when values
change. However, because we still need to handle the validation messages
slightly differently (place them in a popover as opposed to an
EuiFormRow), we also need custom error retrieval in the form of
getValidationResults.

After much pain, it was determined that the default behavior of
_.debounce does not work with async validator functions, as a debounced
call will not "wait" for the eventual invocation but will instead return
the most recently resolved value. This leads to stale validation
results and terrible UX, so I wrote a custom function (debounceAsync)
that behaves like we want/need; see tests for details.

* Invalidate our query field when index patterns change

Since EQL rules actually validate against the relevant indexes, changing
said indexes should invalidate/revalidate the query.

With the form lib, this is beautifully simple :)

* Set a min-height on our EQL textarea

* Remove unused prop from EqlQueryBar

Index corresponds to the value from the index field; now that our EQL
validation is performed by the form we have no need for it here.

* Update EQL overview link to point to elasticsearch docs

Adds an entry in our doclinks service, and uses that.

* Remove unused prop from stale tests

* Update docLinks documentation with new EQL link

* Fix bug where saved query rules had no type selected on Edit

* Wait for kibana requests to complete before moving between rule tabs

With our new async validation, a user can quickly navigate away from the
Definition tab before the validation has completed, resulting in the
form being invalidated. Any subsequent user actions cause the form to
correct itself, but until I can find a better solution here this really
just gives the validation time to complete and sidesteps the issue.
rylnd added a commit that referenced this pull request Oct 2, 2020
* WIP: Adding new route for EQL Validation

This is mostly boilerplate with some rough parameter definitions; the
actual implementation of the validation is going to live in our
validateEql function.

A few tests are failing as the mocks haven't yet been implemented, I
need to see the shape of the responses first.

* Cherry-pick Marshall's EQL types

* Implements actual EQL validation

* Performs an EQL search
* filters out non-parsing errors, and returns what remains in the
  response
* Adds mocks for empty EQL responses (we don't yet have a need for
  mocked data, but we will when we unit-test validateEql)

* Adds validation calls to the EQL form input

* Adds EQL Validation response schema,mocks,tests
* Adds frontend function to call our validation endpoint
* Adds hook, useEqlValidation, to call the above function and return
  state
* Adds labels/help text for EQL Query bar
* EqlQueryBar consumes useEqlValidation and marks the field as invalid,
  but does not yet report errors.

* Do not call the validation API if query is not present

This causes a broader error that results in a 400 response; we can (and
do) handle the case of a blank query in the form itself.

* Remove EQL Help Text

It doesn't add any information for the user, and it currently looks bad
when combined with validation errors.

* Flesh out and use our popover for displaying validation errors

* Fixes issue where old errors were persisted after the user had made
  modifications

* Include verification_exception errors as validation errors

These include errors related to index fields and mappings.

* Generalize our validation helpers

We're concerned with validation errors; the source of those errors is an
implementation detail of these functions.

* Move error popover and EQL reference link to footer

This more closely resembles the new Eui Markdown editor, which places
errors and doc links in a footer.

* Fix jest tests following additional prop

* Add icon for EQL Rule card

* Fixes existing EqlQueryBar tests

These were broken by our use of useAppToasts and the EUI theme.

* Add unit tests around error rendering on EQL Query Bar

* Add tests for ErrorPopover

* Remove unused schema type

Decode doesn't do any additional processing, so we can use t.TypeOf here
(the default for buildRouteValidation).

* Remove duplicated header

* Use ignore parameter to prevent EQL validations from logging errors

Without `ignore: [400]` the ES client will log errors and then throw
them. We can catch the error, but the log is undesirable.

This updates the query to use the ignore parameter, along with updating
the validation logic to work with the updated response.

Adds some mocks and tests around these responses and helpers, since
these will exist independent of the validation implementation.

* Include mapping_exceptions during EQL query validation

These include errors for inaccessible indexes, which should be useful to
the rule writer in writing their EQL query.

* Display toast messages for non-validation messages

* fix type errors

This type was renamed.

* Do not request data in our validation request

By not having the cluster retrieve/send any data, this should saves us
a few CPU cycles.

* Move EQL validation to an async form validator

Rather than invoking a custom validation hook (useEqlValidation) at custom times (onBlur) in our EqlQueryBar
component, we can instead move this functionality to a form validation
function and have it be invoked automatically by our form when values
change. However, because we still need to handle the validation messages
slightly differently (place them in a popover as opposed to an
EuiFormRow), we also need custom error retrieval in the form of
getValidationResults.

After much pain, it was determined that the default behavior of
_.debounce does not work with async validator functions, as a debounced
call will not "wait" for the eventual invocation but will instead return
the most recently resolved value. This leads to stale validation
results and terrible UX, so I wrote a custom function (debounceAsync)
that behaves like we want/need; see tests for details.

* Invalidate our query field when index patterns change

Since EQL rules actually validate against the relevant indexes, changing
said indexes should invalidate/revalidate the query.

With the form lib, this is beautifully simple :)

* Set a min-height on our EQL textarea

* Remove unused prop from EqlQueryBar

Index corresponds to the value from the index field; now that our EQL
validation is performed by the form we have no need for it here.

* Update EQL overview link to point to elasticsearch docs

Adds an entry in our doclinks service, and uses that.

* Remove unused prop from stale tests

* Update docLinks documentation with new EQL link

* Fix bug where saved query rules had no type selected on Edit

* Wait for kibana requests to complete before moving between rule tabs

With our new async validation, a user can quickly navigate away from the
Definition tab before the validation has completed, resulting in the
form being invalidated. Any subsequent user actions cause the form to
correct itself, but until I can find a better solution here this really
just gives the validation time to complete and sidesteps the issue.
@MindyRS MindyRS added the Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. label Sep 23, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/security-solution (Team: SecuritySolution)

rylnd added a commit to rylnd/kibana that referenced this pull request Oct 18, 2023
Previously (since elastic#77493), we had
been leveraging the following facts:

* EQL search endpoint returns syntax errors as a 400 response
* ES client can specify an `ignore` option to opt out of the "throw
  unsuccessful responses" behavior

in order to perform EQL "validation" in a normal, non-exception control
flow.

This commit effectively replaces that with a try/catch, where we no
longer pass (nor _can_ pass) `ignore` to the search strategy, and
instead indicate we'd like the "validation" behavior via a new
`validate` param. When set to true, we will capture the 400 response
and, if valid, return it as before; in all other situations that syntax
error will result in an error response from the search strategy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
release_note:enhancement Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:SIEM v7.10.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants