From 6408fa54ed78ca03c82631aa03255e6e26f4f645 Mon Sep 17 00:00:00 2001 From: MadameSheema Date: Fri, 18 Sep 2020 12:59:45 +0200 Subject: [PATCH 01/40] [Security Solution] Adds "Creates timeline" Cypress test (#76836) * adds "Creates timeline" test * deletes timeline events spec * completes assertions * comments assertion * fixes typecheck error * waits for all the changes in the timeline to be performed before creating a new timeline and closing the toggle * fixes failing problem * fixes loop script * makes test realiable on visual mode * fixes merge issue * makes test more reliable * fixes typecheck issue * fixes typecheck * opens timeline from timeline settings Co-authored-by: Elastic Machine --- package.json | 1 + x-pack/package.json | 1 + .../security_solution/cypress/.eslintrc.json | 3 + .../integration/fields_browser.spec.ts | 6 +- .../cypress/integration/inspect.spec.ts | 4 +- .../integration/timeline_creation.spec.ts | 99 +++++++++++++++++++ .../timeline_data_providers.spec.ts | 4 +- .../integration/timeline_events.spec.ts | 39 -------- .../timeline_flyout_button.spec.ts | 4 +- .../timeline_search_or_filter.spec.ts | 4 +- .../timeline_toggle_column.spec.ts | 4 +- .../integration/timelines_export.spec.ts | 2 +- .../cypress/integration/url_state.spec.ts | 10 +- .../cypress/objects/timeline.ts | 24 +++++ .../cypress/screens/timeline.ts | 45 +++++++-- .../cypress/screens/timelines.ts | 29 ++++++ .../cypress/support/index.d.ts | 1 + .../cypress/support/index.js | 1 + .../cypress/tasks/create_new_rule.ts | 2 +- .../cypress/tasks/security_main.ts | 4 +- .../cypress/tasks/timeline.ts | 67 +++++++++---- .../cypress/tasks/timelines.ts | 27 +++++ x-pack/plugins/security_solution/package.json | 2 +- .../add_data_provider_popover.tsx | 4 +- .../timeline/properties/helpers.tsx | 6 +- .../scripts/loop_cypress_tests.js | 4 +- yarn.lock | 5 + 27 files changed, 307 insertions(+), 95 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts delete mode 100644 x-pack/plugins/security_solution/cypress/integration/timeline_events.spec.ts create mode 100644 x-pack/plugins/security_solution/cypress/screens/timelines.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/timelines.ts diff --git a/package.json b/package.json index c88fbc0e5fd07..d258d21413fe4 100644 --- a/package.json +++ b/package.json @@ -154,6 +154,7 @@ "color": "1.0.3", "commander": "3.0.2", "core-js": "^3.6.4", + "cypress-promise": "^1.1.0", "deep-freeze-strict": "^1.1.1", "del": "^5.1.0", "elastic-apm-node": "^3.7.0", diff --git a/x-pack/package.json b/x-pack/package.json index de0bf0d922b42..9a1d424da4a1d 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -160,6 +160,7 @@ "cronstrue": "^1.51.0", "cypress": "5.0.0", "cypress-multi-reporters": "^1.2.3", + "cypress-promise": "^1.1.0", "d3": "3.5.17", "d3-scale": "1.0.7", "dragselect": "1.13.1", diff --git a/x-pack/plugins/security_solution/cypress/.eslintrc.json b/x-pack/plugins/security_solution/cypress/.eslintrc.json index 96a5a52f13e6c..a738652e2d27b 100644 --- a/x-pack/plugins/security_solution/cypress/.eslintrc.json +++ b/x-pack/plugins/security_solution/cypress/.eslintrc.json @@ -2,5 +2,8 @@ "plugins": ["cypress"], "env": { "cypress/globals": true + }, + "rules": { + "import/no-extraneous-dependencies": "off" } } diff --git a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts index 6438a738580b7..e09d62d2a87d1 100644 --- a/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/fields_browser.spec.ts @@ -28,7 +28,7 @@ import { resetFields, } from '../tasks/fields_browser'; import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline } from '../tasks/security_main'; +import { openTimelineUsingToggle } from '../tasks/security_main'; import { openTimelineFieldsBrowser, populateTimeline } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; @@ -48,7 +48,7 @@ describe('Fields Browser', () => { context('Fields Browser rendering', () => { before(() => { loginAndWaitForPage(HOSTS_URL); - openTimeline(); + openTimelineUsingToggle(); populateTimeline(); openTimelineFieldsBrowser(); }); @@ -111,7 +111,7 @@ describe('Fields Browser', () => { context('Editing the timeline', () => { before(() => { loginAndWaitForPage(HOSTS_URL); - openTimeline(); + openTimelineUsingToggle(); populateTimeline(); openTimelineFieldsBrowser(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts index 53ddff501db82..c19e51c3ada40 100644 --- a/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/inspect.spec.ts @@ -12,7 +12,7 @@ import { import { closesModal, openStatsAndTables } from '../tasks/inspect'; import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline } from '../tasks/security_main'; +import { openTimelineUsingToggle } from '../tasks/security_main'; import { executeTimelineKQL, openTimelineInspectButton, @@ -58,7 +58,7 @@ describe('Inspect', () => { it('inspects the timeline', () => { const hostExistsQuery = 'host.name: *'; loginAndWaitForPage(HOSTS_URL); - openTimeline(); + openTimelineUsingToggle(); executeTimelineKQL(hostExistsQuery); openTimelineSettings(); openTimelineInspectButton(); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts new file mode 100644 index 0000000000000..9f61d11b7ac0f --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_creation.spec.ts @@ -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 { timeline } from '../objects/timeline'; + +import { + FAVORITE_TIMELINE, + LOCKED_ICON, + NOTES, + NOTES_BUTTON, + NOTES_COUNT, + NOTES_TEXT_AREA, + PIN_EVENT, + TIMELINE_DESCRIPTION, + // TIMELINE_FILTER, + TIMELINE_QUERY, + TIMELINE_TITLE, +} from '../screens/timeline'; +import { + TIMELINES_DESCRIPTION, + TIMELINES_PINNED_EVENT_COUNT, + TIMELINES_NOTES_COUNT, + TIMELINES_FAVORITE, +} from '../screens/timelines'; + +import { loginAndWaitForPage } from '../tasks/login'; +import { openTimelineUsingToggle } from '../tasks/security_main'; +import { + addDescriptionToTimeline, + addFilter, + addNameToTimeline, + addNotesToTimeline, + closeNotes, + closeTimeline, + createNewTimeline, + markAsFavorite, + openTimelineFromSettings, + pinFirstEvent, + populateTimeline, + waitForTimelineChanges, +} from '../tasks/timeline'; +import { openTimeline } from '../tasks/timelines'; + +import { OVERVIEW_URL } from '../urls/navigation'; + +describe('Timelines', () => { + before(() => { + cy.server(); + cy.route('PATCH', '**/api/timeline').as('timeline'); + }); + + it('Creates a timeline', async () => { + loginAndWaitForPage(OVERVIEW_URL); + openTimelineUsingToggle(); + populateTimeline(); + addFilter(timeline.filter); + pinFirstEvent(); + + cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event'); + cy.get(LOCKED_ICON).should('be.visible'); + + addNameToTimeline(timeline.title); + + const response = await cy.wait('@timeline').promisify(); + const timelineId = JSON.parse(response.xhr.responseText).data.persistTimeline.timeline + .savedObjectId; + + addDescriptionToTimeline(timeline.description); + addNotesToTimeline(timeline.notes); + closeNotes(); + markAsFavorite(); + waitForTimelineChanges(); + createNewTimeline(); + closeTimeline(); + openTimelineFromSettings(); + + cy.contains(timeline.title).should('exist'); + cy.get(TIMELINES_DESCRIPTION).first().should('have.text', timeline.description); + cy.get(TIMELINES_PINNED_EVENT_COUNT).first().should('have.text', '1'); + cy.get(TIMELINES_NOTES_COUNT).first().should('have.text', '1'); + cy.get(TIMELINES_FAVORITE).first().should('exist'); + + openTimeline(timelineId); + + cy.get(FAVORITE_TIMELINE).should('exist'); + cy.get(TIMELINE_TITLE).should('have.attr', 'value', timeline.title); + cy.get(TIMELINE_DESCRIPTION).should('have.attr', 'value', timeline.description); + cy.get(TIMELINE_QUERY).should('have.text', timeline.query); + // Comments this assertion until we agreed what to do with the filters. + // cy.get(TIMELINE_FILTER(timeline.filter)).should('exist'); + cy.get(NOTES_COUNT).should('have.text', '1'); + cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event'); + cy.get(NOTES_BUTTON).click(); + cy.get(NOTES_TEXT_AREA).should('have.attr', 'placeholder', 'Add a Note'); + cy.get(NOTES).should('have.text', timeline.notes); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts index df0a26f3649c0..f62db083172a4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_data_providers.spec.ts @@ -19,7 +19,7 @@ import { } from '../tasks/hosts/all_hosts'; import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline } from '../tasks/security_main'; +import { openTimelineUsingToggle } from '../tasks/security_main'; import { createNewTimeline } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; @@ -31,7 +31,7 @@ describe('timeline data providers', () => { }); beforeEach(() => { - openTimeline(); + openTimelineUsingToggle(); }); afterEach(() => { diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_events.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_events.spec.ts deleted file mode 100644 index 549cd134a04a4..0000000000000 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_events.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { PIN_EVENT } from '../screens/timeline'; - -import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline } from '../tasks/security_main'; -import { pinFirstEvent, populateTimeline, unpinFirstEvent } from '../tasks/timeline'; - -import { HOSTS_URL } from '../urls/navigation'; - -describe('timeline events', () => { - before(() => { - loginAndWaitForPage(HOSTS_URL); - openTimeline(); - populateTimeline(); - }); - - after(() => { - unpinFirstEvent(); - }); - - it('pins the first event to the timeline', () => { - cy.server(); - cy.route('POST', '**/api/solutions/security/graphql').as('persistTimeline'); - - pinFirstEvent(); - - cy.wait('@persistTimeline', { timeout: 10000 }).then((response) => { - cy.wrap(response.status).should('eql', 200); - cy.wrap(response.xhr.responseText).should('include', 'persistPinnedEventOnTimeline'); - }); - - cy.get(PIN_EVENT).should('have.attr', 'aria-label', 'Pinned event'); - }); -}); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts index 87639f41d4109..9b3434b5521d4 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_flyout_button.spec.ts @@ -8,7 +8,7 @@ import { TIMELINE_FLYOUT_HEADER, TIMELINE_NOT_READY_TO_DROP_BUTTON } from '../sc import { dragFirstHostToTimeline, waitForAllHostsToBeLoaded } from '../tasks/hosts/all_hosts'; import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline, openTimelineIfClosed } from '../tasks/security_main'; +import { openTimelineUsingToggle, openTimelineIfClosed } from '../tasks/security_main'; import { createNewTimeline } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; @@ -25,7 +25,7 @@ describe('timeline flyout button', () => { }); it('toggles open the timeline', () => { - openTimeline(); + openTimelineUsingToggle(); cy.get(TIMELINE_FLYOUT_HEADER).should('have.css', 'visibility', 'visible'); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts index a2e2a72a17946..814fcee2b0c5f 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_search_or_filter.spec.ts @@ -7,7 +7,7 @@ import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline } from '../tasks/security_main'; +import { openTimelineUsingToggle } from '../tasks/security_main'; import { executeTimelineKQL } from '../tasks/timeline'; import { HOSTS_URL } from '../urls/navigation'; @@ -19,7 +19,7 @@ describe('timeline search or filter KQL bar', () => { it('executes a KQL query', () => { const hostExistsQuery = 'host.name: *'; - openTimeline(); + openTimelineUsingToggle(); executeTimelineKQL(hostExistsQuery); cy.get(SERVER_SIDE_EVENT_COUNT) diff --git a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts index 12e6f3db9b61e..e4f303fb89fda 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timeline_toggle_column.spec.ts @@ -12,7 +12,7 @@ import { } from '../screens/timeline'; import { loginAndWaitForPage } from '../tasks/login'; -import { openTimeline } from '../tasks/security_main'; +import { openTimelineUsingToggle } from '../tasks/security_main'; import { checkIdToggleField, createNewTimeline, @@ -30,7 +30,7 @@ describe('toggle column in timeline', () => { }); beforeEach(() => { - openTimeline(); + openTimelineUsingToggle(); populateTimeline(); }); diff --git a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts index d8f96aaf5e563..103bbaad8f303 100644 --- a/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/timelines_export.spec.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { exportTimeline, waitForTimelinesPanelToBeLoaded } from '../tasks/timeline'; +import { exportTimeline, waitForTimelinesPanelToBeLoaded } from '../tasks/timelines'; import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts index 6d605e1d577a9..6c1d73492f30a 100644 --- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts @@ -30,7 +30,7 @@ import { openAllHosts } from '../tasks/hosts/main'; import { waitForIpsTableToBeLoaded } from '../tasks/network/flows'; import { clearSearchBar, kqlSearch, navigateFromHeaderTo } from '../tasks/security_header'; -import { openTimeline } from '../tasks/security_main'; +import { openTimelineUsingToggle } from '../tasks/security_main'; import { addDescriptionToTimeline, addNameToTimeline, @@ -82,7 +82,7 @@ describe('url state', () => { it('sets the timeline start and end dates from the url when locked to global time', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.url); - openTimeline(); + openTimelineUsingToggle(); cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).should( 'have.attr', @@ -105,7 +105,7 @@ describe('url state', () => { ); cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON).should('have.attr', 'title', ABSOLUTE_DATE.endTime); - openTimeline(); + openTimelineUsingToggle(); cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).should( 'have.attr', @@ -121,7 +121,7 @@ describe('url state', () => { it('sets the url state when timeline/global date pickers are unlinked and timeline start and end date are set', () => { loginAndWaitForPageWithoutDateRange(ABSOLUTE_DATE_RANGE.urlUnlinked); - openTimeline(); + openTimelineUsingToggle(); setTimelineStartDate(ABSOLUTE_DATE.newStartTimeTyped); updateTimelineDates(); setTimelineEndDate(ABSOLUTE_DATE.newEndTimeTyped); @@ -220,7 +220,7 @@ describe('url state', () => { it('sets and reads the url state for timeline by id', () => { loginAndWaitForPage(HOSTS_URL); - openTimeline(); + openTimelineUsingToggle(); executeTimelineKQL('host.name: *'); cy.get(SERVER_SIDE_EVENT_COUNT) diff --git a/x-pack/plugins/security_solution/cypress/objects/timeline.ts b/x-pack/plugins/security_solution/cypress/objects/timeline.ts index ff7e80e5661ad..6121cb9a99b14 100644 --- a/x-pack/plugins/security_solution/cypress/objects/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/objects/timeline.ts @@ -10,6 +10,30 @@ export interface Timeline { query: string; } +export interface CompleteTimeline extends Timeline { + notes: string; + filter: TimelineFilter; +} + +export interface TimelineFilter { + field: string; + operator: string; + value?: string; +} + export interface TimelineWithId extends Timeline { id: string; } + +export const filter: TimelineFilter = { + field: 'host.name', + operator: 'exists', +}; + +export const timeline: CompleteTimeline = { + title: 'Security Timeline', + description: 'This is the best timeline', + query: 'host.name: * ', + notes: 'Yes, the best timeline', + filter, +}; diff --git a/x-pack/plugins/security_solution/cypress/screens/timeline.ts b/x-pack/plugins/security_solution/cypress/screens/timeline.ts index bcb64fc947feb..94255a2af8976 100644 --- a/x-pack/plugins/security_solution/cypress/screens/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/screens/timeline.ts @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { TimelineFilter } from '../objects/timeline'; + +export const ADD_NOTE_BUTTON = '[data-test-subj="add-note"]'; + +export const ADD_FILTER = '[data-test-subj="timeline"] [data-test-subj="addFilter"]'; + export const ATTACH_TIMELINE_TO_NEW_CASE_ICON = '[data-test-subj="attach-timeline-case"]'; export const ATTACH_TIMELINE_TO_EXISTING_CASE_ICON = @@ -15,14 +21,18 @@ export const CASE = (id: string) => { return `[data-test-subj="cases-table-row-${id}"]`; }; +export const CLOSE_NOTES_BTN = '[data-test-subj="notesModal"] .euiButtonIcon'; + export const CLOSE_TIMELINE_BTN = '[data-test-subj="close-timeline"]'; +export const COMBO_BOX = '.euiComboBoxOption__content'; + export const CREATE_NEW_TIMELINE = '[data-test-subj="timeline-new"]'; export const DRAGGABLE_HEADER = '[data-test-subj="events-viewer-panel"] [data-test-subj="headers-group"] [data-test-subj="draggable-header"]'; -export const EXPORT_TIMELINE_ACTION = '[data-test-subj="export-timeline-action"]'; +export const FAVORITE_TIMELINE = '[data-test-subj="timeline-favorite-filled-star"]'; export const HEADER = '[data-test-subj="header"]'; @@ -34,6 +44,16 @@ export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; +export const LOCKED_ICON = '[data-test-subj="timeline-date-picker-lock-button"]'; + +export const NOTES = '[data-test-subj="markdown-root"]'; + +export const NOTES_TEXT_AREA = '[data-test-subj="add-a-note"]'; + +export const NOTES_BUTTON = '[data-test-subj="timeline-notes-button-large"]'; + +export const NOTES_COUNT = '[data-test-subj="timeline-notes-count"]'; + export const OPEN_TIMELINE_ICON = '[data-test-subj="open-timeline-button"]'; export const PIN_EVENT = '[data-test-subj="pin"]'; @@ -45,21 +65,17 @@ export const REMOVE_COLUMN = '[data-test-subj="remove-column"]'; export const RESET_FIELDS = '[data-test-subj="events-viewer-panel"] [data-test-subj="reset-fields"]'; +export const SAVE_FILTER_BTN = '[data-test-subj="saveFilter"]'; + export const SEARCH_OR_FILTER_CONTAINER = '[data-test-subj="timeline-search-or-filter-search-container"]'; export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]'; -export const TIMELINE = (id: string) => { - return `[data-test-subj="title-${id}"]`; -}; +export const STAR_ICON = '[data-test-subj="timeline-favorite-empty-star"]'; export const TIMELINE_CHANGES_IN_PROGRESS = '[data-test-subj="timeline"] .euiProgress'; -export const TIMELINE_CHECKBOX = (id: string) => { - return `[data-test-subj="checkboxSelectRow-${id}"]`; -}; - export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]'; export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]'; @@ -74,6 +90,17 @@ export const TIMELINE_DROPPED_DATA_PROVIDERS = '[data-test-subj="providerContain export const TIMELINE_FIELDS_BUTTON = '[data-test-subj="timeline"] [data-test-subj="show-field-browser"]'; +export const TIMELINE_FILTER = (filter: TimelineFilter) => { + return `[data-test-subj="filter filter-enabled filter-key-${filter.field} filter-value-${filter.value} filter-unpinned"]`; +}; + +export const TIMELINE_FILTER_FIELD = '[data-test-subj="filterFieldSuggestionList"]'; + +export const TIMELINE_FILTER_OPERATOR = '[data-test-subj="filterOperatorList"]'; + +export const TIMELINE_FILTER_VALUE = + '[data-test-subj="filterParamsComboBox phraseParamsComboxBox"]'; + export const TIMELINE_FLYOUT_HEADER = '[data-test-subj="eui-flyout-header"]'; export const TIMELINE_FLYOUT_BODY = '[data-test-subj="eui-flyout-body"]'; @@ -89,8 +116,6 @@ export const TIMELINE_SETTINGS_ICON = '[data-test-subj="settings-gear"]'; export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]'; -export const TIMELINES_TABLE = '[data-test-subj="timelines-table"]'; - export const TIMESTAMP_HEADER_FIELD = '[data-test-subj="header-text-@timestamp"]'; export const TIMESTAMP_TOGGLE_FIELD = '[data-test-subj="toggle-field-@timestamp"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/timelines.ts b/x-pack/plugins/security_solution/cypress/screens/timelines.ts new file mode 100644 index 0000000000000..e87e3c4f72ca5 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/timelines.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. + */ + +export const BULK_ACTIONS = '[data-test-subj="utility-bar-action-button"]'; + +export const EXPORT_TIMELINE_ACTION = '[data-test-subj="export-timeline-action"]'; + +export const TIMELINE = (id: string) => { + return `[data-test-subj="title-${id}"]`; +}; + +export const TIMELINE_CHECKBOX = (id: string) => { + return `[data-test-subj="checkboxSelectRow-${id}"]`; +}; + +export const TIMELINES_FAVORITE = '[data-test-subj="favorite-starFilled-star"]'; + +export const TIMELINES_DESCRIPTION = '[data-test-subj="description"]'; + +export const TIMELINES_NOTES_COUNT = '[data-test-subj="notes-count"]'; + +export const TIMELINES_PINNED_EVENT_COUNT = '[data-test-subj="pinned-event-count"]'; + +export const TIMELINES_TABLE = '[data-test-subj="timelines-table"]'; + +export const TIMELINES_USERNAME = '[data-test-subj="username"]'; diff --git a/x-pack/plugins/security_solution/cypress/support/index.d.ts b/x-pack/plugins/security_solution/cypress/support/index.d.ts index f66aeff5d578d..f0b0b8c92c616 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.d.ts +++ b/x-pack/plugins/security_solution/cypress/support/index.d.ts @@ -6,6 +6,7 @@ declare namespace Cypress { interface Chainable { + promisify(): Promise; stubSecurityApi(dataFileName: string): Chainable; stubSearchStrategyApi(dataFileName: string): Chainable; attachFile(fileName: string, fileType?: string): Chainable; diff --git a/x-pack/plugins/security_solution/cypress/support/index.js b/x-pack/plugins/security_solution/cypress/support/index.js index 244781e0ccd01..1328bea0e2918 100644 --- a/x-pack/plugins/security_solution/cypress/support/index.js +++ b/x-pack/plugins/security_solution/cypress/support/index.js @@ -21,6 +21,7 @@ // Import commands.js using ES2015 syntax: import './commands'; +import 'cypress-promise/register'; Cypress.Cookies.defaults({ preserve: 'sid', diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 0daff52de7063..dc89a39d082bc 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -55,7 +55,7 @@ import { EQL_TYPE, EQL_QUERY_INPUT, } from '../screens/create_new_rule'; -import { TIMELINE } from '../screens/timeline'; +import { TIMELINE } from '../screens/timelines'; export const createAndActivateRule = () => { cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/security_main.ts b/x-pack/plugins/security_solution/cypress/tasks/security_main.ts index 47b73db8b96df..6b1f3699d333a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/security_main.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/security_main.ts @@ -6,14 +6,14 @@ import { MAIN_PAGE, TIMELINE_TOGGLE_BUTTON } from '../screens/security_main'; -export const openTimeline = () => { +export const openTimelineUsingToggle = () => { cy.get(TIMELINE_TOGGLE_BUTTON).click(); }; export const openTimelineIfClosed = () => { cy.get(MAIN_PAGE).then(($page) => { if ($page.find(TIMELINE_TOGGLE_BUTTON).length === 1) { - openTimeline(); + openTimelineUsingToggle(); } }); }; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts index cd8b197fc4dec..438700bdfca80 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts @@ -4,36 +4,47 @@ * you may not use this file except in compliance with the Elastic License. */ +import { TimelineFilter } from '../objects/timeline'; + import { ALL_CASES_CREATE_NEW_CASE_TABLE_BTN } from '../screens/all_cases'; + import { - BULK_ACTIONS, + ADD_FILTER, + ADD_NOTE_BUTTON, + ATTACH_TIMELINE_TO_EXISTING_CASE_ICON, + ATTACH_TIMELINE_TO_NEW_CASE_ICON, + CASE, CLOSE_TIMELINE_BTN, + CLOSE_NOTES_BTN, + COMBO_BOX, CREATE_NEW_TIMELINE, - EXPORT_TIMELINE_ACTION, - TIMELINE_CHECKBOX, HEADER, ID_FIELD, ID_HEADER_FIELD, ID_TOGGLE_FIELD, + NOTES_BUTTON, + NOTES_TEXT_AREA, + OPEN_TIMELINE_ICON, PIN_EVENT, + REMOVE_COLUMN, + RESET_FIELDS, + SAVE_FILTER_BTN, SEARCH_OR_FILTER_CONTAINER, SERVER_SIDE_EVENT_COUNT, + STAR_ICON, TIMELINE_CHANGES_IN_PROGRESS, TIMELINE_DESCRIPTION, TIMELINE_FIELDS_BUTTON, + TIMELINE_FILTER_FIELD, + TIMELINE_FILTER_OPERATOR, + TIMELINE_FILTER_VALUE, TIMELINE_INSPECT_BUTTON, TIMELINE_SETTINGS_ICON, TIMELINE_TITLE, - TIMELINES_TABLE, TIMESTAMP_TOGGLE_FIELD, TOGGLE_TIMELINE_EXPAND_EVENT, - REMOVE_COLUMN, - RESET_FIELDS, - ATTACH_TIMELINE_TO_NEW_CASE_ICON, - OPEN_TIMELINE_ICON, - ATTACH_TIMELINE_TO_EXISTING_CASE_ICON, - CASE, } from '../screens/timeline'; +import { TIMELINES_TABLE } from '../screens/timelines'; import { drag, drop } from '../tasks/common'; @@ -49,6 +60,24 @@ export const addNameToTimeline = (name: string) => { cy.get(TIMELINE_TITLE).should('have.attr', 'value', name); }; +export const addNotesToTimeline = (notes: string) => { + cy.get(NOTES_BUTTON).click(); + cy.get(NOTES_TEXT_AREA).type(notes); + cy.get(ADD_NOTE_BUTTON).click(); +}; + +export const addFilter = (filter: TimelineFilter) => { + cy.get(ADD_FILTER).click(); + cy.get(TIMELINE_FILTER_FIELD).type(filter.field); + cy.get(COMBO_BOX).contains(filter.field).click(); + cy.get(TIMELINE_FILTER_OPERATOR).type(filter.operator); + cy.get(COMBO_BOX).contains(filter.operator).click(); + if (filter.operator !== 'exists') { + cy.get(TIMELINE_FILTER_VALUE).type(`${filter.value}{enter}`); + } + cy.get(SAVE_FILTER_BTN).click(); +}; + export const addNewCase = () => { cy.get(ALL_CASES_CREATE_NEW_CASE_TABLE_BTN).click(); }; @@ -71,6 +100,10 @@ export const checkIdToggleField = () => { }); }; +export const closeNotes = () => { + cy.get(CLOSE_NOTES_BTN).click(); +}; + export const closeTimeline = () => { cy.get(CLOSE_TIMELINE_BTN).click({ force: true }); }; @@ -89,10 +122,8 @@ export const expandFirstTimelineEventDetails = () => { cy.get(TOGGLE_TIMELINE_EXPAND_EVENT).first().click({ force: true }); }; -export const exportTimeline = (timelineId: string) => { - cy.get(TIMELINE_CHECKBOX(timelineId)).click({ force: true }); - cy.get(BULK_ACTIONS).click({ force: true }); - cy.get(EXPORT_TIMELINE_ACTION).click(); +export const markAsFavorite = () => { + cy.get(STAR_ICON).click(); }; export const openTimelineFieldsBrowser = () => { @@ -160,11 +191,11 @@ export const selectCase = (caseId: string) => { cy.get(CASE(caseId)).click(); }; -export const waitForTimelinesPanelToBeLoaded = () => { - cy.get(TIMELINES_TABLE).should('exist'); -}; - export const waitForTimelineChanges = () => { cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('exist'); cy.get(TIMELINE_CHANGES_IN_PROGRESS).should('not.exist'); }; + +export const waitForTimelinesPanelToBeLoaded = () => { + cy.get(TIMELINES_TABLE).should('exist'); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/timelines.ts b/x-pack/plugins/security_solution/cypress/tasks/timelines.ts new file mode 100644 index 0000000000000..1c5ce246a35b3 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/timelines.ts @@ -0,0 +1,27 @@ +/* + * 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 { + TIMELINE_CHECKBOX, + BULK_ACTIONS, + EXPORT_TIMELINE_ACTION, + TIMELINES_TABLE, + TIMELINE, +} from '../screens/timelines'; + +export const exportTimeline = (timelineId: string) => { + cy.get(TIMELINE_CHECKBOX(timelineId)).click({ force: true }); + cy.get(BULK_ACTIONS).click({ force: true }); + cy.get(EXPORT_TIMELINE_ACTION).click(); +}; + +export const openTimeline = (id: string) => { + cy.get(TIMELINE(id)).click(); +}; + +export const waitForTimelinesPanelToBeLoaded = () => { + cy.get(TIMELINES_TABLE).should('exist'); +}; diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index fd7941fb17cc5..6982c200a5afd 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -1,4 +1,4 @@ -{ + { "author": "Elastic", "name": "security_solution", "version": "8.0.0", diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx index 71cf81c00dc09..1bfeb482873c7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/data_providers/add_data_provider_popover.tsx @@ -146,7 +146,7 @@ const AddDataProviderPopoverComponent: React.FC = ( = ( {`+ ${ADD_FIELD_LABEL}`} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx index 4ab05af5dd6d4..0f6535015c799 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/properties/helpers.tsx @@ -376,7 +376,11 @@ const NotesButtonComponent = React.memo( )} {size === 'l' && showNotes ? ( - + { const exitIfTimesToRunIsInvalid = (timesToRun) => { if (!timesToRun > 0) { console.error( - '\nERROR: You must specify a valid number of times to run the SIEM Cypress tests.' + '\nERROR: You must specify a valid number of times to run the Security Solution Cypress tests.' ); showUsage(); process.exit(1); @@ -44,7 +44,7 @@ const spawnChild = async () => { const child = spawn('node', [ 'scripts/functional_tests', '--config', - 'x-pack/test/security_solution_cypress/config.ts', + 'x-pack/test/security_solution_cypress/cli_config.ts', ]); for await (const chunk of child.stdout) { console.log(chunk.toString()); diff --git a/yarn.lock b/yarn.lock index 7fb61b10c1bf7..fa5f6ff135670 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9941,6 +9941,11 @@ cypress-multi-reporters@^1.2.3: debug "^4.1.1" lodash "^4.17.11" +cypress-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cypress-promise/-/cypress-promise-1.1.0.tgz#f2d66965945fe198431aaf692d5157cea9d47b25" + integrity sha512-DhIf5PJ/a0iY+Yii6n7Rbwq+9TJxU4pupXYzf9mZd8nPG0AzQrj9i+pqINv4xbI2EV1p+PKW3maCkR7oPG4GrA== + cypress@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cypress/-/cypress-5.0.0.tgz#6957e299b790af8b1cd7bea68261b8935646f72e" From b08594ad5c2cdbd04acfd3805f1a5d244b9f71ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 18 Sep 2020 12:53:42 +0100 Subject: [PATCH 02/40] [APM] Bug: Set minimum scale to 1% for the Transaction error rate chart (#77861) --- .../shared/charts/ErroneousTransactionsRateChart/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx index e806f556347f1..e64357c085209 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/ErroneousTransactionsRateChart/index.tsx @@ -6,6 +6,7 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; +import { max } from 'lodash'; import React, { useCallback } from 'react'; import { useParams } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; @@ -56,6 +57,7 @@ export function ErroneousTransactionsRateChart() { ); const errorRates = data?.erroneousTransactionsRate || []; + const maxRate = max(errorRates.map((errorRate) => errorRate.y)); return ( @@ -70,7 +72,7 @@ export function ErroneousTransactionsRateChart() { Date: Fri, 18 Sep 2020 14:02:37 +0200 Subject: [PATCH 03/40] [APM] Align APM severity levels with ML (#77818) --- x-pack/plugins/apm/common/alert_types.ts | 37 ++++++ .../apm/common/anomaly_detection.test.ts | 39 ------ .../plugins/apm/common/anomaly_detection.ts | 84 ++----------- .../apm/common/service_health_status.ts | 79 ++++++++++++ x-pack/plugins/apm/kibana.json | 12 +- .../SelectAnomalySeverity.tsx | 119 ++++++------------ .../index.tsx | 15 ++- .../ServiceMap/Popover/AnomalyDetection.tsx | 13 +- .../app/ServiceMap/cytoscapeOptions.ts | 33 ++--- .../ServiceList/HealthBadge.tsx | 20 +-- .../ServiceList/__test__/List.test.js | 16 ++- .../__test__/__snapshots__/List.test.js.snap | 10 +- .../app/ServiceOverview/ServiceList/index.tsx | 43 +++---- .../__test__/ServiceOverview.test.tsx | 7 +- .../ServiceOverview.test.tsx.snap | 8 +- .../components/app/ServiceOverview/index.tsx | 3 - ...transaction_duration_anomaly_alert_type.ts | 28 ++++- .../lib/service_map/get_service_anomalies.ts | 10 +- .../transform_service_map_responses.test.ts | 3 + .../get_services/get_services_items_stats.ts | 4 +- x-pack/plugins/ml/common/index.ts | 2 + x-pack/plugins/ml/kibana.json | 3 + .../__snapshots__/service_maps.snap | 36 ++++++ .../trial/tests/service_maps/service_maps.ts | 2 + .../trial/tests/services/top_services.ts | 18 +-- 25 files changed, 362 insertions(+), 282 deletions(-) delete mode 100644 x-pack/plugins/apm/common/anomaly_detection.test.ts create mode 100644 x-pack/plugins/apm/common/service_health_status.ts diff --git a/x-pack/plugins/apm/common/alert_types.ts b/x-pack/plugins/apm/common/alert_types.ts index 15a3c642faf32..a234226d18034 100644 --- a/x-pack/plugins/apm/common/alert_types.ts +++ b/x-pack/plugins/apm/common/alert_types.ts @@ -5,6 +5,8 @@ */ import { i18n } from '@kbn/i18n'; +import { ValuesType } from 'utility-types'; +import { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from '../../ml/common'; export enum AlertType { ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat. @@ -55,6 +57,41 @@ export const ALERT_TYPES_CONFIG = { }, }; +export const ANOMALY_ALERT_SEVERITY_TYPES = [ + { + type: ANOMALY_SEVERITY.CRITICAL, + label: i18n.translate('xpack.apm.alerts.anomalySeverity.criticalLabel', { + defaultMessage: 'critical', + }), + threshold: ANOMALY_THRESHOLD.CRITICAL, + }, + { + type: ANOMALY_SEVERITY.MAJOR, + label: i18n.translate('xpack.apm.alerts.anomalySeverity.majorLabel', { + defaultMessage: 'major', + }), + threshold: ANOMALY_THRESHOLD.MAJOR, + }, + { + type: ANOMALY_SEVERITY.MINOR, + label: i18n.translate('xpack.apm.alerts.anomalySeverity.minor', { + defaultMessage: 'minor', + }), + threshold: ANOMALY_THRESHOLD.MINOR, + }, + { + type: ANOMALY_SEVERITY.WARNING, + label: i18n.translate('xpack.apm.alerts.anomalySeverity.warningLabel', { + defaultMessage: 'warning', + }), + threshold: ANOMALY_THRESHOLD.WARNING, + }, +] as const; + +export type AnomalyAlertSeverityType = ValuesType< + typeof ANOMALY_ALERT_SEVERITY_TYPES +>['type']; + // Server side registrations // x-pack/plugins/apm/server/lib/alerts/.ts // x-pack/plugins/apm/server/lib/alerts/register_apm_alerts.ts diff --git a/x-pack/plugins/apm/common/anomaly_detection.test.ts b/x-pack/plugins/apm/common/anomaly_detection.test.ts deleted file mode 100644 index 21963b5300f83..0000000000000 --- a/x-pack/plugins/apm/common/anomaly_detection.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 { getSeverity, Severity } from './anomaly_detection'; - -describe('getSeverity', () => { - describe('when score is undefined', () => { - it('returns undefined', () => { - expect(getSeverity(undefined)).toEqual(undefined); - }); - }); - - describe('when score < 25', () => { - it('returns warning', () => { - expect(getSeverity(10)).toEqual(Severity.warning); - }); - }); - - describe('when score is between 25 and 50', () => { - it('returns minor', () => { - expect(getSeverity(40)).toEqual(Severity.minor); - }); - }); - - describe('when score is between 50 and 75', () => { - it('returns major', () => { - expect(getSeverity(60)).toEqual(Severity.major); - }); - }); - - describe('when score is 75 or more', () => { - it('returns critical', () => { - expect(getSeverity(100)).toEqual(Severity.critical); - }); - }); -}); diff --git a/x-pack/plugins/apm/common/anomaly_detection.ts b/x-pack/plugins/apm/common/anomaly_detection.ts index 5d80ee6381267..dc5731e88083c 100644 --- a/x-pack/plugins/apm/common/anomaly_detection.ts +++ b/x-pack/plugins/apm/common/anomaly_detection.ts @@ -5,89 +5,31 @@ */ import { i18n } from '@kbn/i18n'; -import { EuiTheme } from '../../../legacy/common/eui_styled_components'; +import { ANOMALY_SEVERITY } from '../../ml/common'; +import { + getSeverityType, + getSeverityColor as mlGetSeverityColor, +} from '../../ml/common'; +import { ServiceHealthStatus } from './service_health_status'; export interface ServiceAnomalyStats { transactionType?: string; anomalyScore?: number; actualValue?: number; jobId?: string; + healthStatus: ServiceHealthStatus; } -export enum Severity { - critical = 'critical', - major = 'major', - minor = 'minor', - warning = 'warning', -} - -// TODO: Replace with `getSeverity` from: -// https://github.com/elastic/kibana/blob/0f964f66916480f2de1f4b633e5afafc08cf62a0/x-pack/plugins/ml/common/util/anomaly_utils.ts#L129 -export function getSeverity(score?: number) { - if (typeof score !== 'number') { - return undefined; - } else if (score < 25) { - return Severity.warning; - } else if (score >= 25 && score < 50) { - return Severity.minor; - } else if (score >= 50 && score < 75) { - return Severity.major; - } else if (score >= 75) { - return Severity.critical; - } else { - return undefined; +export function getSeverity(score: number | undefined) { + if (score === undefined) { + return ANOMALY_SEVERITY.UNKNOWN; } -} -export function getSeverityColor(theme: EuiTheme, severity?: Severity) { - switch (severity) { - case Severity.warning: - return theme.eui.euiColorVis0; - case Severity.minor: - case Severity.major: - return theme.eui.euiColorVis5; - case Severity.critical: - return theme.eui.euiColorVis9; - default: - return; - } + return getSeverityType(score); } -export function getSeverityLabel(severity?: Severity) { - switch (severity) { - case Severity.critical: - return i18n.translate( - 'xpack.apm.servicesTable.serviceHealthStatus.critical', - { - defaultMessage: 'Critical', - } - ); - - case Severity.major: - case Severity.minor: - return i18n.translate( - 'xpack.apm.servicesTable.serviceHealthStatus.warning', - { - defaultMessage: 'Warning', - } - ); - - case Severity.warning: - return i18n.translate( - 'xpack.apm.servicesTable.serviceHealthStatus.healthy', - { - defaultMessage: 'Healthy', - } - ); - - default: - return i18n.translate( - 'xpack.apm.servicesTable.serviceHealthStatus.unknown', - { - defaultMessage: 'Unknown', - } - ); - } +export function getSeverityColor(score: number) { + return mlGetSeverityColor(score); } export const ML_ERRORS = { diff --git a/x-pack/plugins/apm/common/service_health_status.ts b/x-pack/plugins/apm/common/service_health_status.ts new file mode 100644 index 0000000000000..468f06ab97af8 --- /dev/null +++ b/x-pack/plugins/apm/common/service_health_status.ts @@ -0,0 +1,79 @@ +/* + * 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'; +import { ANOMALY_SEVERITY } from '../../ml/common'; + +import { EuiTheme } from '../../../legacy/common/eui_styled_components'; + +export enum ServiceHealthStatus { + healthy = 'healthy', + critical = 'critical', + warning = 'warning', + unknown = 'unknown', +} + +export function getServiceHealthStatus({ + severity, +}: { + severity: ANOMALY_SEVERITY; +}) { + switch (severity) { + case ANOMALY_SEVERITY.CRITICAL: + case ANOMALY_SEVERITY.MAJOR: + return ServiceHealthStatus.critical; + + case ANOMALY_SEVERITY.MINOR: + case ANOMALY_SEVERITY.WARNING: + return ServiceHealthStatus.warning; + + case ANOMALY_SEVERITY.LOW: + return ServiceHealthStatus.healthy; + + case ANOMALY_SEVERITY.UNKNOWN: + return ServiceHealthStatus.unknown; + } +} + +export function getServiceHealthStatusColor( + theme: EuiTheme, + status: ServiceHealthStatus +) { + switch (status) { + case ServiceHealthStatus.healthy: + return theme.eui.euiColorVis0; + case ServiceHealthStatus.warning: + return theme.eui.euiColorVis5; + case ServiceHealthStatus.critical: + return theme.eui.euiColorVis9; + case ServiceHealthStatus.unknown: + return theme.eui.euiColorMediumShade; + } +} + +export function getServiceHealthStatusLabel(status: ServiceHealthStatus) { + switch (status) { + case ServiceHealthStatus.critical: + return i18n.translate('xpack.apm.serviceHealthStatus.critical', { + defaultMessage: 'Critical', + }); + + case ServiceHealthStatus.warning: + return i18n.translate('xpack.apm.serviceHealthStatus.warning', { + defaultMessage: 'Warning', + }); + + case ServiceHealthStatus.healthy: + return i18n.translate('xpack.apm.serviceHealthStatus.healthy', { + defaultMessage: 'Healthy', + }); + + case ServiceHealthStatus.unknown: + return i18n.translate('xpack.apm.serviceHealthStatus.unknown', { + defaultMessage: 'Unknown', + }); + } +} diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index 8aa4417580337..bdef0f9786a3f 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -23,13 +23,19 @@ ], "server": true, "ui": true, - "configPath": ["xpack", "apm"], - "extraPublicDirs": ["public/style/variables"], + "configPath": [ + "xpack", + "apm" + ], + "extraPublicDirs": [ + "public/style/variables" + ], "requiredBundles": [ "kibanaReact", "kibanaUtils", "observability", "home", - "maps" + "maps", + "ml" ] } diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx index 5bddfc67200b1..468d08339431c 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/SelectAnomalySeverity.tsx @@ -5,105 +5,60 @@ */ import React from 'react'; -import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; +import { getSeverityColor } from '../../../../common/anomaly_detection'; import { - getSeverityColor, - Severity, -} from '../../../../common/anomaly_detection'; -import { useTheme } from '../../../hooks/useTheme'; + AnomalyAlertSeverityType, + ANOMALY_ALERT_SEVERITY_TYPES, +} from '../../../../common/alert_types'; -type SeverityScore = 0 | 25 | 50 | 75; -const ANOMALY_SCORES: SeverityScore[] = [0, 25, 50, 75]; - -const anomalyScoreSeverityMap: { - [key in SeverityScore]: { label: string; severity: Severity }; -} = { - 0: { - label: i18n.translate('xpack.apm.alerts.anomalySeverity.warningLabel', { - defaultMessage: 'warning', - }), - severity: Severity.warning, - }, - 25: { - label: i18n.translate('xpack.apm.alerts.anomalySeverity.minorLabel', { - defaultMessage: 'minor', - }), - severity: Severity.minor, - }, - 50: { - label: i18n.translate('xpack.apm.alerts.anomalySeverity.majorLabel', { - defaultMessage: 'major', - }), - severity: Severity.major, - }, - 75: { - label: i18n.translate('xpack.apm.alerts.anomalySeverity.criticalLabel', { - defaultMessage: 'critical', - }), - severity: Severity.critical, - }, -}; - -export function AnomalySeverity({ - severityScore, -}: { - severityScore: SeverityScore; -}) { - const theme = useTheme(); - const { label, severity } = anomalyScoreSeverityMap[severityScore]; - const defaultColor = theme.eui.euiColorMediumShade; - const color = getSeverityColor(theme, severity) || defaultColor; +export function AnomalySeverity({ type }: { type: AnomalyAlertSeverityType }) { + const selectedOption = ANOMALY_ALERT_SEVERITY_TYPES.find( + (option) => option.type === type + )!; return ( - - {label} + + {selectedOption.label} ); } -const getOption = (value: SeverityScore) => { - return { - value: value.toString(10), - inputDisplay: , - dropdownDisplay: ( - <> - - - -

- -

-
- - ), - }; -}; - interface Props { - onChange: (value: SeverityScore) => void; - value: SeverityScore; + onChange: (value: AnomalyAlertSeverityType) => void; + value: AnomalyAlertSeverityType; } export function SelectAnomalySeverity({ onChange, value }: Props) { - const options = ANOMALY_SCORES.map((anomalyScore) => getOption(anomalyScore)); - return ( { - const selectedAnomalyScore = parseInt( - selectedValue, - 10 - ) as SeverityScore; - onChange(selectedAnomalyScore); + options={ANOMALY_ALERT_SEVERITY_TYPES.map((option) => ({ + value: option.type, + inputDisplay: , + dropdownDisplay: ( + <> + + + +

+ +

+
+ + ), + }))} + valueOfSelected={value} + onChange={(selectedValue: AnomalyAlertSeverityType) => { + onChange(selectedValue); }} /> ); diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx index fb4cda56fce04..ca1f55e9d391a 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -7,6 +7,7 @@ import { useParams } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { ANOMALY_SEVERITY } from '../../../../../ml/common'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; import { useEnvironments } from '../../../hooks/useEnvironments'; import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; @@ -34,7 +35,11 @@ interface Params { serviceName: string; transactionType: string; environment: string; - anomalyScore: 0 | 25 | 50 | 75; + anomalySeverityType: + | ANOMALY_SEVERITY.CRITICAL + | ANOMALY_SEVERITY.MAJOR + | ANOMALY_SEVERITY.MINOR + | ANOMALY_SEVERITY.WARNING; } interface Props { @@ -67,7 +72,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { transactionType, serviceName, environment: urlParams.environment || ENVIRONMENT_ALL.value, - anomalyScore: 75, + anomalySeverityType: ANOMALY_SEVERITY.CRITICAL, }; const params = { @@ -84,7 +89,7 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { onChange={(e) => setAlertParams('environment', e.target.value)} />, } + value={} title={i18n.translate( 'xpack.apm.transactionDurationAnomalyAlertTrigger.anomalySeverity', { @@ -93,9 +98,9 @@ export function TransactionDurationAnomalyAlertTrigger(props: Props) { )} > { - setAlertParams('anomalyScore', value); + setAlertParams('anomalySeverityType', value); }} /> , diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx index 5699d0b56219b..c1192f5f18274 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx @@ -14,6 +14,10 @@ import { EuiIconTip, EuiHealth, } from '@elastic/eui'; +import { + getServiceHealthStatus, + getServiceHealthStatusColor, +} from '../../../../../common/service_health_status'; import { useTheme } from '../../../../hooks/useTheme'; import { fontSize, px } from '../../../../style/variables'; import { asInteger, asDuration } from '../../../../utils/formatters'; @@ -22,7 +26,6 @@ import { popoverWidth } from '../cytoscapeOptions'; import { TRANSACTION_REQUEST } from '../../../../../common/transaction_types'; import { getSeverity, - getSeverityColor, ServiceAnomalyStats, } from '../../../../../common/anomaly_detection'; @@ -59,13 +62,15 @@ export function AnomalyDetection({ serviceName, serviceAnomalyStats }: Props) { const theme = useTheme(); const anomalyScore = serviceAnomalyStats?.anomalyScore; - const anomalySeverity = getSeverity(anomalyScore); + const severity = getSeverity(anomalyScore); const actualValue = serviceAnomalyStats?.actualValue; const mlJobId = serviceAnomalyStats?.jobId; const transactionType = serviceAnomalyStats?.transactionType ?? TRANSACTION_REQUEST; const hasAnomalyDetectionScore = anomalyScore !== undefined; + const healthStatus = getServiceHealthStatus({ severity }); + return ( <>
@@ -81,7 +86,9 @@ export function AnomalyDetection({ serviceName, serviceAnomalyStats }: Props) { - + {ANOMALY_DETECTION_SCORE_METRIC} diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 1ac7157cc2aad..61ac9bd7cd54c 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -5,26 +5,26 @@ */ import cytoscape from 'cytoscape'; import { CSSProperties } from 'react'; +import { + getServiceHealthStatusColor, + ServiceHealthStatus, +} from '../../../../common/service_health_status'; import { SERVICE_NAME, SPAN_DESTINATION_SERVICE_RESOURCE, } from '../../../../common/elasticsearch_fieldnames'; import { EuiTheme } from '../../../../../observability/public'; import { defaultIcon, iconForNode } from './icons'; -import { - getSeverity, - getSeverityColor, - ServiceAnomalyStats, - Severity, -} from '../../../../common/anomaly_detection'; +import { ServiceAnomalyStats } from '../../../../common/anomaly_detection'; export const popoverWidth = 280; -function getNodeSeverity(el: cytoscape.NodeSingular) { +function getServiceAnomalyStats(el: cytoscape.NodeSingular) { const serviceAnomalyStats: ServiceAnomalyStats | undefined = el.data( 'serviceAnomalyStats' ); - return getSeverity(serviceAnomalyStats?.anomalyScore); + + return serviceAnomalyStats; } function getBorderColorFn( @@ -32,10 +32,11 @@ function getBorderColorFn( ): cytoscape.Css.MapperFunction { return (el: cytoscape.NodeSingular) => { const hasAnomalyDetectionJob = el.data('serviceAnomalyStats') !== undefined; - const nodeSeverity = getNodeSeverity(el); + const anomalyStats = getServiceAnomalyStats(el); if (hasAnomalyDetectionJob) { - return ( - getSeverityColor(theme, nodeSeverity) || theme.eui.euiColorMediumShade + return getServiceHealthStatusColor( + theme, + anomalyStats?.healthStatus ?? ServiceHealthStatus.unknown ); } if (el.hasClass('primary') || el.selected()) { @@ -49,8 +50,8 @@ const getBorderStyle: cytoscape.Css.MapperFunction< cytoscape.NodeSingular, cytoscape.Css.LineStyle > = (el: cytoscape.NodeSingular) => { - const nodeSeverity = getNodeSeverity(el); - if (nodeSeverity === Severity.critical) { + const status = getServiceAnomalyStats(el)?.healthStatus; + if (status === ServiceHealthStatus.critical) { return 'double'; } else { return 'solid'; @@ -58,11 +59,11 @@ const getBorderStyle: cytoscape.Css.MapperFunction< }; function getBorderWidth(el: cytoscape.NodeSingular) { - const nodeSeverity = getNodeSeverity(el); + const status = getServiceAnomalyStats(el)?.healthStatus; - if (nodeSeverity === Severity.minor || nodeSeverity === Severity.major) { + if (status === ServiceHealthStatus.warning) { return 4; - } else if (nodeSeverity === Severity.critical) { + } else if (status === ServiceHealthStatus.critical) { return 8; } else { return 4; diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx index 94353080bc7d5..c6be0a352ef66 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/HealthBadge.tsx @@ -6,20 +6,22 @@ import React from 'react'; import { EuiBadge } from '@elastic/eui'; import { - getSeverityColor, - getSeverityLabel, - Severity, -} from '../../../../../common/anomaly_detection'; + getServiceHealthStatusColor, + getServiceHealthStatusLabel, + ServiceHealthStatus, +} from '../../../../../common/service_health_status'; import { useTheme } from '../../../../hooks/useTheme'; -export function HealthBadge({ severity }: { severity?: Severity }) { +export function HealthBadge({ + healthStatus, +}: { + healthStatus: ServiceHealthStatus; +}) { const theme = useTheme(); - const unknownColor = theme.eui.euiColorLightShade; - return ( - - {getSeverityLabel(severity)} + + {getServiceHealthStatusLabel(healthStatus)} ); } diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/List.test.js b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/List.test.js index 519d74827097b..7c306c16cca1f 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/List.test.js +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/List.test.js @@ -9,6 +9,7 @@ import { shallow } from 'enzyme'; import { ServiceList, SERVICE_COLUMNS } from '../index'; import props from './props.json'; import { mockMoment } from '../../../../../utils/testHelpers'; +import { ServiceHealthStatus } from '../../../../../../common/service_health_status'; describe('ServiceOverview -> List', () => { beforeAll(() => { @@ -52,25 +53,28 @@ describe('ServiceOverview -> List', () => { describe('without ML data', () => { it('does not render health column', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); const columns = wrapper.props().columns; - expect(columns[0].field).not.toBe('severity'); + expect(columns[0].field).not.toBe('healthStatus'); }); }); describe('with ML data', () => { it('renders health column', () => { const wrapper = shallow( - + ({ + ...item, + healthStatus: ServiceHealthStatus.warning, + }))} + /> ); const columns = wrapper.props().columns; - expect(columns[0].field).toBe('severity'); + expect(columns[0].field).toBe('healthStatus'); }); }); }); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/__snapshots__/List.test.js.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/__snapshots__/List.test.js.snap index da3f6ae89940a..e6a9823f3ee28 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/__snapshots__/List.test.js.snap +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/__test__/__snapshots__/List.test.js.snap @@ -1,6 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ServiceOverview -> List renders columns correctly 1`] = ``; +exports[`ServiceOverview -> List renders columns correctly 1`] = ` + +`; exports[`ServiceOverview -> List renders empty state 1`] = ` List renders empty state 1`] = ` } initialPageSize={50} initialSortDirection="desc" - initialSortField="severity" + initialSortField="healthStatus" items={Array []} sortFn={[Function]} /> @@ -106,7 +110,7 @@ exports[`ServiceOverview -> List renders with data 1`] = ` } initialPageSize={50} initialSortDirection="desc" - initialSortField="severity" + initialSortField="healthStatus" items={ Array [ Object { diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx index ce256137481cb..4c7c0824a7c49 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx @@ -10,6 +10,7 @@ import React from 'react'; import styled from 'styled-components'; import { ValuesType } from 'utility-types'; import { orderBy } from 'lodash'; +import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { asPercent } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceListAPIResponse } from '../../../../../server/lib/services/get_services'; @@ -20,14 +21,12 @@ import { ManagedTable, ITableColumn } from '../../../shared/ManagedTable'; import { EnvironmentBadge } from '../../../shared/EnvironmentBadge'; import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; import { AgentIcon } from '../../../shared/AgentIcon'; -import { Severity } from '../../../../../common/anomaly_detection'; import { HealthBadge } from './HealthBadge'; import { ServiceListMetric } from './ServiceListMetric'; interface Props { items: ServiceListAPIResponse['items']; noItemsMessage?: React.ReactNode; - displayHealthStatus: boolean; } type ServiceListItem = ValuesType; @@ -53,14 +52,18 @@ const AppLink = styled(TransactionOverviewLink)` export const SERVICE_COLUMNS: Array> = [ { - field: 'severity', + field: 'healthStatus', name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { defaultMessage: 'Health', }), width: px(unit * 6), sortable: true, - render: (_, { severity }) => { - return ; + render: (_, { healthStatus }) => { + return ( + + ); }, }, { @@ -172,40 +175,38 @@ export const SERVICE_COLUMNS: Array> = [ }, ]; -const SEVERITY_ORDER = [ - Severity.warning, - Severity.minor, - Severity.major, - Severity.critical, +const SERVICE_HEALTH_STATUS_ORDER = [ + ServiceHealthStatus.unknown, + ServiceHealthStatus.healthy, + ServiceHealthStatus.warning, + ServiceHealthStatus.critical, ]; -export function ServiceList({ - items, - displayHealthStatus, - noItemsMessage, -}: Props) { +export function ServiceList({ items, noItemsMessage }: Props) { + const displayHealthStatus = items.some((item) => 'healthStatus' in item); + const columns = displayHealthStatus ? SERVICE_COLUMNS - : SERVICE_COLUMNS.filter((column) => column.field !== 'severity'); + : SERVICE_COLUMNS.filter((column) => column.field !== 'healthStatus'); return ( { - // For severity, sort items by severity first, then by TPM + // For healthStatus, sort items by healthStatus first, then by TPM - return sortField === 'severity' + return sortField === 'healthStatus' ? orderBy( itemsToSort, [ (item) => { - return item.severity - ? SEVERITY_ORDER.indexOf(item.severity) + return item.healthStatus + ? SERVICE_HEALTH_STATUS_ORDER.indexOf(item.healthStatus) : -1; }, (item) => item.transactionsPerMinute?.value ?? 0, diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index e4ba1e36378d9..d8c8f25616560 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx @@ -10,6 +10,7 @@ import { merge } from 'lodash'; import React, { FunctionComponent, ReactChild } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; +import { ServiceHealthStatus } from '../../../../../common/service_health_status'; import { ServiceOverview } from '..'; import { EuiThemeProvider } from '../../../../../../observability/public'; import { ApmPluginContextValue } from '../../../../context/ApmPluginContext'; @@ -114,7 +115,7 @@ describe('Service Overview -> View', () => { errorsPerMinute: 200, avgResponseTime: 300, environments: ['test', 'dev'], - severity: 1, + healthStatus: ServiceHealthStatus.warning, }, { serviceName: 'My Go Service', @@ -123,7 +124,7 @@ describe('Service Overview -> View', () => { errorsPerMinute: 500, avgResponseTime: 600, environments: [], - severity: 10, + severity: ServiceHealthStatus.healthy, }, ], }); @@ -252,7 +253,7 @@ describe('Service Overview -> View', () => { errorsPerMinute: 200, avgResponseTime: 300, environments: ['test', 'dev'], - severity: 1, + healthStatus: ServiceHealthStatus.warning, }, ], }); diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap index b56f7d6820274..dfc0cc8637ff1 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap @@ -153,8 +153,8 @@ NodeList [ > - Unknown + Warning @@ -435,7 +435,7 @@ NodeList [ > 'severity' in item); - return ( <> @@ -134,7 +132,6 @@ export function ServiceOverview() { option.type === alertParams.anomalySeverityType + ); + + if (!selectedOption) { + throw new Error( + `Anomaly alert severity type ${alertParams.anomalySeverityType} is not supported.` + ); + } + + const threshold = selectedOption.threshold; + if (mlJobIds.length === 0) { return {}; } @@ -96,7 +118,7 @@ export function registerTransactionDurationAnomalyAlertType({ { range: { record_score: { - gte: alertParams.anomalyScore, + gte: threshold, }, }, }, diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts index ed8ae923e6e6c..da087b4c1911a 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from 'boom'; +import { getServiceHealthStatus } from '../../../common/service_health_status'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { PromiseReturnType } from '../../../typings/common'; import { @@ -12,6 +13,7 @@ import { } from '../../../common/transaction_types'; import { ServiceAnomalyStats, + getSeverity, ML_ERRORS, } from '../../../common/anomaly_detection'; import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group'; @@ -130,13 +132,19 @@ function transformResponseToServiceAnomalies( response.aggregations?.services.buckets ?? [] ).reduce( (statsByServiceName, { key: serviceName, top_score: topScoreAgg }) => { + const anomalyScore = topScoreAgg.hits.hits[0]?.sort?.[0]; + + const severity = getSeverity(anomalyScore); + const healthStatus = getServiceHealthStatus({ severity }); + return { ...statsByServiceName, [serviceName]: { transactionType: topScoreAgg.hits.hits[0]?._source?.by_field_value, - anomalyScore: topScoreAgg.hits.hits[0]?.sort?.[0], + anomalyScore, actualValue: topScoreAgg.hits.hits[0]?._source?.actual?.[0], jobId: topScoreAgg.hits.hits[0]?._source?.job_id, + healthStatus, }, }; }, diff --git a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts index e529198e717d3..f30b80feda302 100644 --- a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts +++ b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ServiceHealthStatus } from '../../../common/service_health_status'; + import { AGENT_NAME, SERVICE_ENVIRONMENT, @@ -43,6 +45,7 @@ const anomalies = { actualValue: 10000, anomalyScore: 50, jobId: 'apm-test-1234-ml-module-name', + healthStatus: ServiceHealthStatus.warning, }, }, }; diff --git a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts index e7e18cbff1c15..17799203fe73b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services/get_services_items_stats.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getServiceHealthStatus } from '../../../../common/service_health_status'; import { EventOutcome } from '../../../../common/event_outcome'; import { getSeverity } from '../../../../common/anomaly_detection'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; @@ -413,10 +414,11 @@ export const getHealthStatuses = async ( const stats = anomalies.serviceAnomalies[serviceName]; const severity = getSeverity(stats.anomalyScore); + const healthStatus = getServiceHealthStatus({ severity }); return { serviceName, - severity, + healthStatus, }; }); }; diff --git a/x-pack/plugins/ml/common/index.ts b/x-pack/plugins/ml/common/index.ts index 791a7de48f36f..9a415ac0718b3 100644 --- a/x-pack/plugins/ml/common/index.ts +++ b/x-pack/plugins/ml/common/index.ts @@ -5,3 +5,5 @@ */ export { SearchResponse7 } from './types/es_client'; +export { ANOMALY_SEVERITY, ANOMALY_THRESHOLD } from './constants/anomalies'; +export { getSeverityColor, getSeverityType } from './util/anomaly_utils'; diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 2c5dbe108ab1e..1cd52079b4e39 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -35,5 +35,8 @@ "dashboard", "savedObjects", "home" + ], + "extraPublicDirs": [ + "common" ] } diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap index 1424ca42539c0..199a49dce8f9e 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap @@ -77,6 +77,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -104,6 +105,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -130,6 +132,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -156,6 +159,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -209,6 +213,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -255,6 +260,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -301,6 +307,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -407,6 +414,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -465,6 +473,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -491,6 +500,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -504,6 +514,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -523,6 +534,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -548,6 +560,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -573,6 +586,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -605,6 +619,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -687,6 +702,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -734,6 +750,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -748,6 +765,7 @@ Array [ "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -806,6 +824,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -833,6 +852,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -859,6 +879,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -885,6 +906,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -938,6 +960,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -984,6 +1007,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1030,6 +1054,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -1136,6 +1161,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -1194,6 +1220,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1220,6 +1247,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1233,6 +1261,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -1252,6 +1281,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1277,6 +1307,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1302,6 +1333,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1334,6 +1366,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -1416,6 +1449,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, @@ -1463,6 +1497,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -1477,6 +1512,7 @@ Object { "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts index be3301964bd3c..11b5ca71e64e7 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts @@ -165,6 +165,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) "serviceAnomalyStats": Object { "actualValue": 3933482.1764705875, "anomalyScore": 2.6101702751482714, + "healthStatus": "healthy", "jobId": "apm-testing-d457-high_mean_transaction_duration", "transactionType": "request", }, @@ -179,6 +180,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) "serviceAnomalyStats": Object { "actualValue": 684716.5813953485, "anomalyScore": 0.20498907719907372, + "healthStatus": "healthy", "jobId": "apm-production-229a-high_mean_transaction_duration", "transactionType": "request", }, diff --git a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts index d08064f6aa70e..c93816dfb48b9 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts +++ b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts @@ -45,24 +45,24 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.items.length).to.be.greaterThan(0); }); - it('some items have severity set', () => { + it('some items have a health status set', () => { // Under the assumption that the loaded archive has // at least one APM ML job, and the time range is longer - // than 15m, at least one items should have severity set. - // Note that we currently have a bug where healthy services - // report as unknown (so without any severity status): + // than 15m, at least one items should have a health status + // set. Note that we currently have a bug where healthy + // services report as unknown (so without any health status): // https://github.com/elastic/kibana/issues/77083 - const severityScores = response.body.items.map((item: any) => item.severity); + const healthStatuses = response.body.items.map((item: any) => item.healthStatus); - expect(severityScores.filter(Boolean).length).to.be.greaterThan(0); + expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); - expectSnapshot(severityScores).toMatchInline(` + expectSnapshot(healthStatuses).toMatchInline(` Array [ undefined, undefined, - "warning", - "warning", + "healthy", + "healthy", undefined, undefined, undefined, From 5a31dce92d086bfe199334e6723a59c897feb14a Mon Sep 17 00:00:00 2001 From: Joe Portner <5295965+jportner@users.noreply.github.com> Date: Fri, 18 Sep 2020 08:16:57 -0400 Subject: [PATCH 04/40] Upgrade and consolidate prop-types and create-react-class (#77803) --- package.json | 4 +-- packages/kbn-i18n/package.json | 2 +- packages/kbn-ui-framework/package.json | 2 +- x-pack/package.json | 4 +-- yarn.lock | 34 ++++++-------------------- 5 files changed, 14 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index d258d21413fe4..fb1d7e50d7d22 100644 --- a/package.json +++ b/package.json @@ -308,7 +308,7 @@ "@types/pegjs": "^0.10.1", "@types/pngjs": "^3.3.2", "@types/podium": "^1.0.0", - "@types/prop-types": "^15.5.3", + "@types/prop-types": "^15.7.3", "@types/reach__router": "^1.2.6", "@types/react": "^16.9.36", "@types/react-dom": "^16.9.8", @@ -444,7 +444,7 @@ "pngjs": "^3.4.0", "postcss": "^7.0.32", "prettier": "^2.1.1", - "prop-types": "15.6.0", + "prop-types": "^15.7.2", "proxyquire": "1.8.0", "react-grid-layout": "^0.16.2", "react-markdown": "^4.3.1", diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 680c789bc9d9d..eccdff9060cbe 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -27,7 +27,7 @@ "intl-format-cache": "^2.1.0", "intl-messageformat": "^2.2.0", "intl-relativeformat": "^2.1.0", - "prop-types": "^15.6.2", + "prop-types": "^15.7.2", "react": "^16.12.0", "react-intl": "^2.8.0" } diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index a2151ca3381bc..363f97522a901 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -18,7 +18,7 @@ "classnames": "2.2.6", "focus-trap-react": "^3.1.1", "lodash": "^4.17.15", - "prop-types": "15.6.0", + "prop-types": "^15.7.2", "react": "^16.12.0", "react-ace": "^5.9.0", "react-color": "^2.13.8", diff --git a/x-pack/package.json b/x-pack/package.json index 9a1d424da4a1d..86fb6bec6ba89 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -107,7 +107,7 @@ "@types/papaparse": "^5.0.3", "@types/pngjs": "^3.3.2", "@types/pretty-ms": "^5.0.0", - "@types/prop-types": "^15.5.3", + "@types/prop-types": "^15.7.3", "@types/proper-lockfile": "^3.0.1", "@types/puppeteer": "^1.20.1", "@types/react": "^16.9.36", @@ -354,7 +354,7 @@ "papaparse": "^5.2.0", "pdfmake": "^0.1.65", "pngjs": "3.4.0", - "prop-types": "^15.6.0", + "prop-types": "^15.7.2", "proper-lockfile": "^3.2.0", "puid": "1.0.7", "puppeteer-core": "^1.19.0", diff --git a/yarn.lock b/yarn.lock index fa5f6ff135670..ffca5adcc562f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4388,10 +4388,10 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.1.tgz#f1a11e7babb0c3cad68100be381d1e064c68f1f6" integrity sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg== -"@types/prop-types@^15.5.3": - version "15.5.9" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.9.tgz#f2d14df87b0739041bc53a7d75e3d77d726a3ec0" - integrity sha512-Nha5b+jmBI271jdTMwrHiNXM+DvThjHOfyZtMX9kj/c/LUj2xiLHsG/1L3tJ8DjAoQN48cHwUwtqBotjyXaSdQ== +"@types/prop-types@^15.7.3": + version "15.7.3" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" + integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== "@types/proper-lockfile@^3.0.1": version "3.0.1" @@ -9590,7 +9590,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -create-react-class@^15.5.1: +create-react-class@^15.5.1, create-react-class@^15.5.2: version "15.6.3" resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== @@ -9599,15 +9599,6 @@ create-react-class@^15.5.1: loose-envify "^1.3.1" object-assign "^4.1.1" -create-react-class@^15.5.2: - version "15.6.2" - resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a" - integrity sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co= - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - object-assign "^4.1.1" - create-react-context@^0.1.5: version "0.1.6" resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.1.6.tgz#0f425931d907741127acc6e31acb4f9015dd9fdc" @@ -22793,15 +22784,6 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -prop-types@15.6.0: - version "15.6.0" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" - integrity sha1-zq8IMCL8RrSjX2nhPvda7Q1jmFY= - dependencies: - fbjs "^0.8.16" - loose-envify "^1.3.1" - object-assign "^4.1.1" - prop-types@15.7.2, prop-types@15.x, prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" @@ -28338,9 +28320,9 @@ typescript@4.0.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, types integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== ua-parser-js@^0.7.18: - version "0.7.21" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" - integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== + version "0.7.22" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3" + integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" From 613509d81a8799c34fdd81df00d3019c48adec9d Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Fri, 18 Sep 2020 08:54:08 -0400 Subject: [PATCH 05/40] Improve home screen for limited-access users (#77665) --- .../__snapshots__/home.test.js.snap | 2 + .../public/application/components/home.js | 5 +- .../__snapshots__/manage_data.test.tsx.snap | 3 + .../manage_data/manage_data.test.tsx | 5 + .../components/manage_data/manage_data.tsx | 52 +++---- .../solution_panel.test.tsx.snap | 1 + .../solutions_section/solution_panel.tsx | 1 + .../feature_catalogue_registry.test.ts | 34 +++++ .../feature_catalogue_registry.ts | 7 +- src/plugins/management/public/plugin.ts | 7 +- test/functional/page_objects/home_page.ts | 8 ++ .../server/plugin.ts | 5 +- .../plugins/snapshot_restore/server/plugin.ts | 1 + .../home/feature_controls/home_security.ts | 130 ++++++++++++++++++ .../apps/home/feature_controls/index.ts | 14 ++ x-pack/test/functional/apps/home/index.ts | 14 ++ .../security_and_spaces/tests/catalogue.ts | 10 +- .../security_only/tests/catalogue.ts | 10 +- .../spaces_only/tests/catalogue.ts | 10 +- 19 files changed, 287 insertions(+), 32 deletions(-) create mode 100644 x-pack/test/functional/apps/home/feature_controls/home_security.ts create mode 100644 x-pack/test/functional/apps/home/feature_controls/index.ts create mode 100644 x-pack/test/functional/apps/home/index.ts diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap index 1b10756c2975c..bf1e8c8f0b401 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap @@ -164,6 +164,7 @@ exports[`home directories should not render directory entry when showOnHomePage {stackManagement ? ( - + `; + +exports[`ManageData render empty without any features 1`] = ``; diff --git a/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx b/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx index 5d00370caf2cc..0e86bf7dd3d84 100644 --- a/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx +++ b/src/plugins/home/public/application/components/manage_data/manage_data.test.tsx @@ -88,4 +88,9 @@ describe('ManageData', () => { ); expect(component).toMatchSnapshot(); }); + + test('render empty without any features', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/plugins/home/public/application/components/manage_data/manage_data.tsx b/src/plugins/home/public/application/components/manage_data/manage_data.tsx index 0dfb4f949f0c7..85f1bc04f353b 100644 --- a/src/plugins/home/public/application/components/manage_data/manage_data.tsx +++ b/src/plugins/home/public/application/components/manage_data/manage_data.tsx @@ -36,31 +36,37 @@ export const ManageData: FC = ({ addBasePath, features }) => ( <> {features.length > 1 &&