From 1de67c7c805cdcc730cac1b660bf962dcb64f490 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Mon, 16 Aug 2021 13:07:16 -0400 Subject: [PATCH 01/19] Prevent observability table actions from ever wrapping (#108704) --- .../observability/public/pages/alerts/alerts_table_t_grid.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index 79ed63acd078e..b352d51d091b6 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -232,7 +232,7 @@ function ObservabilityActions({ /> )} - + Date: Mon, 16 Aug 2021 12:09:08 -0500 Subject: [PATCH 02/19] [uuid] Cleanup fs error message (#108574) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/core/server/environment/resolve_uuid.test.ts | 4 ++-- src/core/server/environment/resolve_uuid.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/server/environment/resolve_uuid.test.ts b/src/core/server/environment/resolve_uuid.test.ts index 60d740ef12193..40db2d697906b 100644 --- a/src/core/server/environment/resolve_uuid.test.ts +++ b/src/core/server/environment/resolve_uuid.test.ts @@ -227,7 +227,7 @@ describe('resolveInstanceUuid', () => { await expect( resolveInstanceUuid({ pathConfig, serverConfig, logger }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unable to read Kibana UUID file, please check the uuid.server configuration value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: EACCES"` + `"Unable to read UUID file at data-folder/uuid. Ensure Kibana has sufficient permissions to read / write to this file. Error was: EACCES"` ); }); it('throws an explicit error for file write errors', async () => { @@ -235,7 +235,7 @@ describe('resolveInstanceUuid', () => { await expect( resolveInstanceUuid({ pathConfig, serverConfig, logger }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unable to write Kibana UUID file, please check the uuid.server configuration value in kibana.yml and ensure Kibana has sufficient permissions to read / write to this file. Error was: EISDIR"` + `"Unable to write to UUID file at data-folder/uuid. Ensure Kibana has sufficient permissions to read / write to this file. Error was: EISDIR"` ); }); }); diff --git a/src/core/server/environment/resolve_uuid.ts b/src/core/server/environment/resolve_uuid.ts index aaee205dfbb90..74201a7ffa04f 100644 --- a/src/core/server/environment/resolve_uuid.ts +++ b/src/core/server/environment/resolve_uuid.ts @@ -79,8 +79,8 @@ async function readUuidFromFile(filepath: string, logger: Logger): Promise Date: Mon, 16 Aug 2021 12:15:44 -0500 Subject: [PATCH 03/19] Upgrade EUI to v37.1.1 (#108210) * eui to 37.1.0 * i18n tokens * license checker * disabled prop * i18n shapshot * date title snapshots * date title formatting * date title formatting * Revert "disabled prop" This reverts commit 68a48c435201717616312b64ec25834cf591dd04. * date title formatting * eui to 37.2.0 * trial: outsideClickCloses * Revert "trial: outsideClickCloses" This reverts commit 4da2299e4f93192c00813615de1bf351ceca2799. * eui to 37.1.1 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 2 +- .../i18n/__snapshots__/i18n_service.test.tsx.snap | 2 ++ src/core/public/i18n/i18n_eui_mapping.tsx | 10 ++++++++++ src/dev/license_checker/config.ts | 2 +- .../__snapshots__/time_filter.stories.storyshot | 8 ++++---- .../cypress/integration/urls/compatibility.spec.ts | 4 ++-- .../cypress/integration/urls/state.spec.ts | 10 ++++++---- yarn.lock | 8 ++++---- 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 98b2252cc0dba..950a916b2419f 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.14", "@elastic/ems-client": "7.14.0", - "@elastic/eui": "36.1.0", + "@elastic/eui": "37.1.1", "@elastic/filesaver": "1.1.2", "@elastic/good": "^9.0.1-kibana3", "@elastic/maki": "6.3.0", diff --git a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap index f723cf5bca23a..1f932d62c94b9 100644 --- a/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap +++ b/src/core/public/i18n/__snapshots__/i18n_service.test.tsx.snap @@ -100,6 +100,8 @@ exports[`#start() returns \`Context\` component 1`] = ` "euiDataGridSchema.jsonSortTextDesc": "Large-Small", "euiDataGridSchema.numberSortTextAsc": "Low-High", "euiDataGridSchema.numberSortTextDesc": "High-Low", + "euiDatePopoverButton.invalidTitle": [Function], + "euiDatePopoverButton.outdatedTitle": [Function], "euiFieldPassword.maskPassword": "Mask password", "euiFieldPassword.showPassword": "Show password as plain text. Note: this will visually expose your password on the screen.", "euiFilePicker.clearSelectedFiles": "Clear selected files", diff --git a/src/core/public/i18n/i18n_eui_mapping.tsx b/src/core/public/i18n/i18n_eui_mapping.tsx index 0a6c7a05e1c75..98b3fa8f81211 100644 --- a/src/core/public/i18n/i18n_eui_mapping.tsx +++ b/src/core/public/i18n/i18n_eui_mapping.tsx @@ -497,6 +497,16 @@ export const getEuiContextMapping = (): EuiTokensObject => { description: 'Descending size label', } ), + 'euiDatePopoverButton.invalidTitle': ({ title }: EuiValues) => + i18n.translate('core.euiDatePopoverButton.invalidTitle', { + defaultMessage: 'Invalid date: {title}', + values: { title }, + }), + 'euiDatePopoverButton.outdatedTitle': ({ title }: EuiValues) => + i18n.translate('core.euiDatePopoverButton.outdatedTitle', { + defaultMessage: 'Update needed: {title}', + values: { title }, + }), 'euiFieldPassword.showPassword': i18n.translate('core.euiFieldPassword.showPassword', { defaultMessage: 'Show password as plain text. Note: this will visually expose your password on the screen.', diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index e1900019a5b30..46326fcc3b52e 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -75,7 +75,7 @@ export const LICENSE_OVERRIDES = { '@mapbox/jsonlint-lines-primitives@2.0.2': ['MIT'], // license in readme https://github.com/tmcw/jsonlint 'node-sql-parser@3.6.1': ['(GPL-2.0 OR MIT)'], // GPL-2.0* https://github.com/taozhi8833998/node-sql-parser '@elastic/ems-client@7.14.0': ['Elastic License 2.0'], - '@elastic/eui@36.0.0': ['SSPL-1.0 OR Elastic License 2.0'], + '@elastic/eui@37.1.1': ['SSPL-1.0 OR Elastic License 2.0'], // TODO can be removed if the https://github.com/jindw/xmldom/issues/239 is released 'xmldom@0.1.27': ['MIT'], diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/time_filter/components/__stories__/__snapshots__/time_filter.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/time_filter/components/__stories__/__snapshots__/time_filter.stories.storyshot index a712563966e0b..646a2d3aa3583 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/time_filter/components/__stories__/__snapshots__/time_filter.stories.storyshot +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/filters/time_filter/components/__stories__/__snapshots__/time_filter.stories.storyshot @@ -153,7 +153,7 @@ exports[`Storyshots renderers/TimeFilter with absolute time bounds 1`] = ` data-test-subj="superDatePickerstartDatePopoverButton" disabled={false} onClick={[Function]} - title="01/01/2019" + title="~ 5 months ago" > ~ 5 months ago @@ -180,7 +180,7 @@ exports[`Storyshots renderers/TimeFilter with absolute time bounds 1`] = ` data-test-subj="superDatePickerendDatePopoverButton" disabled={false} onClick={[Function]} - title="12/31/2019" + title="~ in 7 months" > ~ in 7 months @@ -430,7 +430,7 @@ exports[`Storyshots renderers/TimeFilter with relative time bounds 1`] = ` data-test-subj="superDatePickerstartDatePopoverButton" disabled={false} onClick={[Function]} - title="now/w" + title="~ 6 days ago" > ~ 6 days ago @@ -457,7 +457,7 @@ exports[`Storyshots renderers/TimeFilter with relative time bounds 1`] = ` data-test-subj="superDatePickerendDatePopoverButton" disabled={false} onClick={[Function]} - title="now/w" + title="~ in a day" > ~ in a day diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts index fa4a5ee40d126..928555c8fdd28 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/compatibility.spec.ts @@ -28,8 +28,8 @@ import { import { cleanKibana } from '../../tasks/common'; const ABSOLUTE_DATE = { - endTime: '2019-08-01T20:33:29.186Z', - startTime: '2019-08-01T20:03:29.186Z', + endTime: 'Aug 1, 2019 @ 20:33:29.186', + startTime: 'Aug 1, 2019 @ 20:03:29.186', }; const RULE_ID = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3'; diff --git a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts index a72657d78b70d..47e6b0d34e3c5 100644 --- a/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/urls/state.spec.ts @@ -42,12 +42,14 @@ import { TIMELINE } from '../../screens/create_new_case'; import { cleanKibana } from '../../tasks/common'; const ABSOLUTE_DATE = { - endTime: '2019-08-01T20:33:29.186Z', + endTime: 'Aug 1, 2019 @ 20:33:29.186', endTimeTimeline: '2019-08-02T21:03:29.186Z', + endTimeTimelineFormatted: 'Aug 2, 2019 @ 21:03:29.186', newEndTimeTyped: 'Aug 01, 2019 @ 15:03:29.186', newStartTimeTyped: 'Aug 01, 2019 @ 14:33:29.186', - startTime: '2019-08-01T20:03:29.186Z', + startTime: 'Aug 1, 2019 @ 20:03:29.186', startTimeTimeline: '2019-08-02T20:03:29.186Z', + startTimeTimelineFormatted: 'Aug 2, 2019 @ 20:03:29.186', firefoxEndTimeTyped: '2019-08-01T15:03:29', firefoxStartTimeTyped: '2019-08-01T14:33:29', }; @@ -122,12 +124,12 @@ describe('url state', () => { cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).should( 'have.attr', 'title', - ABSOLUTE_DATE.startTimeTimeline + ABSOLUTE_DATE.startTimeTimelineFormatted ); cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE).should( 'have.attr', 'title', - ABSOLUTE_DATE.endTimeTimeline + ABSOLUTE_DATE.endTimeTimelineFormatted ); }); diff --git a/yarn.lock b/yarn.lock index bfceb3f209cf1..121ba05364a81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1466,10 +1466,10 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@36.1.0": - version "36.1.0" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-36.1.0.tgz#99946e193bc9d1616f52cbd16e1a5bc196bd450f" - integrity sha512-j09Kv5K9YBkb9NkM3+cGparJeYfjLfwyypG27UYvSOLCpOYxS3bPycwmD7QrzPjoTqH+HHzDanHSwJMp6ttabA== +"@elastic/eui@37.1.1": + version "37.1.1" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-37.1.1.tgz#06bd1e1908b8b56525107a3eca42af036b950ab5" + integrity sha512-wEehwi9Ei81ydl8ExDuALbzDH6dgaHqKSlv/3Pmm+BcuW8FW4ctr8K5YPSJ7+5hdOdG0Bps5nnIpCDEinXSsLw== dependencies: "@types/chroma-js" "^2.0.0" "@types/lodash" "^4.14.160" From f26eb12728f539a28272886ce75d15bb42ff6485 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 16 Aug 2021 12:17:13 -0500 Subject: [PATCH 04/19] Remove license badge for platinum users (#108596) This PR remove license badge for platinum users on the custom source confirmation page --- .../add_source/save_custom.test.tsx | 12 + .../components/add_source/save_custom.tsx | 298 +++++++++--------- 2 files changed, 165 insertions(+), 145 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx index 5ed777322cc08..a8a5810e7c0a2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.test.tsx @@ -5,6 +5,8 @@ * 2.0. */ +import { setMockValues } from '../../../../../__mocks__/kea_logic'; + import React from 'react'; import { shallow } from 'enzyme'; @@ -13,6 +15,8 @@ import { EuiLink, EuiPanel, EuiTitle } from '@elastic/eui'; import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; +import { LicenseBadge } from '../../../../components/shared/license_badge'; + import { SaveCustom } from './save_custom'; describe('SaveCustom', () => { @@ -28,11 +32,19 @@ describe('SaveCustom', () => { header:

Header

, }; it('renders', () => { + setMockValues({ hasPlatinumLicense: true }); const wrapper = shallow(); expect(wrapper.find(EuiPanel)).toHaveLength(1); expect(wrapper.find(EuiTitle)).toHaveLength(5); expect(wrapper.find(EuiLinkTo)).toHaveLength(1); + }); + + it('renders platinum LicenseBadge and link', () => { + setMockValues({ hasPlatinumLicense: false }); + const wrapper = shallow(); + + expect(wrapper.find(LicenseBadge)).toHaveLength(1); expect(wrapper.find(EuiLink)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index 9689ecfae4a94..bbece2f68c717 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -7,6 +7,8 @@ import React from 'react'; +import { useValues } from 'kea'; + import { EuiFlexGroup, EuiFlexItem, @@ -22,6 +24,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { LicensingLogic } from '../../../../../shared/licensing'; import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; import { CredentialItem } from '../../../../components/shared/credential_item'; import { LicenseBadge } from '../../../../components/shared/license_badge'; @@ -59,152 +62,157 @@ export const SaveCustom: React.FC = ({ newCustomSource: { id, accessToken, name }, isOrganization, header, -}) => ( - <> - {header} - - - - - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.heading', - { - defaultMessage: '{name} Created', - values: { name }, - } - )} -

-
-
- - - {SAVE_CUSTOM_BODY1} -
- {SAVE_CUSTOM_BODY2} -
- - {SAVE_CUSTOM_RETURN_BUTTON} - -
-
-
-
- - - - -

{SAVE_CUSTOM_API_KEYS_TITLE}

-
- -

{SAVE_CUSTOM_API_KEYS_BODY}

-
+}) => { + const { hasPlatinumLicense } = useValues(LicensingLogic); + return ( + <> + {header} + + + + + + + + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.heading', + { + defaultMessage: '{name} Created', + values: { name }, + } + )} +

+
+
+ + + {SAVE_CUSTOM_BODY1} +
+ {SAVE_CUSTOM_BODY2} +
+ + {SAVE_CUSTOM_RETURN_BUTTON} + +
+
+
+
+ + + + +

{SAVE_CUSTOM_API_KEYS_TITLE}

+
+ +

{SAVE_CUSTOM_API_KEYS_BODY}

+
+ + + + +
+
+
+
+ + + + +
+ +

{SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE}

+
+ + +

+ + Check out the documentation + + ), + }} + /> +

+
+
- +
+ +

{SAVE_CUSTOM_STYLING_RESULTS_TITLE}

+
+ + +

+ + Display Settings + + ), + }} + /> +

+
+
- +
+ + {!hasPlatinumLicense && } + + +

{SAVE_CUSTOM_DOC_PERMISSIONS_TITLE}

+
+ + +

+ + Document-level permissions + + ), + }} + /> +

+
+ + {!hasPlatinumLicense && ( + + + {LEARN_CUSTOM_FEATURES_BUTTON} + + + )} +
-
-
- - - - -
- -

{SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE}

-
- - -

- - Check out the documentation - - ), - }} - /> -

-
-
- -
- -

{SAVE_CUSTOM_STYLING_RESULTS_TITLE}

-
- - -

- - Display Settings - - ), - }} - /> -

-
-
- -
- - - - -

{SAVE_CUSTOM_DOC_PERMISSIONS_TITLE}

