From 81bcaed7ae5b4bfd06a5e5a03e86af5b64ac7332 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Sat, 18 Jan 2020 00:24:34 +0100 Subject: [PATCH 01/11] update local (#55177) --- .../location_map/location_status_tags.tsx | 77 +++++++++++-------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx index 923bf2c68cc56..e2855f78262e7 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx @@ -66,44 +66,57 @@ export const LocationStatusTags = ({ locations }: Props) => { return a.label > b.label ? 1 : b.label > a.label ? -1 : 0; }); - moment.updateLocale('en', { - relativeTime: { - future: 'in %s', - past: '%s ago', - s: '%ds', - ss: '%ss', - m: '%dm', - mm: '%dm', - h: '%dh', - hh: '%dh', - d: '%dd', - dd: '%dd', - M: '%d Mon', - MM: '%d Mon', - y: '%d Yr', - yy: '%d Yr', - }, - }); + const tagLabel = (item: StatusTag, ind: number, color: string) => { + return ( + + + + {item.label} + + + + {moment(item.timestamp).fromNow()} + + + ); + }; - const tagLabel = (item: StatusTag, ind: number, color: string) => ( - - - - {item.label} - - - - {moment(item.timestamp).fromNow()} - - - ); + const prevLocal: string = moment.locale() ?? 'en'; - return ( - <> + const renderTags = () => { + moment.defineLocale('en-tag', { + relativeTime: { + future: 'in %s', + past: '%s ago', + s: '%ds', + ss: '%ss', + m: '%dm', + mm: '%dm', + h: '%dh', + hh: '%dh', + d: '%dd', + dd: '%dd', + M: '%d Mon', + MM: '%d Mon', + y: '%d Yr', + yy: '%d Yr', + }, + }); + const tags = ( {downLocations.map((item, ind) => tagLabel(item, ind, danger))} {upLocations.map((item, ind) => tagLabel(item, ind, gray))} + ); + + // Need to reset locale so it doesn't effect other parts of the app + moment.locale(prevLocal); + return tags; + }; + + return ( + <> + {renderTags()} {locations.length > 7 && ( From 6cfd1d48b3c7276d1719ca7c7e5cc65ae6528a72 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 17 Jan 2020 16:53:04 -0700 Subject: [PATCH 02/11] [SIEM][Detection Engine] Fixes critical regression on the backend with immutable and tags ## Summary Fixes regression with immutable caused from: https://github.com/elastic/kibana/pull/55004 * Updated types of Prepackaged * Updated unit tests * Fixed unit test for it Testing: ``` ./post_rule.sh { "created_at": "2020-01-17T19:11:31.813Z", "updated_at": "2020-01-17T19:11:31.813Z", "created_by": "elastic_kibana", "description": "Query with a rule_id that acts like an external id", "enabled": true, "false_positives": [], "from": "now-6m", "id": "41ef6309-ef98-4c9f-8d2d-90a070361fb7", "immutable": false, "interval": "5m", "rule_id": "query-rule-id", "language": "kuery", "output_index": ".siem-signals-frank-hassanabad-default", "max_signals": 100, "risk_score": 1, "name": "Query with a rule id", "query": "user.name: root or user.name: admin", "references": [], "severity": "high", "updated_by": "elastic_kibana", "tags": [], "to": "now", "type": "query", "threats": [], "version": 1 } ``` Then get the saved object using whatever the id is comes back from above. In this example it is 41ef6309-ef98-4c9f-8d2d-90a070361fb7, yours will be different ``` ./get_saved_objects.sh alert 41ef6309-ef98-4c9f-8d2d-90a070361fb7 { "id": "41ef6309-ef98-4c9f-8d2d-90a070361fb7", "type": "alert", "updated_at": "2020-01-17T19:11:32.844Z", "version": "WzY5NTQsMV0=", "attributes": { "name": "Query with a rule id", "tags": [ "__internal_rule_id:query-rule-id", "__internal_immutable:false" ], "alertTypeId": "siem.signals", "consumer": "siem", "params": { "createdAt": "2020-01-17T19:11:31.813Z", "description": "Query with a rule_id that acts like an external id", "ruleId": "query-rule-id", "index": null, "falsePositives": [], "from": "now-6m", "immutable": false, "query": "user.name: root or user.name: admin", "language": "kuery", "outputIndex": ".siem-signals-frank-hassanabad-default", "savedId": null, "timelineId": null, "timelineTitle": null, "meta": null, "filters": null, "maxSignals": 100, "riskScore": 1, "severity": "high", "threats": [], "to": "now", "type": "query", "updatedAt": "2020-01-17T19:11:31.813Z", "references": [], "version": 1 }, "schedule": { "interval": "5m" }, "enabled": true, "actions": [], "throttle": null, "apiKeyOwner": "elastic_kibana", "createdBy": "elastic_kibana", "updatedBy": "elastic_kibana", "createdAt": "2020-01-17T19:11:32.245Z", "muteAll": false, "mutedInstanceIds": [], "scheduledTaskId": "2c5cc340-395d-11ea-9276-d3c1c264ca9a" }, "references": [] } ``` Ensure you have the internal immutable of "__internal_immutable:false" In your tags Next test is to do a find filter of non-packaged rules: ``` ./find_rule_by_filter.sh "alert.attributes.tags:%20%22__internal_immutable:false%22" ``` You should get back the above rule any others you created. ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../routes/__mocks__/request_responses.ts | 6 +- .../routes/rules/create_rules_bulk_route.ts | 3 +- .../routes/rules/create_rules_route.ts | 3 +- .../routes/rules/update_rules_bulk_route.ts | 2 - .../routes/rules/update_rules_route.ts | 2 - .../add_prepackaged_rules_schema.test.ts | 127 +++++++++--------- .../schemas/create_rules_schema.test.ts | 3 - .../schemas/import_rules_schema.test.ts | 116 ++++++++-------- .../rules/get_prepackaged_rules.test.ts | 4 +- .../rules/get_prepackaged_rules.ts | 8 +- .../rules/get_rules_to_install.test.ts | 16 +-- .../rules/get_rules_to_install.ts | 6 +- .../rules/get_rules_to_update.test.ts | 16 +-- .../rules/get_rules_to_update.ts | 6 +- .../rules/install_prepacked_rules.ts | 4 +- .../rules/update_prepacked_rules.ts | 4 +- .../siem/server/lib/detection_engine/types.ts | 15 +++ 17 files changed, 170 insertions(+), 171 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 30a8d9d935128..0c6ab1c82bcb8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -18,9 +18,9 @@ import { DETECTION_ENGINE_PREPACKAGED_URL, } from '../../../../../common/constants'; import { RuleAlertType, IRuleSavedAttributesSavedObjectAttributes } from '../../rules/types'; -import { RuleAlertParamsRest } from '../../types'; +import { RuleAlertParamsRest, PrepackagedRules } from '../../types'; -export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({ +export const mockPrepackagedRule = (): PrepackagedRules => ({ rule_id: 'rule-1', description: 'Detecting root and admin users', index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], @@ -51,8 +51,6 @@ export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({ false_positives: [], saved_id: 'some-id', max_signals: 100, - created_at: '2019-12-13T16:40:33.400Z', - updated_at: '2019-12-13T16:40:33.400Z', timeline_id: 'timeline-id', timeline_title: 'timeline-title', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 9c18f9040008c..00a1d2eb980ec 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -55,7 +55,6 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -109,7 +108,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, falsePositives, from, - immutable, + immutable: false, query, language, outputIndex: finalIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index aa535d325f4b9..23acd12d341ed 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -39,7 +39,6 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -96,7 +95,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = enabled, falsePositives, from, - immutable, + immutable: false, query, language, outputIndex: finalIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 180a75bdaaeea..b01108f0de21f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -44,7 +44,6 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -77,7 +76,6 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou enabled, falsePositives, from, - immutable, query, language, outputIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 147f3f9afa549..533fe9b724943 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -33,7 +33,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { enabled, false_positives: falsePositives, from, - immutable, query, language, output_index: outputIndex, @@ -75,7 +74,6 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { enabled, falsePositives, from, - immutable, query, language, outputIndex, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index 1993948808ef4..abdd5a0c7b508 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -4,20 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UpdateRuleAlertParamsRest } from '../../rules/types'; -import { ThreatParams, RuleAlertParamsRest } from '../../types'; +import { ThreatParams, PrepackagedRules } from '../../types'; import { addPrepackagedRulesSchema } from './add_prepackaged_rules_schema'; describe('add prepackaged rules schema', () => { test('empty objects do not validate', () => { - expect( - addPrepackagedRulesSchema.validate>({}).error - ).toBeTruthy(); + expect(addPrepackagedRulesSchema.validate>({}).error).toBeTruthy(); }); test('made up values do not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ madeUp: 'hi', }).error ).toBeTruthy(); @@ -25,7 +22,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', }).error ).toBeTruthy(); @@ -33,7 +30,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', }).error @@ -42,7 +39,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -52,7 +49,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -63,7 +60,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -75,7 +72,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -88,7 +85,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -102,7 +99,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -117,7 +114,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -133,7 +130,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, name, severity, type, query, index, interval, version] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -152,7 +149,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -170,7 +167,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, version] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -190,7 +187,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does not validate because output_index is not allowed', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -211,7 +208,7 @@ describe('add prepackaged rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -229,7 +226,7 @@ describe('add prepackaged rules schema', () => { test('You can send in an empty array to threats', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -251,7 +248,7 @@ describe('add prepackaged rules schema', () => { }); test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, version, threats] does validate', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -286,7 +283,7 @@ describe('add prepackaged rules schema', () => { test('allows references to be sent as valid', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -307,7 +304,7 @@ describe('add prepackaged rules schema', () => { test('defaults references to an array', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -327,7 +324,7 @@ describe('add prepackaged rules schema', () => { test('defaults immutable to true', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -347,7 +344,7 @@ describe('add prepackaged rules schema', () => { test('immutable cannot be false', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -368,7 +365,7 @@ describe('add prepackaged rules schema', () => { test('immutable can be true', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -389,7 +386,7 @@ describe('add prepackaged rules schema', () => { test('defaults enabled to false', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -409,7 +406,7 @@ describe('add prepackaged rules schema', () => { test('rule_id is required', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ risk_score: 50, description: 'some description', from: 'now-5m', @@ -429,7 +426,7 @@ describe('add prepackaged rules schema', () => { test('references cannot be numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { references: number[] } + Partial> & { references: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -454,7 +451,7 @@ describe('add prepackaged rules schema', () => { test('indexes cannot be numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { index: number[] } + Partial> & { index: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -477,7 +474,7 @@ describe('add prepackaged rules schema', () => { test('defaults interval to 5 min', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -494,7 +491,7 @@ describe('add prepackaged rules schema', () => { test('defaults max signals to 100', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -512,7 +509,7 @@ describe('add prepackaged rules schema', () => { test('saved_id is required when type is saved_query and will not validate without out', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -530,7 +527,7 @@ describe('add prepackaged rules schema', () => { test('saved_id is required when type is saved_query and validates with it', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -549,7 +546,7 @@ describe('add prepackaged rules schema', () => { test('saved_query type can have filters with it', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -570,7 +567,7 @@ describe('add prepackaged rules schema', () => { test('filters cannot be a string', () => { expect( addPrepackagedRulesSchema.validate< - Partial & { filters: string }> + Partial & { filters: string }> >({ rule_id: 'rule-1', risk_score: 50, @@ -591,7 +588,7 @@ describe('add prepackaged rules schema', () => { test('language validates with kuery', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -612,7 +609,7 @@ describe('add prepackaged rules schema', () => { test('language validates with lucene', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -633,7 +630,7 @@ describe('add prepackaged rules schema', () => { test('language does not validate with something made up', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -654,7 +651,7 @@ describe('add prepackaged rules schema', () => { test('max_signals cannot be negative', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -676,7 +673,7 @@ describe('add prepackaged rules schema', () => { test('max_signals cannot be zero', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -698,7 +695,7 @@ describe('add prepackaged rules schema', () => { test('max_signals can be 1', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -720,7 +717,7 @@ describe('add prepackaged rules schema', () => { test('You can optionally send in an array of tags', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -744,7 +741,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of tags that are numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { tags: number[] } + Partial> & { tags: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -771,7 +768,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of threats that are missing "framework"', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -815,7 +812,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of threats that are missing "tactic"', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -855,7 +852,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of threats that are missing "techniques"', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -892,7 +889,7 @@ describe('add prepackaged rules schema', () => { test('You can optionally send in an array of false positives', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -916,7 +913,7 @@ describe('add prepackaged rules schema', () => { test('You cannot send in an array of false positives that are numbers', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { false_positives: number[] } + Partial> & { false_positives: number[] } >({ rule_id: 'rule-1', risk_score: 50, @@ -942,7 +939,7 @@ describe('add prepackaged rules schema', () => { test('You can optionally set the immutable to be true', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -966,7 +963,7 @@ describe('add prepackaged rules schema', () => { test('You cannot set the immutable to be a number', () => { expect( addPrepackagedRulesSchema.validate< - Partial> & { immutable: number } + Partial> & { immutable: number } >({ rule_id: 'rule-1', risk_score: 50, @@ -990,7 +987,7 @@ describe('add prepackaged rules schema', () => { test('You cannot set the risk_score to 101', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 101, description: 'some description', @@ -1013,7 +1010,7 @@ describe('add prepackaged rules schema', () => { test('You cannot set the risk_score to -1', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: -1, description: 'some description', @@ -1036,7 +1033,7 @@ describe('add prepackaged rules schema', () => { test('You can set the risk_score to 0', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 0, description: 'some description', @@ -1059,7 +1056,7 @@ describe('add prepackaged rules schema', () => { test('You can set the risk_score to 100', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 100, description: 'some description', @@ -1082,7 +1079,7 @@ describe('add prepackaged rules schema', () => { test('You can set meta to any object you want', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1109,7 +1106,7 @@ describe('add prepackaged rules schema', () => { test('You cannot create meta as a string', () => { expect( addPrepackagedRulesSchema.validate< - Partial & { meta: string }> + Partial & { meta: string }> >({ rule_id: 'rule-1', risk_score: 50, @@ -1134,7 +1131,7 @@ describe('add prepackaged rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1157,7 +1154,7 @@ describe('add prepackaged rules schema', () => { test('validates with timeline_id and timeline_title', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1180,7 +1177,7 @@ describe('add prepackaged rules schema', () => { test('You cannot omit timeline_title when timeline_id is present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1204,7 +1201,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have a null value for timeline_title when timeline_id is present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1229,7 +1226,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have empty string for timeline_title when timeline_id is present', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1254,7 +1251,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have timeline_title with an empty timeline_id', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1279,7 +1276,7 @@ describe('add prepackaged rules schema', () => { test('You cannot have timeline_title without timeline_id', () => { expect( - addPrepackagedRulesSchema.validate>({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 15f4fa7f05648..c76071047434c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -884,7 +884,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -907,7 +906,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', @@ -999,7 +997,6 @@ describe('create rules schema', () => { description: 'some description', from: 'now-5m', to: 'now', - immutable: true, index: ['index-1'], name: 'some-name', severity: 'severity', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index bed64cc6e7a02..20f418c57b5db 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -9,18 +9,18 @@ import { importRulesQuerySchema, importRulesPayloadSchema, } from './import_rules_schema'; -import { ThreatParams, RuleAlertParamsRest, ImportRuleAlertRest } from '../../types'; +import { ThreatParams, ImportRuleAlertRest } from '../../types'; import { ImportRulesRequest } from '../../rules/types'; describe('import rules schema', () => { describe('importRulesSchema', () => { test('empty objects do not validate', () => { - expect(importRulesSchema.validate>({}).error).toBeTruthy(); + expect(importRulesSchema.validate>({}).error).toBeTruthy(); }); test('made up values do not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ madeUp: 'hi', }).error ).toBeTruthy(); @@ -28,7 +28,7 @@ describe('import rules schema', () => { test('[rule_id] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', }).error ).toBeTruthy(); @@ -36,7 +36,7 @@ describe('import rules schema', () => { test('[rule_id, description] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', }).error @@ -45,7 +45,7 @@ describe('import rules schema', () => { test('[rule_id, description, from] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -55,7 +55,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -66,7 +66,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -78,7 +78,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -91,7 +91,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -105,7 +105,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -120,7 +120,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -136,7 +136,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -154,7 +154,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -172,7 +172,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -191,7 +191,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -211,7 +211,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', description: 'some description', from: 'now-5m', @@ -228,7 +228,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -246,7 +246,7 @@ describe('import rules schema', () => { test('You can send in an empty array to threats', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -269,7 +269,7 @@ describe('import rules schema', () => { test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -304,7 +304,7 @@ describe('import rules schema', () => { test('allows references to be sent as valid', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -325,7 +325,7 @@ describe('import rules schema', () => { test('defaults references to an array', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -346,7 +346,7 @@ describe('import rules schema', () => { test('references cannot be numbers', () => { expect( importRulesSchema.validate< - Partial> & { references: number[] } + Partial> & { references: number[] } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -371,7 +371,7 @@ describe('import rules schema', () => { test('indexes cannot be numbers', () => { expect( importRulesSchema.validate< - Partial> & { index: number[] } + Partial> & { index: number[] } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -394,7 +394,7 @@ describe('import rules schema', () => { test('defaults interval to 5 min', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -411,7 +411,7 @@ describe('import rules schema', () => { test('defaults max signals to 100', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -429,7 +429,7 @@ describe('import rules schema', () => { test('saved_id is required when type is saved_query and will not validate without out', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -447,7 +447,7 @@ describe('import rules schema', () => { test('saved_id is required when type is saved_query and validates with it', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, output_index: '.siem-signals', @@ -466,7 +466,7 @@ describe('import rules schema', () => { test('saved_query type can have filters with it', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -487,7 +487,7 @@ describe('import rules schema', () => { test('filters cannot be a string', () => { expect( importRulesSchema.validate< - Partial & { filters: string }> + Partial & { filters: string }> >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -508,7 +508,7 @@ describe('import rules schema', () => { test('language validates with kuery', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -529,7 +529,7 @@ describe('import rules schema', () => { test('language validates with lucene', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, output_index: '.siem-signals', @@ -550,7 +550,7 @@ describe('import rules schema', () => { test('language does not validate with something made up', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -571,7 +571,7 @@ describe('import rules schema', () => { test('max_signals cannot be negative', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -593,7 +593,7 @@ describe('import rules schema', () => { test('max_signals cannot be zero', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -615,7 +615,7 @@ describe('import rules schema', () => { test('max_signals can be 1', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -637,7 +637,7 @@ describe('import rules schema', () => { test('You can optionally send in an array of tags', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -660,7 +660,7 @@ describe('import rules schema', () => { test('You cannot send in an array of tags that are numbers', () => { expect( - importRulesSchema.validate> & { tags: number[] }>( + importRulesSchema.validate> & { tags: number[] }>( { rule_id: 'rule-1', output_index: '.siem-signals', @@ -688,7 +688,7 @@ describe('import rules schema', () => { test('You cannot send in an array of threats that are missing "framework"', () => { expect( importRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -732,7 +732,7 @@ describe('import rules schema', () => { test('You cannot send in an array of threats that are missing "tactic"', () => { expect( importRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -772,7 +772,7 @@ describe('import rules schema', () => { test('You cannot send in an array of threats that are missing "techniques"', () => { expect( importRulesSchema.validate< - Partial> & { + Partial> & { threats: Array>>; } >({ @@ -809,7 +809,7 @@ describe('import rules schema', () => { test('You can optionally send in an array of false positives', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -833,7 +833,7 @@ describe('import rules schema', () => { test('You cannot send in an array of false positives that are numbers', () => { expect( importRulesSchema.validate< - Partial> & { false_positives: number[] } + Partial> & { false_positives: number[] } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -859,7 +859,7 @@ describe('import rules schema', () => { test('You can optionally set the immutable to be true', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -883,7 +883,7 @@ describe('import rules schema', () => { test('You cannot set the immutable to be a number', () => { expect( importRulesSchema.validate< - Partial> & { immutable: number } + Partial> & { immutable: number } >({ rule_id: 'rule-1', output_index: '.siem-signals', @@ -907,7 +907,7 @@ describe('import rules schema', () => { test('You cannot set the risk_score to 101', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 101, @@ -930,7 +930,7 @@ describe('import rules schema', () => { test('You cannot set the risk_score to -1', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: -1, @@ -953,7 +953,7 @@ describe('import rules schema', () => { test('You can set the risk_score to 0', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 0, @@ -976,7 +976,7 @@ describe('import rules schema', () => { test('You can set the risk_score to 100', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 100, @@ -999,7 +999,7 @@ describe('import rules schema', () => { test('You can set meta to any object you want', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1025,7 +1025,7 @@ describe('import rules schema', () => { test('You cannot create meta as a string', () => { expect( - importRulesSchema.validate & { meta: string }>>({ + importRulesSchema.validate & { meta: string }>>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1049,7 +1049,7 @@ describe('import rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1072,7 +1072,7 @@ describe('import rules schema', () => { test('validates with timeline_id and timeline_title', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1095,7 +1095,7 @@ describe('import rules schema', () => { test('You cannot omit timeline_title when timeline_id is present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1117,7 +1117,7 @@ describe('import rules schema', () => { test('You cannot have a null value for timeline_title when timeline_id is present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1140,7 +1140,7 @@ describe('import rules schema', () => { test('You cannot have empty string for timeline_title when timeline_id is present', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1165,7 +1165,7 @@ describe('import rules schema', () => { test('You cannot have timeline_title with an empty timeline_id', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1188,7 +1188,7 @@ describe('import rules schema', () => { test('You cannot have timeline_title without timeline_id', () => { expect( - importRulesSchema.validate>({ + importRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts index 260147ed0506c..8ca07caef0c7f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.test.ts @@ -5,7 +5,7 @@ */ import { getPrepackagedRules } from './get_prepackaged_rules'; -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { isEmpty } from 'lodash/fp'; describe('get_existing_prepackaged_rules', () => { @@ -15,7 +15,7 @@ describe('get_existing_prepackaged_rules', () => { test('no rule should have the same rule_id as another rule_id', () => { const prePacakgedRules = getPrepackagedRules(); - let existingRuleIds: RuleAlertParamsRest[] = []; + let existingRuleIds: PrepackagedRules[] = []; prePacakgedRules.forEach(rule => { const foundDuplicate = existingRuleIds.reduce((accum, existingRule) => { if (existingRule.rule_id === rule.rule_id) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts index 855d0d73f6796..bcfe6ee203ecd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_prepackaged_rules.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { addPrepackagedRulesSchema } from '../routes/schemas/add_prepackaged_rules_schema'; import { rawRules } from './prepackaged_rules'; @@ -13,9 +13,7 @@ import { rawRules } from './prepackaged_rules'; * that they are adding incorrect schema rules. Also this will auto-flush in all the default * aspects such as default interval of 5 minutes, default arrays, etc... */ -export const validateAllPrepackagedRules = ( - rules: RuleAlertParamsRest[] -): RuleAlertParamsRest[] => { +export const validateAllPrepackagedRules = (rules: PrepackagedRules[]): PrepackagedRules[] => { return rules.map(rule => { const validatedRule = addPrepackagedRulesSchema.validate(rule); if (validatedRule.error != null) { @@ -35,6 +33,6 @@ export const validateAllPrepackagedRules = ( }); }; -export const getPrepackagedRules = (rules = rawRules): RuleAlertParamsRest[] => { +export const getPrepackagedRules = (rules = rawRules): PrepackagedRules[] => { return validateAllPrepackagedRules(rules); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts index 1a2bd4a10ac2d..ee76bf2ef15b8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.test.ts @@ -5,7 +5,7 @@ */ import { getRulesToInstall } from './get_rules_to_install'; -import { getResult, fullRuleAlertParamsRest } from '../routes/__mocks__/request_responses'; +import { getResult, mockPrepackagedRule } from '../routes/__mocks__/request_responses'; describe('get_rules_to_install', () => { test('should return empty array if both rule sets are empty', () => { @@ -14,7 +14,7 @@ describe('get_rules_to_install', () => { }); test('should return empty array if the two rule ids match', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getResult(); @@ -24,7 +24,7 @@ describe('get_rules_to_install', () => { }); test('should return the rule to install if the id of the two rules do not match', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; const installedRule = getResult(); @@ -34,10 +34,10 @@ describe('get_rules_to_install', () => { }); test('should return two rules to install if both the ids of the two rules do not match', () => { - const ruleFromFileSystem1 = fullRuleAlertParamsRest(); + const ruleFromFileSystem1 = mockPrepackagedRule(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = fullRuleAlertParamsRest(); + const ruleFromFileSystem2 = mockPrepackagedRule(); ruleFromFileSystem2.rule_id = 'rule-2'; const installedRule = getResult(); @@ -47,13 +47,13 @@ describe('get_rules_to_install', () => { }); test('should return two rules of three to install if both the ids of the two rules do not match but the third does', () => { - const ruleFromFileSystem1 = fullRuleAlertParamsRest(); + const ruleFromFileSystem1 = mockPrepackagedRule(); ruleFromFileSystem1.rule_id = 'rule-1'; - const ruleFromFileSystem2 = fullRuleAlertParamsRest(); + const ruleFromFileSystem2 = mockPrepackagedRule(); ruleFromFileSystem2.rule_id = 'rule-2'; - const ruleFromFileSystem3 = fullRuleAlertParamsRest(); + const ruleFromFileSystem3 = mockPrepackagedRule(); ruleFromFileSystem3.rule_id = 'rule-3'; const installedRule = getResult(); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts index 1c795941cbb83..c44e4fb812c35 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_install.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { RuleAlertType } from './types'; export const getRulesToInstall = ( - rulesFromFileSystem: RuleAlertParamsRest[], + rulesFromFileSystem: PrepackagedRules[], installedRules: RuleAlertType[] -): RuleAlertParamsRest[] => { +): PrepackagedRules[] => { return rulesFromFileSystem.filter( rule => !installedRules.some(installedRule => installedRule.params.ruleId === rule.rule_id) ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts index 7f1b64d33cd9b..40e303bddac1a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.test.ts @@ -5,7 +5,7 @@ */ import { getRulesToUpdate } from './get_rules_to_update'; -import { getResult, fullRuleAlertParamsRest } from '../routes/__mocks__/request_responses'; +import { getResult, mockPrepackagedRule } from '../routes/__mocks__/request_responses'; describe('get_rules_to_update', () => { test('should return empty array if both rule sets are empty', () => { @@ -14,7 +14,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the id of the two rules do not match', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -26,7 +26,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the id of file system rule is less than the installed version', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -38,7 +38,7 @@ describe('get_rules_to_update', () => { }); test('should return empty array if the id of file system rule is the same as the installed version', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 1; @@ -50,7 +50,7 @@ describe('get_rules_to_update', () => { }); test('should return the rule to update if the id of file system rule is greater than the installed version', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -62,7 +62,7 @@ describe('get_rules_to_update', () => { }); test('should return 1 rule out of 2 to update if the id of file system rule is greater than the installed version of just one', () => { - const ruleFromFileSystem = fullRuleAlertParamsRest(); + const ruleFromFileSystem = mockPrepackagedRule(); ruleFromFileSystem.rule_id = 'rule-1'; ruleFromFileSystem.version = 2; @@ -79,11 +79,11 @@ describe('get_rules_to_update', () => { }); test('should return 2 rules out of 2 to update if the id of file system rule is greater than the installed version of both', () => { - const ruleFromFileSystem1 = fullRuleAlertParamsRest(); + const ruleFromFileSystem1 = mockPrepackagedRule(); ruleFromFileSystem1.rule_id = 'rule-1'; ruleFromFileSystem1.version = 2; - const ruleFromFileSystem2 = fullRuleAlertParamsRest(); + const ruleFromFileSystem2 = mockPrepackagedRule(); ruleFromFileSystem2.rule_id = 'rule-2'; ruleFromFileSystem2.version = 2; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts index 10b849493858a..31eff6a4ec87a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_rules_to_update.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; import { RuleAlertType } from './types'; export const getRulesToUpdate = ( - rulesFromFileSystem: RuleAlertParamsRest[], + rulesFromFileSystem: PrepackagedRules[], installedRules: RuleAlertType[] -): RuleAlertParamsRest[] => { +): PrepackagedRules[] => { return rulesFromFileSystem.filter(rule => installedRules.some(installedRule => { return ( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 9c3be64f71a0d..98c04f95387f4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -7,12 +7,12 @@ import { ActionsClient } from '../../../../../actions'; import { AlertsClient } from '../../../../../alerting'; import { createRules } from './create_rules'; -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; export const installPrepackagedRules = async ( alertsClient: AlertsClient, actionsClient: ActionsClient, - rules: RuleAlertParamsRest[], + rules: PrepackagedRules[], outputIndex: string ): Promise => { await rules.forEach(async rule => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts index 3d2ca8f91281b..756634c8fa042 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -7,12 +7,12 @@ import { ActionsClient } from '../../../../../actions'; import { AlertsClient } from '../../../../../alerting'; import { updateRules } from './update_rules'; -import { RuleAlertParamsRest } from '../types'; +import { PrepackagedRules } from '../types'; export const updatePrepackagedRules = async ( alertsClient: AlertsClient, actionsClient: ActionsClient, - rules: RuleAlertParamsRest[], + rules: PrepackagedRules[], outputIndex: string ): Promise => { await rules.forEach(async rule => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index 8a9e050c039b4..c7bd92322360a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -58,6 +58,7 @@ export type RuleAlertParamsRest = Omit< RuleAlertParams, | 'ruleId' | 'falsePositives' + | 'immutable' | 'maxSignals' | 'savedId' | 'riskScore' @@ -99,11 +100,25 @@ export type OutputRuleAlertRest = RuleAlertParamsRest & { id: string; created_by: string | undefined | null; updated_by: string | undefined | null; + immutable: boolean; }; export type ImportRuleAlertRest = Omit & { id: string | undefined | null; rule_id: string; + immutable: boolean; }; +export type PrepackagedRules = Omit< + RuleAlertParamsRest, + | 'status' + | 'status_date' + | 'last_failure_at' + | 'last_success_at' + | 'last_failure_message' + | 'last_success_message' + | 'updated_at' + | 'created_at' +> & { rule_id: string; immutable: boolean }; + export type CallWithRequest = (endpoint: string, params: T, options?: U) => Promise; From c1ccb304b6e17475c1361e24c6557753c3eba963 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Fri, 17 Jan 2020 17:57:24 -0600 Subject: [PATCH 03/11] Management API - redirect on disabled app path (#55136) * redirect on disabled management app path --- .../kibana/public/management/landing.html | 2 +- src/plugins/management/public/management_app.tsx | 7 ++++++- .../management_test_plugin/public/plugin.tsx | 16 ++++++++++++++++ .../test_suites/management/management_plugin.js | 8 ++++++++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/management/landing.html b/src/legacy/core_plugins/kibana/public/management/landing.html index a69033e4131c9..39459b26f7415 100644 --- a/src/legacy/core_plugins/kibana/public/management/landing.html +++ b/src/legacy/core_plugins/kibana/public/management/landing.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx index f7e8dba4f8210..705d98eaaf2ff 100644 --- a/src/plugins/management/public/management_app.tsx +++ b/src/plugins/management/public/management_app.tsx @@ -34,7 +34,7 @@ export class ManagementApp { readonly basePath: string; readonly order: number; readonly mount: ManagementSectionMount; - protected enabledStatus: boolean = true; + private enabledStatus = true; constructor( { id, title, basePath, order = 100, mount }: CreateManagementApp, @@ -54,6 +54,11 @@ export class ManagementApp { title, mount: async ({}, params) => { let appUnmount: Unmount; + if (!this.enabledStatus) { + const [coreStart] = await getStartServices(); + coreStart.application.navigateToApp('kibana#/management'); + return () => {}; + } async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) { const [coreStart] = await getStartServices(); coreStart.chrome.setBreadcrumbs([ diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx index 8b7cdd653ed8c..f3b7a19f70ae3 100644 --- a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -62,6 +62,22 @@ export class ManagementTestPlugin }; }, }); + + testSection! + .registerApp({ + id: 'test-management-disabled', + title: 'Management Test Disabled', + mount(params) { + params.setBreadcrumbs([{ text: 'Management Test Disabled' }]); + ReactDOM.render(
This is a secret that should never be seen!
, params.element); + + return () => { + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }) + .disable(); + return {}; } diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js index d65fb1dcd3a7e..0c185f4b385b5 100644 --- a/test/plugin_functional/test_suites/management/management_plugin.js +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -36,5 +36,13 @@ export default function({ getService, getPageObjects }) { await testSubjects.click('test-management-link-basepath'); await testSubjects.existOrFail('test-management-link-one'); }); + + it('should redirect when app is disabled', async () => { + await PageObjects.common.navigateToActualUrl( + 'kibana', + 'management/test-section/test-management-disabled' + ); + await testSubjects.existOrFail('management-landing'); + }); }); } From 6760c3394d3573cef7c97ef56af0b5940cc2912e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Fri, 17 Jan 2020 19:22:22 -0500 Subject: [PATCH 04/11] [SIEM] Detections add alert & signal tab (#55127) * add alert on detections * review I + fix unit test * review II * review III * review IV + bug fixes found during review * review VI --- .../public/components/alerts_viewer/index.tsx | 7 +- .../components/alerts_viewer/translations.ts | 6 +- .../public/components/link_to/link_to.tsx | 7 ++ .../link_to/redirect_to_detection_engine.tsx | 19 ++- .../components/matrix_histogram/index.tsx | 5 +- .../anomalies_query_tab_body/index.tsx | 17 ++- .../containers/matrix_histogram/index.tsx | 1 - .../containers/matrix_histogram/utils.ts | 12 +- .../components/signals/translations.ts | 2 +- .../signals_histogram_panel/config.ts | 21 ++-- .../signals_histogram_panel/index.tsx | 2 +- .../signals_histogram/index.tsx | 2 +- .../detection_engine/detection_engine.tsx | 116 +++++++++++++----- .../public/pages/detection_engine/index.tsx | 16 ++- .../detection_engine/rules/details/index.tsx | 47 ++++--- .../detection_engine/rules/translations.ts | 2 +- .../pages/detection_engine/translations.ts | 6 +- .../public/pages/detection_engine/types.ts | 10 ++ .../authentications_query_tab_body.tsx | 3 +- .../navigation/events_query_tab_body.tsx | 5 +- .../siem/public/pages/hosts/translations.ts | 21 ---- .../network/navigation/dns_query_tab_body.tsx | 3 +- .../siem/public/pages/network/translations.ts | 4 - .../overview/alerts_by_category/index.tsx | 31 ++--- .../overview/events_by_dataset/index.tsx | 12 +- .../public/pages/overview/translations.ts | 4 + 26 files changed, 240 insertions(+), 141 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx index 87d83f7f2972c..0b99a8b059df7 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/index.tsx @@ -16,11 +16,11 @@ import { MatrixHistogramGqlQuery } from '../../containers/matrix_histogram/index const ID = 'alertsOverTimeQuery'; export const alertsStackByOptions: MatrixHistogramOption[] = [ { - text: i18n.CATEGORY, + text: 'event.category', value: 'event.category', }, { - text: i18n.MODULE, + text: 'event.module', value: 'event.module', }, ]; @@ -54,7 +54,6 @@ export const AlertsView = ({ <> diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts index 8c6248e38c057..408c406a854be 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/translations.ts @@ -14,10 +14,14 @@ export const TOTAL_COUNT_OF_ALERTS = i18n.translate('xpack.siem.alertsView.total defaultMessage: 'alerts match the search criteria', }); -export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsDocumentType', { +export const ALERTS_TABLE_TITLE = i18n.translate('xpack.siem.alertsView.alertsTableTitle', { defaultMessage: 'Alerts', }); +export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.alertsView.alertsGraphTitle', { + defaultMessage: 'Alert detection frequency', +}); + export const ALERTS_STACK_BY_MODULE = i18n.translate( 'xpack.siem.alertsView.alertsStackByOptions.module', { diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 7c15af3fe642a..0519f5c7c956b 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -20,6 +20,7 @@ import { RedirectToHostsPage, RedirectToHostDetailsPage } from './redirect_to_ho import { RedirectToNetworkPage } from './redirect_to_network'; import { RedirectToOverviewPage } from './redirect_to_overview'; import { RedirectToTimelinesPage } from './redirect_to_timelines'; +import { DetectionEngineTab } from '../../pages/detection_engine/types'; interface LinkToPageProps { match: RouteMatch<{}>; @@ -63,6 +64,12 @@ export const LinkToPage = React.memo(({ match }) => ( path={`${match.url}/:pageName(${SiemPageName.detectionEngine})`} strict /> + ; export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine'; export const RedirectToDetectionEnginePage = ({ + match: { + params: { tabName }, + }, location: { search }, -}: DetectionEngineComponentProps) => ( - -); +}: DetectionEngineComponentProps) => { + const defaultSelectedTab = DetectionEngineTab.signals; + const selectedTab = tabName ? tabName : defaultSelectedTab; + const to = `/${DETECTION_ENGINE_PAGE_NAME}/${selectedTab}${search}`; + + return ; +}; export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineComponentProps) => { return ; @@ -28,7 +37,7 @@ export const RedirectToRulesPage = ({ location: { search } }: DetectionEngineCom export const RedirectToCreateRulePage = ({ location: { search }, }: DetectionEngineComponentProps) => { - return ; + return ; }; export const RedirectToRuleDetailsPage = ({ @@ -44,6 +53,8 @@ export const RedirectToEditRulePage = ({ location: { search } }: DetectionEngine }; export const getDetectionEngineUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}`; +export const getDetectionEngineAlertUrl = () => + `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/${DetectionEngineTab.alerts}`; export const getRulesUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules`; export const getCreateRuleUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/create-rule`; export const getRuleDetailsUrl = () => `#/link-to/${DETECTION_ENGINE_PAGE_NAME}/rules/rule-details`; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx index 56ebbb06f3eb9..cdd62c430a50c 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.tsx @@ -46,12 +46,12 @@ export const MatrixHistogramComponent: React.FC { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + const [, siemJobs] = useSiemJobs(true); const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); @@ -51,21 +59,12 @@ export const AnomaliesQueryTabBody = ({ ip ); - useEffect(() => { - return () => { - if (deleteQuery) { - deleteQuery({ id: ID }); - } - }; - }, []); - return ( <> void; errorMessage: string; headerChildren?: React.ReactNode; hideHistogramIfEmpty?: boolean; diff --git a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts index 9cda9d8f6115f..1df1aec76627c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/containers/matrix_histogram/utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { getOr } from 'lodash/fp'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { MatrixHistogramDataTypes, MatrixHistogramQueryProps, @@ -35,7 +35,7 @@ export const useQuery = ({ }: MatrixHistogramQueryProps) => { const [defaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); const [, dispatchToaster] = useStateToaster(); - const [refetch, setRefetch] = useState(); + const refetch = useRef(); const [loading, setLoading] = useState(false); const [data, setData] = useState(null); const [inspect, setInspect] = useState(null); @@ -71,7 +71,7 @@ export const useQuery = ({ return apolloClient .query({ query, - fetchPolicy: 'cache-first', + fetchPolicy: 'network-only', variables: matrixHistogramVariables, context: { fetchOptions: { @@ -103,9 +103,7 @@ export const useQuery = ({ } ); } - setRefetch(() => { - fetchData(); - }); + refetch.current = fetchData; fetchData(); return () => { isSubscribed = false; @@ -122,5 +120,5 @@ export const useQuery = ({ endDate, ]); - return { data, loading, inspect, totalCount, refetch }; + return { data, loading, inspect, totalCount, refetch: refetch.current }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts index d1ba946be41de..c262f907c9876 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/translations.ts @@ -11,7 +11,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', }); export const SIGNALS_TABLE_TITLE = i18n.translate('xpack.siem.detectionEngine.signals.tableTitle', { - defaultMessage: 'All signals', + defaultMessage: 'Signals', }); export const SIGNALS_DOCUMENT_TYPE = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts index f329780b075e3..d475fd155ea25 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts @@ -4,18 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as i18n from './translations'; import { SignalsHistogramOption } from './types'; export const signalsHistogramOptions: SignalsHistogramOption[] = [ - { text: i18n.STACK_BY_RISK_SCORES, value: 'signal.rule.risk_score' }, - { text: i18n.STACK_BY_SEVERITIES, value: 'signal.rule.severity' }, - { text: i18n.STACK_BY_DESTINATION_IPS, value: 'destination.ip' }, - { text: i18n.STACK_BY_ACTIONS, value: 'event.action' }, - { text: i18n.STACK_BY_CATEGORIES, value: 'event.category' }, - { text: i18n.STACK_BY_HOST_NAMES, value: 'host.name' }, - { text: i18n.STACK_BY_RULE_TYPES, value: 'signal.rule.type' }, - { text: i18n.STACK_BY_RULE_NAMES, value: 'signal.rule.name' }, - { text: i18n.STACK_BY_SOURCE_IPS, value: 'source.ip' }, - { text: i18n.STACK_BY_USERS, value: 'user.name' }, + { text: 'signal.rule.risk_score', value: 'signal.rule.risk_score' }, + { text: 'signal.rule.severity', value: 'signal.rule.severity' }, + { text: 'destination.ip', value: 'destination.ip' }, + { text: 'event.action', value: 'event.action' }, + { text: 'event.category', value: 'event.category' }, + { text: 'host.name', value: 'host.name' }, + { text: 'signal.rule.type', value: 'signal.rule.type' }, + { text: 'signal.rule.name', value: 'signal.rule.name' }, + { text: 'source.ip', value: 'source.ip' }, + { text: 'user.name', value: 'user.name' }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx index fda40f5f9fa5d..64bc7ba24c689 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx @@ -46,7 +46,7 @@ export const SignalsHistogramPanel = memo( filters, query, from, - legendPosition = 'bottom', + legendPosition = 'right', loadingInitial = false, showLinkToSignals = false, showTotalSignalsCount = false, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx index 218fcc3a70f79..d4db8cc7c37e8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx @@ -44,7 +44,7 @@ export const SignalsHistogram = React.memo( from, query, filters, - legendPosition = 'bottom', + legendPosition = 'right', loadingInitial, setTotalSignalsCount, stackByField, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 388f667f47fe1..26a9ad128b1dc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -4,27 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButton, EuiSpacer } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import { EuiButton, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; - import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; + +import { Query } from '../../../../../../../src/plugins/data/common/query'; +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; + +import { GlobalTime } from '../../containers/global_time'; +import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; +import { AlertsTable } from '../../components/alerts_viewer/alerts_table'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine'; import { SiemSearchBar } from '../../components/search_bar'; import { WrapperPage } from '../../components/wrapper_page'; -import { GlobalTime } from '../../containers/global_time'; -import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; -import { SpyRoute } from '../../utils/route/spy_routes'; - -import { Query } from '../../../../../../../src/plugins/data/common/query'; -import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; import { State } from '../../store'; import { inputsSelectors } from '../../store/inputs'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; +import { SpyRoute } from '../../utils/route/spy_routes'; import { InputsModelId } from '../../store/inputs/constants'; import { InputsRange } from '../../store/inputs/model'; +import { AlertsByCategory } from '../overview/alerts_by_category'; import { useSignalInfo } from './components/signals_info'; import { SignalsTable } from './components/signals'; import { NoWriteSignalsCallOut } from './components/no_write_signals_callout'; @@ -35,6 +39,7 @@ import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; +import { DetectionEngineTab } from './types'; interface ReduxProps { filters: esFilters.Filter[]; @@ -51,8 +56,22 @@ export interface DispatchProps { type DetectionEngineComponentProps = ReduxProps & DispatchProps; +const detectionsTabs = [ + { + id: DetectionEngineTab.signals, + name: i18n.SIGNAL, + disabled: false, + }, + { + id: DetectionEngineTab.alerts, + name: i18n.ALERT, + disabled: false, + }, +]; + const DetectionEngineComponent = React.memo( ({ filters, query, setAbsoluteRangeDatePicker }) => { + const { tabName = DetectionEngineTab.signals } = useParams(); const { loading, isSignalIndexExists, @@ -87,6 +106,25 @@ const DetectionEngineComponent = React.memo( ); } + + const tabs = useMemo( + () => ( + + {detectionsTabs.map(tab => ( + + {tab.name} + + ))} + + ), + [detectionsTabs, tabName] + ); + return ( <> {hasIndexWrite != null && !hasIndexWrite && } @@ -99,7 +137,6 @@ const DetectionEngineComponent = React.memo( @@ -117,26 +154,49 @@ const DetectionEngineComponent = React.memo( - {({ to, from }) => ( + {({ to, from, deleteQuery, setQuery }) => ( <> - + {tabs} - + {tabName === DetectionEngineTab.signals && ( + <> + + + + + )} + {tabName === DetectionEngineTab.alerts && ( + <> + + + + + )} )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index c4e83429aebdb..7a0b8df85416c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -7,12 +7,13 @@ import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; +import { ManageUserInfo } from './components/user_info'; import { CreateRuleComponent } from './rules/create'; import { DetectionEngine } from './detection_engine'; import { EditRuleComponent } from './rules/edit'; import { RuleDetails } from './rules/details'; import { RulesComponent } from './rules'; -import { ManageUserInfo } from './components/user_info'; +import { DetectionEngineTab } from './types'; const detectionEnginePath = `/:pageName(detection-engine)`; @@ -21,7 +22,11 @@ type Props = Partial> & { url: string }; export const DetectionEngineContainer = React.memo(() => ( - + @@ -30,7 +35,7 @@ export const DetectionEngineContainer = React.memo(() => ( - + @@ -39,7 +44,10 @@ export const DetectionEngineContainer = React.memo(() => ( ( - + )} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 099006a34920c..86d7178e73c60 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -12,6 +12,7 @@ import { EuiSpacer, EuiHealth, EuiTab, + EuiTabs, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo, useCallback, useMemo, useState } from 'react'; @@ -78,14 +79,19 @@ export interface DispatchProps { }>; } +enum RuleDetailTabs { + signals = 'signals', + failures = 'failures', +} + const ruleDetailTabs = [ { - id: 'signal', + id: RuleDetailTabs.signals, name: detectionI18n.SIGNAL, disabled: false, }, { - id: 'failure', + id: RuleDetailTabs.failures, name: i18n.FAILURE_HISTORY_TAB, disabled: false, }, @@ -106,7 +112,7 @@ const RuleDetailsComponent = memo( } = useUserInfo(); const { ruleId } = useParams(); const [isLoading, rule] = useRule(ruleId); - const [ruleDetailTab, setRuleDetailTab] = useState('signal'); + const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ rule, detailsView: true, @@ -187,22 +193,27 @@ const RuleDetailsComponent = memo( : 'subdued'; const tabs = useMemo( - () => - ruleDetailTabs.map(tab => ( - setRuleDetailTab(tab.id)} - isSelected={tab.id === ruleDetailTab} - disabled={tab.disabled} - key={tab.name} - > - {tab.name} - - )), + () => ( + + {ruleDetailTabs.map(tab => ( + setRuleDetailTab(tab.id)} + isSelected={tab.id === ruleDetailTab} + disabled={tab.disabled} + key={tab.id} + > + {tab.name} + + ))} + + ), [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] ); const ruleError = useMemo( () => - rule?.status === 'failed' && ruleDetailTab === 'signal' && rule?.last_failure_at != null ? ( + rule?.status === 'failed' && + ruleDetailTab === RuleDetailTabs.signals && + rule?.last_failure_at != null ? ( ( {ruleError} {tabs} - {ruleDetailTab === 'signal' && ( + {ruleDetailTab === RuleDetailTabs.signals && ( <> @@ -381,7 +392,9 @@ const RuleDetailsComponent = memo( )} )} - {ruleDetailTab === 'failure' && } + {ruleDetailTab === RuleDetailTabs.failures && ( + + )} )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index aeeef925d60e5..30b50c8cce209 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -22,7 +22,7 @@ export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.add }); export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { - defaultMessage: 'Rules', + defaultMessage: 'Signal detection rules', }); export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules.refreshTitle', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index e5f830d3a49b0..d1935b4fd581d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -22,8 +22,12 @@ export const SIGNAL = i18n.translate('xpack.siem.detectionEngine.signalTitle', { defaultMessage: 'Signals', }); +export const ALERT = i18n.translate('xpack.siem.detectionEngine.alertTitle', { + defaultMessage: 'Third-party alerts', +}); + export const BUTTON_MANAGE_RULES = i18n.translate('xpack.siem.detectionEngine.buttonManageRules', { - defaultMessage: 'Manage rules', + defaultMessage: 'Manage signal detection rules', }); export const PANEL_SUBTITLE_SHOWING = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts new file mode 100644 index 0000000000000..d529d99ad3ad4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/types.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum DetectionEngineTab { + signals = 'signals', + alerts = 'alerts', +} diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx index 0bb9563296316..0109eeef91463 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/authentications_query_tab_body.tsx @@ -25,7 +25,7 @@ const AuthenticationTableManage = manageQuery(AuthenticationTable); const ID = 'authenticationsOverTimeQuery'; const authStackByOptions: MatrixHistogramOption[] = [ { - text: i18n.NAVIGATION_AUTHENTICATIONS_STACK_BY_EVENT_TYPE, + text: 'event.type', value: 'event.type', }, ]; @@ -71,7 +71,6 @@ export const AuthenticationsQueryTabBody = ({ isAuthenticationsHistogram={true} dataKey="AuthenticationsHistogram" defaultStackByOption={authStackByOptions[0]} - deleteQuery={deleteQuery} endDate={endDate} errorMessage={i18n.ERROR_FETCHING_AUTHENTICATIONS_DATA} filterQuery={filterQuery} diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx index a07cbc8484a1b..85bca90cc8e04 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/navigation/events_query_tab_body.tsx @@ -20,11 +20,11 @@ const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery'; export const eventsStackByOptions: MatrixHistogramOption[] = [ { - text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_ACTION, + text: 'event.action', value: 'event.action', }, { - text: i18n.NAVIGATION_EVENTS_STACK_BY_EVENT_DATASET, + text: 'event.dataset', value: 'event.dataset', }, ]; @@ -50,7 +50,6 @@ export const EventsQueryTabBody = ({ void; filters?: esFilters.Filter[]; from: number; + hideHeaderChildren?: boolean; indexPattern: IIndexPattern; query?: Query; setAbsoluteRangeDatePicker: SetAbsoluteRangeDatePicker; @@ -60,14 +60,24 @@ export const AlertsByCategory = React.memo( deleteQuery, filters = NO_FILTERS, from, + hideHeaderChildren = false, indexPattern, query = DEFAULT_QUERY, setAbsoluteRangeDatePicker, setQuery, to, }) => { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const updateDateRangeCallback = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); @@ -76,17 +86,11 @@ export const AlertsByCategory = React.memo( ); const alertsCountViewAlertsButton = useMemo( () => ( - - {i18n.VIEW_ALERTS} - + {i18n.VIEW_ALERTS} ), [] ); - const getTitle = useCallback( - (option: MatrixHistogramOption) => i18n.ALERTS_COUNT_BY(option.text), - [] - ); const getSubtitle = useCallback( (totalCount: number) => `${SHOWING}: ${numeral(totalCount).format(defaultNumberFormat)} ${UNIT(totalCount)}`, @@ -96,7 +100,6 @@ export const AlertsByCategory = React.memo( return ( ( queries: [query], filters, })} - headerChildren={alertsCountViewAlertsButton} + headerChildren={hideHeaderChildren ? null : alertsCountViewAlertsButton} id={ID} isAlertsHistogram={true} legendPosition={'right'} @@ -115,7 +118,7 @@ export const AlertsByCategory = React.memo( sourceId="default" stackByOptions={alertsStackByOptions} startDate={from} - title={getTitle} + title={i18n.ALERTS_GRAPH_TITLE} subtitle={getSubtitle} type={HostsType.page} updateDateRange={updateDateRangeCallback} diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx index 52084c4bfc280..191b4a2592695 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/events_by_dataset/index.tsx @@ -6,7 +6,7 @@ import { EuiButton } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { esFilters, IIndexPattern, Query } from 'src/plugins/data/public'; import styled from 'styled-components'; @@ -66,8 +66,17 @@ export const EventsByDataset = React.memo( setQuery, to, }) => { + useEffect(() => { + return () => { + if (deleteQuery) { + deleteQuery({ id: ID }); + } + }; + }, []); + const kibana = useKibana(); const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const updateDateRangeCallback = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker!({ id: 'global', from: min, to: max }); @@ -96,7 +105,6 @@ export const EventsByDataset = React.memo( return ( defaultMessage: 'Alerts count by {groupByField}', }); +export const ALERTS_GRAPH_TITLE = i18n.translate('xpack.siem.overview.alertsGraphTitle', { + defaultMessage: 'Alert detection frequency', +}); + export const EVENTS_COUNT_BY = (groupByField: string) => i18n.translate('xpack.siem.overview.eventsCountByTitle', { values: { groupByField }, From dfb35787934ca401bb39a17c22417fe4ec2c4c5b Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Fri, 17 Jan 2020 18:41:00 -0700 Subject: [PATCH 05/11] [SIEM] [Detection Engine] Fixes duplicate rule action (#55252) ## Summary This PR fixes the duplication of rules. The DE backend was updated to not allow `immutable` when creating a rule, so this broke the `Duplicate Rule` action as we were creating a new rule with `immutable: false`. This PR also switches rule duplication over to use the bulk `create` API introduced in https://github.com/elastic/kibana/pull/53543, so now we can duplicate multiple rules. And lastly, this PR removes the limitation of not being able to delete immutable rules. So long as you have the appropriate `write` permissions the delete action is now always available. ![duplicate_batch](https://user-images.githubusercontent.com/2946766/72652638-cee69a00-3944-11ea-9e15-cce3f2b8cefe.gif) ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. - [ ] ~This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~ - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] ~[Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~ - [ ] ~[Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~ - [ ] ~This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~ ### For maintainers - [ ] ~This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ - [ ] ~This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~ --- .../containers/detection_engine/rules/api.ts | 50 +++++++++---------- .../detection_engine/rules/types.ts | 2 +- .../detection_engine/rules/all/actions.tsx | 22 +++++--- .../rules/all/batch_actions.tsx | 27 ++++++---- .../detection_engine/rules/all/columns.tsx | 5 +- .../rule_actions_overflow/index.tsx | 6 +-- .../detection_engine/rules/translations.ts | 6 +-- 7 files changed, 65 insertions(+), 53 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 1e621363f5f29..8cd3e8f2d45c7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -178,41 +178,41 @@ export const deleteRules = async ({ ids }: DeleteRulesProps): Promise => { - const requests = rules.map(rule => - fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'kbn-xsrf': 'true', }, - body: JSON.stringify({ - ...rule, - name: `${rule.name} [${i18n.DUPLICATE}]`, - created_at: undefined, - created_by: undefined, - id: undefined, - rule_id: undefined, - updated_at: undefined, - updated_by: undefined, - enabled: rule.enabled, - immutable: false, - last_success_at: undefined, - last_success_message: undefined, - status: undefined, - status_date: undefined, - }), - }) + body: JSON.stringify( + rules.map(rule => ({ + ...rule, + name: `${rule.name} [${i18n.DUPLICATE}]`, + created_at: undefined, + created_by: undefined, + id: undefined, + rule_id: undefined, + updated_at: undefined, + updated_by: undefined, + enabled: rule.enabled, + immutable: undefined, + last_success_at: undefined, + last_success_message: undefined, + status: undefined, + status_date: undefined, + })) + ), + } ); - const responses = await Promise.all(requests); - await responses.map(response => throwIfNotOk(response)); - return Promise.all( - responses.map>(response => response.json()) - ); + await throwIfNotOk(response); + return response.json(); }; /** diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index feef888c0d47f..334daa8d1d028 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -146,7 +146,7 @@ export interface DeleteRulesProps { } export interface DuplicateRulesProps { - rules: Rules; + rules: Rule[]; } export interface BasicFetchProps { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index f83a19445acd6..435edcab433b6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -29,17 +29,25 @@ export const editRuleAction = (rule: Rule, history: H.History) => { history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`); }; -export const duplicateRuleAction = async ( - rule: Rule, +export const duplicateRulesAction = async ( + rules: Rule[], dispatch: React.Dispatch, dispatchToaster: Dispatch ) => { try { - dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true }); - const duplicatedRule = await duplicateRules({ rules: [rule] }); - dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false }); - dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id }); - displaySuccessToast(i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRule.length), dispatchToaster); + const ruleIds = rules.map(r => r.id); + dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: true }); + const duplicatedRules = await duplicateRules({ rules }); + dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: false }); + dispatch({ + type: 'updateRules', + rules: duplicatedRules, + appendRuleId: rules[rules.length - 1].id, + }); + displaySuccessToast( + i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRules.length), + dispatchToaster + ); } catch (e) { displayErrorToast(i18n.DUPLICATE_RULE_ERROR, [e.message], dispatchToaster); } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index 06d4c709a32bf..8a10d4f7100b9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -10,9 +10,13 @@ import * as H from 'history'; import * as i18n from '../translations'; import { TableData } from '../types'; import { Action } from './reducer'; -import { deleteRulesAction, enableRulesAction, exportRulesAction } from './actions'; +import { + deleteRulesAction, + duplicateRulesAction, + enableRulesAction, + exportRulesAction, +} from './actions'; import { ActionToaster } from '../../../../components/toasters'; -import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; export const getBatchItems = ( selectedState: TableData[], @@ -25,7 +29,6 @@ export const getBatchItems = ( const containsDisabled = selectedState.some(v => !v.activate); const containsLoading = selectedState.some(v => v.isLoading); const containsImmutable = selectedState.some(v => v.immutable); - const containsMultipleRules = Array.from(new Set(selectedState.map(v => v.rule_id))).length > 1; return [ , { closePopover(); - history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${selectedState[0].id}/edit`); + await duplicateRulesAction( + selectedState.map(s => s.sourceRule), + dispatch, + dispatchToaster + ); }} > - {i18n.BATCH_ACTION_EDIT_INDEX_PATTERNS} + {i18n.BATCH_ACTION_DUPLICATE_SELECTED} , { closePopover(); await deleteRulesAction( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 91b018eb3078f..a73e656dddd96 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -18,7 +18,7 @@ import React, { Dispatch } from 'react'; import { getEmptyTagValue } from '../../../../components/empty_value'; import { deleteRulesAction, - duplicateRuleAction, + duplicateRulesAction, editRuleAction, exportRulesAction, } from './actions'; @@ -48,7 +48,7 @@ const getActions = ( icon: 'copy', name: i18n.DUPLICATE_RULE, onClick: (rowItem: TableData) => - duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), + duplicateRulesAction([rowItem.sourceRule], dispatch, dispatchToaster), }, { description: i18n.EXPORT_RULE, @@ -62,7 +62,6 @@ const getActions = ( icon: 'trash', name: i18n.DELETE_RULE, onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), - enabled: (rowItem: TableData) => !rowItem.immutable, }, ]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx index 0a823ce545d72..b996bce8ab500 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_overflow/index.tsx @@ -18,7 +18,7 @@ import { useHistory } from 'react-router-dom'; import { Rule } from '../../../../../containers/detection_engine/rules'; import * as i18n from './translations'; import * as i18nActions from '../../../rules/translations'; -import { deleteRulesAction, duplicateRuleAction } from '../../all/actions'; +import { deleteRulesAction, duplicateRulesAction } from '../../all/actions'; import { displaySuccessToast, useStateToaster } from '../../../../../components/toasters'; import { RuleDownloader } from '../rule_downloader'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine'; @@ -54,7 +54,7 @@ const RuleActionsOverflowComponent = ({ disabled={userHasNoPermissions} onClick={async () => { setIsPopoverOpen(false); - await duplicateRuleAction(rule, noop, dispatchToaster); + await duplicateRulesAction([rule], noop, dispatchToaster); }} > {i18nActions.DUPLICATE_RULE} @@ -73,7 +73,7 @@ const RuleActionsOverflowComponent = ({ { setIsPopoverOpen(false); await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index 30b50c8cce209..83479b819f81e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -75,10 +75,10 @@ export const BATCH_ACTION_EXPORT_SELECTED = i18n.translate( } ); -export const BATCH_ACTION_EDIT_INDEX_PATTERNS = i18n.translate( - 'xpack.siem.detectionEngine.rules.allRules.batchActions.editIndexPatternsTitle', +export const BATCH_ACTION_DUPLICATE_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.batchActions.duplicateSelectedTitle', { - defaultMessage: 'Edit selected index patterns…', + defaultMessage: 'Duplicate selected…', } ); From 2598d15ea1b45da641e6fca4f26d86e729429e23 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Fri, 17 Jan 2020 21:47:01 -0700 Subject: [PATCH 06/11] [Reporting/Migration] ReportingSetup, LegacySetup (#54198) * ReportingSetup, LegacySetup * fix ts --- x-pack/legacy/plugins/reporting/index.ts | 78 +++---------- .../legacy/plugins/reporting/server/plugin.ts | 107 ++++++++++++++++++ x-pack/legacy/plugins/reporting/types.d.ts | 6 +- 3 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 x-pack/legacy/plugins/reporting/server/plugin.ts diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index 82d94422b70ce..52e26b3132007 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -7,47 +7,20 @@ import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; -import { IUiSettingsClient } from 'src/core/server'; -import { XPackMainPlugin } from '../xpack_main/server/xpack_main'; import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; -// @ts-ignore untyped module defintition -import { mirrorPluginStatus } from '../../server/lib/mirror_plugin_status'; -import { registerRoutes } from './server/routes'; -import { - LevelLogger, - checkLicenseFactory, - getExportTypesRegistry, - runValidations, -} from './server/lib'; -import { createBrowserDriverFactory } from './server/browsers'; -import { registerReportingUsageCollector } from './server/usage'; import { ReportingConfigOptions, ReportingPluginSpecOptions } from './types.d'; import { config as reportingConfig } from './config'; -import { logConfiguration } from './log_configuration'; +import { + LegacySetup, + ReportingPlugin, + ReportingSetupDeps, + reportingPluginFactory, +} from './server/plugin'; const kbToBase64Length = (kb: number) => { return Math.floor((kb * 1024 * 8) / 6); }; -type LegacyPlugins = Legacy.Server['plugins']; - -export interface ServerFacade { - config: Legacy.Server['config']; - info: Legacy.Server['info']; - log: Legacy.Server['log']; - plugins: { - elasticsearch: LegacyPlugins['elasticsearch']; - security: LegacyPlugins['security']; - xpack_main: XPackMainPlugin & { - status?: any; - }; - }; - route: Legacy.Server['route']; - savedObjects: Legacy.Server['savedObjects']; - uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory']; - fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown; -} - export const reporting = (kibana: any) => { return new kibana.Plugin({ id: PLUGIN_ID, @@ -93,7 +66,11 @@ export const reporting = (kibana: any) => { }, async init(server: Legacy.Server) { - const serverFacade: ServerFacade = { + const coreSetup = server.newPlatform.setup.core; + const pluginsSetup: ReportingSetupDeps = { + usageCollection: server.newPlatform.setup.plugins.usageCollection, + }; + const __LEGACY: LegacySetup = { config: server.config, info: server.info, route: server.route.bind(server), @@ -108,38 +85,9 @@ export const reporting = (kibana: any) => { fieldFormatServiceFactory: server.fieldFormatServiceFactory, log: server.log.bind(server), }; - const exportTypesRegistry = getExportTypesRegistry(); - - let isCollectorReady = false; - // Register a function with server to manage the collection of usage stats - const { usageCollection } = server.newPlatform.setup.plugins; - registerReportingUsageCollector( - usageCollection, - serverFacade, - () => isCollectorReady, - exportTypesRegistry - ); - - const logger = LevelLogger.createForServer(serverFacade, [PLUGIN_ID]); - const browserDriverFactory = await createBrowserDriverFactory(serverFacade); - - logConfiguration(serverFacade, logger); - runValidations(serverFacade, logger, browserDriverFactory); - - const { xpack_main: xpackMainPlugin } = serverFacade.plugins; - mirrorPluginStatus(xpackMainPlugin, this); - const checkLicense = checkLicenseFactory(exportTypesRegistry); - (xpackMainPlugin as any).status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(this.id).registerLicenseCheckResultsGenerator(checkLicense); - }); - - // Post initialization of the above code, the collector is now ready to fetch its data - isCollectorReady = true; - // Reporting routes - registerRoutes(serverFacade, exportTypesRegistry, browserDriverFactory, logger); + const plugin: ReportingPlugin = reportingPluginFactory(__LEGACY, this); + await plugin.setup(coreSetup, pluginsSetup); }, deprecations({ unused }: any) { diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts new file mode 100644 index 0000000000000..934a3487209c4 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Legacy } from 'kibana'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/server'; +import { IUiSettingsClient } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; +// @ts-ignore +import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; +import { PLUGIN_ID } from '../common/constants'; +import { ReportingPluginSpecOptions } from '../types.d'; +import { registerRoutes } from './routes'; +import { LevelLogger, checkLicenseFactory, getExportTypesRegistry, runValidations } from './lib'; +import { createBrowserDriverFactory } from './browsers'; +import { registerReportingUsageCollector } from './usage'; +import { logConfiguration } from '../log_configuration'; + +// For now there is no exposed functionality to other plugins +export type ReportingSetup = object; +export type ReportingStart = object; + +export interface ReportingSetupDeps { + usageCollection: UsageCollectionSetup; +} +export type ReportingStartDeps = object; + +type LegacyPlugins = Legacy.Server['plugins']; + +export interface LegacySetup { + config: Legacy.Server['config']; + info: Legacy.Server['info']; + log: Legacy.Server['log']; + plugins: { + elasticsearch: LegacyPlugins['elasticsearch']; + security: LegacyPlugins['security']; + xpack_main: XPackMainPlugin & { + status?: any; + }; + }; + route: Legacy.Server['route']; + savedObjects: Legacy.Server['savedObjects']; + uiSettingsServiceFactory: Legacy.Server['uiSettingsServiceFactory']; + fieldFormatServiceFactory: (uiConfig: IUiSettingsClient) => unknown; +} + +export type ReportingPlugin = Plugin< + ReportingSetup, + ReportingStart, + ReportingSetupDeps, + ReportingStartDeps +>; + +/* We need a factory that returns an instance of the class because the class + * implementation itself restricts against having Legacy dependencies passed + * into `setup`. The factory parameters take the legacy dependencies, and the + * `setup` method gets it from enclosure */ +export function reportingPluginFactory( + __LEGACY: LegacySetup, + legacyPlugin: ReportingPluginSpecOptions +) { + return new (class ReportingPlugin implements ReportingPlugin { + public async setup(core: CoreSetup, plugins: ReportingSetupDeps): Promise { + const exportTypesRegistry = getExportTypesRegistry(); + + let isCollectorReady = false; + // Register a function with server to manage the collection of usage stats + const { usageCollection } = plugins; + registerReportingUsageCollector( + usageCollection, + __LEGACY, + () => isCollectorReady, + exportTypesRegistry + ); + + const logger = LevelLogger.createForServer(__LEGACY, [PLUGIN_ID]); + const browserDriverFactory = await createBrowserDriverFactory(__LEGACY); + + logConfiguration(__LEGACY, logger); + runValidations(__LEGACY, logger, browserDriverFactory); + + const { xpack_main: xpackMainPlugin } = __LEGACY.plugins; + mirrorPluginStatus(xpackMainPlugin, legacyPlugin); + const checkLicense = checkLicenseFactory(exportTypesRegistry); + (xpackMainPlugin as any).status.once('green', () => { + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info.feature(PLUGIN_ID).registerLicenseCheckResultsGenerator(checkLicense); + }); + + // Post initialization of the above code, the collector is now ready to fetch its data + isCollectorReady = true; + + // Reporting routes + registerRoutes(__LEGACY, exportTypesRegistry, browserDriverFactory, logger); + + return {}; + } + + public start(core: CoreStart, plugins: ReportingStartDeps): ReportingStart { + return {}; + } + })(); +} diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index f67f44860f14a..9fae60afee4e8 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -15,7 +15,7 @@ import { CancellationToken } from './common/cancellation_token'; import { LevelLogger } from './server/lib/level_logger'; import { HeadlessChromiumDriverFactory } from './server/browsers/chromium/driver_factory'; import { BrowserType } from './server/browsers/types'; -import { ServerFacade } from './index'; +import { LegacySetup } from './server/plugin'; export type ReportingPlugin = object; // For Plugin contract @@ -69,6 +69,8 @@ interface GenerateExportTypePayload { * Legacy System */ +export type ServerFacade = LegacySetup; + export type ReportingPluginSpecOptions = Legacy.PluginSpecOptions; export type EnqueueJobFn = ( @@ -353,5 +355,3 @@ export interface InterceptedRequest { frameId: string; resourceType: string; } - -export { ServerFacade }; From b98a636de2ce2cfb4e137d3e95ab40c3706be8a9 Mon Sep 17 00:00:00 2001 From: Matt Bargar Date: Sat, 18 Jan 2020 05:01:05 -0500 Subject: [PATCH 07/11] Remove nested root from index pattern (#54978) * Revert "Add label and icon to nested fields in the doc table (#54199)" This reverts commit f77b3620 * Apply label and icon to nested fields in the doc table * Add nested type to field_icon * Improve nested test and add comment * Fix tests * Always pass the field type --- .../np_ready/components/table/table.test.tsx | 72 +++++++++---------- .../np_ready/components/table/table.tsx | 47 ++++++++++++ .../np_ready/components/table/table_row.tsx | 4 +- .../field_caps_response.test.js | 12 ++-- .../field_capabilities/field_caps_response.ts | 2 +- .../public/field_icon/field_icon.tsx | 4 +- .../fields_for_wildcard_route/response.js | 16 ----- 7 files changed, 93 insertions(+), 64 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx index f816b33bcd0ae..386f405544a61 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.test.tsx @@ -26,50 +26,48 @@ import { IndexPattern, indexPatterns } from '../../../kibana_services'; jest.mock('ui/new_platform'); -// @ts-ignore const indexPattern = { - fields: { - getByName: (name: string) => { - const fields: { [name: string]: {} } = { - _index: { - name: '_index', - type: 'string', - scripted: false, - filterable: true, - }, - message: { - name: 'message', - type: 'string', - scripted: false, - filterable: false, - }, - extension: { - name: 'extension', - type: 'string', - scripted: false, - filterable: true, - }, - bytes: { - name: 'bytes', - type: 'number', - scripted: false, - filterable: true, - }, - scripted: { - name: 'scripted', - type: 'number', - scripted: true, - filterable: false, - }, - }; - return fields[name]; + fields: [ + { + name: '_index', + type: 'string', + scripted: false, + filterable: true, + }, + { + name: 'message', + type: 'string', + scripted: false, + filterable: false, + }, + { + name: 'extension', + type: 'string', + scripted: false, + filterable: true, }, - }, + { + name: 'bytes', + type: 'number', + scripted: false, + filterable: true, + }, + { + name: 'scripted', + type: 'number', + scripted: true, + filterable: false, + }, + ], metaFields: ['_index', '_score'], flattenHit: undefined, formatHit: jest.fn(hit => hit._source), } as IndexPattern; +indexPattern.fields.getByName = (name: string) => { + return indexPattern.fields.find(field => field.name === name); +}; + indexPattern.flattenHit = indexPatterns.flattenHitWrapper(indexPattern, indexPattern.metaFields); describe('DocViewTable at Discover', () => { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.tsx index 4bb2f83016c22..85689768eb88e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table.tsx @@ -17,6 +17,7 @@ * under the License. */ import React, { useState } from 'react'; +import { escapeRegExp } from 'lodash'; import { DocViewTableRow } from './table_row'; import { arrayContainsObjects, trimAngularSpan } from './table_helper'; import { DocViewRenderProps } from '../../doc_views/doc_views_types'; @@ -68,11 +69,57 @@ export function DocViewTable({ const displayNoMappingWarning = !mapping(field) && !displayUnderscoreWarning && !isArrayOfObjects; + // Discover doesn't flatten arrays of objects, so for documents with an `object` or `nested` field that + // contains an array, Discover will only detect the top level root field. We want to detect when those + // root fields are `nested` so that we can display the proper icon and label. However, those root + // `nested` fields are not a part of the index pattern. Their children are though, and contain nested path + // info. So to detect nested fields we look through the index pattern for nested children + // whose path begins with the current field. There are edge cases where + // this could incorrectly identify a plain `object` field as `nested`. Say we had the following document + // where `foo` is a plain object field and `bar` is a nested field. + // { + // "foo": [ + // { + // "bar": [ + // { + // "baz": "qux" + // } + // ] + // }, + // { + // "bar": [ + // { + // "baz": "qux" + // } + // ] + // } + // ] + // } + // + // The following code will search for `foo`, find it at the beginning of the path to the nested child field + // `foo.bar.baz` and incorrectly mark `foo` as nested. Any time we're searching for the name of a plain object + // field that happens to match a segment of a nested path, we'll get a false positive. + // We're aware of this issue and we'll have to live with + // it in the short term. The long term fix will be to add info about the `nested` and `object` root fields + // to the index pattern, but that has its own complications which you can read more about in the following + // issue: https://github.com/elastic/kibana/issues/54957 + const isNestedField = + !indexPattern.fields.find(patternField => patternField.name === field) && + !!indexPattern.fields.find(patternField => { + // We only want to match a full path segment + const nestedRootRegex = new RegExp(escapeRegExp(field) + '(\\.|$)'); + return nestedRootRegex.test(patternField.subType?.nested?.path ?? ''); + }); + const fieldType = isNestedField + ? 'nested' + : indexPattern.fields.find(patternField => patternField.name === field)?.type; + return ( )} - + {isCollapsible && ( diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index 3ec903d5b18e4..0d787fa56b400 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -37,7 +37,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { describe('conflicts', () => { it('returns a field for each in response, no filtering', () => { const fields = readFieldCapsResponse(esResponse); - expect(fields).toHaveLength(25); + expect(fields).toHaveLength(24); }); it( @@ -68,8 +68,8 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { sandbox.spy(shouldReadFieldFromDocValuesNS, 'shouldReadFieldFromDocValues'); const fields = readFieldCapsResponse(esResponse); const conflictCount = fields.filter(f => f.type === 'conflict').length; - // +1 is for the object field which is filtered out of the final return value from readFieldCapsResponse - sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 1); + // +2 is for the object and nested fields which get filtered out of the final return value from readFieldCapsResponse + sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 2); }); it('converts es types to kibana types', () => { @@ -159,12 +159,10 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { }); }); - it('returns the nested parent as not searchable or aggregatable', () => { + it('does not include the field actually mapped as nested itself', () => { const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'nested_object_parent'); - expect(child.type).toBe('nested'); - expect(child.aggregatable).toBe(false); - expect(child.searchable).toBe(false); + expect(child).toBeUndefined(); }); it('should not confuse object children for multi or nested field children', () => { diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 0c8c2ce48fa84..2215bd8a95a1d 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -195,6 +195,6 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie }); return kibanaFormattedCaps.filter(field => { - return !['object'].includes(field.type); + return !['object', 'nested'].includes(field.type); }); } diff --git a/src/plugins/kibana_react/public/field_icon/field_icon.tsx b/src/plugins/kibana_react/public/field_icon/field_icon.tsx index 7c44fe89d0e7f..2e199a7471a64 100644 --- a/src/plugins/kibana_react/public/field_icon/field_icon.tsx +++ b/src/plugins/kibana_react/public/field_icon/field_icon.tsx @@ -36,8 +36,8 @@ interface FieldIconProps { | 'number' | '_source' | 'string' - | 'nested' - | string; + | string + | 'nested'; label?: string; size?: IconSize; useColor?: boolean; diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index 555056173ec62..25a533d39dd81 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -71,14 +71,6 @@ export default function({ getService }) { name: 'foo', readFromDocValues: true, }, - { - aggregatable: false, - esTypes: ['nested'], - name: 'nestedField', - readFromDocValues: false, - searchable: false, - type: 'nested', - }, { aggregatable: false, esTypes: ['keyword'], @@ -161,14 +153,6 @@ export default function({ getService }) { name: 'foo', readFromDocValues: true, }, - { - aggregatable: false, - esTypes: ['nested'], - name: 'nestedField', - readFromDocValues: false, - searchable: false, - type: 'nested', - }, { aggregatable: false, esTypes: ['keyword'], From 13c648b47aa2935148ad4803daf5ec4d16b55b67 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Sat, 18 Jan 2020 07:49:31 -0500 Subject: [PATCH 08/11] [SIEM] Detection engine cleanup for rule details/creation/edit page (#55069) * update extra action on rule detail to match design * remove experimental label * allow pre-package to be deleted + do not allow wrong user to create pre-packages rules * Additional look back minimum value to 1 * fix flow with edit rule * add success toaster when rule is created or updated * Fix Timeline selector loading * review ben doc + change detectin engine to detection even in url * Succeeded text size consistency in rule details page * fix description of threats * fix test * fix type * fix internatinalization * Update x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts Co-Authored-By: Garrett Spong * Update x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts Co-Authored-By: Garrett Spong * Update x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx Co-Authored-By: Garrett Spong * review I * fix type Co-authored-by: Garrett Spong --- .../public/components/header_global/index.tsx | 2 +- .../public/components/link_to/link_to.tsx | 12 +-- .../link_to/redirect_to_detection_engine.tsx | 2 +- .../components/navigation/index.test.tsx | 20 ++--- .../timeline/search_super_select/index.tsx | 18 ++++- .../public/components/url_state/constants.ts | 4 +- .../components/url_state/helpers.test.ts | 38 +-------- .../public/components/url_state/helpers.ts | 25 +----- .../siem/public/components/url_state/types.ts | 4 +- .../rules/use_create_packaged_rules.tsx | 81 +++++++++++++++++++ .../signals/use_signal_index.tsx | 2 - .../components/activity_monitor/index.tsx | 42 +++++----- .../signals_histogram_panel/translations.ts | 2 +- .../components/user_info/index.tsx | 9 +++ .../detection_engine/detection_engine.tsx | 2 +- .../public/pages/detection_engine/index.tsx | 9 +-- .../rules/all/__mocks__/mock.ts | 4 +- .../detection_engine/rules/all/columns.tsx | 4 +- .../detection_engine/rules/all/helpers.ts | 2 +- .../components/description_step/helpers.tsx | 4 +- .../__snapshots__/index.test.tsx.snap | 2 +- .../rule_actions_overflow/index.tsx | 43 +++++++--- .../components/schedule_item_form/index.tsx | 8 +- .../step_about_rule/default_value.ts | 3 +- .../components/step_about_rule/index.tsx | 14 +--- .../components/step_define_rule/index.tsx | 14 +--- .../components/step_schedule_rule/index.tsx | 14 +--- .../components/step_schedule_rule/schema.tsx | 10 +-- .../step_schedule_rule/translations.tsx | 4 +- .../detection_engine/rules/create/index.tsx | 25 ++++-- .../rules/create/translations.ts | 13 +++ .../detection_engine/rules/details/index.tsx | 4 +- .../rules/details/translations.ts | 2 +- .../detection_engine/rules/edit/index.tsx | 7 +- .../rules/edit/translations.ts | 6 ++ .../pages/detection_engine/rules/helpers.tsx | 15 +++- .../detection_engine/rules/translations.ts | 8 +- .../pages/detection_engine/translations.ts | 4 +- .../public/pages/home/home_navigations.tsx | 6 +- .../plugins/siem/public/pages/home/index.tsx | 2 +- .../siem/public/pages/home/translations.ts | 2 +- .../plugins/siem/public/pages/home/types.ts | 4 +- 42 files changed, 301 insertions(+), 195 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx index db6ff7cf55f92..5a286532fabfc 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -55,7 +55,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine display="condensed" navTabs={ hideDetectionEngine - ? pickBy((_, key) => key !== SiemPageName.detectionEngine, navTabs) + ? pickBy((_, key) => key !== SiemPageName.detections, navTabs) : navTabs } /> diff --git a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx index 0519f5c7c956b..3180fc955c690 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_to/link_to.tsx @@ -61,32 +61,32 @@ export const LinkToPage = React.memo(({ match }) => ( ; -export const DETECTION_ENGINE_PAGE_NAME = 'detection-engine'; +export const DETECTION_ENGINE_PAGE_NAME = 'detections'; export const RedirectToDetectionEnginePage = ({ match: { diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index cae209a76fc1c..b6efc07ad8fe3 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -64,12 +64,12 @@ describe('SIEM Navigation', () => { expect(setBreadcrumbs).toHaveBeenNthCalledWith(1, { detailName: undefined, navTabs: { - 'detection-engine': { + detections: { disabled: false, - href: '#/link-to/detection-engine', - id: 'detection-engine', - name: 'Detection engine', - urlKey: 'detection-engine', + href: '#/link-to/detections', + id: 'detections', + name: 'Detections', + urlKey: 'detections', }, hosts: { disabled: false, @@ -146,12 +146,12 @@ describe('SIEM Navigation', () => { detailName: undefined, filters: [], navTabs: { - 'detection-engine': { + detections: { disabled: false, - href: '#/link-to/detection-engine', - id: 'detection-engine', - name: 'Detection engine', - urlKey: 'detection-engine', + href: '#/link-to/detections', + id: 'detections', + name: 'Detections', + urlKey: 'detections', }, hosts: { disabled: false, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx index c7259edbdc593..009ab141e958e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx @@ -45,11 +45,25 @@ const MyEuiFlexItem = styled(EuiFlexItem)` white-space: nowrap; `; -const EuiSelectableContainer = styled.div` +const EuiSelectableContainer = styled.div<{ loading: boolean }>` .euiSelectable { .euiFormControlLayout__childrenWrapper { display: flex; } + ${({ loading }) => `${ + loading + ? ` + .euiFormControlLayoutIcons { + display: none; + } + .euiFormControlLayoutIcons.euiFormControlLayoutIcons--right { + display: block; + left: 12px; + top: 12px; + }` + : '' + } + `} } `; @@ -265,7 +279,7 @@ const SearchTimelineSuperSelectComponent: React.FC {({ timelines, loading, totalCount }) => ( - + { - describe('isKqlForRoute', () => { - test('host page and host page kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsPage); - expect(result).toBeTruthy(); - }); - test('host page and host details kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, undefined, CONSTANTS.hostsDetails); - expect(result).toBeFalsy(); - }); - test('host details and host details kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsDetails); - expect(result).toBeTruthy(); - }); - test('host details and host page kuery', () => { - const result = isKqlForRoute(SiemPageName.hosts, 'siem-kibana', CONSTANTS.hostsPage); - expect(result).toBeFalsy(); - }); - test('network page and network page kuery', () => { - const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkPage); - expect(result).toBeTruthy(); - }); - test('network page and network details kuery', () => { - const result = isKqlForRoute(SiemPageName.network, undefined, CONSTANTS.networkDetails); - expect(result).toBeFalsy(); - }); - test('network details and network details kuery', () => { - const result = isKqlForRoute(SiemPageName.network, '10.100.7.198', CONSTANTS.networkDetails); - expect(result).toBeTruthy(); - }); - test('network details and network page kuery', () => { - const result = isKqlForRoute(SiemPageName.network, '123.234.34', CONSTANTS.networkPage); - expect(result).toBeFalsy(); - }); - }); describe('getTitle', () => { test('host page name', () => { const result = getTitle('hosts', undefined, navTabs); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts index aa340b54c1699..6ba5810f794b0 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts @@ -78,8 +78,8 @@ export const getUrlType = (pageName: string): UrlStateType => { return 'host'; } else if (pageName === SiemPageName.network) { return 'network'; - } else if (pageName === SiemPageName.detectionEngine) { - return 'detection-engine'; + } else if (pageName === SiemPageName.detections) { + return 'detections'; } else if (pageName === SiemPageName.timelines) { return 'timeline'; } @@ -111,31 +111,14 @@ export const getCurrentLocation = ( return CONSTANTS.networkDetails; } return CONSTANTS.networkPage; - } else if (pageName === SiemPageName.detectionEngine) { - return CONSTANTS.detectionEnginePage; + } else if (pageName === SiemPageName.detections) { + return CONSTANTS.detectionsPage; } else if (pageName === SiemPageName.timelines) { return CONSTANTS.timelinePage; } return CONSTANTS.unknown; }; -export const isKqlForRoute = ( - pageName: string, - detailName: string | undefined, - queryLocation: LocationTypes | null = null -): boolean => { - const currentLocation = getCurrentLocation(pageName, detailName); - if ( - (currentLocation === CONSTANTS.hostsPage && queryLocation === CONSTANTS.hostsPage) || - (currentLocation === CONSTANTS.networkPage && queryLocation === CONSTANTS.networkPage) || - (currentLocation === CONSTANTS.hostsDetails && queryLocation === CONSTANTS.hostsDetails) || - (currentLocation === CONSTANTS.networkDetails && queryLocation === CONSTANTS.networkDetails) - ) { - return true; - } - return false; -}; - export const makeMapStateToProps = () => { const getInputsSelector = inputsSelectors.inputsSelector(); const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts index a48653a7ea6f4..be1ae1ad63bd4 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/types.ts @@ -24,7 +24,7 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [ ]; export const URL_STATE_KEYS: Record = { - 'detection-engine': [ + detections: [ CONSTANTS.appQuery, CONSTANTS.filters, CONSTANTS.savedQuery, @@ -56,7 +56,7 @@ export const URL_STATE_KEYS: Record = { }; export type LocationTypes = - | CONSTANTS.detectionEnginePage + | CONSTANTS.detectionsPage | CONSTANTS.hostsDetails | CONSTANTS.hostsPage | CONSTANTS.networkDetails diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx new file mode 100644 index 0000000000000..592419f879011 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_create_packaged_rules.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState } from 'react'; + +import { createPrepackagedRules } from './api'; + +type Return = [boolean, boolean | null]; + +interface UseCreatePackagedRules { + canUserCRUD: boolean | null; + hasIndexManage: boolean | null; + hasManageApiKey: boolean | null; + isAuthenticated: boolean | null; + isSignalIndexExists: boolean | null; +} + +/** + * Hook for creating the packages rules + * + * @param canUserCRUD boolean + * @param hasIndexManage boolean + * @param hasManageApiKey boolean + * @param isAuthenticated boolean + * @param isSignalIndexExists boolean + * + * @returns [loading, hasCreatedPackageRules] + */ +export const useCreatePackagedRules = ({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, +}: UseCreatePackagedRules): Return => { + const [hasCreatedPackageRules, setHasCreatedPackageRules] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function createRules() { + try { + await createPrepackagedRules({ + signal: abortCtrl.signal, + }); + + if (isSubscribed) { + setHasCreatedPackageRules(true); + } + } catch (error) { + if (isSubscribed) { + setHasCreatedPackageRules(false); + } + } + if (isSubscribed) { + setLoading(false); + } + } + if ( + canUserCRUD && + hasIndexManage && + hasManageApiKey && + isAuthenticated && + isSignalIndexExists + ) { + createRules(); + } + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]); + + return [loading, hasCreatedPackageRules]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx index 189d8a1bf3f75..c1ee5fd12b8c1 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx @@ -8,7 +8,6 @@ import { useEffect, useState, useRef } from 'react'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import { useStateToaster } from '../../../components/toasters'; -import { createPrepackagedRules } from '../rules'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; import { PostSignalError, SignalIndexError } from './types'; @@ -41,7 +40,6 @@ export const useSignalIndex = (): Return => { if (isSubscribed && signal != null) { setSignalIndexName(signal.name); setSignalIndexExists(true); - createPrepackagedRules({ signal: abortCtrl.signal }); } } catch (error) { if (isSubscribed) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx index 8290da1ba3220..5f017a3a1f67f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/index.tsx @@ -45,7 +45,7 @@ export const ActivityMonitor = React.memo(() => { { id: 1, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -55,7 +55,7 @@ export const ActivityMonitor = React.memo(() => { { id: 2, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -65,7 +65,7 @@ export const ActivityMonitor = React.memo(() => { { id: 3, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -76,7 +76,7 @@ export const ActivityMonitor = React.memo(() => { { id: 4, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -87,7 +87,7 @@ export const ActivityMonitor = React.memo(() => { { id: 5, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -98,7 +98,7 @@ export const ActivityMonitor = React.memo(() => { { id: 6, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -109,7 +109,7 @@ export const ActivityMonitor = React.memo(() => { { id: 7, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -120,7 +120,7 @@ export const ActivityMonitor = React.memo(() => { { id: 8, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -131,7 +131,7 @@ export const ActivityMonitor = React.memo(() => { { id: 9, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -142,7 +142,7 @@ export const ActivityMonitor = React.memo(() => { { id: 10, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -153,7 +153,7 @@ export const ActivityMonitor = React.memo(() => { { id: 11, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -164,7 +164,7 @@ export const ActivityMonitor = React.memo(() => { { id: 12, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -175,7 +175,7 @@ export const ActivityMonitor = React.memo(() => { { id: 13, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -186,7 +186,7 @@ export const ActivityMonitor = React.memo(() => { { id: 14, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -197,7 +197,7 @@ export const ActivityMonitor = React.memo(() => { { id: 15, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -208,7 +208,7 @@ export const ActivityMonitor = React.memo(() => { { id: 16, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -219,7 +219,7 @@ export const ActivityMonitor = React.memo(() => { { id: 17, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -230,7 +230,7 @@ export const ActivityMonitor = React.memo(() => { { id: 18, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -241,7 +241,7 @@ export const ActivityMonitor = React.memo(() => { { id: 19, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -252,7 +252,7 @@ export const ActivityMonitor = React.memo(() => { { id: 20, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', @@ -263,7 +263,7 @@ export const ActivityMonitor = React.memo(() => { { id: 21, rule: { - href: '#/detection-engine/rules/rule-details', + href: '#/detections/rules/rule-details', name: 'Automated exfiltration', }, ran: '2019-12-28 00:00:00.000-05:00', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts index 0245b9968cc36..8c88fa4a5dae6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts @@ -86,7 +86,7 @@ export const STACK_BY_USERS = i18n.translate( export const HISTOGRAM_HEADER = i18n.translate( 'xpack.siem.detectionEngine.signals.histogram.headerTitle', { - defaultMessage: 'Signal detection frequency', + defaultMessage: 'Signal count', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx index bbaccb7882484..24e14473d40e9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -10,6 +10,7 @@ import React, { useEffect, useReducer, Dispatch, createContext, useContext } fro import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; import { useKibana } from '../../../../lib/kibana'; +import { useCreatePackagedRules } from '../../../../containers/detection_engine/rules/use_create_packaged_rules'; export interface State { canUserCRUD: boolean | null; @@ -161,6 +162,14 @@ export const useUserInfo = (): State => { createSignalIndex, ] = useSignalIndex(); + useCreatePackagedRules({ + canUserCRUD, + hasIndexManage, + hasManageApiKey, + isAuthenticated, + isSignalIndexExists, + }); + const uiCapabilities = useKibana().services.application.capabilities; const capabilitiesCanUserCRUD: boolean = typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 26a9ad128b1dc..d9e0377b34060 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -148,7 +148,7 @@ const DetectionEngineComponent = React.memo( } title={i18n.PAGE_TITLE} > - + {i18n.BUTTON_MANAGE_RULES} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 7a0b8df85416c..33186d2787d8a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -15,7 +15,7 @@ import { RuleDetails } from './rules/details'; import { RulesComponent } from './rules'; import { DetectionEngineTab } from './types'; -const detectionEnginePath = `/:pageName(detection-engine)`; +const detectionEnginePath = `/:pageName(detections)`; type Props = Partial> & { url: string }; @@ -42,12 +42,9 @@ export const DetectionEngineContainer = React.memo(() => ( ( - + )} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts index 757c1fabfc9cd..b79b3ed091f16 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -60,7 +60,7 @@ export const mockTableData: TableData[] = [ lastResponse: { type: '—' }, method: 'saved_query', rule: { - href: '#/detection-engine/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', + href: '#/detections/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', name: 'Home Grown!', status: 'Status Placeholder', }, @@ -112,7 +112,7 @@ export const mockTableData: TableData[] = [ lastResponse: { type: '—' }, method: 'saved_query', rule: { - href: '#/detection-engine/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', + href: '#/detections/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', name: 'Home Grown!', status: 'Status Placeholder', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index a73e656dddd96..f62343f77c499 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -86,7 +86,7 @@ export const getColumns = ( field: 'method', name: i18n.COLUMN_METHOD, truncateText: true, - width: '16%', + width: '14%', }, { field: 'severity', @@ -161,7 +161,7 @@ export const getColumns = ( /> ), sortable: true, - width: '85px', + width: '95px', }, ]; const actions: RulesColumns[] = [ diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index 9666b7a5688cf..07a2f2f278987 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -24,7 +24,7 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] immutable: rule.immutable, rule_id: rule.rule_id, rule: { - href: `#/detection-engine/rules/id/${encodeURIComponent(rule.id)}`, + href: `#/detections/rules/id/${encodeURIComponent(rule.id)}`, name: rule.name, status: 'Status Placeholder', }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx index e8b6919165c8b..011c008c5b2d2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx @@ -125,7 +125,7 @@ export const buildThreatsDescription = ({ description: ( {threats.map((threat, index) => { - const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); + const tactic = tacticsOptions.find(t => t.id === threat.tactic.id); return ( @@ -133,7 +133,7 @@ export const buildThreatsDescription = ({ {threat.techniques.map(technique => { - const myTechnique = techniquesOptions.find(t => t.name === technique.name); + const myTechnique = techniquesOptions.find(t => t.id === technique.id); return ( - theme.euiColorPrimary}; + width: 40px; + height: 40px; + } +`; + interface RuleActionsOverflowComponentProps { rule: Rule | null; userHasNoPermissions: boolean; @@ -86,20 +98,29 @@ const RuleActionsOverflowComponent = ({ [rule, userHasNoPermissions] ); + const handlePopoverOpen = useCallback(() => { + setIsPopoverOpen(!isPopoverOpen); + }, [setIsPopoverOpen, isPopoverOpen]); + + const button = useMemo( + () => ( + + + + ), + [handlePopoverOpen, userHasNoPermissions] + ); + return ( <> - setIsPopoverOpen(!isPopoverOpen)} - /> - - } + button={button} closePopover={() => setIsPopoverOpen(false)} id="ruleActionsOverflow" isOpen={isPopoverOpen} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx index 0ef104e6891df..3bde2087f26b1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx @@ -150,7 +150,13 @@ export const ScheduleItem = ({ /> } > - + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts index 328c4a0f96066..92aca1cecf9f3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts @@ -5,6 +5,7 @@ */ import { AboutStepRule } from '../../types'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/search_super_select/translations'; export const threatsDefault = [ { @@ -25,7 +26,7 @@ export const stepAboutDefaultValue: AboutStepRule = { tags: [], timeline: { id: null, - title: null, + title: DEFAULT_TIMELINE_TITLE, }, threats: threatsDefault, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index 0e03a11776fb7..73c07673a82f4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -5,10 +5,11 @@ */ import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { isEqual, get } from 'lodash/fp'; +import { isEqual } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import styled from 'styled-components'; +import { setFieldValue } from '../../helpers'; import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; import * as RuleI18n from '../../translations'; import { AddItem } from '../add_item_form'; @@ -71,14 +72,7 @@ const StepAboutRuleComponent: FC = ({ isNew: false, }; setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + setFieldValue(form, schema, myDefaultValues); } }, [defaultValues]); @@ -88,7 +82,7 @@ const StepAboutRuleComponent: FC = ({ } }, [form]); - return isReadOnlyView && myStepData != null ? ( + return isReadOnlyView && myStepData.name != null ? ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx index 6bdef4a69af1e..5409a5f161bba 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx @@ -11,7 +11,7 @@ import { EuiFlexItem, EuiButton, } from '@elastic/eui'; -import { isEmpty, isEqual, get } from 'lodash/fp'; +import { isEmpty, isEqual } from 'lodash/fp'; import React, { FC, memo, useCallback, useState, useEffect } from 'react'; import styled from 'styled-components'; @@ -19,6 +19,7 @@ import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/pu import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules'; import { DEFAULT_INDEX_KEY } from '../../../../../../common/constants'; import { useUiSetting$ } from '../../../../../lib/kibana'; +import { setFieldValue } from '../../helpers'; import * as RuleI18n from '../../translations'; import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; import { StepRuleDescription } from '../description_step'; @@ -121,14 +122,7 @@ const StepDefineRuleComponent: FC = ({ if (!isEqual(myDefaultValues, myStepData)) { setMyStepData(myDefaultValues); setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig)); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + setFieldValue(form, schema, myDefaultValues); } } }, [defaultValues, indicesConfig]); @@ -152,7 +146,7 @@ const StepDefineRuleComponent: FC = ({ setOpenTimelineSearch(false); }, []); - return isReadOnlyView && myStepData != null ? ( + return isReadOnlyView && myStepData?.queryBar != null ? ( = ({ @@ -67,14 +68,7 @@ const StepScheduleRuleComponent: FC = ({ isNew: false, }; setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + setFieldValue(form, schema, myDefaultValues); } }, [defaultValues]); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx index 4da17b88b9ad0..a951c1fab7cc8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx @@ -14,13 +14,14 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalLabel', { - defaultMessage: 'Rule run interval & look-back', + defaultMessage: 'Runs every', } ), helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldIntervalHelpText', { - defaultMessage: 'How often and how far back this rule will search specified indices.', + defaultMessage: + 'Rules run periodically and detect signals within the specified time frame.', } ), }, @@ -28,15 +29,14 @@ export const schema: FormSchema = { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackLabel', { - defaultMessage: 'Additional look-back', + defaultMessage: 'Additional look-back time', } ), labelAppend: OptionalFieldLabel, helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { - defaultMessage: - 'Add more time to the look-back range in order to prevent potential gaps in signal reporting.', + defaultMessage: 'Adds time to the look-back period to prevent missed signals.', } ), }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx index feaaf4e85b2af..67bcc1af8150b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx @@ -9,13 +9,13 @@ import { i18n } from '@kbn/i18n'; export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate( 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle', { - defaultMessage: 'Complete rule without activating', + defaultMessage: 'Create rule without activating it', } ); export const COMPLETE_WITH_ACTIVATING = i18n.translate( 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle', { - defaultMessage: 'Complete rule & activate', + defaultMessage: 'Create & activate rule', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index e5656f5b081fb..cbc60015d9c87 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -9,10 +9,11 @@ import React, { useCallback, useRef, useState } from 'react'; import { Redirect } from 'react-router-dom'; import styled from 'styled-components'; +import { usePersistRule } from '../../../../containers/detection_engine/rules'; import { HeaderPage } from '../../../../components/header_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { WrapperPage } from '../../../../components/wrapper_page'; -import { usePersistRule } from '../../../../containers/detection_engine/rules'; +import { displaySuccessToast, useStateToaster } from '../../../../components/toasters'; import { SpyRoute } from '../../../../utils/route/spy_routes'; import { useUserInfo } from '../../components/user_info'; import { AccordionTitle } from '../components/accordion_title'; @@ -55,6 +56,7 @@ export const CreateRuleComponent = React.memo(() => { canUserCRUD, hasManageApiKey, } = useUserInfo(); + const [, dispatchToaster] = useStateToaster(); const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); @@ -95,6 +97,7 @@ export const CreateRuleComponent = React.memo(() => { const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); if ([0, 1].includes(stepRuleIdx)) { if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) { + setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); setIsStepRuleInEditView({ ...isStepRuleInReadOnlyView, [step]: true, @@ -203,12 +206,15 @@ export const CreateRuleComponent = React.memo(() => { async (id: RuleStep) => { const activeForm = await stepsForm.current[openAccordionId]?.submit(); if (activeForm != null && activeForm?.isValid) { + stepsData.current[openAccordionId] = { + ...stepsData.current[openAccordionId], + data: activeForm.data, + isValid: activeForm.isValid, + }; setOpenAccordionId(id); - openCloseAccordion(openAccordionId); - setIsStepRuleInEditView({ ...isStepRuleInReadOnlyView, - [openAccordionId]: openAccordionId === RuleStep.scheduleRule ? false : true, + [openAccordionId]: true, [id]: false, }); } @@ -217,6 +223,8 @@ export const CreateRuleComponent = React.memo(() => { ); if (isSaved) { + const ruleName = (stepsData.current[RuleStep.aboutRule].data as AboutStepRule).name; + displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster); return ; } @@ -224,7 +232,7 @@ export const CreateRuleComponent = React.memo(() => { <> { { { + i18n.translate('xpack.siem.detectionEngine.rules.create.successfullyCreatedRuleTitle', { + values: { ruleName }, + defaultMessage: '{ruleName} was created', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 86d7178e73c60..32e40c7547400 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -12,6 +12,7 @@ import { EuiSpacer, EuiHealth, EuiTab, + EuiText, EuiTabs, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -249,7 +250,6 @@ const RuleDetailsComponent = memo( href: `#${DETECTION_ENGINE_PAGE_NAME}/rules`, text: i18n.BACK_TO_RULES, }} - badgeOptions={{ text: i18n.EXPERIMENTAL }} border subtitle={subTitle} subtitle2={[ @@ -273,7 +273,7 @@ const RuleDetailsComponent = memo( - {rule?.status ?? getEmptyTagValue()} + {rule?.status ?? getEmptyTagValue()} {rule?.status_date && ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts index 9976abc8412bf..7b349ec646ba9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts @@ -13,7 +13,7 @@ export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.ruleDetails export const BACK_TO_RULES = i18n.translate( 'xpack.siem.detectionEngine.ruleDetails.backToRulesDescription', { - defaultMessage: 'Back to rules', + defaultMessage: 'Back to signal detection rules', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index e583461f52439..9b7833afd7f4d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -17,11 +17,12 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Redirect, useParams } from 'react-router-dom'; +import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; import { HeaderPage } from '../../../../components/header_page'; import { WrapperPage } from '../../../../components/wrapper_page'; -import { SpyRoute } from '../../../../utils/route/spy_routes'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; -import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; +import { displaySuccessToast, useStateToaster } from '../../../../components/toasters'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; import { useUserInfo } from '../../components/user_info'; import { FormHook, FormData } from '../components/shared_imports'; import { StepPanel } from '../components/step_panel'; @@ -48,6 +49,7 @@ interface ScheduleStepRuleForm extends StepRuleForm { } export const EditRuleComponent = memo(() => { + const [, dispatchToaster] = useStateToaster(); const { loading: initLoading, isSignalIndexExists, @@ -271,6 +273,7 @@ export const EditRuleComponent = memo(() => { }, []); if (isSaved || (rule != null && rule.immutable)) { + displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); return ; } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts index b81ae58e565f0..f6e56dca19c21 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/translations.ts @@ -28,3 +28,9 @@ export const SORRY_ERRORS = i18n.translate( export const BACK_TO = i18n.translate('xpack.siem.detectionEngine.editRule.backToDescription', { defaultMessage: 'Back to', }); + +export const SUCCESSFULLY_SAVED_RULE = (ruleName: string) => + i18n.translate('xpack.siem.detectionEngine.rules.update.successfullySavedRuleTitle', { + values: { ruleName }, + defaultMessage: '{ruleName} was saved', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index cc0882dd7e426..cfe6cb8da1cb0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pick } from 'lodash/fp'; +import { get, pick } from 'lodash/fp'; import { useLocation } from 'react-router-dom'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; import { Rule } from '../../../containers/detection_engine/rules'; +import { FormData, FormHook, FormSchema } from './components/shared_imports'; import { AboutStepRule, DefineStepRule, IMitreEnterpriseAttack, ScheduleStepRule } from './types'; interface GetStepsData { @@ -67,3 +68,15 @@ export const getStepsData = ({ }; export const useQuery = () => new URLSearchParams(useLocation().search); + +export const setFieldValue = ( + form: FormHook, + schema: FormSchema, + defaultValues: unknown +) => + Object.keys(schema).forEach(key => { + const val = get(key, defaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index 83479b819f81e..e1257007d44a3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const BACK_TO_DETECTION_ENGINE = i18n.translate( 'xpack.siem.detectionEngine.rules.backOptionsHeader', { - defaultMessage: 'Back to detection engine', + defaultMessage: 'Back to detections', } ); @@ -18,7 +18,7 @@ export const IMPORT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.impo }); export const ADD_NEW_RULE = i18n.translate('xpack.siem.detectionEngine.rules.addNewRuleTitle', { - defaultMessage: 'Add new rule', + defaultMessage: 'Create new rule', }); export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.rules.pageTitle', { @@ -32,7 +32,7 @@ export const REFRESH = i18n.translate('xpack.siem.detectionEngine.rules.allRules export const BATCH_ACTIONS = i18n.translate( 'xpack.siem.detectionEngine.rules.allRules.batchActionsTitle', { - defaultMessage: 'Batch actions', + defaultMessage: 'Bulk actions', } ); @@ -243,7 +243,7 @@ export const COLUMN_TAGS = i18n.translate( export const COLUMN_ACTIVATE = i18n.translate( 'xpack.siem.detectionEngine.rules.allRules.columns.activateTitle', { - defaultMessage: 'Activate', + defaultMessage: 'Activated', } ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index d1935b4fd581d..ab785a8ad2c6d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -6,8 +6,8 @@ import { i18n } from '@kbn/i18n'; -export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.pageTitle', { - defaultMessage: 'Detection engine', +export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.detectionsPageTitle', { + defaultMessage: 'Detections', }); export const LAST_SIGNAL = i18n.translate('xpack.siem.detectionEngine.lastSignalTitle', { diff --git a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx index 220f8a958aa43..c0e959c5e97fa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/home_navigations.tsx @@ -36,12 +36,12 @@ export const navTabs: SiemNavTab = { disabled: false, urlKey: 'network', }, - [SiemPageName.detectionEngine]: { - id: SiemPageName.detectionEngine, + [SiemPageName.detections]: { + id: SiemPageName.detections, name: i18n.DETECTION_ENGINE, href: getDetectionEngineUrl(), disabled: false, - urlKey: 'detection-engine', + urlKey: 'detections', }, [SiemPageName.timelines]: { id: SiemPageName.timelines, diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx index a545be447796d..b5bfdbde306ca 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx @@ -105,7 +105,7 @@ export const HomePage: React.FC = () => ( )} /> ( )} diff --git a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts index b87ea1c17a117..80800a3bd4198 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/home/translations.ts @@ -19,7 +19,7 @@ export const NETWORK = i18n.translate('xpack.siem.navigation.network', { }); export const DETECTION_ENGINE = i18n.translate('xpack.siem.navigation.detectionEngine', { - defaultMessage: 'Detection engine', + defaultMessage: 'Detections', }); export const TIMELINES = i18n.translate('xpack.siem.navigation.timelines', { diff --git a/x-pack/legacy/plugins/siem/public/pages/home/types.ts b/x-pack/legacy/plugins/siem/public/pages/home/types.ts index 101c6a69b08d1..678de6dbcc128 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/home/types.ts @@ -10,7 +10,7 @@ export enum SiemPageName { overview = 'overview', hosts = 'hosts', network = 'network', - detectionEngine = 'detection-engine', + detections = 'detections', timelines = 'timelines', } @@ -18,7 +18,7 @@ export type SiemNavTabKey = | SiemPageName.overview | SiemPageName.hosts | SiemPageName.network - | SiemPageName.detectionEngine + | SiemPageName.detections | SiemPageName.timelines; export type SiemNavTab = Record; From ec31611911c8d7630d61361f6aa22d57f86a964f Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Sat, 18 Jan 2020 14:17:36 +0100 Subject: [PATCH 09/11] Enforce camelCase format for a plugin id (#53759) * add isCamelCase function * add a warning if id is not in camelCase * document pluginId expected in camelCase * regen docs * add a test for logging * update tests. warn can be called several times for different reasons * pluginPath falls back to plugin id in snake_case * update tests * update docs * add example with id & configPath different formats Co-authored-by: Elastic Machine --- ...ugin-server.discoveredplugin.configpath.md | 2 +- .../kibana-plugin-server.discoveredplugin.md | 2 +- ...plugin-server.pluginmanifest.configpath.md | 7 +- .../kibana-plugin-server.pluginmanifest.id.md | 2 +- .../kibana-plugin-server.pluginmanifest.md | 4 +- src/core/CONVENTIONS.md | 1 + .../config_deprecation.test.ts | 25 +-- .../plugins/discovery/is_camel_case.test.ts | 42 +++++ .../server/plugins/discovery/is_camel_case.ts | 22 +++ .../discovery/plugin_manifest_parser.test.ts | 158 +++++++++++++----- .../discovery/plugin_manifest_parser.ts | 11 +- .../plugins/discovery/plugins_discovery.ts | 2 +- src/core/server/plugins/types.ts | 11 +- src/core/server/server.api.md | 8 +- 14 files changed, 224 insertions(+), 73 deletions(-) create mode 100644 src/core/server/plugins/discovery/is_camel_case.test.ts create mode 100644 src/core/server/plugins/discovery/is_camel_case.ts diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md index b0830b8d72238..4de20b9c6cccf 100644 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md +++ b/docs/development/core/server/kibana-plugin-server.discoveredplugin.configpath.md @@ -4,7 +4,7 @@ ## DiscoveredPlugin.configPath property -Root configuration path used by the plugin, defaults to "id". +Root configuration path used by the plugin, defaults to "id" in snake\_case format. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.discoveredplugin.md b/docs/development/core/server/kibana-plugin-server.discoveredplugin.md index aadf9763b1604..ea13422458c7f 100644 --- a/docs/development/core/server/kibana-plugin-server.discoveredplugin.md +++ b/docs/development/core/server/kibana-plugin-server.discoveredplugin.md @@ -16,7 +16,7 @@ export interface DiscoveredPlugin | Property | Type | Description | | --- | --- | --- | -| [configPath](./kibana-plugin-server.discoveredplugin.configpath.md) | ConfigPath | Root configuration path used by the plugin, defaults to "id". | +| [configPath](./kibana-plugin-server.discoveredplugin.configpath.md) | ConfigPath | Root configuration path used by the plugin, defaults to "id" in snake\_case format. | | [id](./kibana-plugin-server.discoveredplugin.id.md) | PluginName | Identifier of the plugin. | | [optionalPlugins](./kibana-plugin-server.discoveredplugin.optionalplugins.md) | readonly PluginName[] | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. | | [requiredPlugins](./kibana-plugin-server.discoveredplugin.requiredplugins.md) | readonly PluginName[] | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. | diff --git a/docs/development/core/server/kibana-plugin-server.pluginmanifest.configpath.md b/docs/development/core/server/kibana-plugin-server.pluginmanifest.configpath.md index 39c1eeda47e0e..6ffe396aa2ed1 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginmanifest.configpath.md +++ b/docs/development/core/server/kibana-plugin-server.pluginmanifest.configpath.md @@ -4,10 +4,15 @@ ## PluginManifest.configPath property -Root [configuration path](./kibana-plugin-server.configpath.md) used by the plugin, defaults to "id". +Root [configuration path](./kibana-plugin-server.configpath.md) used by the plugin, defaults to "id" in snake\_case format. Signature: ```typescript readonly configPath: ConfigPath; ``` + +## Example + +id: myPlugin configPath: my\_plugin + diff --git a/docs/development/core/server/kibana-plugin-server.pluginmanifest.id.md b/docs/development/core/server/kibana-plugin-server.pluginmanifest.id.md index 44e61f11fa215..104046f3ce7d0 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginmanifest.id.md +++ b/docs/development/core/server/kibana-plugin-server.pluginmanifest.id.md @@ -4,7 +4,7 @@ ## PluginManifest.id property -Identifier of the plugin. +Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. Other plugins leverage it to access plugin API, navigate to the plugin, etc. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-server.pluginmanifest.md index 9bb208a809b22..c39a702389fb3 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-server.pluginmanifest.md @@ -20,8 +20,8 @@ Should never be used in code outside of Core but is exported for documentation p | Property | Type | Description | | --- | --- | --- | -| [configPath](./kibana-plugin-server.pluginmanifest.configpath.md) | ConfigPath | Root [configuration path](./kibana-plugin-server.configpath.md) used by the plugin, defaults to "id". | -| [id](./kibana-plugin-server.pluginmanifest.id.md) | PluginName | Identifier of the plugin. | +| [configPath](./kibana-plugin-server.pluginmanifest.configpath.md) | ConfigPath | Root [configuration path](./kibana-plugin-server.configpath.md) used by the plugin, defaults to "id" in snake\_case format. | +| [id](./kibana-plugin-server.pluginmanifest.id.md) | PluginName | Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. Other plugins leverage it to access plugin API, navigate to the plugin, etc. | | [kibanaVersion](./kibana-plugin-server.pluginmanifest.kibanaversion.md) | string | The version of Kibana the plugin is compatible with, defaults to "version". | | [optionalPlugins](./kibana-plugin-server.pluginmanifest.optionalplugins.md) | readonly PluginName[] | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. | | [requiredPlugins](./kibana-plugin-server.pluginmanifest.requiredplugins.md) | readonly PluginName[] | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. | diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 61c5d5b076a44..dd83ab2daca82 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -38,6 +38,7 @@ my_plugin/    ├── index.ts    └── plugin.ts ``` +- [Manifest file](/docs/development/core/server/kibana-plugin-server.pluginmanifest.md) should be defined on top level. - Both `server` and `public` should have an `index.ts` and a `plugin.ts` file: - `index.ts` should only contain: - The `plugin` export diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts index e85f8567bfc68..5bc3887f05f93 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.ts @@ -36,7 +36,13 @@ describe('configuration deprecations', () => { await root.setup(); const logs = loggingServiceMock.collect(mockLoggingService); - expect(logs.warn).toMatchInlineSnapshot(`Array []`); + const warnings = logs.warn.flatMap(i => i); + expect(warnings).not.toContain( + '"optimize.lazy" is deprecated and has been replaced by "optimize.watch"' + ); + expect(warnings).not.toContain( + '"optimize.lazyPort" is deprecated and has been replaced by "optimize.watchPort"' + ); }); it('should log deprecation warnings for core deprecations', async () => { @@ -50,15 +56,12 @@ describe('configuration deprecations', () => { await root.setup(); const logs = loggingServiceMock.collect(mockLoggingService); - expect(logs.warn).toMatchInlineSnapshot(` - Array [ - Array [ - "\\"optimize.lazy\\" is deprecated and has been replaced by \\"optimize.watch\\"", - ], - Array [ - "\\"optimize.lazyPort\\" is deprecated and has been replaced by \\"optimize.watchPort\\"", - ], - ] - `); + const warnings = logs.warn.flatMap(i => i); + expect(warnings).toContain( + '"optimize.lazy" is deprecated and has been replaced by "optimize.watch"' + ); + expect(warnings).toContain( + '"optimize.lazyPort" is deprecated and has been replaced by "optimize.watchPort"' + ); }); }); diff --git a/src/core/server/plugins/discovery/is_camel_case.test.ts b/src/core/server/plugins/discovery/is_camel_case.test.ts new file mode 100644 index 0000000000000..eb8cb8f170dac --- /dev/null +++ b/src/core/server/plugins/discovery/is_camel_case.test.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isCamelCase } from './is_camel_case'; + +describe('isCamelCase', () => { + it('matches a string in camelCase', () => { + expect(isCamelCase('foo')).toBe(true); + expect(isCamelCase('foo1')).toBe(true); + expect(isCamelCase('fooBar')).toBe(true); + expect(isCamelCase('fooBarBaz')).toBe(true); + expect(isCamelCase('fooBAR')).toBe(true); + }); + + it('does not match strings in other cases', () => { + expect(isCamelCase('AAA')).toBe(false); + expect(isCamelCase('FooBar')).toBe(false); + expect(isCamelCase('3Foo')).toBe(false); + expect(isCamelCase('o_O')).toBe(false); + expect(isCamelCase('foo_bar')).toBe(false); + expect(isCamelCase('foo_')).toBe(false); + expect(isCamelCase('_fooBar')).toBe(false); + expect(isCamelCase('fooBar_')).toBe(false); + expect(isCamelCase('_fooBar_')).toBe(false); + }); +}); diff --git a/src/core/server/plugins/discovery/is_camel_case.ts b/src/core/server/plugins/discovery/is_camel_case.ts new file mode 100644 index 0000000000000..12069ec473f8d --- /dev/null +++ b/src/core/server/plugins/discovery/is_camel_case.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +const camelCaseRegExp = /^[a-z]{1}([a-zA-Z0-9]{1,})$/; +export function isCamelCase(candidate: string) { + return camelCaseRegExp.test(candidate); +} diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts index 17b1ac7b86045..979accb1f769e 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -20,10 +20,12 @@ import { PluginDiscoveryErrorType } from './plugin_discovery_error'; import { mockReadFile } from './plugin_manifest_parser.test.mocks'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; import { resolve } from 'path'; import { parseManifest } from './plugin_manifest_parser'; +const logger = loggingServiceMock.createLogger(); const pluginPath = resolve('path', 'existent-dir'); const pluginManifestPath = resolve(pluginPath, 'kibana.json'); const packageInfo = { @@ -43,7 +45,7 @@ test('return error when manifest is empty', async () => { cb(null, Buffer.from('')); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ message: `Unexpected end of JSON input (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, @@ -55,7 +57,7 @@ test('return error when manifest content is null', async () => { cb(null, Buffer.from('null')); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ message: `Plugin manifest must contain a JSON encoded object. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, @@ -67,7 +69,7 @@ test('return error when manifest content is not a valid JSON', async () => { cb(null, Buffer.from('not-json')); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ message: `Unexpected token o in JSON at position 1 (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, @@ -79,7 +81,7 @@ test('return error when plugin id is missing', async () => { cb(null, Buffer.from(JSON.stringify({ version: 'some-version' }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ message: `Plugin manifest must contain an "id" property. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, @@ -91,20 +93,36 @@ test('return error when plugin id includes `.` characters', async () => { cb(null, Buffer.from(JSON.stringify({ id: 'some.name', version: 'some-version' }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ message: `Plugin "id" must not include \`.\` characters. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); }); +test('logs warning if pluginId is not in camelCase format', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb(null, Buffer.from(JSON.stringify({ id: 'some_name', version: 'kibana', server: true }))); + }); + + expect(loggingServiceMock.collect(logger).warn).toHaveLength(0); + await parseManifest(pluginPath, packageInfo, logger); + expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Expect plugin \\"id\\" in camelCase, but found: some_name", + ], + ] + `); +}); + test('return error when plugin version is missing', async () => { mockReadFile.mockImplementation((path, cb) => { - cb(null, Buffer.from(JSON.stringify({ id: 'some-id' }))); + cb(null, Buffer.from(JSON.stringify({ id: 'someId' }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Plugin manifest for "some-id" must contain a "version" property. (invalid-manifest, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Plugin manifest for "someId" must contain a "version" property. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); @@ -112,11 +130,11 @@ test('return error when plugin version is missing', async () => { test('return error when plugin expected Kibana version is lower than actual version', async () => { mockReadFile.mockImplementation((path, cb) => { - cb(null, Buffer.from(JSON.stringify({ id: 'some-id', version: '6.4.2' }))); + cb(null, Buffer.from(JSON.stringify({ id: 'someId', version: '6.4.2' }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Plugin "some-id" is only compatible with Kibana version "6.4.2", but used Kibana version is "7.0.0-alpha1". (incompatible-version, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Plugin "someId" is only compatible with Kibana version "6.4.2", but used Kibana version is "7.0.0-alpha1". (incompatible-version, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.IncompatibleVersion, path: pluginManifestPath, }); @@ -126,12 +144,12 @@ test('return error when plugin expected Kibana version cannot be interpreted as mockReadFile.mockImplementation((path, cb) => { cb( null, - Buffer.from(JSON.stringify({ id: 'some-id', version: '1.0.0', kibanaVersion: 'non-sem-ver' })) + Buffer.from(JSON.stringify({ id: 'someId', version: '1.0.0', kibanaVersion: 'non-sem-ver' })) ); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Plugin "some-id" is only compatible with Kibana version "non-sem-ver", but used Kibana version is "7.0.0-alpha1". (incompatible-version, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Plugin "someId" is only compatible with Kibana version "non-sem-ver", but used Kibana version is "7.0.0-alpha1". (incompatible-version, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.IncompatibleVersion, path: pluginManifestPath, }); @@ -139,11 +157,11 @@ test('return error when plugin expected Kibana version cannot be interpreted as test('return error when plugin config path is not a string', async () => { mockReadFile.mockImplementation((path, cb) => { - cb(null, Buffer.from(JSON.stringify({ id: 'some-id', version: '7.0.0', configPath: 2 }))); + cb(null, Buffer.from(JSON.stringify({ id: 'someId', version: '7.0.0', configPath: 2 }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `The "configPath" in plugin manifest for "some-id" should either be a string or an array of strings. (invalid-manifest, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `The "configPath" in plugin manifest for "someId" should either be a string or an array of strings. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); @@ -153,12 +171,12 @@ test('return error when plugin config path is an array that contains non-string mockReadFile.mockImplementation((path, cb) => { cb( null, - Buffer.from(JSON.stringify({ id: 'some-id', version: '7.0.0', configPath: ['config', 2] })) + Buffer.from(JSON.stringify({ id: 'someId', version: '7.0.0', configPath: ['config', 2] })) ); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `The "configPath" in plugin manifest for "some-id" should either be a string or an array of strings. (invalid-manifest, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `The "configPath" in plugin manifest for "someId" should either be a string or an array of strings. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); @@ -166,11 +184,11 @@ test('return error when plugin config path is an array that contains non-string test('return error when plugin expected Kibana version is higher than actual version', async () => { mockReadFile.mockImplementation((path, cb) => { - cb(null, Buffer.from(JSON.stringify({ id: 'some-id', version: '7.0.1' }))); + cb(null, Buffer.from(JSON.stringify({ id: 'someId', version: '7.0.1' }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Plugin "some-id" is only compatible with Kibana version "7.0.1", but used Kibana version is "7.0.0-alpha1". (incompatible-version, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Plugin "someId" is only compatible with Kibana version "7.0.1", but used Kibana version is "7.0.0-alpha1". (incompatible-version, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.IncompatibleVersion, path: pluginManifestPath, }); @@ -178,11 +196,11 @@ test('return error when plugin expected Kibana version is higher than actual ver test('return error when both `server` and `ui` are set to `false` or missing', async () => { mockReadFile.mockImplementation((path, cb) => { - cb(null, Buffer.from(JSON.stringify({ id: 'some-id', version: '7.0.0' }))); + cb(null, Buffer.from(JSON.stringify({ id: 'someId', version: '7.0.0' }))); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Both "server" and "ui" are missing or set to "false" in plugin manifest for "some-id", but at least one of these must be set to "true". (invalid-manifest, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Both "server" and "ui" are missing or set to "false" in plugin manifest for "someId", but at least one of these must be set to "true". (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); @@ -190,12 +208,12 @@ test('return error when both `server` and `ui` are set to `false` or missing', a mockReadFile.mockImplementation((path, cb) => { cb( null, - Buffer.from(JSON.stringify({ id: 'some-id', version: '7.0.0', server: false, ui: false })) + Buffer.from(JSON.stringify({ id: 'someId', version: '7.0.0', server: false, ui: false })) ); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Both "server" and "ui" are missing or set to "false" in plugin manifest for "some-id", but at least one of these must be set to "true". (invalid-manifest, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Both "server" and "ui" are missing or set to "false" in plugin manifest for "someId", but at least one of these must be set to "true". (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); @@ -207,7 +225,7 @@ test('return error when manifest contains unrecognized properties', async () => null, Buffer.from( JSON.stringify({ - id: 'some-id', + id: 'someId', version: '7.0.0', server: true, unknownOne: 'one', @@ -217,21 +235,69 @@ test('return error when manifest contains unrecognized properties', async () => ); }); - await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ - message: `Manifest for plugin "some-id" contains the following unrecognized properties: unknownOne,unknownTwo. (invalid-manifest, ${pluginManifestPath})`, + await expect(parseManifest(pluginPath, packageInfo, logger)).rejects.toMatchObject({ + message: `Manifest for plugin "someId" contains the following unrecognized properties: unknownOne,unknownTwo. (invalid-manifest, ${pluginManifestPath})`, type: PluginDiscoveryErrorType.InvalidManifest, path: pluginManifestPath, }); }); +describe('configPath', () => { + test('falls back to plugin id if not specified', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb(null, Buffer.from(JSON.stringify({ id: 'plugin', version: '7.0.0', server: true }))); + }); + + const manifest = await parseManifest(pluginPath, packageInfo, logger); + expect(manifest.configPath).toBe(manifest.id); + }); + + test('falls back to plugin id in snakeCase format', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb(null, Buffer.from(JSON.stringify({ id: 'SomeId', version: '7.0.0', server: true }))); + }); + + const manifest = await parseManifest(pluginPath, packageInfo, logger); + expect(manifest.configPath).toBe('some_id'); + }); + + test('not formated to snakeCase if defined explicitly as string', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb( + null, + Buffer.from( + JSON.stringify({ id: 'someId', configPath: 'somePath', version: '7.0.0', server: true }) + ) + ); + }); + + const manifest = await parseManifest(pluginPath, packageInfo, logger); + expect(manifest.configPath).toBe('somePath'); + }); + + test('not formated to snakeCase if defined explicitly as an array of strings', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb( + null, + Buffer.from( + JSON.stringify({ id: 'someId', configPath: ['somePath'], version: '7.0.0', server: true }) + ) + ); + }); + + const manifest = await parseManifest(pluginPath, packageInfo, logger); + expect(manifest.configPath).toEqual(['somePath']); + }); +}); + test('set defaults for all missing optional fields', async () => { mockReadFile.mockImplementation((path, cb) => { - cb(null, Buffer.from(JSON.stringify({ id: 'some-id', version: '7.0.0', server: true }))); + cb(null, Buffer.from(JSON.stringify({ id: 'someId', version: '7.0.0', server: true }))); }); - await expect(parseManifest(pluginPath, packageInfo)).resolves.toEqual({ - id: 'some-id', - configPath: 'some-id', + await expect(parseManifest(pluginPath, packageInfo, logger)).resolves.toEqual({ + id: 'someId', + configPath: 'some_id', version: '7.0.0', kibanaVersion: '7.0.0', optionalPlugins: [], @@ -247,7 +313,7 @@ test('return all set optional fields as they are in manifest', async () => { null, Buffer.from( JSON.stringify({ - id: 'some-id', + id: 'someId', configPath: ['some', 'path'], version: 'some-version', kibanaVersion: '7.0.0', @@ -259,8 +325,8 @@ test('return all set optional fields as they are in manifest', async () => { ); }); - await expect(parseManifest(pluginPath, packageInfo)).resolves.toEqual({ - id: 'some-id', + await expect(parseManifest(pluginPath, packageInfo, logger)).resolves.toEqual({ + id: 'someId', configPath: ['some', 'path'], version: 'some-version', kibanaVersion: '7.0.0', @@ -277,7 +343,7 @@ test('return manifest when plugin expected Kibana version matches actual version null, Buffer.from( JSON.stringify({ - id: 'some-id', + id: 'someId', configPath: 'some-path', version: 'some-version', kibanaVersion: '7.0.0-alpha2', @@ -288,8 +354,8 @@ test('return manifest when plugin expected Kibana version matches actual version ); }); - await expect(parseManifest(pluginPath, packageInfo)).resolves.toEqual({ - id: 'some-id', + await expect(parseManifest(pluginPath, packageInfo, logger)).resolves.toEqual({ + id: 'someId', configPath: 'some-path', version: 'some-version', kibanaVersion: '7.0.0-alpha2', @@ -306,7 +372,7 @@ test('return manifest when plugin expected Kibana version is `kibana`', async () null, Buffer.from( JSON.stringify({ - id: 'some-id', + id: 'someId', version: 'some-version', kibanaVersion: 'kibana', requiredPlugins: ['some-required-plugin'], @@ -317,9 +383,9 @@ test('return manifest when plugin expected Kibana version is `kibana`', async () ); }); - await expect(parseManifest(pluginPath, packageInfo)).resolves.toEqual({ - id: 'some-id', - configPath: 'some-id', + await expect(parseManifest(pluginPath, packageInfo, logger)).resolves.toEqual({ + id: 'someId', + configPath: 'some_id', version: 'some-version', kibanaVersion: 'kibana', optionalPlugins: [], diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 93c993a0fa373..573109c9db35a 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -21,9 +21,12 @@ import { readFile, stat } from 'fs'; import { resolve } from 'path'; import { coerce } from 'semver'; import { promisify } from 'util'; +import { snakeCase } from 'lodash'; import { isConfigPath, PackageInfo } from '../../config'; +import { Logger } from '../../logging'; import { PluginManifest } from '../types'; import { PluginDiscoveryError } from './plugin_discovery_error'; +import { isCamelCase } from './is_camel_case'; const fsReadFileAsync = promisify(readFile); const fsStatAsync = promisify(stat); @@ -67,7 +70,7 @@ const KNOWN_MANIFEST_FIELDS = (() => { * @param packageInfo Kibana package info. * @internal */ -export async function parseManifest(pluginPath: string, packageInfo: PackageInfo) { +export async function parseManifest(pluginPath: string, packageInfo: PackageInfo, log: Logger) { const manifestPath = resolve(pluginPath, MANIFEST_FILE_NAME); let manifestContent; @@ -107,6 +110,10 @@ export async function parseManifest(pluginPath: string, packageInfo: PackageInfo ); } + if (!isCamelCase(manifest.id)) { + log.warn(`Expect plugin "id" in camelCase, but found: ${manifest.id}`); + } + if (!manifest.version || typeof manifest.version !== 'string') { throw PluginDiscoveryError.invalidManifest( manifestPath, @@ -161,7 +168,7 @@ export async function parseManifest(pluginPath: string, packageInfo: PackageInfo id: manifest.id, version: manifest.version, kibanaVersion: expectedKibanaVersion, - configPath: manifest.configPath || manifest.id, + configPath: manifest.configPath || snakeCase(manifest.id), requiredPlugins: Array.isArray(manifest.requiredPlugins) ? manifest.requiredPlugins : [], optionalPlugins: Array.isArray(manifest.optionalPlugins) ? manifest.optionalPlugins : [], ui: includesUiPlugin, diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index 79238afdf5c81..e7f82c9dc15ad 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -112,7 +112,7 @@ function processPluginSearchPaths$(pluginDirs: readonly string[], log: Logger) { * @param coreContext Kibana core context. */ function createPlugin$(path: string, log: Logger, coreContext: CoreContext) { - return from(parseManifest(path, coreContext.env.packageInfo)).pipe( + return from(parseManifest(path, coreContext.env.packageInfo, log)).pipe( map(manifest => { log.debug(`Successfully discovered plugin "${manifest.id}" at "${path}"`); const opaqueId = Symbol(manifest.id); diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index e717871912f46..a89e2f8c684e4 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -105,7 +105,8 @@ export type PluginOpaqueId = symbol; */ export interface PluginManifest { /** - * Identifier of the plugin. + * Identifier of the plugin. Must be a string in camelCase. Part of a plugin public contract. + * Other plugins leverage it to access plugin API, navigate to the plugin, etc. */ readonly id: PluginName; @@ -121,7 +122,11 @@ export interface PluginManifest { /** * Root {@link ConfigPath | configuration path} used by the plugin, defaults - * to "id". + * to "id" in snake_case format. + * + * @example + * id: myPlugin + * configPath: my_plugin */ readonly configPath: ConfigPath; @@ -162,7 +167,7 @@ export interface DiscoveredPlugin { readonly id: PluginName; /** - * Root configuration path used by the plugin, defaults to "id". + * Root configuration path used by the plugin, defaults to "id" in snake_case format. */ readonly configPath: ConfigPath; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 6a58666716f42..7e3fc2eb66001 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2005,9 +2005,9 @@ export const validBodyOutput: readonly ["data", "stream"]; // src/core/server/legacy/types.ts:161:3 - (ae-forgotten-export) The symbol "LegacyAppSpec" needs to be exported by the entry point index.d.ts // src/core/server/legacy/types.ts:162:16 - (ae-forgotten-export) The symbol "LegacyPluginSpec" needs to be exported by the entry point index.d.ts // src/core/server/plugins/plugins_service.ts:43:5 - (ae-forgotten-export) The symbol "InternalPluginInfo" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:221:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:222:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts -// src/core/server/plugins/types.ts:223:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:226:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:226:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:227:3 - (ae-forgotten-export) The symbol "ElasticsearchConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:228:3 - (ae-forgotten-export) The symbol "PathConfigType" needs to be exported by the entry point index.d.ts ``` From 8ae51abe193e7e5274eb04426150c3e1d79f17ba Mon Sep 17 00:00:00 2001 From: Matt Bargar Date: Sat, 18 Jan 2020 10:25:51 -0500 Subject: [PATCH 10/11] Fix KQL value suggestions for nested fields (#54820) * Revert "Flag nested fields as non-aggregatable (#51774)" This reverts commit c7046a08 * Filter out nested fields at the agg param level * Forbid nested fields in TSVB * Revert "Forbid nested fields in TSVB" This reverts commit afcaa348 Co-authored-by: Tim Roes Co-authored-by: Elastic Machine --- src/legacy/ui/public/agg_types/param_types/field.ts | 5 ++++- .../lib/field_capabilities/field_caps_response.test.js | 7 ------- .../fetcher/lib/field_capabilities/field_caps_response.ts | 8 -------- .../index_patterns/fields_for_wildcard_route/response.js | 4 ++-- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/ui/public/agg_types/param_types/field.ts index 0ca60267becec..a0fa6ad6e3189 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.ts +++ b/src/legacy/ui/public/agg_types/param_types/field.ts @@ -115,7 +115,10 @@ export class FieldParamType extends BaseParamType { const filteredFields = fields.filter((field: Field) => { const { onlyAggregatable, scriptable, filterFieldTypes } = this; - if ((onlyAggregatable && !field.aggregatable) || (!scriptable && field.scripted)) { + if ( + (onlyAggregatable && (!field.aggregatable || field.subType?.nested)) || + (!scriptable && field.scripted) + ) { return false; } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index 0d787fa56b400..8ddd18c2c67f4 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -143,13 +143,6 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } }); }); - it('returns nested sub-fields as non-aggregatable', () => { - const fields = readFieldCapsResponse(esResponse); - // Normally a keyword field would be aggregatable, but the fact that it is nested overrides that - const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); - expect(child).toHaveProperty('aggregatable', false); - }); - it('handles fields that are both nested and multi', () => { const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 2215bd8a95a1d..06eb30db0b24b 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -182,14 +182,6 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie if (Object.keys(subType).length > 0) { field.subType = subType; - - // We don't support aggregating on nested fields, trying to do so in the UI will return - // blank results. For now we will stop showing nested fields as an option for aggregation. - // Once we add support for nested fields this condition should be removed and old index - // patterns should be migrated. - if (field.subType.nested) { - field.aggregatable = false; - } } } }); diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index 25a533d39dd81..c4c71abdae125 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -72,7 +72,7 @@ export default function({ getService }) { readFromDocValues: true, }, { - aggregatable: false, + aggregatable: true, esTypes: ['keyword'], name: 'nestedField.child', readFromDocValues: true, @@ -154,7 +154,7 @@ export default function({ getService }) { readFromDocValues: true, }, { - aggregatable: false, + aggregatable: true, esTypes: ['keyword'], name: 'nestedField.child', readFromDocValues: true, From 9567cca7d0a2def8df05994a981cd1c7f6f59fe2 Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Sat, 18 Jan 2020 12:41:47 -0500 Subject: [PATCH 11/11] [SIEM] [Detection Engine] Update status on rule details page (#55201) * adds logic for returning / updating status when a rule is switched from enabled to disabled and vice versa. * update response for find rules statuses to include current status and failures * update status on demand and on enable/disable * adds ternary to allow removal of 'let' * adds savedObjectsClient to the add and upate prepackaged rules and import rules route. * fix bug where convertToSnakeCase would throw error 'cannot convert null or undefined to object' if passed null * genericize snake_case converter and updates isAuthorized to snake_case (different situation) * renaming to 'going to run' instead of executing because when task manager exits because of api key error it won't write the error status so the actual status is 'going to run' on the next interval. This is more accurate than being stuck on 'executing' because of an error we don't control and can't write a status for. * fix missed merge conflict Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> --- .../containers/detection_engine/rules/api.ts | 2 +- .../detection_engine/rules/index.ts | 1 + .../detection_engine/rules/types.ts | 8 +- .../rules/use_rule_status.tsx | 15 +-- .../detection_engine/signals/types.ts | 2 +- .../signals/use_privilege_user.tsx | 2 +- .../detection_engine/rules/all/columns.tsx | 15 +-- .../rules/components/rule_status/helpers.ts | 18 ++++ .../rules/components/rule_status/index.tsx | 99 +++++++++++++++++++ .../components/rule_status/translations.ts | 29 ++++++ .../rules/components/rule_switch/index.tsx | 5 + .../rules/details/failure_history.tsx | 9 +- .../detection_engine/rules/details/index.tsx | 58 +++-------- .../rules/details/translations.ts | 18 ---- .../routes/__mocks__/request_responses.ts | 2 +- .../privileges/read_privileges_route.ts | 2 +- .../rules/add_prepackaged_rules_route.ts | 14 ++- .../routes/rules/find_rules_status_route.ts | 30 ++++-- .../routes/rules/import_rules_route.ts | 7 +- .../routes/rules/update_rules_bulk_route.ts | 25 ++++- .../routes/rules/update_rules_route.ts | 1 + .../lib/detection_engine/rules/types.ts | 20 +++- .../rules/update_prepacked_rules.ts | 3 + .../detection_engine/rules/update_rules.ts | 33 ++++++- .../signals/signal_rule_alert_type.ts | 4 +- 25 files changed, 310 insertions(+), 112 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 8cd3e8f2d45c7..a83e874437c10 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -322,7 +322,7 @@ export const getRuleStatusById = async ({ }: { id: string; signal: AbortSignal; -}): Promise> => { +}): Promise> => { const response = await fetch( `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS}?ids=${encodeURIComponent( JSON.stringify([id]) diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts index a61cbabd80626..e9a0f27b34696 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/index.ts @@ -10,3 +10,4 @@ export * from './persist_rule'; export * from './types'; export * from './use_rule'; export * from './use_rules'; +export * from './use_rule_status'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index 334daa8d1d028..0dcd0da5be8f6 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -181,9 +181,15 @@ export interface ExportRulesProps { } export interface RuleStatus { + current_status: RuleInfoStatus; + failures: RuleInfoStatus[]; +} + +export type RuleStatusType = 'executing' | 'failed' | 'going to run' | 'succeeded'; +export interface RuleInfoStatus { alert_id: string; status_date: string; - status: string; + status: RuleStatusType | null; last_failure_at: string | null; last_success_at: string | null; last_failure_message: string | null; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx index 216fbcea861a3..466c2cddac97d 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule_status.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useStateToaster } from '../../../components/toasters'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; @@ -12,7 +12,8 @@ import { getRuleStatusById } from './api'; import * as i18n from './translations'; import { RuleStatus } from './types'; -type Return = [boolean, RuleStatus[] | null]; +type Func = (ruleId: string) => void; +type Return = [boolean, RuleStatus | null, Func | null]; /** * Hook for using to get a Rule from the Detection Engine API @@ -21,7 +22,8 @@ type Return = [boolean, RuleStatus[] | null]; * */ export const useRuleStatus = (id: string | undefined | null): Return => { - const [ruleStatus, setRuleStatus] = useState(null); + const [ruleStatus, setRuleStatus] = useState(null); + const fetchRuleStatus = useRef(null); const [loading, setLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); @@ -29,7 +31,7 @@ export const useRuleStatus = (id: string | undefined | null): Return => { let isSubscribed = true; const abortCtrl = new AbortController(); - async function fetchData(idToFetch: string) { + const fetchData = async (idToFetch: string) => { try { setLoading(true); const ruleStatusResponse = await getRuleStatusById({ @@ -49,15 +51,16 @@ export const useRuleStatus = (id: string | undefined | null): Return => { if (isSubscribed) { setLoading(false); } - } + }; if (id != null) { fetchData(id); } + fetchRuleStatus.current = fetchData; return () => { isSubscribed = false; abortCtrl.abort(); }; }, [id]); - return [loading, ruleStatus]; + return [loading, ruleStatus, fetchRuleStatus.current]; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts index 34cb7684a0399..ea4860dafd40f 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts @@ -96,5 +96,5 @@ export interface Privilege { write: boolean; }; }; - isAuthenticated: boolean; + is_authenticated: boolean; } diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx index 792ff29ad2488..7d0e331200d55 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -42,7 +42,7 @@ export const usePrivilegeUser = (): Return => { }); if (isSubscribed && privilege != null) { - setAuthenticated(privilege.isAuthenticated); + setAuthenticated(privilege.is_authenticated); if (privilege.index != null && Object.keys(privilege.index).length > 0) { const indexName = Object.keys(privilege.index)[0]; setHasIndexManage(privilege.index[indexName].manage); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index f62343f77c499..d546c4edb55d3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -30,6 +30,7 @@ import { FormattedDate } from '../../../../components/formatted_date'; import { RuleSwitch } from '../components/rule_switch'; import { SeverityBadge } from '../components/severity_badge'; import { ActionToaster } from '../../../../components/toasters'; +import { getStatusColor } from '../components/rule_status/helpers'; const getActions = ( dispatch: React.Dispatch, @@ -113,19 +114,11 @@ export const getColumns = ( field: 'status', name: i18n.COLUMN_LAST_RESPONSE, render: (value: TableData['status']) => { - const color = - value == null - ? 'subdued' - : value === 'succeeded' - ? 'success' - : value === 'failed' - ? 'danger' - : value === 'executing' - ? 'warning' - : 'subdued'; return ( <> - {value ?? getEmptyTagValue()} + + {value ?? getEmptyTagValue()} + ); }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts new file mode 100644 index 0000000000000..263f602251ea7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/helpers.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RuleStatusType } from '../../../../../containers/detection_engine/rules'; + +export const getStatusColor = (status: RuleStatusType | string | null) => + status == null + ? 'subdued' + : status === 'succeeded' + ? 'success' + : status === 'failed' + ? 'danger' + : status === 'executing' || status === 'going to run' + ? 'warning' + : 'subdued'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx new file mode 100644 index 0000000000000..2c9173cbeb694 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/index.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiHealth, + EuiLoadingSpinner, + EuiText, +} from '@elastic/eui'; +import { isEqual } from 'lodash/fp'; +import React, { memo, useCallback, useEffect, useState } from 'react'; + +import { useRuleStatus, RuleInfoStatus } from '../../../../../containers/detection_engine/rules'; +import { FormattedDate } from '../../../../../components/formatted_date'; +import { getEmptyTagValue } from '../../../../../components/empty_value'; +import { getStatusColor } from './helpers'; +import * as i18n from './translations'; + +interface RuleStatusProps { + ruleId: string | null; + ruleEnabled?: boolean | null; +} + +const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled }) => { + const [loading, ruleStatus, fetchRuleStatus] = useRuleStatus(ruleId); + const [myRuleEnabled, setMyRuleEnabled] = useState(ruleEnabled ?? null); + const [currentStatus, setCurrentStatus] = useState( + ruleStatus?.current_status ?? null + ); + + useEffect(() => { + if (myRuleEnabled !== ruleEnabled && fetchRuleStatus != null && ruleId != null) { + fetchRuleStatus(ruleId); + if (myRuleEnabled !== ruleEnabled) { + setMyRuleEnabled(ruleEnabled ?? null); + } + } + }, [fetchRuleStatus, myRuleEnabled, ruleId, ruleEnabled, setMyRuleEnabled]); + + useEffect(() => { + if (!isEqual(currentStatus, ruleStatus?.current_status)) { + setCurrentStatus(ruleStatus?.current_status ?? null); + } + }, [currentStatus, ruleStatus, setCurrentStatus]); + + const handleRefresh = useCallback(() => { + if (fetchRuleStatus != null && ruleId != null) { + fetchRuleStatus(ruleId); + } + }, [fetchRuleStatus, ruleId]); + + return ( + + + {i18n.STATUS} + {':'} + + {loading && ( + + + + )} + {!loading && ( + <> + + + {currentStatus?.status ?? getEmptyTagValue()} + + + {currentStatus?.status_date != null && currentStatus?.status != null && ( + <> + + <>{i18n.STATUS_AT} + + + + + + )} + + + + + )} + + ); +}; + +export const RuleStatus = memo(RuleStatusComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts new file mode 100644 index 0000000000000..e03cc252ad729 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_status/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const STATUS = i18n.translate('xpack.siem.detectionEngine.ruleStatus.statusDescription', { + defaultMessage: 'Status', +}); + +export const STATUS_AT = i18n.translate( + 'xpack.siem.detectionEngine.ruleStatus.statusAtDescription', + { + defaultMessage: 'at', + } +); + +export const STATUS_DATE = i18n.translate( + 'xpack.siem.detectionEngine.ruleStatus.statusDateDescription', + { + defaultMessage: 'Status date', + } +); + +export const REFRESH = i18n.translate('xpack.siem.detectionEngine.ruleStatus.refreshButton', { + defaultMessage: 'Refresh', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx index 9cb0323ed8987..09b7ecc9df982 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx @@ -36,6 +36,7 @@ export interface RuleSwitchProps { isDisabled?: boolean; isLoading?: boolean; optionLabel?: string; + onChange?: (enabled: boolean) => void; } /** @@ -48,6 +49,7 @@ export const RuleSwitchComponent = ({ isLoading, enabled, optionLabel, + onChange, }: RuleSwitchProps) => { const [myIsLoading, setMyIsLoading] = useState(false); const [myEnabled, setMyEnabled] = useState(enabled ?? false); @@ -65,6 +67,9 @@ export const RuleSwitchComponent = ({ enabled: event.target.checked!, }); setMyEnabled(updatedRules[0].enabled); + if (onChange != null) { + onChange(updatedRules[0].enabled); + } } catch { setMyIsLoading(false); } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx index 3b49cd30c9aab..f660c1763d5e0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/failure_history.tsx @@ -15,8 +15,7 @@ import { } from '@elastic/eui'; import React, { memo } from 'react'; -import { useRuleStatus } from '../../../../containers/detection_engine/rules/use_rule_status'; -import { RuleStatus } from '../../../../containers/detection_engine/rules'; +import { useRuleStatus, RuleInfoStatus } from '../../../../containers/detection_engine/rules'; import { HeaderSection } from '../../../../components/header_section'; import * as i18n from './translations'; import { FormattedDate } from '../../../../components/formatted_date'; @@ -35,7 +34,7 @@ const FailureHistoryComponent: React.FC = ({ id }) => { ); } - const columns: Array> = [ + const columns: Array> = [ { name: i18n.COLUMN_STATUS_TYPE, render: () => {i18n.TYPE_FAILED}, @@ -65,7 +64,9 @@ const FailureHistoryComponent: React.FC = ({ id }) => { rs.last_failure_at != null) : []} + items={ + ruleStatus != null ? ruleStatus?.failures.filter(rs => rs.last_failure_at != null) : [] + } sorting={{ sort: { field: 'status_date', direction: 'desc' } }} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index 32e40c7547400..a23c681a5aab2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -10,9 +10,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, - EuiHealth, EuiTab, - EuiText, EuiTabs, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -62,10 +60,10 @@ import { inputsSelectors } from '../../../../store/inputs'; import { State } from '../../../../store'; import { InputsRange } from '../../../../store/inputs/model'; import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../../store/inputs/actions'; -import { getEmptyTagValue } from '../../../../components/empty_value'; +import { RuleActionsOverflow } from '../components/rule_actions_overflow'; import { RuleStatusFailedCallOut } from './status_failed_callout'; import { FailureHistory } from './failure_history'; -import { RuleActionsOverflow } from '../components/rule_actions_overflow'; +import { RuleStatus } from '../components/rule_status'; interface ReduxProps { filters: esFilters.Filter[]; @@ -113,6 +111,8 @@ const RuleDetailsComponent = memo( } = useUserInfo(); const { ruleId } = useParams(); const [isLoading, rule] = useRule(ruleId); + // This is used to re-trigger api rule status when user de/activate rule + const [ruleEnabled, setRuleEnabled] = useState(null); const [ruleDetailTab, setRuleDetailTab] = useState(RuleDetailTabs.signals); const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ rule, @@ -182,17 +182,6 @@ const RuleDetailsComponent = memo( filters, ]); - const statusColor = - rule?.status == null - ? 'subdued' - : rule?.status === 'succeeded' - ? 'success' - : rule?.status === 'failed' - ? 'danger' - : rule?.status === 'executing' - ? 'warning' - : 'subdued'; - const tabs = useMemo( () => ( @@ -230,6 +219,15 @@ const RuleDetailsComponent = memo( [setAbsoluteRangeDatePicker] ); + const handleOnChangeEnabledRule = useCallback( + (enabled: boolean) => { + if (ruleEnabled == null || enabled !== ruleEnabled) { + setRuleEnabled(enabled); + } + }, + [ruleEnabled, setRuleEnabled] + ); + return ( <> {hasIndexWrite != null && !hasIndexWrite && } @@ -262,34 +260,7 @@ const RuleDetailsComponent = memo( , ] : []), - - - {i18n.STATUS} - {':'} - - - - {rule?.status ?? getEmptyTagValue()} - - - {rule?.status_date && ( - <> - - <>{i18n.STATUS_AT} - - - - - - )} - , + , ]} title={title} > @@ -300,6 +271,7 @@ const RuleDetailsComponent = memo( isDisabled={userHasNoPermissions} enabled={rule?.enabled ?? false} optionLabel={i18n.ACTIVATE_RULE} + onChange={handleOnChangeEnabledRule} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts index 7b349ec646ba9..46b6984ab323f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/translations.ts @@ -35,24 +35,6 @@ export const UNKNOWN = i18n.translate('xpack.siem.detectionEngine.ruleDetails.un defaultMessage: 'Unknown', }); -export const STATUS = i18n.translate('xpack.siem.detectionEngine.ruleDetails.statusDescription', { - defaultMessage: 'Status', -}); - -export const STATUS_AT = i18n.translate( - 'xpack.siem.detectionEngine.ruleDetails.statusAtDescription', - { - defaultMessage: 'at', - } -); - -export const STATUS_DATE = i18n.translate( - 'xpack.siem.detectionEngine.ruleDetails.statusDateDescription', - { - defaultMessage: 'Status date', - } -); - export const ERROR_CALLOUT_TITLE = i18n.translate( 'xpack.siem.detectionEngine.ruleDetails.errorCalloutTitle', { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 0c6ab1c82bcb8..a84fcb64d9ff7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -391,7 +391,7 @@ export const getMockPrivileges = () => ({ }, }, application: {}, - isAuthenticated: false, + is_authenticated: false, }); export const getFindResultStatus = (): SavedObjectsFindResponse => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index 240200af8b585..803d9d645aadb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -30,7 +30,7 @@ export const createReadPrivilegesRulesRoute = (server: ServerFacade): Hapi.Serve const index = getIndex(request, server); const permissions = await readPrivileges(callWithRequest, index); return merge(permissions, { - isAuthenticated: request?.auth?.isAuthenticated ?? false, + is_authenticated: request?.auth?.isAuthenticated ?? false, }); } catch (err) { return transformError(err); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts index 5ceecdb058e5f..3c9cad8dc4d4b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/add_prepackaged_rules_route.ts @@ -36,8 +36,10 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - - if (!alertsClient || !actionsClient) { + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !actionsClient || !savedObjectsClient) { return headers.response().code(404); } @@ -59,7 +61,13 @@ export const createAddPrepackedRulesRoute = (server: ServerFacade): Hapi.ServerR } } await installPrepackagedRules(alertsClient, actionsClient, rulesToInstall, spaceIndex); - await updatePrepackagedRules(alertsClient, actionsClient, rulesToUpdate, spaceIndex); + await updatePrepackagedRules( + alertsClient, + actionsClient, + savedObjectsClient, + rulesToUpdate, + spaceIndex + ); return { rules_installed: rulesToInstall.length, rules_updated: rulesToUpdate.length, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts index e56c440f5a415..545c2e488b1c8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/find_rules_status_route.ts @@ -13,10 +13,16 @@ import { findRulesStatusesSchema } from '../schemas/find_rules_statuses_schema'; import { FindRulesStatusesRequest, IRuleSavedAttributesSavedObjectAttributes, + RuleStatusResponse, + IRuleStatusAttributes, } from '../../rules/types'; import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; -const convertToSnakeCase = (obj: IRuleSavedAttributesSavedObjectAttributes) => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const convertToSnakeCase = >(obj: T): Partial | null => { + if (!obj) { + return null; + } return Object.keys(obj).reduce((acc, item) => { const newKey = snakeCase(item); return { ...acc, [newKey]: obj[item] }; @@ -53,7 +59,7 @@ export const createFindRulesStatusRoute: Hapi.ServerRoute = { "anotherAlertId": ... } */ - const statuses = await query.ids.reduce(async (acc, id) => { + const statuses = await query.ids.reduce>(async (acc, id) => { const lastFiveErrorsForId = await savedObjectsClient.find< IRuleSavedAttributesSavedObjectAttributes >({ @@ -64,15 +70,21 @@ export const createFindRulesStatusRoute: Hapi.ServerRoute = { search: id, searchFields: ['alertId'], }); - const toDisplay = - lastFiveErrorsForId.saved_objects.length <= 5 - ? lastFiveErrorsForId.saved_objects - : lastFiveErrorsForId.saved_objects.slice(1); + const accumulated = await acc; + const currentStatus = convertToSnakeCase( + lastFiveErrorsForId.saved_objects[0]?.attributes + ); + const failures = lastFiveErrorsForId.saved_objects + .slice(1) + .map(errorItem => convertToSnakeCase(errorItem.attributes)); return { - ...(await acc), - [id]: toDisplay.map(errorItem => convertToSnakeCase(errorItem.attributes)), + ...accumulated, + [id]: { + current_status: currentStatus, + failures, + }, }; - }, {}); + }, Promise.resolve({})); return statuses; }, }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index e312b5fc6bb10..6efaa1fea60d0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -52,8 +52,10 @@ export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute = const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - - if (!alertsClient || !actionsClient) { + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !actionsClient || !savedObjectsClient) { return headers.response().code(404); } const { filename } = request.payload.file.hapi; @@ -161,6 +163,7 @@ export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute = const updatedRule = await updateRules({ alertsClient, actionsClient, + savedObjectsClient, description, enabled, falsePositives, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index b01108f0de21f..e0d2672cf356a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -7,12 +7,16 @@ import Hapi from 'hapi'; import { isFunction } from 'lodash/fp'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; -import { BulkUpdateRulesRequest } from '../../rules/types'; +import { + BulkUpdateRulesRequest, + IRuleSavedAttributesSavedObjectAttributes, +} from '../../rules/types'; import { ServerFacade } from '../../../../types'; import { transformOrBulkError, getIdBulkError } from './utils'; import { transformBulkError } from '../utils'; import { updateRulesBulkSchema } from '../schemas/update_rules_bulk_schema'; import { updateRules } from '../../rules/update_rules'; +import { ruleStatusSavedObjectType } from '../../rules/saved_object_mappings'; export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRoute => { return { @@ -32,8 +36,10 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou const actionsClient = isFunction(request.getActionsClient) ? request.getActionsClient() : null; - - if (!alertsClient || !actionsClient) { + const savedObjectsClient = isFunction(request.getSavedObjectsClient) + ? request.getSavedObjectsClient() + : null; + if (!alertsClient || !actionsClient || !savedObjectsClient) { return headers.response().code(404); } @@ -80,6 +86,7 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou language, outputIndex, savedId, + savedObjectsClient, timelineId, timelineTitle, meta, @@ -100,7 +107,17 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou version, }); if (rule != null) { - return transformOrBulkError(rule.id, rule); + const ruleStatuses = await savedObjectsClient.find< + IRuleSavedAttributesSavedObjectAttributes + >({ + type: ruleStatusSavedObjectType, + perPage: 1, + sortField: 'statusDate', + sortOrder: 'desc', + search: rule.id, + searchFields: ['alertId'], + }); + return transformOrBulkError(rule.id, rule, ruleStatuses.saved_objects[0]); } else { return getIdBulkError({ id, ruleId }); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 533fe9b724943..49c9304ae2d25 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -78,6 +78,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { language, outputIndex, savedId, + savedObjectsClient, timelineId, timelineTitle, meta, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index 5a3f19c0bf0ef..e238e6398845c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -7,7 +7,12 @@ import { get } from 'lodash/fp'; import { Readable } from 'stream'; -import { SavedObject, SavedObjectAttributes, SavedObjectsFindResponse } from 'kibana/server'; +import { + SavedObject, + SavedObjectAttributes, + SavedObjectsFindResponse, + SavedObjectsClientContract, +} from 'kibana/server'; import { SIGNALS_ID } from '../../../../common/constants'; import { AlertsClient } from '../../../../../alerting/server/alerts_client'; import { ActionsClient } from '../../../../../actions/server/actions_client'; @@ -41,14 +46,22 @@ export interface RuleAlertType extends Alert { params: RuleTypeParams; } -export interface IRuleStatusAttributes { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface IRuleStatusAttributes extends Record { alertId: string; // created alert id. statusDate: string; lastFailureAt: string | null | undefined; lastFailureMessage: string | null | undefined; lastSuccessAt: string | null | undefined; lastSuccessMessage: string | null | undefined; - status: RuleStatusString; + status: RuleStatusString | null | undefined; +} + +export interface RuleStatusResponse { + [key: string]: { + current_status: IRuleStatusAttributes | null | undefined; + failures: IRuleStatusAttributes[] | null | undefined; + }; } export interface IRuleSavedAttributesSavedObjectAttributes @@ -142,6 +155,7 @@ export interface Clients { export type UpdateRuleParams = Partial & { id: string | undefined | null; + savedObjectsClient: SavedObjectsClientContract; } & Clients; export type DeleteRuleParams = Clients & { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts index 756634c8fa042..0d7fb7918b67e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_prepacked_rules.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SavedObjectsClientContract } from 'kibana/server'; import { ActionsClient } from '../../../../../actions'; import { AlertsClient } from '../../../../../alerting'; import { updateRules } from './update_rules'; @@ -12,6 +13,7 @@ import { PrepackagedRules } from '../types'; export const updatePrepackagedRules = async ( alertsClient: AlertsClient, actionsClient: ActionsClient, + savedObjectsClient: SavedObjectsClientContract, rules: PrepackagedRules[], outputIndex: string ): Promise => { @@ -55,6 +57,7 @@ export const updatePrepackagedRules = async ( outputIndex, id: undefined, // We never have an id when updating from pre-packaged rules savedId, + savedObjectsClient, meta, filters, ruleId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index 0fe4b15437af8..e2632791f859e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -7,8 +7,9 @@ import { defaults } from 'lodash/fp'; import { AlertAction, IntervalSchedule } from '../../../../../alerting/server/types'; import { readRules } from './read_rules'; -import { UpdateRuleParams } from './types'; +import { UpdateRuleParams, IRuleSavedAttributesSavedObjectAttributes } from './types'; import { addTags } from './add_tags'; +import { ruleStatusSavedObjectType } from './saved_object_mappings'; export const calculateInterval = ( interval: string | undefined, @@ -66,6 +67,7 @@ export const calculateName = ({ export const updateRules = async ({ alertsClient, actionsClient, // TODO: Use this whenever we add feature support for different action types + savedObjectsClient, description, falsePositives, enabled, @@ -135,10 +137,39 @@ export const updateRules = async ({ } ); + const ruleCurrentStatus = savedObjectsClient + ? await savedObjectsClient.find({ + type: ruleStatusSavedObjectType, + perPage: 1, + sortField: 'statusDate', + sortOrder: 'desc', + search: rule.id, + searchFields: ['alertId'], + }) + : null; + if (rule.enabled && enabled === false) { await alertsClient.disable({ id: rule.id }); + // set current status for this rule to null to represent disabled, + // but keep last_success_at / last_failure_at properties intact for + // use on frontend while rule is disabled. + if (ruleCurrentStatus && ruleCurrentStatus.saved_objects.length > 0) { + const currentStatusToDisable = ruleCurrentStatus.saved_objects[0]; + currentStatusToDisable.attributes.status = null; + await savedObjectsClient?.update(ruleStatusSavedObjectType, currentStatusToDisable.id, { + ...currentStatusToDisable.attributes, + }); + } } else if (!rule.enabled && enabled === true) { await alertsClient.enable({ id: rule.id }); + // set current status for this rule to be 'going to run' + if (ruleCurrentStatus && ruleCurrentStatus.saved_objects.length > 0) { + const currentStatusToDisable = ruleCurrentStatus.saved_objects[0]; + currentStatusToDisable.attributes.status = 'going to run'; + await savedObjectsClient?.update(ruleStatusSavedObjectType, currentStatusToDisable.id, { + ...currentStatusToDisable.attributes, + }); + } } else { // enabled is null or undefined and we do not touch the rule } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index d80eadd2c088b..32f2c86914770 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -96,7 +96,7 @@ export const signalRulesAlertType = ({ >(ruleStatusSavedObjectType, { alertId, // do a search for this id. statusDate: date, - status: 'executing', + status: 'going to run', lastFailureAt: null, lastSuccessAt: null, lastFailureMessage: null, @@ -106,7 +106,7 @@ export const signalRulesAlertType = ({ // update 0th to executing. currentStatusSavedObject = ruleStatusSavedObjects.saved_objects[0]; const sDate = new Date().toISOString(); - currentStatusSavedObject.attributes.status = 'executing'; + currentStatusSavedObject.attributes.status = 'going to run'; currentStatusSavedObject.attributes.statusDate = sDate; await services.savedObjectsClient.update( ruleStatusSavedObjectType,