-
- - -

- - Document-level permissions - - ), - }} - /> -

-
- - - - {LEARN_CUSTOM_FEATURES_BUTTON} - - -
-
-
-
-
- -); +
+
+ + ); +}; From 2456803f9c25f81ad0869bf0c33a5aa9790845e7 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Mon, 16 Aug 2021 10:22:13 -0700 Subject: [PATCH 05/19] [APM] APM agent instrumentation instructions in Fleet (#108242) * [APM] APM Agent instrumentation instructions in Fleet integration (#106440) * moves agent/server instructions to common dir * fix misspelling of accordion * more spelling fixes * fixes missing i18n * fixes linting errors and missing key * fixes restricted import issue --- .../instructions/apm_agent_instructions.ts | 4 +- .../instructions/apm_server_instructions.ts | 0 .../agent_instructions_accordion.tsx | 205 ++++++++++++++++++ .../apm_agents/agent_instructions_mappings.ts | 99 +++++++++ .../fleet_integration/apm_agents/index.tsx | 45 ++++ .../apm_agents/replace_template_strings.ts | 28 +++ .../lazy_apm_agents_tab_extension.tsx | 16 ++ .../components/shared/agent_icon/index.tsx | 7 +- x-pack/plugins/apm/public/plugin.ts | 9 + .../apm/server/tutorial/envs/elastic_cloud.ts | 2 +- .../apm/server/tutorial/envs/on_prem.ts | 4 +- 11 files changed, 411 insertions(+), 8 deletions(-) rename x-pack/plugins/apm/{server => common}/tutorial/instructions/apm_agent_instructions.ts (99%) rename x-pack/plugins/apm/{server => common}/tutorial/instructions/apm_server_instructions.ts (100%) create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_agents_tab_extension.tsx diff --git a/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts b/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts similarity index 99% rename from x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts rename to x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts index e2bf09dae5bed..daac81298008e 100644 --- a/x-pack/plugins/apm/server/tutorial/instructions/apm_agent_instructions.ts +++ b/x-pack/plugins/apm/common/tutorial/instructions/apm_agent_instructions.ts @@ -457,8 +457,8 @@ export const createDotNetAgentInstructions = ( Core download the [Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink}) package. This package will automatically add every \ agent component to your application. \n\n In case you would like to minimize the dependencies, you can use the \ [Elastic.Apm.AspNetCore]({aspNetCorePackageLink}) package for just \ - ASP.NET Core monitoring or the [Elastic.Apm.EfCore]({efCorePackageLink}) package for just Entity Framework Core monitoring. \n\n \ - In case you only want to use the public Agent API for manual instrumentation use the [Elastic.Apm]({elasticApmPackageLink}) package.', + ASP.NET Core monitoring or the [Elastic.Apm.EfCore]({efCorePackageLink}) package for just Entity Framework Core monitoring. \n\n In \ + case you only want to use the public Agent API for manual instrumentation use the [Elastic.Apm]({elasticApmPackageLink}) package.', values: { allNuGetPackagesLink: 'https://www.nuget.org/packages?q=Elastic.apm', netCoreAllApmPackageLink: diff --git a/x-pack/plugins/apm/server/tutorial/instructions/apm_server_instructions.ts b/x-pack/plugins/apm/common/tutorial/instructions/apm_server_instructions.ts similarity index 100% rename from x-pack/plugins/apm/server/tutorial/instructions/apm_server_instructions.ts rename to x-pack/plugins/apm/common/tutorial/instructions/apm_server_instructions.ts diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx new file mode 100644 index 0000000000000..de9c7f651019e --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiAccordion, + EuiSpacer, + EuiText, + EuiCodeBlock, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { CreateAgentInstructions } from './agent_instructions_mappings'; +import { + Markdown, + useKibana, +} from '../../../../../../../src/plugins/kibana_react/public'; +import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; +import { AgentIcon } from '../../shared/agent_icon'; +import { NewPackagePolicy } from '../apm_policy_form/typings'; +import { getCommands } from '../../../tutorial/config_agent/commands/get_commands'; +import { CopyCommands } from '../../../tutorial/config_agent/copy_commands'; +import { replaceTemplateStrings } from './replace_template_strings'; + +function AccordionButtonContent({ + agentName, + title, +}: { + agentName: AgentName; + title: string; +}) { + return ( + + + + + + + + +

{title}

+
+
+ + +

+ {i18n.translate( + 'xpack.apm.fleet_integration.settings.apmAgent.description', + { + defaultMessage: + 'Configure instrumentation for {title} applications.', + values: { title }, + } + )} +

+
+
+
+
+
+ ); +} + +function InstructionsContent({ markdown }: { markdown: string }) { + return ( + + ); +} + +function TutorialConfigAgent({ + variantId, + apmServerUrl, + secretToken, +}: { + variantId: string; + apmServerUrl?: string; + secretToken?: string; +}) { + const commandBlock = getCommands({ + variantId, + policyDetails: { apmServerUrl, secretToken }, + }); + return ( + + + + + + {commandBlock} + + + ); +} + +interface Props { + newPolicy: NewPackagePolicy; + agentName: AgentName; + title: string; + variantId: string; + createAgentInstructions: CreateAgentInstructions; +} + +export function AgentInstructionsAccordion({ + newPolicy, + agentName, + title, + createAgentInstructions, + variantId, +}: Props) { + const docLinks = useKibana().services.docLinks; + const vars = newPolicy?.inputs?.[0]?.vars; + const apmServerUrl = vars?.url.value; + const secretToken = vars?.secret_token.value; + const steps = createAgentInstructions(apmServerUrl, secretToken); + return ( + + } + > + + {steps.map( + ( + { + title: stepTitle, + textPre, + textPost, + customComponentName, + commands, + }, + index + ) => { + const commandBlock = replaceTemplateStrings( + Array.isArray(commands) ? commands.join('\n') : commands || '', + docLinks + ); + return ( +
+ +

{stepTitle}

+
+ + + {textPre && ( + + + + + {commandBlock && ( + + + + )} + + )} + {commandBlock && ( + <> + + {commandBlock} + + )} + {customComponentName === 'TutorialConfigAgent' && ( + + )} + {customComponentName === 'TutorialConfigAgentRumScript' && ( + + )} + {textPost && ( + <> + + + + )} + + +
+ ); + } + )} +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts new file mode 100644 index 0000000000000..8bfdafe61d44e --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + createDotNetAgentInstructions, + createDjangoAgentInstructions, + createFlaskAgentInstructions, + createGoAgentInstructions, + createJavaAgentInstructions, + createJsAgentInstructions, + createNodeAgentInstructions, + createPhpAgentInstructions, + createRailsAgentInstructions, + createRackAgentInstructions, +} from '../../../../common/tutorial/instructions/apm_agent_instructions'; +import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; + +export type CreateAgentInstructions = ( + apmServerUrl?: string, + secretToken?: string +) => Array<{ + title: string; + textPre?: string; + commands?: string[]; + textPost?: string; + customComponentName?: string; +}>; + +export const ApmAgentInstructionsMappings: Array<{ + agentName: AgentName; + title: string; + variantId: string; + createAgentInstructions: CreateAgentInstructions; +}> = [ + { + agentName: 'java', + title: 'Java', + variantId: 'java', + createAgentInstructions: createJavaAgentInstructions, + }, + { + agentName: 'rum-js', + title: 'JavaScript (Real User Monitoring)', + variantId: 'js', + createAgentInstructions: createJsAgentInstructions, + }, + { + agentName: 'nodejs', + title: 'Node.js', + variantId: 'node', + createAgentInstructions: createNodeAgentInstructions, + }, + { + agentName: 'python', + title: 'Django', + variantId: 'django', + createAgentInstructions: createDjangoAgentInstructions, + }, + { + agentName: 'python', + title: 'Flask', + variantId: 'flask', + createAgentInstructions: createFlaskAgentInstructions, + }, + { + agentName: 'ruby', + title: 'Ruby on Rails', + variantId: 'rails', + createAgentInstructions: createRailsAgentInstructions, + }, + { + agentName: 'ruby', + title: 'Rack', + variantId: 'rack', + createAgentInstructions: createRackAgentInstructions, + }, + { + agentName: 'go', + title: 'Go', + variantId: 'go', + createAgentInstructions: createGoAgentInstructions, + }, + { + agentName: 'dotnet', + title: '.NET', + variantId: 'dotnet', + createAgentInstructions: createDotNetAgentInstructions, + }, + { + agentName: 'php', + title: 'PHP', + variantId: 'php', + createAgentInstructions: createPhpAgentInstructions, + }, +]; diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx new file mode 100644 index 0000000000000..d6a43a1e1268a --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiPanel, EuiSpacer } from '@elastic/eui'; +import React, { Fragment } from 'react'; +import { AgentInstructionsAccordion } from './agent_instructions_accordion'; +import { ApmAgentInstructionsMappings } from './agent_instructions_mappings'; +import { + NewPackagePolicy, + PackagePolicy, + PackagePolicyEditExtensionComponentProps, +} from '../apm_policy_form/typings'; + +interface Props { + policy: PackagePolicy; + newPolicy: NewPackagePolicy; + onChange: PackagePolicyEditExtensionComponentProps['onChange']; +} + +export function ApmAgents({ newPolicy }: Props) { + return ( +
+ {ApmAgentInstructionsMappings.map( + ({ agentName, title, createAgentInstructions, variantId }) => ( + + + + + + + ) + )} +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts new file mode 100644 index 0000000000000..2e0c0da8d0fb7 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CoreStart } from 'kibana/public'; +import Mustache from 'mustache'; + +const TEMPLATE_TAGS = ['{', '}']; + +export function replaceTemplateStrings( + text: string, + docLinks?: CoreStart['docLinks'] +) { + Mustache.parse(text, TEMPLATE_TAGS); + return Mustache.render(text, { + curlyOpen: '{', + curlyClose: '}', + config: { + docs: { + base_url: docLinks?.ELASTIC_WEBSITE_URL, + version: docLinks?.DOC_LINK_VERSION, + }, + }, + }); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_agents_tab_extension.tsx b/x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_agents_tab_extension.tsx new file mode 100644 index 0000000000000..e1fb6dee9ca96 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/lazy_apm_agents_tab_extension.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { lazy } from 'react'; +import { PackagePolicyEditExtensionComponent } from '../../../../fleet/public'; + +export const getLazyApmAgentsTabExtension = () => { + return lazy(async () => { + const { ApmAgents } = await import('./apm_agents'); + return { default: ApmAgents }; + }); +}; diff --git a/x-pack/plugins/apm/public/components/shared/agent_icon/index.tsx b/x-pack/plugins/apm/public/components/shared/agent_icon/index.tsx index a91ddb49d5e8f..b995e2bbe7cfa 100644 --- a/x-pack/plugins/apm/public/components/shared/agent_icon/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/agent_icon/index.tsx @@ -6,19 +6,20 @@ */ import React from 'react'; -import { EuiIcon } from '@elastic/eui'; +import { EuiIcon, EuiIconProps } from '@elastic/eui'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { getAgentIcon } from './get_agent_icon'; import { useTheme } from '../../../hooks/use_theme'; interface Props { agentName?: AgentName; + size?: EuiIconProps['size']; } export function AgentIcon(props: Props) { - const { agentName } = props; + const { agentName, size = 'l' } = props; const theme = useTheme(); const icon = getAgentIcon(agentName, theme.darkMode); - return ; + return ; } diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 1644e5ceed8ee..c884f228c85d2 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -52,6 +52,7 @@ import { } from './components/fleet_integration'; import { getLazyAPMPolicyCreateExtension } from './components/fleet_integration/lazy_apm_policy_create_extension'; import { getLazyAPMPolicyEditExtension } from './components/fleet_integration/lazy_apm_policy_edit_extension'; +import { getLazyApmAgentsTabExtension } from './components/fleet_integration/lazy_apm_agents_tab_extension'; export type ApmPluginSetup = ReturnType; @@ -365,6 +366,14 @@ export class ApmPlugin implements Plugin { view: 'package-policy-edit', Component: getLazyAPMPolicyEditExtension(), }); + + fleet.registerExtension({ + package: 'apm', + view: 'package-policy-edit-tabs', + tabs: [ + { title: 'APM Agents', Component: getLazyApmAgentsTabExtension() }, + ], + }); } } } diff --git a/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts index a595ae1dc8a8b..a7efaa3b00f34 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/elastic_cloud.ts @@ -23,7 +23,7 @@ import { createJavaAgentInstructions, createDotNetAgentInstructions, createPhpAgentInstructions, -} from '../instructions/apm_agent_instructions'; +} from '../../../common/tutorial/instructions/apm_agent_instructions'; import { CloudSetup } from '../../../../cloud/server'; export function createElasticCloudInstructions( diff --git a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts index 400da79e3d2d0..38c8dbfcbe8ba 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -21,7 +21,7 @@ import { createPhpAgentInstructions, createRackAgentInstructions, createRailsAgentInstructions, -} from '../instructions/apm_agent_instructions'; +} from '../../../common/tutorial/instructions/apm_agent_instructions'; import { createDownloadServerDeb, createDownloadServerOsx, @@ -30,7 +30,7 @@ import { createStartServerUnix, createStartServerUnixSysv, createWindowsServerInstructions, -} from '../instructions/apm_server_instructions'; +} from '../../../common/tutorial/instructions/apm_server_instructions'; export function onPremInstructions({ errorIndices, From ac4b3050230e5fa455e8f872c44fecbc7537581b Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Mon, 16 Aug 2021 12:24:41 -0500 Subject: [PATCH 06/19] [Security Solution] bump isolation timeout to 5 minutes (#108568) --- .../security_solution/common/endpoint/types/actions.ts | 3 +++ .../server/endpoint/routes/actions/isolation.test.ts | 8 ++++++++ .../server/endpoint/routes/actions/isolation.ts | 1 + 3 files changed, 12 insertions(+) diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts index be11ec3f99910..d49868aae9227 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -23,6 +23,9 @@ export interface EndpointAction { input_type: 'endpoint'; agents: string[]; user_id: string; + // the number of seconds Elastic Agent (on the host) should + // wait to send back an action result before it will timeout + timeout?: number; data: EndpointActionData; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts index d6660effde38c..2bfa5ae1aa3c4 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.test.ts @@ -274,6 +274,14 @@ describe('Host Isolation', () => { actionID ); }); + it('records the timeout in the action payload', async () => { + const ctx = await callRoute(ISOLATE_HOST_ROUTE, { + body: { endpoint_ids: ['XYZ'] }, + }); + const actionDoc: EndpointAction = (ctx.core.elasticsearch.client.asCurrentUser + .index as jest.Mock).mock.calls[0][0].body; + expect(actionDoc.timeout).toEqual(300); + }); it('succeeds when just an endpoint ID is provided', async () => { await callRoute(ISOLATE_HOST_ROUTE, { body: { endpoint_ids: ['XYZ'] } }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts index fceb45b17c258..45f0e851dfdd1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/isolation.ts @@ -120,6 +120,7 @@ export const isolationRequestHandler = function ( input_type: 'endpoint', agents: endpointData.map((endpt: HostMetadata) => endpt.elastic.agent.id), user_id: user!.username, + timeout: 300, // 5 minutes data: { command: isolate ? 'isolate' : 'unisolate', comment: req.body.comment ?? undefined, From 9fa62bfd2059c507024fe9aaa761434291db9238 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Mon, 16 Aug 2021 11:25:02 -0600 Subject: [PATCH 07/19] [Security Solution][Detection Engine] Fixes agnostic type bug (#108610) ## Summary Fixes agnostic type bug where in part 1 (#108225), I incorrectly used the same saved object type for both `single` and `agnostic`. Before the references for SO's were: ```json "references" : [ { "name" : "param:exceptionsList_0", "id" : "endpoint_list", "type" : "exception-list" <--- This should have been "exception-list-agnostic" type }, { "name" : "param:exceptionsList_1", "id" : "50e3bd70-ef1b-11eb-ad71-7de7959be71c", "type" : "exception-list" } ], ``` After: ```json "references" : [ { "name" : "param:exceptionsList_0", "id" : "endpoint_list", "type" : "exception-list-agnostic" <--- This should now be the "exception-list-agnostic" type }, { "name" : "param:exceptionsList_1", "id" : "50e3bd70-ef1b-11eb-ad71-7de7959be71c", "type" : "exception-list" } ], ``` Manual testing: Add a new `security_solution` alert and exception list as well as an endpoint list to it. Then save it Screen Shot 2021-08-13 at 5 00 39 PM Screen Shot 2021-08-13 at 5 00 47 PM Do this query in dev tools: ```json GET .kibana-hassanabad19/_search { "query": { "terms": { "alert.alertTypeId": [ "siem.signals" ] } }, "size": 10000 } ``` And check to ensure that the references look like the after picture where type has : `"type" : "exception-list-agnostic"` if we have an agnostic list. Ensure that on a page reload that the exception types are still there on the rule. Ensure that there are no errors in the console about not finding the correct SO type or anything else odd. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../signals/saved_object_references/README.md | 2 +- .../extract_exceptions_list.test.ts | 15 +++++--- .../extract_exceptions_list.ts | 4 +- .../extract_references.test.ts | 37 ++++++++++++++++++- .../inject_exceptions_list.test.ts | 2 +- .../inject_references.test.ts | 2 +- .../utils/get_saved_object_name_pattern.ts | 2 +- ...ct_name_pattern_for_exception_list.test.ts | 6 +-- 8 files changed, 52 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md index 059005707625f..893797afa44d7 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/README.md @@ -58,7 +58,7 @@ to any newly saved rule: { "name" : "param:exceptionsList_0", "id" : "endpoint_list", - "type" : "exception-list" + "type" : "exception-list-agnostic" }, { "name" : "param:exceptionsList_1", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts index 56a1e875ac5e7..75180c6eaee48 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.test.ts @@ -8,8 +8,11 @@ import { extractExceptionsList } from './extract_exceptions_list'; import { loggingSystemMock } from 'src/core/server/mocks'; import { RuleParams } from '../../schemas/rule_schemas'; -import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; -import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils'; +import { + EXCEPTION_LIST_NAMESPACE, + EXCEPTION_LIST_NAMESPACE_AGNOSTIC, +} from '@kbn/securitysolution-list-constants'; +import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('extract_exceptions_list', () => { type FuncReturn = ReturnType; @@ -48,21 +51,21 @@ describe('extract_exceptions_list', () => { { id: '123', name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, - type: EXCEPTION_LIST_NAMESPACE, + type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, }, ]); }); - test('It returns two exception lists transformed into a saved object references', () => { + test('It returns 2 exception lists transformed into a saved object references', () => { const twoInputs: RuleParams['exceptionsList'] = [ mockExceptionsList()[0], - { ...mockExceptionsList()[0], id: '976' }, + { ...mockExceptionsList()[0], id: '976', namespace_type: 'single' }, ]; expect(extractExceptionsList({ logger, exceptionsList: twoInputs })).toEqual([ { id: '123', name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, - type: EXCEPTION_LIST_NAMESPACE, + type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, }, { id: '976', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts index 9b7f8bbcefee1..93deae014005b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_exceptions_list.ts @@ -6,7 +6,7 @@ */ import { Logger, SavedObjectReference } from 'src/core/server'; -import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; +import { getSavedObjectType } from '@kbn/securitysolution-list-utils'; import { RuleParams } from '../../schemas/rule_schemas'; import { getSavedObjectNamePatternForExceptionsList } from './utils'; @@ -35,7 +35,7 @@ export const extractExceptionsList = ({ return exceptionsList.map((exceptionItem, index) => ({ name: getSavedObjectNamePatternForExceptionsList(index), id: exceptionItem.id, - type: EXCEPTION_LIST_NAMESPACE, + type: getSavedObjectType({ namespaceType: exceptionItem.namespace_type }), })); } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts index 31288559e9437..df5fdf4fcead4 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/extract_references.test.ts @@ -8,8 +8,11 @@ import { loggingSystemMock } from 'src/core/server/mocks'; import { extractReferences } from './extract_references'; import { RuleParams } from '../../schemas/rule_schemas'; -import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; -import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils'; +import { + EXCEPTION_LIST_NAMESPACE, + EXCEPTION_LIST_NAMESPACE_AGNOSTIC, +} from '@kbn/securitysolution-list-constants'; +import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('extract_references', () => { type FuncReturn = ReturnType; @@ -43,6 +46,36 @@ describe('extract_references', () => { { id: '123', name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, + type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + }, + ], + }); + }); + + test('It returns params untouched and the references extracted as 2 exception list saved object references', () => { + const params: Partial = { + note: 'some note', + exceptionsList: [ + mockExceptionsList()[0], + { ...mockExceptionsList()[0], id: '456', namespace_type: 'single' }, + ], + }; + expect( + extractReferences({ + logger, + params: params as RuleParams, + }) + ).toEqual({ + params: params as RuleParams, + references: [ + { + id: '123', + name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_0`, + type: EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + }, + { + id: '456', + name: `${EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME}_1`, type: EXCEPTION_LIST_NAMESPACE, }, ], diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts index fc35088da66fc..72373eebf4fba 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_exceptions_list.test.ts @@ -7,10 +7,10 @@ import { loggingSystemMock } from 'src/core/server/mocks'; import { SavedObjectReference } from 'src/core/server'; -import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils'; import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; import { injectExceptionsReferences } from './inject_exceptions_list'; import { RuleParams } from '../../schemas/rule_schemas'; +import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('inject_exceptions_list', () => { type FuncReturn = ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts index a80f19ae011d7..eae4cb20f7948 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/inject_references.test.ts @@ -7,10 +7,10 @@ import { loggingSystemMock } from 'src/core/server/mocks'; import { SavedObjectReference } from 'src/core/server'; -import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils'; import { EXCEPTION_LIST_NAMESPACE } from '@kbn/securitysolution-list-constants'; import { injectReferences } from './inject_references'; import { RuleParams } from '../../schemas/rule_schemas'; +import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './utils/constants'; describe('inject_references', () => { type FuncReturn = ReturnType; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts index f4e33cf57fa2b..f0fd4e86e44b0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern.ts @@ -6,7 +6,7 @@ */ /** - * Given a name and index this will return the pattern of "${name_${index}" + * Given a name and index this will return the pattern of "${name}_${index}" * @param name The name to suffix the string * @param index The index to suffix the string * @returns The pattern "${name_${index}" diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts index 98c575e8835be..82bf2e1506f97 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/saved_object_references/utils/get_saved_object_name_pattern_for_exception_list.test.ts @@ -5,10 +5,8 @@ * 2.0. */ -import { - EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME, - getSavedObjectNamePatternForExceptionsList, -} from '.'; +import { EXCEPTIONS_SAVED_OBJECT_REFERENCE_NAME } from './constants'; +import { getSavedObjectNamePatternForExceptionsList } from './get_saved_object_name_pattern_for_exception_list'; describe('get_saved_object_name_pattern_for_exception_list', () => { test('returns expected pattern given a zero', () => { From da259600222bd348e0b6c14b50cc5e6ac478ecfa Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Mon, 16 Aug 2021 12:55:32 -0500 Subject: [PATCH 08/19] [package testing] Bump status page timeout (#107813) The socket timeout for testing whether the status page is available or not is currently 30 seconds. This test was disabled for being flaky. Reproducing this locally hasn't been straight forward, but I am seeing an average of ~20 seconds, which is close enough to the timeout that I'd like to rule out machine differences. This gives the status check 120 seconds before dropping the connection. Related to #106749 and #107300 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- test/package/deb.yml | 3 +-- test/package/docker.yml | 3 +-- test/package/roles/assert_kibana_available/tasks/main.yml | 1 + test/package/rpm.yml | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/test/package/deb.yml b/test/package/deb.yml index 9133402d3eee3..294657e99473f 100644 --- a/test/package/deb.yml +++ b/test/package/deb.yml @@ -6,7 +6,6 @@ - assert_keystore_cli - assert_kibana_yml - assert_kibana_listening - # flaky https://github.com/elastic/kibana/issues/106749 - # - assert_kibana_available + - assert_kibana_available - assert_kibana_log - assert_kibana_data diff --git a/test/package/docker.yml b/test/package/docker.yml index 141f18e2c222a..bcc3f3e8f7792 100644 --- a/test/package/docker.yml +++ b/test/package/docker.yml @@ -4,5 +4,4 @@ - install_docker - install_kibana_docker - assert_kibana_listening - # flaky https://github.com/elastic/kibana/issues/106749 - # - assert_kibana_available \ No newline at end of file + - assert_kibana_available \ No newline at end of file diff --git a/test/package/roles/assert_kibana_available/tasks/main.yml b/test/package/roles/assert_kibana_available/tasks/main.yml index b096f9b87350d..db3805f7ad7fb 100644 --- a/test/package/roles/assert_kibana_available/tasks/main.yml +++ b/test/package/roles/assert_kibana_available/tasks/main.yml @@ -2,6 +2,7 @@ uri: url: "http://localhost:5601/api/status" status_code: [200, 401] + timeout: 120 register: result until: result.status != 503 retries: 3 diff --git a/test/package/rpm.yml b/test/package/rpm.yml index 5fb1d37a96918..456c2bdf18b72 100644 --- a/test/package/rpm.yml +++ b/test/package/rpm.yml @@ -6,7 +6,6 @@ - assert_keystore_cli - assert_kibana_yml - assert_kibana_listening - # flaky https://github.com/elastic/kibana/issues/106749 - # - assert_kibana_available + - assert_kibana_available - assert_kibana_log - assert_kibana_data From 024eeed3c3c587711d0fbfec113bf7cd2a7d1c8e Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Mon, 16 Aug 2021 14:01:01 -0400 Subject: [PATCH 09/19] [Uptime] adjust RAC RBAC and index settings (#108200) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts | 4 ++-- .../observability/public/pages/alerts/alerts_table_t_grid.tsx | 2 +- x-pack/plugins/uptime/server/plugin.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts index 476425487df1b..3295f7a971aa1 100644 --- a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts +++ b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts @@ -24,7 +24,7 @@ export const AlertConsumers = { INFRASTRUCTURE: 'infrastructure', OBSERVABILITY: 'observability', SIEM: 'siem', - SYNTHETICS: 'synthetics', + UPTIME: 'uptime', } as const; export type AlertConsumers = typeof AlertConsumers[keyof typeof AlertConsumers]; export type STATUS_VALUES = 'open' | 'acknowledged' | 'closed' | 'in-progress'; // TODO: remove 'in-progress' after migration to 'acknowledged' @@ -35,7 +35,7 @@ export const mapConsumerToIndexName: Record = infrastructure: '.alerts-observability.metrics', observability: '.alerts-observability', siem: ['.alerts-security.alerts', '.siem-signals'], - synthetics: '.alerts-observability-synthetics', + uptime: '.alerts-observability.uptime', }; export type ValidFeatureId = keyof typeof mapConsumerToIndexName; diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx index b352d51d091b6..06252f3de05ce 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table_t_grid.tsx @@ -155,7 +155,7 @@ const OBSERVABILITY_ALERT_CONSUMERS = [ AlertConsumers.APM, AlertConsumers.LOGS, AlertConsumers.INFRASTRUCTURE, - AlertConsumers.SYNTHETICS, + AlertConsumers.UPTIME, ]; function ObservabilityActions({ diff --git a/x-pack/plugins/uptime/server/plugin.ts b/x-pack/plugins/uptime/server/plugin.ts index 3e935eab153ac..a201eddc45345 100644 --- a/x-pack/plugins/uptime/server/plugin.ts +++ b/x-pack/plugins/uptime/server/plugin.ts @@ -36,8 +36,8 @@ export class Plugin implements PluginType { const { ruleDataService } = plugins.ruleRegistry; const ruleDataClient = ruleDataService.initializeIndex({ - feature: 'synthetics', - registrationContext: 'observability.synthetics', + feature: 'uptime', + registrationContext: 'observability.uptime', dataset: Dataset.alerts, componentTemplateRefs: [], componentTemplates: [ From c5ec5468c58f8ebe9e4544a175b5d58a2085d175 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Mon, 16 Aug 2021 14:02:49 -0400 Subject: [PATCH 10/19] [Uptime] migrate legacy alert apis (#107863) * uptime - migrate legacy alert apis * update alert fetch actions Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../triggers_actions_ui/public/index.ts | 1 + .../triggers_actions_ui/public/types.ts | 2 + .../uptime/common/constants/rest_api.ts | 10 +-- .../public/state/api/alert_actions.test.ts | 4 -- .../uptime/public/state/api/alert_actions.ts | 10 +-- .../plugins/uptime/public/state/api/alerts.ts | 65 +++++++++++++++---- 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index eedc5566ba1d5..b50a7efc9d907 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -21,6 +21,7 @@ export type { IErrorObject, AlertFlyoutCloseReason, AlertTypeParams, + AsApiContract, } from './types'; export { diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index b17d0493c4494..f01967592ea8c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -17,6 +17,7 @@ import { AlertHistoryDocumentTemplate, ALERT_HISTORY_PREFIX, AlertHistoryDefaultIndexName, + AsApiContract, } from '../../actions/common'; import { TypeRegistry } from './application/type_registry'; import { @@ -58,6 +59,7 @@ export { AlertHistoryDocumentTemplate, AlertHistoryDefaultIndexName, ALERT_HISTORY_PREFIX, + AsApiContract, }; export type ActionTypeIndex = Record; diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index 6a75686266533..b49ccc3222a07 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -26,9 +26,9 @@ export enum API_URLS { ML_CAPABILITIES = '/api/ml/ml_capabilities', ML_ANOMALIES_RESULT = `/api/ml/results/anomalies_table_data`, - ALERT_ACTIONS = '/api/actions', - CREATE_ALERT = '/api/alerts/alert', - ALERT = '/api/alerts/alert/', - ALERTS_FIND = '/api/alerts/_find', - ACTION_TYPES = '/api/actions/list_action_types', + RULE_CONNECTORS = '/api/actions/connectors', + CREATE_RULE = '/api/alerting/rule', + DELETE_RULE = '/api/alerting/rule/', + RULES_FIND = '/api/alerting/rules/_find', + CONNECTOR_TYPES = '/api/actions/connector_types', } diff --git a/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts b/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts index 15fb95f5d4a4d..96c581f7fab98 100644 --- a/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts +++ b/x-pack/plugins/uptime/public/state/api/alert_actions.test.ts @@ -58,7 +58,6 @@ describe('Alert Actions factory', () => { }); expect(resp).toEqual([ { - actionTypeId: '.pagerduty', group: 'recovered', id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', params: { @@ -69,7 +68,6 @@ describe('Alert Actions factory', () => { }, }, { - actionTypeId: '.pagerduty', group: 'xpack.uptime.alerts.actionGroups.monitorStatus', id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', params: { @@ -103,7 +101,6 @@ describe('Alert Actions factory', () => { }); expect(resp).toEqual([ { - actionTypeId: '.pagerduty', group: 'recovered', id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', params: { @@ -114,7 +111,6 @@ describe('Alert Actions factory', () => { }, }, { - actionTypeId: '.pagerduty', group: 'xpack.uptime.alerts.actionGroups.monitorStatus', id: 'f2a3b195-ed76-499a-805d-82d24d4eeba9', params: { diff --git a/x-pack/plugins/uptime/public/state/api/alert_actions.ts b/x-pack/plugins/uptime/public/state/api/alert_actions.ts index fff5fa9a67804..b0f5f3ea490e5 100644 --- a/x-pack/plugins/uptime/public/state/api/alert_actions.ts +++ b/x-pack/plugins/uptime/public/state/api/alert_actions.ts @@ -33,6 +33,8 @@ export const WEBHOOK_ACTION_ID: ActionTypeId = '.webhook'; const { MONITOR_STATUS } = ACTION_GROUP_DEFINITIONS; +export type RuleAction = Omit; + const getRecoveryMessage = (selectedMonitor: Ping) => { return i18n.translate('xpack.uptime.alerts.monitorStatus.recoveryMessage', { defaultMessage: 'Monitor {monitor} with url {url} has recovered with status Up', @@ -44,18 +46,16 @@ const getRecoveryMessage = (selectedMonitor: Ping) => { }; export function populateAlertActions({ defaultActions, selectedMonitor }: NewAlertParams) { - const actions: AlertAction[] = []; + const actions: RuleAction[] = []; defaultActions.forEach((aId) => { - const action: AlertAction = { + const action: RuleAction = { id: aId.id, - actionTypeId: aId.actionTypeId, group: MONITOR_STATUS.id, params: {}, }; - const recoveredAction: AlertAction = { + const recoveredAction: RuleAction = { id: aId.id, - actionTypeId: aId.actionTypeId, group: 'recovered', params: { message: getRecoveryMessage(selectedMonitor), diff --git a/x-pack/plugins/uptime/public/state/api/alerts.ts b/x-pack/plugins/uptime/public/state/api/alerts.ts index 5931936c48163..d74a73befb5d0 100644 --- a/x-pack/plugins/uptime/public/state/api/alerts.ts +++ b/x-pack/plugins/uptime/public/state/api/alerts.ts @@ -10,18 +10,35 @@ import { apiService } from './utils'; import { ActionConnector } from '../alerts/alerts'; import { AlertsResult, MonitorIdParam } from '../actions/types'; -import { ActionType, AlertAction } from '../../../../triggers_actions_ui/public'; +import { ActionType, AsApiContract } from '../../../../triggers_actions_ui/public'; import { API_URLS } from '../../../common/constants'; import { Alert, AlertTypeParams } from '../../../../alerting/common'; import { AtomicStatusCheckParams } from '../../../common/runtime_types/alerts'; -import { populateAlertActions } from './alert_actions'; +import { populateAlertActions, RuleAction } from './alert_actions'; import { Ping } from '../../../common/runtime_types/ping'; const UPTIME_AUTO_ALERT = 'UPTIME_AUTO'; -export const fetchConnectors = async () => { - return await apiService.get(API_URLS.ALERT_ACTIONS); +export const fetchConnectors = async (): Promise => { + const response = (await apiService.get(API_URLS.RULE_CONNECTORS)) as Array< + AsApiContract + >; + return response.map( + ({ + connector_type_id: actionTypeId, + referenced_by_count: referencedByCount, + is_preconfigured: isPreconfigured, + is_missing_secrets: isMissingSecrets, + ...res + }) => ({ + ...res, + actionTypeId, + referencedByCount, + isPreconfigured, + isMissingSecrets, + }) + ); }; export interface NewAlertParams extends AlertTypeParams { @@ -41,14 +58,21 @@ type NewMonitorStatusAlert = Omit< | 'muteAll' | 'mutedInstanceIds' | 'executionStatus' ->; + | 'alertTypeId' + | 'notifyWhen' + | 'actions' +> & { + rule_type_id: Alert['alertTypeId']; + notify_when: Alert['notifyWhen']; + actions: RuleAction[]; +}; export const createAlert = async ({ defaultActions, monitorId, selectedMonitor, }: NewAlertParams): Promise => { - const actions: AlertAction[] = populateAlertActions({ + const actions: RuleAction[] = populateAlertActions({ defaultActions, selectedMonitor, }); @@ -66,16 +90,16 @@ export const createAlert = async ({ filters: { 'url.port': [], 'observer.geo.name': [], 'monitor.type': [], tags: [] }, }, consumer: 'uptime', - alertTypeId: CLIENT_ALERT_TYPES.MONITOR_STATUS, + rule_type_id: CLIENT_ALERT_TYPES.MONITOR_STATUS, schedule: { interval: '1m' }, - notifyWhen: 'onActionGroupChange', + notify_when: 'onActionGroupChange', tags: [UPTIME_AUTO_ALERT], name: `${selectedMonitor?.monitor.name || selectedMonitor?.url?.full}(Simple status alert)`, enabled: true, throttle: null, }; - return await apiService.post(API_URLS.CREATE_ALERT, data); + return await apiService.post(API_URLS.CREATE_RULE, data); }; export const fetchMonitorAlertRecords = async (): Promise => { @@ -89,7 +113,7 @@ export const fetchMonitorAlertRecords = async (): Promise => { search_fields: ['name', 'tags'], search: 'UPTIME_AUTO', }; - return await apiService.get(API_URLS.ALERTS_FIND, data); + return await apiService.get(API_URLS.RULES_FIND, data); }; export const fetchAlertRecords = async ({ @@ -103,14 +127,29 @@ export const fetchAlertRecords = async ({ sort_field: 'name.keyword', sort_order: 'asc', }; - const alerts = await apiService.get(API_URLS.ALERTS_FIND, data); + const alerts = await apiService.get(API_URLS.RULES_FIND, data); return alerts.data.find((alert: Alert) => alert.params.monitorId === monitorId); }; export const disableAlertById = async ({ alertId }: { alertId: string }) => { - return await apiService.delete(API_URLS.ALERT + alertId); + return await apiService.delete(API_URLS.DELETE_RULE + alertId); }; export const fetchActionTypes = async (): Promise => { - return await apiService.get(API_URLS.ACTION_TYPES); + const response = (await apiService.get(API_URLS.CONNECTOR_TYPES)) as Array< + AsApiContract + >; + return response.map( + ({ + enabled_in_config: enabledInConfig, + enabled_in_license: enabledInLicense, + minimum_license_required: minimumLicenseRequired, + ...res + }: AsApiContract) => ({ + ...res, + enabledInConfig, + enabledInLicense, + minimumLicenseRequired, + }) + ); }; From 911271053bd78de31dee28b3e3eb13ce0ba518b9 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Mon, 16 Aug 2021 14:07:19 -0400 Subject: [PATCH 11/19] [Maps] Track proxied EMS requests (#108717) --- x-pack/plugins/maps/server/routes.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/server/routes.js b/x-pack/plugins/maps/server/routes.js index f41e9a9d199b8..635770a562c2d 100644 --- a/x-pack/plugins/maps/server/routes.js +++ b/x-pack/plugins/maps/server/routes.js @@ -77,7 +77,10 @@ export async function initRoutes(core, getLicenseId, emsSettings, kbnVersion, lo landingPageUrl: emsSettings.getEMSLandingPageUrl(), fetchFunction: fetch, }); - emsClient.addQueryParams({ license: currentLicenseId }); + emsClient.addQueryParams({ + license: currentLicenseId, + is_kibana_proxy: '1', // identifies this is proxied request from kibana + }); return emsClient; } else { return EMPTY_EMS_CLIENT; From 768957a2c2d5658267ecb88b6ce223a37ec8ad9e Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 16 Aug 2021 19:26:20 +0100 Subject: [PATCH 12/19] [ML] Addming failed job import telemetry (#108724) --- .../plugins/ml/common/constants/usage_collection.ts | 2 ++ .../import_jobs_flyout/import_jobs_flyout.tsx | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/common/constants/usage_collection.ts b/x-pack/plugins/ml/common/constants/usage_collection.ts index 647999d1903af..5d09b90e185db 100644 --- a/x-pack/plugins/ml/common/constants/usage_collection.ts +++ b/x-pack/plugins/ml/common/constants/usage_collection.ts @@ -7,7 +7,9 @@ export const ML_USAGE_EVENT = { IMPORTED_ANOMALY_DETECTOR_JOBS: 'imported_anomaly_detector_jobs', + IMPORT_FAILED_ANOMALY_DETECTOR_JOBS: 'import_failed_anomaly_detector_jobs', IMPORTED_DATA_FRAME_ANALYTICS_JOBS: 'imported_data_frame_analytics_jobs', + IMPORT_FAILED_DATA_FRAME_ANALYTICS_JOBS: 'import_failed_data_frame_analytics_jobs', EXPORTED_ANOMALY_DETECTOR_JOBS: 'exported_anomaly_detector_jobs', EXPORTED_DATA_FRAME_ANALYTICS_JOBS: 'exported_data_frame_analytics_jobs', } as const; diff --git a/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx b/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx index 34e77d1b64922..01e3e667f0560 100644 --- a/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx +++ b/x-pack/plugins/ml/public/application/components/import_export_jobs/import_jobs_flyout/import_jobs_flyout.tsx @@ -197,8 +197,10 @@ export const ImportJobsFlyout: FC = ({ isDisabled }) => { const results = await bulkCreateJobs(jobs); let successCount = 0; const errors: ErrorType[] = []; + const failedJobIds = new Set(); Object.entries(results).forEach(([jobId, { job, datafeed }]) => { if (job.error || datafeed.error) { + failedJobIds.add(jobId); if (job.error) { errors.push(job.error); } @@ -214,7 +216,8 @@ export const ImportJobsFlyout: FC = ({ isDisabled }) => { displayImportSuccessToast(successCount); } if (errors.length > 0) { - displayImportErrorToast(errors); + displayImportErrorToast(errors, failedJobIds.size); + mlUsageCollection.count('import_failed_anomaly_detector_jobs', failedJobIds.size); } }, []); @@ -234,7 +237,8 @@ export const ImportJobsFlyout: FC = ({ isDisabled }) => { displayImportSuccessToast(successCount); } if (errors.length > 0) { - displayImportErrorToast(errors); + displayImportErrorToast(errors, errors.length); + mlUsageCollection.count('import_failed_data_frame_analytics_jobs', errors.length); } }, []); @@ -246,10 +250,10 @@ export const ImportJobsFlyout: FC = ({ isDisabled }) => { displaySuccessToast(title); }, []); - const displayImportErrorToast = useCallback((errors: ErrorType[]) => { + const displayImportErrorToast = useCallback((errors: ErrorType[], failureCount: number) => { const title = i18n.translate('xpack.ml.importExport.importFlyout.importJobErrorToast', { defaultMessage: '{count, plural, one {# job} other {# jobs}} failed to import correctly', - values: { count: errors.length }, + values: { count: failureCount }, }); const errorList = errors.map(extractErrorProperties); From 85e07662d8f3981b9db32ae2b74600b8a816d983 Mon Sep 17 00:00:00 2001 From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Date: Mon, 16 Aug 2021 20:01:27 +0100 Subject: [PATCH 13/19] [RAC] Disable RAC multi-tenancy (#108506) * Disable RAC multi-tenancy --- .../src/alerts_as_data_rbac.ts | 3 --- x-pack/plugins/rule_registry/server/config.ts | 8 +++++- x-pack/plugins/rule_registry/server/plugin.ts | 27 ++++++++++++++++--- .../test/apm_api_integration/configs/index.ts | 1 - .../tests/alerts/rule_registry.ts | 2 +- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts index 3295f7a971aa1..719301bce8e06 100644 --- a/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts +++ b/packages/kbn-rule-data-utils/src/alerts_as_data_rbac.ts @@ -13,9 +13,6 @@ import type { EsQueryConfig } from '@kbn/es-query'; * registering a new instance of the rule data client * in a new plugin will require updating the below data structure * to include the index name where the alerts as data will be written to. - * - * This doesn't work in combination with the `xpack.ruleRegistry.index` - * setting, with which the user can change the index prefix. */ export const AlertConsumers = { diff --git a/x-pack/plugins/rule_registry/server/config.ts b/x-pack/plugins/rule_registry/server/config.ts index ce1d44cdb94ee..3b9155f319032 100644 --- a/x-pack/plugins/rule_registry/server/config.ts +++ b/x-pack/plugins/rule_registry/server/config.ts @@ -13,8 +13,14 @@ export const config = { write: schema.object({ enabled: schema.boolean({ defaultValue: false }), }), - index: schema.string({ defaultValue: '.alerts' }), + unsafe: schema.object({ + legacyMultiTenancy: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), + }), }), }; export type RuleRegistryPluginConfig = TypeOf; + +export const INDEX_PREFIX = '.alerts' as const; diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 2325e5dd20233..ed6f19cd3af56 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -13,12 +13,13 @@ import { KibanaRequest, CoreStart, IContextProvider, + SharedGlobalConfig, } from 'src/core/server'; import { PluginStartContract as AlertingStart } from '../../alerting/server'; import { SecurityPluginSetup } from '../../security/server'; -import { RuleRegistryPluginConfig } from './config'; +import { INDEX_PREFIX, RuleRegistryPluginConfig } from './config'; import { RuleDataPluginService } from './rule_data_plugin_service'; import { AlertsClientFactory } from './alert_data_client/alerts_client_factory'; import { AlertsClient } from './alert_data_client/alerts_client'; @@ -51,6 +52,7 @@ export class RuleRegistryPlugin RuleRegistryPluginStartDependencies > { private readonly config: RuleRegistryPluginConfig; + private readonly legacyConfig: SharedGlobalConfig; private readonly logger: Logger; private readonly alertsClientFactory: AlertsClientFactory; private ruleDataService: RuleDataPluginService | null; @@ -58,6 +60,8 @@ export class RuleRegistryPlugin constructor(initContext: PluginInitializerContext) { this.config = initContext.config.get(); + // TODO: Can be removed in 8.0.0. Exists to work around multi-tenancy users. + this.legacyConfig = initContext.config.legacy.get(); this.logger = initContext.logger.get(); this.ruleDataService = null; this.alertsClientFactory = new AlertsClientFactory(); @@ -67,7 +71,7 @@ export class RuleRegistryPlugin core: CoreSetup, plugins: RuleRegistryPluginSetupDependencies ): RuleRegistryPluginSetupContract { - const { config, logger } = this; + const { logger } = this; const startDependencies = core.getStartServices().then(([coreStart, pluginStart]) => { return { @@ -78,10 +82,25 @@ export class RuleRegistryPlugin this.security = plugins.security; + const isWriteEnabled = (config: RuleRegistryPluginConfig, legacyConfig: SharedGlobalConfig) => { + const hasEnabledWrite = config.write.enabled; + const hasSetCustomKibanaIndex = legacyConfig.kibana.index !== '.kibana'; + const hasSetUnsafeAccess = config.unsafe.legacyMultiTenancy.enabled; + + if (!hasEnabledWrite) return false; + + // Not using legacy multi-tenancy + if (!hasSetCustomKibanaIndex) { + return hasEnabledWrite; + } else { + return hasSetUnsafeAccess; + } + }; + this.ruleDataService = new RuleDataPluginService({ logger, - isWriteEnabled: config.write.enabled, - index: config.index, + isWriteEnabled: isWriteEnabled(this.config, this.legacyConfig), + index: INDEX_PREFIX, getClusterClient: async () => { const deps = await startDependencies; return deps.core.elasticsearch.client.asInternalUser; diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts index 793197e9fc1b8..51bcb30a0c176 100644 --- a/x-pack/test/apm_api_integration/configs/index.ts +++ b/x-pack/test/apm_api_integration/configs/index.ts @@ -27,7 +27,6 @@ const apmFtrConfigs = { license: 'trial' as const, kibanaConfig: { 'migrations.enableV2': 'false', - 'xpack.ruleRegistry.index': '.kibana-alerts', 'xpack.ruleRegistry.write.enabled': 'true', }, }, diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts index 7ff81defe5ff2..e1bd5a8d05b48 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts @@ -42,7 +42,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { const BULK_INDEX_DELAY = 1000; const INDEXING_DELAY = 5000; - const ALERTS_INDEX_TARGET = '.kibana-alerts-observability.apm.alerts*'; + const ALERTS_INDEX_TARGET = '.alerts-observability.apm.alerts*'; const APM_METRIC_INDEX_NAME = 'apm-8.0.0-transaction'; const createTransactionMetric = (override: Record) => { From 619db1cc5fe73b17242fa8178798b168c573b0cc Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 16 Aug 2021 12:25:11 -0700 Subject: [PATCH 14/19] [ML] Edits introductory text for uploading files (#108608) --- .../components/about_panel/welcome_content.tsx | 5 ++--- x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx index 7b091e699b617..13120b2b46dbe 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/about_panel/welcome_content.tsx @@ -39,8 +39,7 @@ export const WelcomeContent: FC = () => {

@@ -49,7 +48,7 @@ export const WelcomeContent: FC = () => {

diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 6160f22675cb9..b6471c07db0db 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8067,7 +8067,6 @@ "xpack.dataVisualizer.file.welcomeContent.newlineDelimitedJsonDescription": "改行区切りの JSON", "xpack.dataVisualizer.file.welcomeContent.supportedFileFormatDescription": "ファイルデータビジュアライザーはこれらのファイル形式をサポートしています:", "xpack.dataVisualizer.file.welcomeContent.uploadedFilesAllowedSizeDescription": "最大{maxFileSize}のファイルをアップロードできます。", - "xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileDescription": "ファイルデータビジュアライザーは、ログファイルのフィールドとメトリックの理解に役立ちます。ファイルをアップロードして、データを分析し、 Elasticsearch インデックスにインポートするか選択できます。", "xpack.dataVisualizer.file.xmlNotCurrentlySupportedErrorMessage": "XML は現在サポートされていません", "xpack.dataVisualizer.fileBeatConfig.paths": "ファイルのパスをここに追加してください", "xpack.dataVisualizer.fileBeatConfigFlyout.closeButton": "閉じる", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 0a65035a585e5..9c9fe3e001faa 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8301,7 +8301,6 @@ "xpack.dataVisualizer.file.welcomeContent.newlineDelimitedJsonDescription": "换行符分隔的 JSON", "xpack.dataVisualizer.file.welcomeContent.supportedFileFormatDescription": "File Data Visualizer 支持以下文件格式:", "xpack.dataVisualizer.file.welcomeContent.uploadedFilesAllowedSizeDescription": "您可以上传不超过 {maxFileSize} 的文件。", - "xpack.dataVisualizer.file.welcomeContent.visualizeDataFromLogFileDescription": "File Data Visualizer 可帮助您理解日志文件中的字段和指标。上传文件、分析文件数据,然后选择是否将数据导入 Elasticsearch 索引。", "xpack.dataVisualizer.file.xmlNotCurrentlySupportedErrorMessage": "当前不支持 XML", "xpack.dataVisualizer.fileBeatConfig.paths": "在此处将路径添加您的文件中", "xpack.dataVisualizer.fileBeatConfigFlyout.closeButton": "关闭", From fa7b414905f2371c3313b4f3431a7c24a1d7d715 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 16 Aug 2021 20:36:53 +0100 Subject: [PATCH 15/19] [ML] Add support for model_prune_window in job wizard (#108734) --- .../plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index ec39d08ee357d..158e522ddf9b3 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -101,6 +101,7 @@ export const analysisConfigSchema = schema.object({ stop_on_warn: schema.maybe(schema.boolean()), }) ), + model_prune_window: schema.maybe(schema.string()), }); export const anomalyDetectionJobSchema = { From ea3fcbd8e92ad1e9c5a05951b6f5892f4046febc Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 16 Aug 2021 22:38:52 +0300 Subject: [PATCH 16/19] provide execution context information for task and alerting (#108197) * provide execution context information for task.run and alert.execute * log default space * expose setRequestId to plugins for cases when a runtime scope exists outside of requestHandler * fix typescript errors in jest test files Unfortunately, some of the tests are still failing. Not quite sure what's going on, but it looks like the calls to `withContext()` are not returning the result of the function passed in, but only in the tests. Because the code seems to run fine when I run Kibana - but perhaps we're just lucky and don't need the results the tests are looking for, for my simple smoke tests. But seems unlikely to me - guessing the mock is not being set up correctly, or there's some weird interaction with jest mocks and async hooks. * fix alerting unit tests * add tests that withContext is called even when exection_context service is disabled * update docs * add fakerequestid registration * do not attach request id when undefined (FakeRequest, for example) * Revert "add fakerequestid registration" This reverts commit ca5a396dcc3980c988081c6679c3861cc2e832d0. * not to expose setRequestId for time being * cleanup alerting code * update docs * add a unit test for alerting execution context * add a test for execution context of task runner * improve description readability * update type definitons * fall back to unknownId if no requestId is provided Co-authored-by: Patrick Mueller --- .../execution_context_service.mock.ts | 8 ++ .../execution_context_service.test.ts | 37 ++++++++- .../execution_context_service.ts | 11 ++- x-pack/plugins/alerting/server/plugin.ts | 1 + .../server/task_runner/task_runner.test.ts | 17 ++++ .../server/task_runner/task_runner.ts | 79 +++++++++++-------- .../task_runner/task_runner_factory.test.ts | 4 + .../server/task_runner/task_runner_factory.ts | 4 +- .../server/ephemeral_task_lifecycle.test.ts | 4 + .../server/ephemeral_task_lifecycle.ts | 7 +- x-pack/plugins/task_manager/server/plugin.ts | 8 +- .../server/polling_lifecycle.test.ts | 3 + .../task_manager/server/polling_lifecycle.ts | 7 +- .../task_running/ephemeral_task_runner.ts | 16 +++- .../server/task_running/task_runner.test.ts | 53 +++++++++++++ .../server/task_running/task_runner.ts | 20 ++++- 16 files changed, 231 insertions(+), 48 deletions(-) diff --git a/src/core/server/execution_context/execution_context_service.mock.ts b/src/core/server/execution_context/execution_context_service.mock.ts index 2e31145f6c0ee..68aab7a5b84f8 100644 --- a/src/core/server/execution_context/execution_context_service.mock.ts +++ b/src/core/server/execution_context/execution_context_service.mock.ts @@ -6,12 +6,18 @@ * Side Public License, v 1. */ +import type { KibanaExecutionContext } from '../../types'; import type { IExecutionContext, InternalExecutionContextSetup, ExecutionContextSetup, } from './execution_context_service'; +// attempted removal of any: unsuccessful! In theory, replaceable with /R +function withContextMock(context: KibanaExecutionContext | undefined, fn: () => any): any { + return fn(); +} + const createExecutionContextMock = () => { const mock: jest.Mocked = { set: jest.fn(), @@ -21,6 +27,7 @@ const createExecutionContextMock = () => { getParentContextFrom: jest.fn(), getAsHeader: jest.fn(), }; + mock.withContext.mockImplementation(withContextMock); return mock; }; const createInternalSetupContractMock = () => { @@ -32,6 +39,7 @@ const createSetupContractMock = () => { const mock: jest.Mocked = { withContext: jest.fn(), }; + mock.withContext.mockImplementation(withContextMock); return mock; }; diff --git a/src/core/server/execution_context/execution_context_service.test.ts b/src/core/server/execution_context/execution_context_service.test.ts index 3abaa13d11103..3fa4de34ebda0 100644 --- a/src/core/server/execution_context/execution_context_service.test.ts +++ b/src/core/server/execution_context/execution_context_service.test.ts @@ -346,7 +346,7 @@ describe('ExecutionContextService', () => { id: 'id-a', description: 'description-a', }, - (i) => i + () => null ); expect(loggingSystemMock.collect(core.logger).debug).toMatchInlineSnapshot(` Array [ @@ -378,6 +378,26 @@ describe('ExecutionContextService', () => { expect(result).toBeUndefined(); }); + it('executes provided function when disabled', async () => { + const coreWithDisabledService = mockCoreContext.create(); + coreWithDisabledService.configService.atPath.mockReturnValue( + new BehaviorSubject({ enabled: false }) + ); + const disabledService = new ExecutionContextService(coreWithDisabledService).setup(); + const fn = jest.fn(); + + disabledService.withContext( + { + type: 'type-b', + name: 'name-b', + id: 'id-b', + description: 'description-b', + }, + fn + ); + + expect(fn).toHaveBeenCalledTimes(1); + }); }); describe('getAsHeader', () => { @@ -387,6 +407,21 @@ describe('ExecutionContextService', () => { expect(service.getAsHeader()).toBe('1234'); }); + it('falls back to "unknownId" if no id provided', async () => { + expect(service.getAsHeader()).toBe('unknownId'); + }); + + it('falls back to "unknownId" and context if no id provided', async () => { + service.set({ + type: 'type-a', + name: 'name-a', + id: 'id-a', + description: 'description-a', + }); + + expect(service.getAsHeader()).toBe('unknownId;kibana:type-a:name-a:id-a'); + }); + it('returns request id and registered context', async () => { service.setRequestId('1234'); service.set({ diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index 5a8d104cebcd9..41b225cf1d0f3 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -34,7 +34,7 @@ export interface IExecutionContext { * https://nodejs.org/api/async_context.html#async_context_asynclocalstorage_enterwith_store */ get(): IExecutionContextContainer | undefined; - withContext(context: KibanaExecutionContext | undefined, fn: (...args: any[]) => R): R; + withContext(context: KibanaExecutionContext | undefined, fn: () => R): R; /** * returns serialized representation to send as a header **/ @@ -153,8 +153,11 @@ export class ExecutionContextService private getAsHeader(): string | undefined { if (!this.enabled) return; - const stringifiedCtx = this.contextStore.getStore()?.toString(); - const requestId = this.requestIdStore.getStore()?.requestId; - return stringifiedCtx ? `${requestId};kibana:${stringifiedCtx}` : requestId; + // requestId may not be present in the case of FakeRequest + const requestId = this.requestIdStore.getStore()?.requestId ?? 'unknownId'; + const executionContext = this.contextStore.getStore()?.toString(); + const executionContextStr = executionContext ? `;kibana:${executionContext}` : ''; + + return `${requestId}${executionContextStr}`; } } diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 197cf49edc147..6f257fa1e0e68 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -381,6 +381,7 @@ export class AlertingPlugin { basePathService: core.http.basePath, eventLogger: this.eventLogger!, internalSavedObjectsRepository: core.savedObjects.createInternalRepository(['alert']), + executionContext: core.executionContext, ruleTypeRegistry: this.ruleTypeRegistry!, kibanaBaseUrl: this.kibanaBaseUrl, supportsEphemeralTasks: plugins.taskManager.supportsEphemeralTasks(), diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 4d876a1d6ecb5..26b17d76553fd 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -27,6 +27,7 @@ import { loggingSystemMock, savedObjectsRepositoryMock, httpServiceMock, + executionContextServiceMock, } from '../../../../../src/core/server/mocks'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; import { actionsMock, actionsClientMock } from '../../../actions/server/mocks'; @@ -89,6 +90,7 @@ describe('Task Runner', () => { type TaskRunnerFactoryInitializerParamsType = jest.Mocked & { actionsPlugin: jest.Mocked; eventLogger: jest.Mocked; + executionContext: ReturnType; }; const taskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType = { @@ -97,6 +99,7 @@ describe('Task Runner', () => { getRulesClientWithRequest: jest.fn().mockReturnValue(rulesClient), encryptedSavedObjectsClient, logger: loggingSystemMock.create().get(), + executionContext: executionContextServiceMock.createInternalStartContract(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), basePathService: httpServiceMock.createBasePath(), eventLogger: eventLoggerMock.create(), @@ -185,6 +188,9 @@ describe('Task Runner', () => { (actionTypeId, actionId, params) => params ); ruleTypeRegistry.get.mockReturnValue(alertType); + taskRunnerFactoryInitializerParams.executionContext.withContext.mockImplementation((ctx, fn) => + fn() + ); }); test('successfully executes the task', async () => { @@ -338,6 +344,17 @@ describe('Task Runner', () => { }, { refresh: false, namespace: undefined } ); + + expect(taskRunnerFactoryInitializerParams.executionContext.withContext).toBeCalledTimes(1); + expect(taskRunnerFactoryInitializerParams.executionContext.withContext).toHaveBeenCalledWith( + { + id: '1', + name: 'execute test', + type: 'alert', + description: 'execute [test] with name [alert-name] in [default] namespace', + }, + expect.any(Function) + ); }); testAgainstEphemeralSupport( diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 63748eafe037c..6694146c3e2b9 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -262,44 +262,55 @@ export class TaskRunner< let updatedAlertTypeState: void | Record; try { - updatedAlertTypeState = await this.alertType.executor({ - alertId, - services: { - ...services, - alertInstanceFactory: createAlertInstanceFactory< - InstanceState, - InstanceContext, - WithoutReservedActionGroups - >(alertInstances), - }, - params, - state: alertTypeState as State, - startedAt: this.taskInstance.startedAt!, - previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, - spaceId, - namespace, - name, - tags, - createdBy, - updatedBy, - rule: { + const ctx = { + type: 'alert', + name: `execute ${alert.alertTypeId}`, + id: alertId, + description: `execute [${alert.alertTypeId}] with name [${name}] in [${ + namespace ?? 'default' + }] namespace`, + }; + + updatedAlertTypeState = await this.context.executionContext.withContext(ctx, () => + this.alertType.executor({ + alertId, + services: { + ...services, + alertInstanceFactory: createAlertInstanceFactory< + InstanceState, + InstanceContext, + WithoutReservedActionGroups + >(alertInstances), + }, + params, + state: alertTypeState as State, + startedAt: this.taskInstance.startedAt!, + previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, + spaceId, + namespace, name, tags, - consumer, - producer: alertType.producer, - ruleTypeId: alert.alertTypeId, - ruleTypeName: alertType.name, - enabled, - schedule, - actions, createdBy, updatedBy, - createdAt, - updatedAt, - throttle, - notifyWhen, - }, - }); + rule: { + name, + tags, + consumer, + producer: alertType.producer, + ruleTypeId: alert.alertTypeId, + ruleTypeName: alertType.name, + enabled, + schedule, + actions, + createdBy, + updatedBy, + createdAt, + updatedAt, + throttle, + notifyWhen, + }, + }) + ); } catch (err) { event.message = `alert execution failure: ${alertLabel}`; event.error = event.error || {}; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index 61aa168acf617..d262607958347 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -19,6 +19,9 @@ import { alertsMock, rulesClientMock } from '../mocks'; import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; import { UntypedNormalizedAlertType } from '../rule_type_registry'; import { ruleTypeRegistryMock } from '../rule_type_registry.mock'; +import { executionContextServiceMock } from '../../../../../src/core/server/mocks'; + +const executionContext = executionContextServiceMock.createSetupContract(); const alertType: UntypedNormalizedAlertType = { id: 'test', @@ -81,6 +84,7 @@ describe('Task Runner Factory', () => { kibanaBaseUrl: 'https://localhost:5601', supportsEphemeralTasks: true, maxEphemeralActionsPerAlert: new Promise((resolve) => resolve(10)), + executionContext, }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index 4d9c7b37ed645..524b779a0d9ac 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -6,11 +6,12 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { +import type { Logger, KibanaRequest, ISavedObjectsRepository, IBasePath, + ExecutionContextStart, } from '../../../../../src/core/server'; import { RunContext } from '../../../task_manager/server'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; @@ -36,6 +37,7 @@ export interface TaskRunnerContext { actionsPlugin: ActionsPluginStartContract; eventLogger: IEventLogger; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; + executionContext: ExecutionContextStart; spaceIdToNamespace: SpaceIdToNamespaceFunction; basePathService: IBasePath; internalSavedObjectsRepository: ISavedObjectsRepository; diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index 182e7cd5bcabf..5dbea8e2f4dee 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -21,6 +21,9 @@ import { asTaskPollingCycleEvent, asTaskRunEvent, TaskPersistence } from './task import { TaskRunResult } from './task_running'; import { TaskPoolRunResult } from './task_pool'; import { TaskPoolMock } from './task_pool.mock'; +import { executionContextServiceMock } from '../../../../src/core/server/mocks'; + +const executionContext = executionContextServiceMock.createSetupContract(); describe('EphemeralTaskLifecycle', () => { function initTaskLifecycleParams({ @@ -37,6 +40,7 @@ describe('EphemeralTaskLifecycle', () => { const opts: EphemeralTaskLifecycleOpts = { logger: taskManagerLogger, definitions: new TaskTypeDictionary(taskManagerLogger), + executionContext, config: { enabled: true, max_workers: 10, diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts index ce719ebed36e4..ded9091b4f226 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.ts @@ -7,7 +7,7 @@ import { Subject, Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; -import { Logger } from '../../../../src/core/server'; +import { Logger, ExecutionContextStart } from '../../../../src/core/server'; import { Result, asErr, asOk } from './lib/result_type'; import { TaskManagerConfig } from './config'; @@ -28,6 +28,7 @@ export interface EphemeralTaskLifecycleOpts { elasticsearchAndSOAvailability$: Observable; pool: TaskPool; lifecycleEvent: Observable; + executionContext: ExecutionContextStart; } export type EphemeralTaskInstanceRequest = Omit; @@ -46,6 +47,7 @@ export class EphemeralTaskLifecycle { private config: TaskManagerConfig; private middleware: Middleware; private lifecycleSubscription: Subscription = Subscription.EMPTY; + private readonly executionContext: ExecutionContextStart; constructor({ logger, @@ -54,6 +56,7 @@ export class EphemeralTaskLifecycle { pool, lifecycleEvent, config, + executionContext, }: EphemeralTaskLifecycleOpts) { this.logger = logger; this.middleware = middleware; @@ -61,6 +64,7 @@ export class EphemeralTaskLifecycle { this.pool = pool; this.lifecycleEvent = lifecycleEvent; this.config = config; + this.executionContext = executionContext; if (this.enabled) { this.lifecycleSubscription = this.lifecycleEvent @@ -179,6 +183,7 @@ export class EphemeralTaskLifecycle { beforeRun: this.middleware.beforeRun, beforeMarkRunning: this.middleware.beforeMarkRunning, onTaskEvent: this.emitEvent, + executionContext: this.executionContext, }); }; } diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 3d3d180fc0665..11746e2da2847 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -126,7 +126,11 @@ export class TaskManagerPlugin }; } - public start({ savedObjects, elasticsearch }: CoreStart): TaskManagerStartContract { + public start({ + savedObjects, + elasticsearch, + executionContext, + }: CoreStart): TaskManagerStartContract { const savedObjectsRepository = savedObjects.createInternalRepository(['task']); const serializer = savedObjects.createSerializer(); @@ -150,6 +154,7 @@ export class TaskManagerPlugin config: this.config!, definitions: this.definitions, logger: this.logger, + executionContext, taskStore, middleware: this.middleware, elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, @@ -160,6 +165,7 @@ export class TaskManagerPlugin config: this.config!, definitions: this.definitions, logger: this.logger, + executionContext, middleware: this.middleware, elasticsearchAndSOAvailability$: this.elasticsearchAndSOAvailability$!, pool: this.taskPollingLifecycle.pool, diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts index aad03951bbb9b..42bccbcbaba89 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts @@ -20,7 +20,9 @@ import type { TaskClaiming as TaskClaimingClass } from './queries/task_claiming' import { asOk, Err, isErr, isOk, Result } from './lib/result_type'; import { FillPoolResult } from './lib/fill_pool'; import { ElasticsearchResponseError } from './lib/identify_es_error'; +import { executionContextServiceMock } from '../../../../src/core/server/mocks'; +const executionContext = executionContextServiceMock.createSetupContract(); let mockTaskClaiming = taskClaimingMock.create({}); jest.mock('./queries/task_claiming', () => { return { @@ -69,6 +71,7 @@ describe('TaskPollingLifecycle', () => { middleware: createInitialMiddleware(), maxWorkersConfiguration$: of(100), pollIntervalConfiguration$: of(100), + executionContext, }; beforeEach(() => { diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.ts index 16b15d0c46e3e..847e1f32e8f2a 100644 --- a/x-pack/plugins/task_manager/server/polling_lifecycle.ts +++ b/x-pack/plugins/task_manager/server/polling_lifecycle.ts @@ -9,7 +9,7 @@ import { Subject, Observable, Subscription } from 'rxjs'; import { pipe } from 'fp-ts/lib/pipeable'; import { Option, some, map as mapOptional } from 'fp-ts/lib/Option'; import { tap } from 'rxjs/operators'; -import { Logger } from '../../../../src/core/server'; +import type { Logger, ExecutionContextStart } from '../../../../src/core/server'; import { Result, asErr, mapErr, asOk, map, mapOk } from './lib/result_type'; import { ManagedConfiguration } from './lib/create_managed_configuration'; @@ -53,6 +53,7 @@ export type TaskPollingLifecycleOpts = { config: TaskManagerConfig; middleware: Middleware; elasticsearchAndSOAvailability$: Observable; + executionContext: ExecutionContextStart; } & ManagedConfiguration; export type TaskLifecycleEvent = @@ -73,6 +74,7 @@ export class TaskPollingLifecycle { private store: TaskStore; private taskClaiming: TaskClaiming; private bufferedStore: BufferedTaskStore; + private readonly executionContext: ExecutionContextStart; private logger: Logger; public pool: TaskPool; @@ -100,11 +102,13 @@ export class TaskPollingLifecycle { config, taskStore, definitions, + executionContext, }: TaskPollingLifecycleOpts) { this.logger = logger; this.middleware = middleware; this.definitions = definitions; this.store = taskStore; + this.executionContext = executionContext; const emitEvent = (event: TaskLifecycleEvent) => this.events$.next(event); @@ -227,6 +231,7 @@ export class TaskPollingLifecycle { beforeMarkRunning: this.middleware.beforeMarkRunning, onTaskEvent: this.emitEvent, defaultMaxAttempts: this.taskClaiming.maxAttempts, + executionContext: this.executionContext, }); }; diff --git a/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts b/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts index bc1ff0541fdff..358a8003382b0 100644 --- a/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/ephemeral_task_runner.ts @@ -14,7 +14,7 @@ import apm from 'elastic-apm-node'; import { withSpan } from '@kbn/apm-utils'; import { identity } from 'lodash'; -import { Logger } from '../../../../../src/core/server'; +import { Logger, ExecutionContextStart } from '../../../../../src/core/server'; import { Middleware } from '../lib/middleware'; import { asOk, asErr, eitherAsync, Result } from '../lib/result_type'; @@ -54,6 +54,7 @@ type Opts = { definitions: TaskTypeDictionary; instance: EphemeralTaskInstance; onTaskEvent?: (event: TaskRun | TaskMarkRunning) => void; + executionContext: ExecutionContextStart; } & Pick; // ephemeral tasks cannot be rescheduled or scheduled to run again in the future @@ -74,6 +75,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { private beforeRun: Middleware['beforeRun']; private beforeMarkRunning: Middleware['beforeMarkRunning']; private onTaskEvent: (event: TaskRun | TaskMarkRunning) => void; + private readonly executionContext: ExecutionContextStart; /** * Creates an instance of EphemeralTaskManagerRunner. @@ -91,6 +93,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { beforeRun, beforeMarkRunning, onTaskEvent = identity, + executionContext, }: Opts) { this.instance = asPending(asConcreteInstance(sanitizeInstance(instance))); this.definitions = definitions; @@ -98,6 +101,7 @@ export class EphemeralTaskManagerRunner implements TaskRunner { this.beforeRun = beforeRun; this.beforeMarkRunning = beforeMarkRunning; this.onTaskEvent = onTaskEvent; + this.executionContext = executionContext; } /** @@ -193,8 +197,14 @@ export class EphemeralTaskManagerRunner implements TaskRunner { const stopTaskTimer = startTaskTimer(); try { this.task = this.definition.createTaskRunner(modifiedContext); - const result = await withSpan({ name: 'ephemeral run', type: 'task manager' }, () => - this.task!.run() + const ctx = { + type: 'task manager', + name: `run ephemeral ${this.instance.task.taskType}`, + id: this.instance.task.id, + description: 'run ephemeral task', + }; + const result = await this.executionContext.withContext(ctx, () => + withSpan({ name: 'ephemeral run', type: 'task manager' }, () => this.task!.run()) ); const validatedResult = this.validateResult(result); const processedResult = await withSpan( diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts index e54962c7c8857..b78232d59803e 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.test.ts @@ -25,7 +25,9 @@ import { mockLogger } from '../test_utils'; import { throwUnrecoverableError } from './errors'; import { taskStoreMock } from '../task_store.mock'; import apm from 'elastic-apm-node'; +import { executionContextServiceMock } from '../../../../../src/core/server/mocks'; +const executionContext = executionContextServiceMock.createSetupContract(); const minutesFromNow = (mins: number): Date => secondsFromNow(mins * 60); let fakeTimer: sinon.SinonFakeTimers; @@ -103,6 +105,31 @@ describe('TaskManagerRunner', () => { ); expect(mockApmTrans.end).toHaveBeenCalledWith('failure'); }); + test('provides execution context on run', async () => { + const { runner } = await readyToRunStageSetup({ + definitions: { + bar: { + title: 'Bar!', + createTaskRunner: () => ({ + async run() { + return { state: {} }; + }, + }), + }, + }, + }); + await runner.run(); + expect(executionContext.withContext).toHaveBeenCalledTimes(1); + expect(executionContext.withContext).toHaveBeenCalledWith( + { + description: 'run task', + id: 'foo', + name: 'run bar', + type: 'task manager', + }, + expect.any(Function) + ); + }); test('provides details about the task that is running', async () => { const { runner } = await pendingStageSetup({ instance: { @@ -690,6 +717,31 @@ describe('TaskManagerRunner', () => { }); expect(mockApmTrans.end).toHaveBeenCalledWith('failure'); }); + test('provides execution context on run', async () => { + const { runner } = await readyToRunStageSetup({ + definitions: { + bar: { + title: 'Bar!', + createTaskRunner: () => ({ + async run() { + return { state: {} }; + }, + }), + }, + }, + }); + await runner.run(); + expect(executionContext.withContext).toHaveBeenCalledTimes(1); + expect(executionContext.withContext).toHaveBeenCalledWith( + { + description: 'run task', + id: 'foo', + name: 'run bar', + type: 'task manager', + }, + expect.any(Function) + ); + }); test('queues a reattempt if the task fails', async () => { const initialAttempts = _.random(0, 2); const id = Date.now().toString(); @@ -1465,6 +1517,7 @@ describe('TaskManagerRunner', () => { instance, definitions, onTaskEvent: opts.onTaskEvent, + executionContext, }); if (stage === TaskRunningStage.READY_TO_RUN) { diff --git a/x-pack/plugins/task_manager/server/task_running/task_runner.ts b/x-pack/plugins/task_manager/server/task_running/task_runner.ts index 97b40a75a59c4..30aa0cd0c522e 100644 --- a/x-pack/plugins/task_manager/server/task_running/task_runner.ts +++ b/x-pack/plugins/task_manager/server/task_running/task_runner.ts @@ -15,7 +15,11 @@ import apm from 'elastic-apm-node'; import { withSpan } from '@kbn/apm-utils'; import { performance } from 'perf_hooks'; import { identity, defaults, flow } from 'lodash'; -import { Logger, SavedObjectsErrorHelpers } from '../../../../../src/core/server'; +import { + Logger, + SavedObjectsErrorHelpers, + ExecutionContextStart, +} from '../../../../../src/core/server'; import { Middleware } from '../lib/middleware'; import { @@ -93,6 +97,7 @@ type Opts = { store: Updatable; onTaskEvent?: (event: TaskRun | TaskMarkRunning) => void; defaultMaxAttempts: number; + executionContext: ExecutionContextStart; } & Pick; export enum TaskRunResult { @@ -137,6 +142,7 @@ export class TaskManagerRunner implements TaskRunner { private beforeMarkRunning: Middleware['beforeMarkRunning']; private onTaskEvent: (event: TaskRun | TaskMarkRunning) => void; private defaultMaxAttempts: number; + private readonly executionContext: ExecutionContextStart; /** * Creates an instance of TaskManagerRunner. @@ -157,6 +163,7 @@ export class TaskManagerRunner implements TaskRunner { beforeMarkRunning, defaultMaxAttempts, onTaskEvent = identity, + executionContext, }: Opts) { this.instance = asPending(sanitizeInstance(instance)); this.definitions = definitions; @@ -166,6 +173,7 @@ export class TaskManagerRunner implements TaskRunner { this.beforeMarkRunning = beforeMarkRunning; this.onTaskEvent = onTaskEvent; this.defaultMaxAttempts = defaultMaxAttempts; + this.executionContext = executionContext; } /** @@ -261,7 +269,15 @@ export class TaskManagerRunner implements TaskRunner { try { this.task = this.definition.createTaskRunner(modifiedContext); - const result = await withSpan({ name: 'run', type: 'task manager' }, () => this.task!.run()); + const ctx = { + type: 'task manager', + name: `run ${this.instance.task.taskType}`, + id: this.instance.task.id, + description: 'run task', + }; + const result = await this.executionContext.withContext(ctx, () => + withSpan({ name: 'run', type: 'task manager' }, () => this.task!.run()) + ); const validatedResult = this.validateResult(result); const processedResult = await withSpan({ name: 'process result', type: 'task manager' }, () => this.processResult(validatedResult, stopTaskTimer()) From fa64146244912204974265cb3a2171ee6daba872 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Mon, 16 Aug 2021 13:51:07 -0600 Subject: [PATCH 17/19] [Maps] Add edit tools defaults for user and timestamp (#103588) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/maps/common/constants.ts | 3 +- x-pack/plugins/maps/kibana.json | 3 +- .../create_new_index_pattern.ts | 16 ++++-- .../layers/new_vector_layer_wizard/wizard.tsx | 18 ++++++- .../layers/vector_layer/vector_layer.tsx | 4 +- .../es_search_source/es_search_source.tsx | 44 ++++++++++++++-- .../es_search_source/util/feature_edit.ts | 19 +++++-- .../mvt_single_layer_vector_source.tsx | 4 ++ .../sources/vector_source/vector_source.tsx | 15 +++++- x-pack/plugins/maps/public/kibana_services.ts | 1 + x-pack/plugins/maps/public/plugin.ts | 2 + .../server/data_indexing/create_doc_source.ts | 8 +-- .../server/data_indexing/indexing_routes.ts | 41 +++++++++++++++ .../test/api_integration/apis/maps/index.js | 1 + .../apis/maps/validate_drawing_index.js | 51 +++++++++++++++++++ 15 files changed, 209 insertions(+), 21 deletions(-) create mode 100644 x-pack/test/api_integration/apis/maps/validate_drawing_index.js diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index e2de2c412e828..1a3065cb4518d 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -44,6 +44,7 @@ export const INDEX_SOURCE_API_PATH = `${GIS_API_PATH}/docSource`; export const API_ROOT_PATH = `/${GIS_API_PATH}`; export const INDEX_FEATURE_PATH = `/${GIS_API_PATH}/feature`; export const GET_MATCHING_INDEXES_PATH = `/${GIS_API_PATH}/getMatchingIndexes`; +export const CHECK_IS_DRAWING_INDEX = `/${GIS_API_PATH}/checkIsDrawingIndex`; export const MVT_GETTILE_API_PATH = 'mvt/getTile'; export const MVT_GETGRIDTILE_API_PATH = 'mvt/getGridTile'; @@ -310,7 +311,7 @@ export type RawValue = string | string[] | number | boolean | undefined | null; export type FieldFormatter = (value: RawValue) => string | number; -export const INDEX_META_DATA_CREATED_BY = 'maps-drawing-data-ingest'; +export const MAPS_NEW_VECTOR_LAYER_META_CREATED_BY = 'maps-new-vector-layer'; export const MAX_DRAWING_SIZE_BYTES = 10485760; // 10MB diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index aa643b431721c..1cccfaa7748b1 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -26,7 +26,8 @@ "optionalPlugins": [ "home", "savedObjectsTagging", - "charts" + "charts", + "security" ], "ui": true, "server": true, diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts index d612c25157095..2e57014824a3c 100644 --- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts +++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/create_new_index_pattern.ts @@ -6,20 +6,30 @@ */ import { getHttp } from '../../../kibana_services'; -import { CreateDocSourceResp, INDEX_SOURCE_API_PATH } from '../../../../common'; +import { + CreateDocSourceResp, + INDEX_SOURCE_API_PATH, + IndexSourceMappings, +} from '../../../../common'; -export const createNewIndexAndPattern = async (indexName: string) => { +export const createNewIndexAndPattern = async ({ + indexName, + defaultMappings = {}, +}: { + indexName: string; + defaultMappings: IndexSourceMappings | {}; +}) => { return await getHttp().fetch({ path: `/${INDEX_SOURCE_API_PATH}`, method: 'POST', body: JSON.stringify({ index: indexName, - // Initially set to static mappings mappings: { properties: { coordinates: { type: 'geo_shape', }, + ...defaultMappings, }, }, }), diff --git a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx index 4f9e9c39ce466..6dac22581efdb 100644 --- a/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx +++ b/x-pack/plugins/maps/public/classes/layers/new_vector_layer_wizard/wizard.tsx @@ -22,6 +22,19 @@ interface State { createIndexError: string; } +const DEFAULT_MAPPINGS = { + created: { + properties: { + '@timestamp': { + type: 'date', + }, + user: { + type: 'keyword', + }, + }, + }, +}; + export class NewVectorLayerEditor extends Component { private _isMounted: boolean = false; @@ -59,7 +72,10 @@ export class NewVectorLayerEditor extends Component { let indexPatternId: string | undefined; try { - const response = await createNewIndexAndPattern(this.state.indexName); + const response = await createNewIndexAndPattern({ + indexName: this.state.indexName, + defaultMappings: DEFAULT_MAPPINGS, + }); indexPatternId = response.indexPatternId; } catch (e) { this._setCreateIndexError(e.message); diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index 74a0ea1e63882..54e0c00141cd3 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -184,7 +184,6 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { supportsFeatureEditing(): boolean { const dataRequest = this.getDataRequest(SUPPORTS_FEATURE_EDITING_REQUEST_ID); const data = dataRequest?.getData() as { supportsFeatureEditing: boolean } | undefined; - return data ? data.supportsFeatureEditing : false; } @@ -1116,7 +1115,8 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { async addFeature(geometry: Geometry | Position[]) { const layerSource = this.getSource(); - await layerSource.addFeature(geometry); + const defaultFields = await layerSource.getDefaultFields(); + await layerSource.addFeature(geometry, defaultFields); } async deleteFeature(featureId: string) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index b63a821cdb0c7..464ba024663ec 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -13,7 +13,12 @@ import type { Filter, IFieldType, IndexPattern } from 'src/plugins/data/public'; import { GeoJsonProperties, Geometry, Position } from 'geojson'; import { esFilters } from '../../../../../../../src/plugins/data/public'; import { AbstractESSource } from '../es_source'; -import { getHttp, getSearchService, getTimeFilter } from '../../../kibana_services'; +import { + getHttp, + getSearchService, + getSecurityService, + getTimeFilter, +} from '../../../kibana_services'; import { addFieldToDSL, getField, @@ -62,7 +67,12 @@ import { isValidStringConfig } from '../../util/valid_string_config'; import { TopHitsUpdateSourceEditor } from './top_hits'; import { getDocValueAndSourceFields, ScriptField } from './util/get_docvalue_source_fields'; import { ITiledSingleLayerMvtParams } from '../tiled_single_layer_vector_source/tiled_single_layer_vector_source'; -import { addFeatureToIndex, deleteFeatureFromIndex, getMatchingIndexes } from './util/feature_edit'; +import { + addFeatureToIndex, + deleteFeatureFromIndex, + getIsDrawLayer, + getMatchingIndexes, +} from './util/feature_edit'; export function timerangeToTimeextent(timerange: TimeRange): Timeslice | undefined { const timeRangeBounds = getTimeFilter().calculateBounds(timerange); @@ -444,6 +454,29 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return matchingIndexes.length === 1; } + async getDefaultFields(): Promise>> { + if (!(await this._isDrawingIndex())) { + return {}; + } + const user = await getSecurityService()?.authc.getCurrentUser(); + const timestamp = new Date().toISOString(); + return { + created: { + ...(user ? { user: user.username } : {}), + '@timestamp': timestamp, + }, + }; + } + + async _isDrawingIndex(): Promise { + await this.getIndexPattern(); + if (!(this.indexPattern && this.indexPattern.title)) { + return false; + } + const { success, isDrawingIndex } = await getIsDrawLayer(this.indexPattern.title); + return success && isDrawingIndex; + } + _hasSort(): boolean { const { sortField, sortOrder } = this._descriptor; return !!sortField && !!sortOrder; @@ -716,9 +749,12 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return MVT_SOURCE_LAYER_NAME; } - async addFeature(geometry: Geometry | Position[]) { + async addFeature( + geometry: Geometry | Position[], + defaultFields: Record> + ) { const indexPattern = await this.getIndexPattern(); - await addFeatureToIndex(indexPattern.title, geometry, this.getGeoFieldName()); + await addFeatureToIndex(indexPattern.title, geometry, this.getGeoFieldName(), defaultFields); } async deleteFeature(featureId: string) { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts index f306a225df69a..c9a967bea3e2c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/util/feature_edit.ts @@ -7,15 +7,20 @@ import { Geometry, Position } from 'geojson'; import { set } from '@elastic/safer-lodash-set'; -import { GET_MATCHING_INDEXES_PATH, INDEX_FEATURE_PATH } from '../../../../../common'; +import { + CHECK_IS_DRAWING_INDEX, + GET_MATCHING_INDEXES_PATH, + INDEX_FEATURE_PATH, +} from '../../../../../common'; import { getHttp } from '../../../../kibana_services'; export const addFeatureToIndex = async ( indexName: string, geometry: Geometry | Position[], - path: string + path: string, + defaultFields: Record> ) => { - const data = set({}, path, geometry); + const data = set({ ...defaultFields }, path, geometry); return await getHttp().fetch({ path: `${INDEX_FEATURE_PATH}`, method: 'POST', @@ -42,3 +47,11 @@ export const getMatchingIndexes = async (indexPattern: string) => { method: 'GET', }); }; + +export const getIsDrawLayer = async (index: string) => { + return await getHttp().fetch({ + path: CHECK_IS_DRAWING_INDEX, + method: 'GET', + query: { index }, + }); +}; diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index f825a85f50bbd..6911cbabdf971 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -239,6 +239,10 @@ export class MVTSingleLayerVectorSource async supportsFeatureEditing(): Promise { return false; } + + async getDefaultFields(): Promise>> { + return {}; + } } registerSource({ diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 4dbf5f16b0673..9d02c21d00aab 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -69,7 +69,11 @@ export interface IVectorSource extends ISource { getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig; getTimesliceMaskFieldName(): Promise; supportsFeatureEditing(): Promise; - addFeature(geometry: Geometry | Position[]): Promise; + getDefaultFields(): Promise>>; + addFeature( + geometry: Geometry | Position[], + defaultFields: Record> + ): Promise; deleteFeature(featureId: string): Promise; isFilterByMapBounds(): boolean; } @@ -164,7 +168,10 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc return null; } - async addFeature(geometry: Geometry | Position[]) { + async addFeature( + geometry: Geometry | Position[], + defaultFields: Record> + ) { throw new Error('Should implement VectorSource#addFeature'); } @@ -175,4 +182,8 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc async supportsFeatureEditing(): Promise { return false; } + + async getDefaultFields(): Promise>> { + return {}; + } } diff --git a/x-pack/plugins/maps/public/kibana_services.ts b/x-pack/plugins/maps/public/kibana_services.ts index 4fce4c276c336..c4340a051a07d 100644 --- a/x-pack/plugins/maps/public/kibana_services.ts +++ b/x-pack/plugins/maps/public/kibana_services.ts @@ -52,6 +52,7 @@ export const getEmbeddableService = () => pluginsStart.embeddable; export const getNavigateToApp = () => coreStart.application.navigateToApp; export const getSavedObjectsTagging = () => pluginsStart.savedObjectsTagging; export const getPresentationUtilContext = () => pluginsStart.presentationUtil.ContextProvider; +export const getSecurityService = () => pluginsStart.security; // xpack.maps.* kibana.yml settings from this plugin let mapAppConfig: MapsConfigType; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index b526e7b24d90f..7ee84eb8b67e2 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -71,6 +71,7 @@ import { MapsAppRegionMapLocatorDefinition, MapsAppTileMapLocatorDefinition, } from './locators'; +import { SecurityPluginStart } from '../../security/public'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -97,6 +98,7 @@ export interface MapsPluginStartDependencies { dashboard: DashboardStart; savedObjectsTagging?: SavedObjectTaggingPluginStart; presentationUtil: PresentationUtilPluginStart; + security: SecurityPluginStart; } /** diff --git a/x-pack/plugins/maps/server/data_indexing/create_doc_source.ts b/x-pack/plugins/maps/server/data_indexing/create_doc_source.ts index 22c3da61244af..cb2e144dc0cf2 100644 --- a/x-pack/plugins/maps/server/data_indexing/create_doc_source.ts +++ b/x-pack/plugins/maps/server/data_indexing/create_doc_source.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient, IScopedClusterClient } from 'kibana/server'; import { - INDEX_META_DATA_CREATED_BY, + MAPS_NEW_VECTOR_LAYER_META_CREATED_BY, CreateDocSourceResp, IndexSourceMappings, BodySettings, @@ -15,9 +15,9 @@ import { import { IndexPatternsCommonService } from '../../../../../src/plugins/data/server'; const DEFAULT_SETTINGS = { number_of_shards: 1 }; -const DEFAULT_MAPPINGS = { +const DEFAULT_META = { _meta: { - created_by: INDEX_META_DATA_CREATED_BY, + created_by: MAPS_NEW_VECTOR_LAYER_META_CREATED_BY, }, }; @@ -50,7 +50,7 @@ async function createIndex( ) { const body: { mappings: IndexSourceMappings; settings: BodySettings } = { mappings: { - ...DEFAULT_MAPPINGS, + ...DEFAULT_META, ...mappings, }, settings: DEFAULT_SETTINGS, diff --git a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts index 6cae0d0630f90..52dd1c56d2435 100644 --- a/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts +++ b/x-pack/plugins/maps/server/data_indexing/indexing_routes.ts @@ -14,11 +14,14 @@ import { MAX_DRAWING_SIZE_BYTES, GET_MATCHING_INDEXES_PATH, INDEX_FEATURE_PATH, + CHECK_IS_DRAWING_INDEX, + MAPS_NEW_VECTOR_LAYER_META_CREATED_BY, } from '../../common/constants'; import { createDocSource } from './create_doc_source'; import { writeDataToIndex } from './index_data'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; import { getMatchingIndexes } from './get_indexes_matching_pattern'; +import { SecurityPluginStart } from '../../../security/server'; export function initIndexingRoutes({ router, @@ -28,6 +31,7 @@ export function initIndexingRoutes({ router: IRouter; logger: Logger; dataPlugin: DataPluginStart; + securityPlugin?: SecurityPluginStart; }) { router.post( { @@ -174,4 +178,41 @@ export function initIndexingRoutes({ return response.ok({ body: result }); } ); + + router.get( + { + path: CHECK_IS_DRAWING_INDEX, + validate: { + query: schema.object({ + index: schema.string(), + }), + }, + }, + async (context, request, response) => { + const { index } = request.query; + try { + const { + body: mappingsResp, + } = await context.core.elasticsearch.client.asCurrentUser.indices.getMapping({ + index: request.query.index, + }); + const isDrawingIndex = + mappingsResp[index].mappings?._meta?.created_by === MAPS_NEW_VECTOR_LAYER_META_CREATED_BY; + return response.ok({ + body: { + success: true, + isDrawingIndex, + }, + }); + } catch (error) { + // Index likely doesn't exist + return response.ok({ + body: { + success: false, + error, + }, + }); + } + } + ); } diff --git a/x-pack/test/api_integration/apis/maps/index.js b/x-pack/test/api_integration/apis/maps/index.js index 6d4122163d66a..bd2505905c395 100644 --- a/x-pack/test/api_integration/apis/maps/index.js +++ b/x-pack/test/api_integration/apis/maps/index.js @@ -22,6 +22,7 @@ export default function ({ loadTestFile, getService }) { describe('', () => { loadTestFile(require.resolve('./get_indexes_matching_pattern')); loadTestFile(require.resolve('./create_doc_source')); + loadTestFile(require.resolve('./validate_drawing_index')); loadTestFile(require.resolve('./delete_feature')); loadTestFile(require.resolve('./index_data')); loadTestFile(require.resolve('./fonts_api')); diff --git a/x-pack/test/api_integration/apis/maps/validate_drawing_index.js b/x-pack/test/api_integration/apis/maps/validate_drawing_index.js new file mode 100644 index 0000000000000..491847c37be6d --- /dev/null +++ b/x-pack/test/api_integration/apis/maps/validate_drawing_index.js @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + + describe('validate drawing index', () => { + it('confirm valid drawing index', async () => { + await supertest + .post(`/api/maps/docSource`) + .set('kbn-xsrf', 'kibana') + .send({ + index: 'valid-drawing-index', + mappings: { properties: { coordinates: { type: 'geo_point' } } }, + }); + + const resp = await supertest + .get(`/api/maps/checkIsDrawingIndex?index=valid-drawing-index`) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body.success).to.be(true); + expect(resp.body.isDrawingIndex).to.be(true); + }); + + it('confirm valid index that is not a drawing index', async () => { + const resp = await supertest + .get(`/api/maps/checkIsDrawingIndex?index=geo_shapes`) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body.success).to.be(true); + expect(resp.body.isDrawingIndex).to.be(false); + }); + + it('confirm invalid index', async () => { + const resp = await supertest + .get(`/api/maps/checkIsDrawingIndex?index=not-an-index`) + .set('kbn-xsrf', 'kibana') + .expect(200); + + expect(resp.body.success).to.be(false); + }); + }); +} From 36bba6ffe0aa4ab532ed857946205ee98ba2d5a4 Mon Sep 17 00:00:00 2001 From: Ryan Keairns Date: Mon, 16 Aug 2021 14:54:22 -0500 Subject: [PATCH 18/19] Update Analytics overview page to new empty state template (#108532) * Use empty state page template * Remove unused translations * Fixed snaps * Use docLinks service * Fix test * Revert "Use docLinks service" Use exisiting docLinks.ELASTIC_WEBSITE_URL instead * Update learn more link and test * fix test Co-authored-by: cchaos Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../getting_started.test.tsx.snap | 391 ------------------ .../getting_started/getting_started.test.tsx | 107 ----- .../getting_started/getting_started.tsx | 115 ------ .../components/getting_started/index.ts | 9 - .../__snapshots__/overview.test.tsx.snap | 70 ++++ .../components/overview/overview.test.tsx | 5 + .../public/components/overview/overview.tsx | 223 +++++----- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 9 files changed, 190 insertions(+), 736 deletions(-) delete mode 100644 src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap delete mode 100644 src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx delete mode 100644 src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx delete mode 100644 src/plugins/kibana_overview/public/components/getting_started/index.ts diff --git a/src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap b/src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap deleted file mode 100644 index b5f501eb88a01..0000000000000 --- a/src/plugins/kibana_overview/public/components/getting_started/__snapshots__/getting_started.test.tsx.snap +++ /dev/null @@ -1,391 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GettingStarted dark mode on 1`] = ` -
- - -
- -

- -

-
- - -

- -

-
- - - - - } - layout="horizontal" - paddingSize="none" - title="Dashboard" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Discover" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Canvas" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Maps" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Machine Learning" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Graph" - titleElement="h3" - titleSize="xs" - /> - - - - - - - - -
-
- - - -
-
-`; - -exports[`GettingStarted render 1`] = ` -
- - -
- -

- -

-
- - -

- -

-
- - - - - } - layout="horizontal" - paddingSize="none" - title="Dashboard" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Discover" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Canvas" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Maps" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Machine Learning" - titleElement="h3" - titleSize="xs" - /> - - - - } - layout="horizontal" - paddingSize="none" - title="Graph" - titleElement="h3" - titleSize="xs" - /> - - - - - - - - -
-
- - - -
-
-`; diff --git a/src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx b/src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx deleted file mode 100644 index a2ce059f913f4..0000000000000 --- a/src/plugins/kibana_overview/public/components/getting_started/getting_started.test.tsx +++ /dev/null @@ -1,107 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { GettingStarted } from './getting_started'; -import { shallowWithIntl } from '@kbn/test/jest'; -import { FeatureCatalogueCategory } from 'src/plugins/home/public'; - -const addBasePathMock = jest.fn((path: string) => (path ? path : 'path')); - -const mockApps = [ - { - category: FeatureCatalogueCategory.DATA, - description: 'Display and share a collection of visualizations and saved searches.', - icon: 'dashboardApp', - id: 'dashboard', - order: 100, - path: 'path-to-dashboard', - showOnHomePage: false, - solutionId: 'kibana', - subtitle: 'Analyze data in dashboards.', - title: 'Dashboard', - }, - { - category: FeatureCatalogueCategory.DATA, - description: 'Interactively explore your data by querying and filtering raw documents.', - icon: 'discoverApp', - id: 'discover', - order: 200, - path: 'path-to-discover', - - showOnHomePage: false, - solutionId: 'kibana', - subtitle: 'Search and find insights.', - title: 'Discover', - }, - { - category: FeatureCatalogueCategory.DATA, - description: 'Showcase your data in a pixel-perfect way.', - icon: 'canvasApp', - id: 'canvas', - order: 300, - path: 'path-to-canvas', - - showOnHomePage: false, - solutionId: 'kibana', - subtitle: 'Design pixel-perfect reports.', - title: 'Canvas', - }, - { - category: FeatureCatalogueCategory.DATA, - description: 'Explore geospatial data from Elasticsearch and the Elastic Maps Service.', - icon: 'gisApp', - id: 'maps', - order: 400, - path: 'path-to-maps', - showOnHomePage: false, - solutionId: 'kibana', - subtitle: 'Plot geographic data.', - title: 'Maps', - }, - { - category: FeatureCatalogueCategory.DATA, - description: - 'Automatically model the normal behavior of your time series data to detect anomalies.', - icon: 'machineLearningApp', - id: 'ml', - order: 500, - path: 'path-to-ml', - showOnHomePage: false, - solutionId: 'kibana', - subtitle: 'Model, predict, and detect.', - title: 'Machine Learning', - }, - { - category: FeatureCatalogueCategory.DATA, - description: 'Surface and analyze relevant relationships in your Elasticsearch data.', - icon: 'graphApp', - id: 'graph', - order: 600, - path: 'path-to-graph', - showOnHomePage: false, - solutionId: 'kibana', - subtitle: 'Reveal patterns and relationships.', - title: 'Graph', - }, -]; - -describe('GettingStarted', () => { - test('render', () => { - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); - test('dark mode on', () => { - const component = shallowWithIntl( - - ); - expect(component).toMatchSnapshot(); - }); -}); diff --git a/src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx b/src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx deleted file mode 100644 index 56247c4e91fa1..0000000000000 --- a/src/plugins/kibana_overview/public/components/getting_started/getting_started.tsx +++ /dev/null @@ -1,115 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React, { FC } from 'react'; -import { - EuiButton, - EuiCard, - EuiFlexGrid, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiImage, - EuiSpacer, - EuiText, - EuiTitle, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { CoreStart } from 'kibana/public'; -import { RedirectAppLinks, useKibana } from '../../../../../../src/plugins/kibana_react/public'; -import { FeatureCatalogueEntry } from '../../../../../../src/plugins/home/public'; -import { PLUGIN_ID } from '../../../common'; - -interface Props { - addBasePath: (path: string) => string; - isDarkTheme: boolean; - apps: FeatureCatalogueEntry[]; -} - -export const GettingStarted: FC = ({ addBasePath, isDarkTheme, apps }) => { - const { - services: { application }, - } = useKibana(); - const gettingStartedGraphicURL = `/plugins/${PLUGIN_ID}/assets/kibana_montage_${ - isDarkTheme ? 'dark' : 'light' - }.svg`; - - return ( -
- - -
- -

- -

-
- - - - -

- -

-
- - - - - {apps.map(({ subtitle = '', icon, title }) => ( - - } - layout="horizontal" - paddingSize="none" - title={title} - titleElement="h3" - titleSize="xs" - /> - - ))} - - - - - - - - - -
-
- - - - -
-
- ); -}; diff --git a/src/plugins/kibana_overview/public/components/getting_started/index.ts b/src/plugins/kibana_overview/public/components/getting_started/index.ts deleted file mode 100644 index 10977941afeee..0000000000000 --- a/src/plugins/kibana_overview/public/components/getting_started/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './getting_started'; diff --git a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap index a32e27050fad1..6581fcd88cb77 100644 --- a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap +++ b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap @@ -233,6 +233,9 @@ exports[`Overview render 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "home#/tutorial_directory", + ], Array [ "kibana_landing_page", ], @@ -259,6 +262,10 @@ exports[`Overview render 1`] = ` ], ], "results": Array [ + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, Object { "type": "return", "value": "kibana_landing_page", @@ -534,6 +541,9 @@ exports[`Overview without features 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "home#/tutorial_directory", + ], Array [ "kibana_landing_page", ], @@ -558,6 +568,12 @@ exports[`Overview without features 1`] = ` Array [ "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", ], + Array [ + "home#/tutorial_directory", + ], + Array [ + "home#/tutorial_directory", + ], Array [ "kibana_landing_page", ], @@ -584,6 +600,10 @@ exports[`Overview without features 1`] = ` ], ], "results": Array [ + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, Object { "type": "return", "value": "kibana_landing_page", @@ -616,6 +636,14 @@ exports[`Overview without features 1`] = ` "type": "return", "value": "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", }, + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, Object { "type": "return", "value": "kibana_landing_page", @@ -760,6 +788,9 @@ exports[`Overview without solutions 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "home#/tutorial_directory", + ], Array [ "kibana_landing_page", ], @@ -784,8 +815,15 @@ exports[`Overview without solutions 1`] = ` Array [ "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", ], + Array [ + "home#/tutorial_directory", + ], ], "results": Array [ + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, Object { "type": "return", "value": "kibana_landing_page", @@ -818,6 +856,10 @@ exports[`Overview without solutions 1`] = ` "type": "return", "value": "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", }, + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, ], } } @@ -829,6 +871,9 @@ exports[`Overview without solutions 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "home#/tutorial_directory", + ], Array [ "kibana_landing_page", ], @@ -853,8 +898,15 @@ exports[`Overview without solutions 1`] = ` Array [ "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", ], + Array [ + "home#/tutorial_directory", + ], ], "results": Array [ + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, Object { "type": "return", "value": "kibana_landing_page", @@ -887,6 +939,10 @@ exports[`Overview without solutions 1`] = ` "type": "return", "value": "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", }, + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, ], } } @@ -904,6 +960,9 @@ exports[`Overview without solutions 1`] = ` addBasePath={ [MockFunction] { "calls": Array [ + Array [ + "home#/tutorial_directory", + ], Array [ "kibana_landing_page", ], @@ -928,8 +987,15 @@ exports[`Overview without solutions 1`] = ` Array [ "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", ], + Array [ + "home#/tutorial_directory", + ], ], "results": Array [ + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, Object { "type": "return", "value": "kibana_landing_page", @@ -962,6 +1028,10 @@ exports[`Overview without solutions 1`] = ` "type": "return", "value": "/plugins/kibanaOverview/assets/solutions_solution_4_2x.png", }, + Object { + "type": "return", + "value": "home#/tutorial_directory", + }, ], } } diff --git a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx index 9d260469625ad..6320b5070a8a6 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx @@ -18,6 +18,11 @@ jest.mock('../../../../../../src/plugins/kibana_react/public', () => ({ http: { basePath: { prepend: jest.fn((path: string) => (path ? path : 'path')) } }, data: { indexPatterns: {} }, uiSettings: { get: jest.fn() }, + docLinks: { + links: { + kibana: 'kibana_docs_url', + }, + }, }, }), RedirectAppLinks: jest.fn((element: JSX.Element) => element), diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx index 9bba923371f64..a037cbc214266 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx @@ -24,6 +24,7 @@ import { RedirectAppLinks, useKibana, KibanaPageTemplate, + KibanaPageTemplateProps, overviewPageActions, OverviewPageFooter, } from '../../../../../../src/plugins/kibana_react/public'; @@ -36,7 +37,6 @@ import { import { PLUGIN_ID, PLUGIN_PATH } from '../../../common'; import { AppPluginStartDependencies } from '../../types'; import { AddData } from '../add_data'; -import { GettingStarted } from '../getting_started'; import { ManageData } from '../manage_data'; import { NewsFeed } from '../news_feed'; import { METRIC_TYPE, trackUiMetric } from '../../lib/ui_metric'; @@ -53,7 +53,7 @@ interface Props { export const Overview: FC = ({ newsFetchResult, solutions, features }) => { const [isNewKibanaInstance, setNewKibanaInstance] = useState(false); const { - services: { http, data, uiSettings, application }, + services: { http, docLinks, data, uiSettings, application }, } = useKibana(); const addBasePath = http.basePath.prepend; const indexPatternService = data.indexPatterns; @@ -72,6 +72,16 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => const addDataFeatures = getFeaturesByCategory(FeatureCatalogueCategory.DATA); const manageDataFeatures = getFeaturesByCategory(FeatureCatalogueCategory.ADMIN); const devTools = findFeatureById('console'); + const noDataConfig: KibanaPageTemplateProps['noDataConfig'] = { + solution: 'Analytics', + logo: 'logoKibana', + actions: { + beats: { + href: addBasePath(`home#/tutorial_directory`), + }, + }, + docsLink: docLinks.links.kibana, + }; // Show card for console if none of the manage data plugins are available, most likely in OSS if (manageDataFeatures.length < 1 && devTools) { @@ -127,126 +137,123 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => hidden: isNewKibanaInstance, }), }} + noDataConfig={isNewKibanaInstance ? noDataConfig : undefined} template="empty" > - {isNewKibanaInstance ? ( - - ) : ( - <> -
- -

- -

-
- - {mainApps.length ? ( - <> - - {mainApps.map(renderAppCard)} - + <> +
+ +

+ +

+
- - - ) : null} - - {remainingApps.length ? ( + {mainApps.length ? ( + <> - {remainingApps.map(renderAppCard)} + {mainApps.map(renderAppCard)} - ) : null} -
-
- - {newsFetchResult && newsFetchResult.feedItems.length ? ( - - - - ) : null} + +