From 3e118c4e23d716d71bbbccd63beeab7da0c7a577 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Fri, 9 Jul 2021 15:04:37 +0200 Subject: [PATCH 01/36] [Reporting] First accessibility test (#104410) * makeAllReportingPoliciesUnmanaged -> makeAllReportingIndicesUnmanaged * expose the reporting services on the functional services object shared with a11y * added data-test-subjs for a11y test * added reporting a11y test * updated jest test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../report_listing.test.tsx.snap | 9 +++ .../public/management/report_listing.tsx | 3 +- x-pack/test/accessibility/apps/reporting.ts | 76 +++++++++++++++++++ x-pack/test/accessibility/config.ts | 1 + x-pack/test/functional/services/index.ts | 2 + .../ilm_migration_apis.ts | 4 +- .../services/scenarios.ts | 6 +- 7 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 x-pack/test/accessibility/apps/reporting.ts diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap index 9ce249aa32a1d..744a3b2d405c3 100644 --- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap @@ -425,6 +425,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -1434,6 +1435,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -2457,6 +2459,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -3527,6 +3530,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -4630,6 +4634,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -5700,6 +5705,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -6770,6 +6776,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -7840,6 +7847,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
@@ -8910,6 +8918,7 @@ exports[`ReportListing Report job listing with some items 1`] = ` >
diff --git a/x-pack/plugins/reporting/public/management/report_listing.tsx b/x-pack/plugins/reporting/public/management/report_listing.tsx index 749e42de526d3..dd41314b4883f 100644 --- a/x-pack/plugins/reporting/public/management/report_listing.tsx +++ b/x-pack/plugins/reporting/public/management/report_listing.tsx @@ -151,6 +151,7 @@ class ReportListingUi extends Component { return ( <> @@ -375,7 +376,7 @@ class ReportListingUi extends Component { }), render: (objectTitle: string, record: Job) => { return ( -
+
{objectTitle}
{record.object_type} diff --git a/x-pack/test/accessibility/apps/reporting.ts b/x-pack/test/accessibility/apps/reporting.ts new file mode 100644 index 0000000000000..bccb650fa08ca --- /dev/null +++ b/x-pack/test/accessibility/apps/reporting.ts @@ -0,0 +1,76 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; + +import { JOB_PARAMS_RISON_CSV_DEPRECATED } from '../../reporting_api_integration/services/fixtures'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { common } = getPageObjects(['common']); + const retry = getService('retry'); + const a11y = getService('a11y'); + const testSubjects = getService('testSubjects'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const reporting = getService('reporting'); + const esArchiver = getService('esArchiver'); + const security = getService('security'); + + describe('Reporting', () => { + const createReportingUser = async () => { + await security.user.create(reporting.REPORTING_USER_USERNAME, { + password: reporting.REPORTING_USER_PASSWORD, + roles: ['reporting_user', 'data_analyst', 'kibana_user'], // Deprecated: using built-in `reporting_user` role grants all Reporting privileges + full_name: 'a reporting user', + }); + }; + + const deleteReportingUser = async () => { + await security.user.delete(reporting.REPORTING_USER_USERNAME); + }; + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/reporting/logs'); + await esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + + await createReportingUser(); + await reporting.loginReportingUser(); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/reporting/logs'); + await esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + + await deleteReportingUser(); + }); + + beforeEach(async () => { + // Add one report + await supertestWithoutAuth + .post(`/api/reporting/generate/csv`) + .auth(reporting.REPORTING_USER_USERNAME, reporting.REPORTING_USER_PASSWORD) + .set('kbn-xsrf', 'xxx') + .send({ jobParams: JOB_PARAMS_RISON_CSV_DEPRECATED }) + .expect(200); + + await retry.waitFor('Reporting app', async () => { + await common.navigateToApp('reporting'); + return testSubjects.exists('reportingPageHeader'); + }); + }); + + afterEach(async () => { + await reporting.deleteAllReports(); + }); + + it('List reports view', async () => { + await retry.waitForWithTimeout('A reporting list item', 5000, () => { + return testSubjects.exists('reportingListItemObjectTitle'); + }); + await a11y.testAppSnapshot(); + }); + }); +} diff --git a/x-pack/test/accessibility/config.ts b/x-pack/test/accessibility/config.ts index 81cfd70a23956..e79bbdb86a88a 100644 --- a/x-pack/test/accessibility/config.ts +++ b/x-pack/test/accessibility/config.ts @@ -37,6 +37,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { require.resolve('./apps/security_solution'), require.resolve('./apps/ml_embeddables_in_dashboard'), require.resolve('./apps/remote_clusters'), + require.resolve('./apps/reporting'), ], pageObjects, diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 99293c71676b4..273db212400ab 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -9,6 +9,7 @@ import { services as kibanaFunctionalServices } from '../../../../test/functiona import { services as kibanaApiIntegrationServices } from '../../../../test/api_integration/services'; import { services as kibanaXPackApiIntegrationServices } from '../../api_integration/services'; import { services as commonServices } from '../../common/services'; +import { ReportingFunctionalProvider } from '../../reporting_functional/services'; import { MonitoringNoDataProvider, @@ -107,5 +108,6 @@ export const services = { dashboardDrilldownPanelActions: DashboardDrilldownPanelActionsProvider, dashboardDrilldownsManage: DashboardDrilldownsManageProvider, dashboardPanelTimeRange: DashboardPanelTimeRangeProvider, + reporting: ReportingFunctionalProvider, searchSessions: SearchSessionsService, }; diff --git a/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts b/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts index a0f4a3f91fe32..a9b6798a0224f 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security/ilm_migration_apis.ts @@ -47,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('detects when reporting indices should be migrated due to missing ILM policy', async () => { - await reportingAPI.makeAllReportingPoliciesUnmanaged(); + await reportingAPI.makeAllReportingIndicesUnmanaged(); // TODO: Remove "any" when no longer through type issue "policy_id" missing await es.ilm.deleteLifecycle({ policy: ILM_POLICY_NAME } as any); @@ -63,7 +63,7 @@ export default function ({ getService }: FtrProviderContext) { }); it('detects when reporting indices should be migrated due to unmanaged indices', async () => { - await reportingAPI.makeAllReportingPoliciesUnmanaged(); + await reportingAPI.makeAllReportingIndicesUnmanaged(); await supertestNoAuth .post(`/api/reporting/generate/csv`) .set('kbn-xsrf', 'xxx') diff --git a/x-pack/test/reporting_api_integration/services/scenarios.ts b/x-pack/test/reporting_api_integration/services/scenarios.ts index eb32de9d0dc9c..08c07e0e257ed 100644 --- a/x-pack/test/reporting_api_integration/services/scenarios.ts +++ b/x-pack/test/reporting_api_integration/services/scenarios.ts @@ -181,8 +181,8 @@ export function createScenarios({ getService }: Pick { - log.debug('ReportingAPI.makeAllReportingPoliciesUnmanaged'); + const makeAllReportingIndicesUnmanaged = async () => { + log.debug('ReportingAPI.makeAllReportingIndicesUnmanaged'); const settings: any = { 'index.lifecycle.name': null, }; @@ -214,6 +214,6 @@ export function createScenarios({ getService }: Pick Date: Fri, 9 Jul 2021 16:18:38 +0300 Subject: [PATCH 02/36] [Alerting UI] Fixed bug when rule state was updated on Edit flyout opening: Index action set empty documents property for non history index. (#105014) --- .../builtin_action_types/es_index/es_index_params.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx index 5d526e74564c5..56f333396908b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_params.tsx @@ -43,6 +43,7 @@ export const IndexParamsFields = ({ ALERT_HISTORY_PREFIX, '' ); + const [isActionConnectorChanged, setIsActionConnectorChanged] = useState(false); const getDocumentToIndex = (doc: Array> | undefined) => doc && doc.length > 0 ? ((doc[0] as unknown) as string) : undefined; @@ -67,11 +68,12 @@ export const IndexParamsFields = ({ setUsePreconfiguredSchema(true); editAction('documents', [JSON.stringify(AlertHistoryDocumentTemplate)], index); setDocumentToIndex(JSON.stringify(AlertHistoryDocumentTemplate)); - } else { + } else if (isActionConnectorChanged) { setUsePreconfiguredSchema(false); editAction('documents', undefined, index); setDocumentToIndex(undefined); } + setIsActionConnectorChanged(true); // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionConnector?.id]); From 4806cf3fd6055ab0be9cec6c9711ad8104bf49d6 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 9 Jul 2021 16:46:56 +0300 Subject: [PATCH 03/36] [TSVB] Table view - fix display of item urls (#105051) --- .../components/panel_config/table.tsx | 1 + .../components/vis_types/table/vis.js | 8 ++++++-- test/functional/apps/visualize/_tsvb_table.ts | 17 ++++++++++++++++- .../page_objects/visual_builder_page.ts | 7 +++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx index 9ba0822402562..3633f8add7457 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/table.tsx @@ -207,6 +207,7 @@ export class TablePanelConfig extends Component< diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js index 4dd8f672c9ea3..4db038de912f5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/vis.js @@ -58,7 +58,11 @@ class TableVis extends Component { renderRow = (row) => { const { model } = this.props; - let rowDisplay = model.pivot_type === 'date' ? this.dateFormatter.convert(row.key) : row.key; + + let rowDisplay = getValueOrEmpty( + model.pivot_type === 'date' ? this.dateFormatter.convert(row.key) : row.key + ); + if (model.drilldown_url) { const url = replaceVars(model.drilldown_url, {}, { key: row.key }); rowDisplay = {rowDisplay}; @@ -98,7 +102,7 @@ class TableVis extends Component { }); return ( - {getValueOrEmpty(rowDisplay)} + {rowDisplay} {columns} ); diff --git a/test/functional/apps/visualize/_tsvb_table.ts b/test/functional/apps/visualize/_tsvb_table.ts index abe3b799e4711..de0771d3c8ec5 100644 --- a/test/functional/apps/visualize/_tsvb_table.ts +++ b/test/functional/apps/visualize/_tsvb_table.ts @@ -10,12 +10,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; -export default function ({ getPageObjects }: FtrProviderContext) { +export default function ({ getPageObjects, getService }: FtrProviderContext) { const { visualBuilder, visualize, visChart } = getPageObjects([ 'visualBuilder', 'visualize', 'visChart', ]); + const findService = getService('find'); + const retry = getService('retry'); describe('visual builder', function describeIndexTests() { before(async () => { @@ -43,6 +45,19 @@ export default function ({ getPageObjects }: FtrProviderContext) { expect(tableData).to.be(EXPECTED); }); + it('should display drilldown urls', async () => { + const baseURL = 'http://elastic.co/foo/'; + + await visualBuilder.clickPanelOptions('table'); + await visualBuilder.setDrilldownUrl(`${baseURL}{{key}}`); + + await retry.try(async () => { + const links = await findService.allByCssSelector(`a[href="${baseURL}ios"]`); + + expect(links.length).to.be(1); + }); + }); + it('should display correct values on changing metrics aggregation', async () => { const EXPECTED = 'OS Cardinality\nwin 8 12\nwin xp 9\nwin 7 8\nios 5\nosx 3'; diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index ea11560e37b6f..0f6c09f6ee464 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -277,6 +277,13 @@ export class VisualBuilderPageObject extends FtrService { await this.comboBox.setElement(formatterEl, formatter, { clickWithMouse: true }); } + public async setDrilldownUrl(value: string) { + const drilldownEl = await this.testSubjects.find('drilldownUrl'); + + await drilldownEl.clearValue(); + await drilldownEl.type(value); + } + /** * set duration formatter additional settings * From ef991b7c2b93c53c1ccf1db0a3ba7506fa549f23 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 9 Jul 2021 16:53:48 +0300 Subject: [PATCH 04/36] [TSVB] fix include/exclude fields appear to migrated TSVB visualization when using Group by Terms (#104848) * [TSVB] Include/exclude fields appear to migrated TSVB visualization when using Group by Terms Closes: #104829 * add functional test * fix JEST --- .../components/aggs/cumulative_sum.js | 3 ++- .../application/components/aggs/derivative.js | 3 ++- .../components/aggs/moving_average.js | 3 ++- .../components/aggs/positive_only.js | 3 ++- .../components/aggs/serial_diff.js | 3 ++- .../components/aggs/std_sibling.js | 3 ++- .../application/components/aggs/vars.js | 3 ++- .../splits/__snapshots__/terms.test.js.snap | 2 ++ .../application/components/splits/terms.js | 8 +++++-- .../apps/visualize/_tsvb_time_series.ts | 5 ++++- .../page_objects/visual_builder_page.ts | 21 ++++++++++++++++++- 11 files changed, 46 insertions(+), 11 deletions(-) diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js index f167bc35c06e9..a232a1dc03ae3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/cumulative_sum.js @@ -23,6 +23,7 @@ import { EuiFormRow, EuiSpacer, } from '@elastic/eui'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export function CumulativeSumAgg(props) { const { model, siblings, fields, indexPattern } = props; @@ -70,7 +71,7 @@ export function CumulativeSumAgg(props) { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js index 9bed7015b0245..616f40128ff22 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/derivative.js @@ -25,6 +25,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const DerivativeAgg = (props) => { const { siblings, fields, indexPattern } = props; @@ -80,7 +81,7 @@ export const DerivativeAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} fullWidth diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js index 79f70f45d6256..a3ce43f97a36a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/moving_average.js @@ -26,6 +26,7 @@ import { EuiFieldNumber, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; const DEFAULTS = { model_type: MODEL_TYPES.UNWEIGHTED, @@ -141,7 +142,7 @@ export const MovingAverageAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js index 156a042abb4e2..c974f5d5f05f5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_only.js @@ -23,6 +23,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const PositiveOnlyAgg = (props) => { const { siblings, fields, indexPattern } = props; @@ -74,7 +75,7 @@ export const PositiveOnlyAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js index a553b1a4c6671..efc2a72c3dd67 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/serial_diff.js @@ -24,6 +24,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const SerialDiffAgg = (props) => { const { siblings, fields, indexPattern, model } = props; @@ -74,7 +75,7 @@ export const SerialDiffAgg = (props) => { onChange={handleSelectChange('field')} metrics={siblings} metric={model} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} value={model.field} exclude={[METRIC_TYPES.TOP_HIT]} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js index 9a30988d252e5..d2b3f45a70164 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_sibling.js @@ -27,6 +27,7 @@ import { EuiSpacer, } from '@elastic/eui'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; const StandardSiblingAggUi = (props) => { const { siblings, intl, fields, indexPattern } = props; @@ -147,7 +148,7 @@ const StandardSiblingAggUi = (props) => { onChange={handleSelectChange('field')} exclude={[METRIC_TYPES.PERCENTILE, METRIC_TYPES.TOP_HIT]} metrics={siblings} - fields={fields[indexPattern]} + fields={fields[getIndexPatternKey(indexPattern)]} metric={model} value={model.field} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js index b9d554e254bcc..ba06b0fffd307 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/vars.js @@ -15,6 +15,7 @@ import { AddDeleteButtons } from '../add_delete_buttons'; import { collectionActions } from '../lib/collection_actions'; import { MetricSelect } from './metric_select'; import { EuiFlexGroup, EuiFlexItem, EuiFieldText } from '@elastic/eui'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; export const newVariable = (opts) => ({ id: uuid.v1(), name: '', field: '', ...opts }); @@ -59,7 +60,7 @@ export class CalculationVars extends Component { metrics={this.props.metrics} metric={this.props.model} value={row.field} - fields={this.props.fields[this.props.indexPattern]} + fields={this.props.fields[getIndexPatternKey(this.props.indexPattern)]} includeSiblings={this.props.includeSiblings} exclude={this.props.exclude} /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap index 562c463f6c83c..ce381a0e539d0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap @@ -78,6 +78,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > @@ -100,6 +101,7 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js index 7db6a75e2392c..9c097de38d56a 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js @@ -27,6 +27,7 @@ import { import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { KBN_FIELD_TYPES } from '../../../../../data/public'; import { STACKED_OPTIONS } from '../../visualizations/constants'; +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; const DEFAULTS = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' }; @@ -75,10 +76,11 @@ export const SplitByTermsUI = ({ }), }, ]; + const fieldsSelector = getIndexPatternKey(indexPattern); const selectedDirectionOption = dirOptions.find((option) => { return model.terms_direction === option.value; }); - const selectedField = find(fields[indexPattern], ({ name }) => name === model.terms_field); + const selectedField = find(fields[fieldsSelector], ({ name }) => name === model.terms_field); const selectedFieldType = get(selectedField, 'type'); if ( @@ -144,6 +146,7 @@ export const SplitByTermsUI = ({ @@ -160,6 +163,7 @@ export const SplitByTermsUI = ({ @@ -198,7 +202,7 @@ export const SplitByTermsUI = ({ metrics={metrics} clearable={false} additionalOptions={[defaultCount, terms]} - fields={fields[indexPattern]} + fields={fields[fieldsSelector]} onChange={handleSelectChange('terms_order_by')} restrict="basic" value={model.terms_order_by} diff --git a/test/functional/apps/visualize/_tsvb_time_series.ts b/test/functional/apps/visualize/_tsvb_time_series.ts index a0c9d806facc6..cc57d58348180 100644 --- a/test/functional/apps/visualize/_tsvb_time_series.ts +++ b/test/functional/apps/visualize/_tsvb_time_series.ts @@ -155,7 +155,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('Clicking on the chart', () => { it(`should create a filter`, async () => { - await visualBuilder.setMetricsGroupByTerms('machine.os.raw'); + await visualBuilder.setMetricsGroupByTerms('machine.os.raw', { + include: 'win 7', + exclude: 'ios', + }); await visualBuilder.clickSeriesOption(); await testSubjects.click('visualizeSaveButton'); diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 0f6c09f6ee464..8e28ffab6c9c3 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -635,7 +635,10 @@ export class VisualBuilderPageObject extends FtrService { return await this.find.allByCssSelector('.tvbSeriesEditor'); } - public async setMetricsGroupByTerms(field: string) { + public async setMetricsGroupByTerms( + field: string, + filtering: { include?: string; exclude?: string } = {} + ) { const groupBy = await this.find.byCssSelector( '.tvbAggRow--split [data-test-subj="comboBoxInput"]' ); @@ -643,6 +646,22 @@ export class VisualBuilderPageObject extends FtrService { await this.common.sleep(1000); const byField = await this.testSubjects.find('groupByField'); await this.comboBox.setElement(byField, field); + + await this.setMetricsGroupByFiltering(filtering.include, filtering.exclude); + } + + public async setMetricsGroupByFiltering(include?: string, exclude?: string) { + const setFilterValue = async (value: string | undefined, subjectKey: string) => { + if (typeof value === 'string') { + const valueSubject = await this.testSubjects.find(subjectKey); + + await valueSubject.clearValue(); + await valueSubject.type(value); + } + }; + + await setFilterValue(include, 'groupByInclude'); + await setFilterValue(exclude, 'groupByExclude'); } public async checkSelectedMetricsGroupByValue(value: string) { From efa4ce22faa06a7a0df288ead1e4b905939fe416 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Fri, 9 Jul 2021 08:54:48 -0500 Subject: [PATCH 05/36] Storybook EUI theme decorator (#103582) Add an `EuiThemeProviderDecorator` to kibanaReact which uses the Storybook globals to set the EUI theme. Add global decorators to the APM and Observability plugins so all stories are wrapped in the `EuiThemeProviderDecorator`, and they don't need to specify it in the stories. Add [jest setup helpers recommended by @storybook/testing-react](https://github.com/storybookjs/testing-react#global-config). --- .../common/eui_styled_components.tsx | 17 +++- x-pack/plugins/apm/.storybook/jest_setup.js | 11 +++ x-pack/plugins/apm/.storybook/preview.js | 10 +++ x-pack/plugins/apm/jest.config.js | 2 + .../index.stories.tsx | 23 ++--- .../__stories__/MapTooltip.stories.tsx | 85 +++++++++---------- .../index.stories.tsx | 14 ++- .../Distribution/index.stories.tsx | 13 ++- .../exception_stacktrace.stories.tsx | 8 -- .../service_map/Popover/Popover.stories.tsx | 17 ++-- .../Popover/service_stats_list.stories.tsx | 10 +-- .../__stories__/Cytoscape.stories.tsx | 10 +-- .../cytoscape_example_data.stories.tsx | 10 +-- .../WaterfallContainer.stories.tsx | 9 +- .../shared/agent_icon/agent_icon.stories.tsx | 18 ++-- .../custom_tooltip.stories.tsx | 10 +-- ...ces_latency_distribution_chart.stories.tsx | 10 +-- .../latency_chart/latency_chart.stories.tsx | 27 +++--- .../shared/span_icon/span_icon.stories.tsx | 17 +--- .../observability/.storybook/jest_setup.js | 11 +++ .../observability/.storybook/preview.js | 10 +++ x-pack/plugins/observability/jest.config.js | 1 + .../__stories__/core_vitals.stories.tsx | 5 +- .../field_value_selection.stories.tsx | 19 ++--- .../public/pages/landing/landing.stories.tsx | 5 +- .../pages/overview/overview.stories.tsx | 5 +- 26 files changed, 165 insertions(+), 212 deletions(-) create mode 100644 x-pack/plugins/apm/.storybook/jest_setup.js create mode 100644 x-pack/plugins/apm/.storybook/preview.js create mode 100644 x-pack/plugins/observability/.storybook/jest_setup.js create mode 100644 x-pack/plugins/observability/.storybook/preview.js diff --git a/src/plugins/kibana_react/common/eui_styled_components.tsx b/src/plugins/kibana_react/common/eui_styled_components.tsx index 10cd168da6faa..62876a03c7d83 100644 --- a/src/plugins/kibana_react/common/eui_styled_components.tsx +++ b/src/plugins/kibana_react/common/eui_styled_components.tsx @@ -6,15 +6,14 @@ * Side Public License, v 1. */ +import type { DecoratorFn } from '@storybook/react'; import React from 'react'; import * as styledComponents from 'styled-components'; import { ThemedStyledComponentsModule, ThemeProvider, ThemeProviderProps } from 'styled-components'; - -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { euiThemeVars, euiLightVars, euiDarkVars } from '@kbn/ui-shared-deps/theme'; export interface EuiTheme { - eui: typeof euiLightVars | typeof euiDarkVars; + eui: typeof euiThemeVars; darkMode: boolean; } @@ -36,6 +35,16 @@ const EuiThemeProvider = < /> ); +/** + * Storybook decorator using the EUI theme provider. Uses the value from + * `globals` provided by the Storybook theme switcher. + */ +export const EuiThemeProviderDecorator: DecoratorFn = (storyFn, { globals }) => { + const darkMode = globals.euiTheme === 'v8.dark' || globals.euiTheme === 'v7.dark'; + + return {storyFn()}; +}; + const { default: euiStyled, css, diff --git a/x-pack/plugins/apm/.storybook/jest_setup.js b/x-pack/plugins/apm/.storybook/jest_setup.js new file mode 100644 index 0000000000000..32071b8aa3f62 --- /dev/null +++ b/x-pack/plugins/apm/.storybook/jest_setup.js @@ -0,0 +1,11 @@ +/* + * 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 { setGlobalConfig } from '@storybook/testing-react'; +import * as globalStorybookConfig from './preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/x-pack/plugins/apm/.storybook/preview.js b/x-pack/plugins/apm/.storybook/preview.js new file mode 100644 index 0000000000000..18343c15a6465 --- /dev/null +++ b/x-pack/plugins/apm/.storybook/preview.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiThemeProviderDecorator } from '../../../../src/plugins/kibana_react/common'; + +export const decorators = [EuiThemeProviderDecorator]; diff --git a/x-pack/plugins/apm/jest.config.js b/x-pack/plugins/apm/jest.config.js index caa8256cdb7ea..5bce9bbfb5b1b 100644 --- a/x-pack/plugins/apm/jest.config.js +++ b/x-pack/plugins/apm/jest.config.js @@ -11,4 +11,6 @@ module.exports = { preset: '@kbn/test', rootDir: path.resolve(__dirname, '../../..'), roots: ['/x-pack/plugins/apm'], + setupFiles: ['/x-pack/plugins/apm/.storybook/jest_setup.js'], + testPathIgnorePatterns: ['/x-pack/plugins/apm/e2e/'], }; diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx index 83874e9584510..23afb9646dea7 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.stories.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorCountAlertTrigger } from '.'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, @@ -20,19 +19,15 @@ export default { component: ErrorCountAlertTrigger, decorators: [ (Story: React.ComponentClass) => ( - - - -
- -
-
-
-
+ + +
+ +
+
+
), ], }; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx index 1aad25fc89c0b..8263db648cd39 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/__stories__/MapTooltip.stories.tsx @@ -7,53 +7,50 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; -import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; import { MapToolTip } from '../MapToolTip'; import { COUNTRY_NAME, TRANSACTION_DURATION_COUNTRY } from '../useLayerList'; -storiesOf('app/RumDashboard/VisitorsRegionMap', module) - .addDecorator((storyFn) => {storyFn()}) - .add( - 'Tooltip', - () => { - const loadFeatureProps = async () => { - return [ +storiesOf('app/RumDashboard/VisitorsRegionMap', module).add( + 'Tooltip', + () => { + const loadFeatureProps = async () => { + return [ + { + getPropertyKey: () => COUNTRY_NAME, + getRawValue: () => 'United States', + }, + { + getPropertyKey: () => TRANSACTION_DURATION_COUNTRY, + getRawValue: () => 2434353, + }, + ]; + }; + return ( + COUNTRY_NAME, - getRawValue: () => 'United States', - }, - { - getPropertyKey: () => TRANSACTION_DURATION_COUNTRY, - getRawValue: () => 2434353, - }, - ]; - }; - return ( - - ); + actions: [], + }, + ]} + /> + ); + }, + { + info: { + propTables: false, + source: false, }, - { - info: { - propTables: false, - source: false, - }, - } - ); + } +); diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx index cd5fa5db89a31..02ecf902f00a3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_configurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -8,7 +8,6 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { CoreStart } from 'kibana/public'; -import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types'; import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { createCallApmApi } from '../../../../../services/rest/createCallApmApi'; @@ -37,13 +36,11 @@ storiesOf( }; return ( - - - {storyFn()} - - + + {storyFn()} + ); }) .add( @@ -67,7 +64,6 @@ storiesOf( propTablesExclude: [ AgentConfigurationCreateEdit, ApmPluginContext.Provider, - EuiThemeProvider, ], source: false, }, diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx index 8cc16dd801c25..d434a155c9cf4 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/Distribution/index.stories.tsx @@ -11,7 +11,6 @@ import { ApmPluginContext, ApmPluginContextValue, } from '../../../../context/apm_plugin/apm_plugin_context'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { ErrorDistribution } from './'; export default { @@ -28,13 +27,11 @@ export default { }; return ( - - - - - - - + + + + + ); }, ], diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx index f21c189584d31..9468202edf4d6 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/exception_stacktrace.stories.tsx @@ -7,7 +7,6 @@ import { Story } from '@storybook/react'; import React, { ComponentProps, ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { ExceptionStacktrace } from './exception_stacktrace'; type Args = ComponentProps; @@ -15,13 +14,6 @@ type Args = ComponentProps; export default { title: 'app/ErrorGroupDetails/DetailView/ExceptionStacktrace', component: ExceptionStacktrace, - decorators: [ - (StoryComponent: ComponentType) => ( - - - - ), - ], }; export const JavaWithLongLines: Story = (args) => ( diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx index 6b7626514d03f..324a38ea5db39 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/Popover.stories.tsx @@ -8,7 +8,6 @@ import cytoscape from 'cytoscape'; import { CoreStart } from 'kibana/public'; import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../../services/rest/createCallApmApi'; @@ -38,15 +37,13 @@ export default { createCallApmApi(coreMock); return ( - - - -
- -
-
-
-
+ + +
+ +
+
+
); }, ], diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx index a8f004a7295d9..f1a89043f826e 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_stats_list.stories.tsx @@ -5,20 +5,12 @@ * 2.0. */ -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { ServiceStatsList } from './ServiceStatsList'; export default { title: 'app/ServiceMap/Popover/ServiceStatsList', component: ServiceStatsList, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example() { diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx index 8bc0d7239e9c5..7ce9c3e943613 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/Cytoscape.stories.tsx @@ -6,21 +6,13 @@ */ import cytoscape from 'cytoscape'; -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { Cytoscape } from '../Cytoscape'; import { Centerer } from './centerer'; export default { title: 'app/ServiceMap/Cytoscape', component: Cytoscape, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example() { diff --git a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx index 45de632a152d4..192447ef7591a 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/__stories__/cytoscape_example_data.stories.tsx @@ -16,8 +16,7 @@ import { EuiSpacer, EuiToolTip, } from '@elastic/eui'; -import React, { ComponentType, useEffect, useState } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React, { useEffect, useState } from 'react'; import { Cytoscape } from '../Cytoscape'; import { Centerer } from './centerer'; import exampleResponseHipsterStore from './example_response_hipster_store.json'; @@ -42,13 +41,6 @@ function getHeight() { export default { title: 'app/ServiceMap/Example data', component: Cytoscape, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function GenerateMap() { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx index 5ea2fca2dfa32..20ca3194fbfdf 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/WaterfallContainer.stories.tsx @@ -7,7 +7,6 @@ import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../../../../src/plugins/kibana_react/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TraceAPIResponse } from '../../../../../../server/lib/traces/get_trace'; import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; @@ -27,11 +26,9 @@ export default { decorators: [ (Story: ComponentType) => ( - - - - - + + + ), ], diff --git a/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx b/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx index bc41fd58ea5d2..68c3edabfa44e 100644 --- a/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/agent_icon/agent_icon.stories.tsx @@ -7,30 +7,22 @@ import { EuiCard, + EuiCodeBlock, EuiFlexGroup, - EuiImage, EuiFlexItem, + EuiImage, EuiSpacer, EuiToolTip, - EuiCodeBlock, } from '@elastic/eui'; -import React, { ComponentType } from 'react'; -import { AgentIcon } from './index'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { AGENT_NAMES } from '../../../../common/agent_name'; -import { getAgentIcon } from './get_agent_icon'; import { useTheme } from '../../../hooks/use_theme'; +import { getAgentIcon } from './get_agent_icon'; +import { AgentIcon } from './index'; export default { title: 'shared/icons', component: AgentIcon, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function AgentIcons() { diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx index 0eb5b0e84ff39..6128526c577e4 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/custom_tooltip.stories.tsx @@ -6,8 +6,7 @@ */ import { TooltipInfo } from '@elastic/charts'; -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; import { MainStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table'; import { CustomTooltip } from './custom_tooltip'; @@ -25,13 +24,6 @@ function getLatencyFormatter(props: TooltipInfo) { export default { title: 'shared/charts/InstancesLatencyDistributionChart/CustomTooltip', component: CustomTooltip, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example(props: TooltipInfo) { diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx index c574645d485d5..80bfcca05aabc 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/instances_latency_distribution_chart.stories.tsx @@ -5,8 +5,7 @@ * 2.0. */ -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { InstancesLatencyDistributionChart, @@ -16,13 +15,6 @@ import { export default { title: 'shared/charts/InstancesLatencyDistributionChart', component: InstancesLatencyDistributionChart, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function Example({ items }: InstancesLatencyDistributionChartProps) { diff --git a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx index d1dcd831eadd7..ff2b95667a63a 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/latency_chart/latency_chart.stories.tsx @@ -8,7 +8,6 @@ import { StoryContext } from '@storybook/react'; import React, { ComponentType } from 'react'; import { MemoryRouter, Route } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; import { LatencyAggregationType } from '../../../../../common/latency_aggregation_types'; import { @@ -76,20 +75,18 @@ export default { - - - - - - - - - + + + + + + + diff --git a/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx b/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx index b053f441e9632..7d2e2fbefc359 100644 --- a/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/span_icon/span_icon.stories.tsx @@ -6,32 +6,23 @@ */ import { - EuiImage, EuiCard, + EuiCodeBlock, EuiFlexGroup, EuiFlexItem, + EuiImage, EuiSpacer, - EuiCodeBlock, EuiToolTip, } from '@elastic/eui'; -import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; +import React from 'react'; +import { getSpanIcon, spanTypeIcons } from './get_span_icon'; import { SpanIcon } from './index'; -import { getSpanIcon } from './get_span_icon'; -import { spanTypeIcons } from './get_span_icon'; const spanTypes = Object.keys(spanTypeIcons); export default { title: 'shared/icons', component: SpanIcon, - decorators: [ - (Story: ComponentType) => ( - - - - ), - ], }; export function SpanIcons() { diff --git a/x-pack/plugins/observability/.storybook/jest_setup.js b/x-pack/plugins/observability/.storybook/jest_setup.js new file mode 100644 index 0000000000000..32071b8aa3f62 --- /dev/null +++ b/x-pack/plugins/observability/.storybook/jest_setup.js @@ -0,0 +1,11 @@ +/* + * 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 { setGlobalConfig } from '@storybook/testing-react'; +import * as globalStorybookConfig from './preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/x-pack/plugins/observability/.storybook/preview.js b/x-pack/plugins/observability/.storybook/preview.js new file mode 100644 index 0000000000000..18343c15a6465 --- /dev/null +++ b/x-pack/plugins/observability/.storybook/preview.js @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiThemeProviderDecorator } from '../../../../src/plugins/kibana_react/common'; + +export const decorators = [EuiThemeProviderDecorator]; diff --git a/x-pack/plugins/observability/jest.config.js b/x-pack/plugins/observability/jest.config.js index 66d42122382f3..6fdeab06df053 100644 --- a/x-pack/plugins/observability/jest.config.js +++ b/x-pack/plugins/observability/jest.config.js @@ -9,4 +9,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/observability'], + setupFiles: ['/x-pack/plugins/observability/.storybook/jest_setup.js'], }; diff --git a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx index 5f5cf2cb4da21..5c07b4626cf19 100644 --- a/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx +++ b/x-pack/plugins/observability/public/components/shared/core_web_vitals/__stories__/core_vitals.stories.tsx @@ -9,7 +9,6 @@ import React, { ComponentType } from 'react'; import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; import { Observable } from 'rxjs'; import { CoreStart } from 'src/core/public'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public'; import { CoreVitalItem } from '../core_vital_item'; import { LCP_HELP_LABEL, LCP_LABEL } from '../translations'; @@ -25,9 +24,7 @@ export default { (Story: ComponentType) => ( - - - + ), diff --git a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx index 80a25b82eb8cb..1152ba32960ed 100644 --- a/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx +++ b/x-pack/plugins/observability/public/components/shared/field_value_suggestions/__stories__/field_value_selection.stories.tsx @@ -10,7 +10,6 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n/react'; import { Observable } from 'rxjs'; import { CoreStart } from 'src/core/public'; import { text } from '@storybook/addon-knobs'; -import { EuiThemeProvider } from '../../../../../../../../src/plugins/kibana_react/common'; import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public'; import { FieldValueSelectionProps } from '../types'; import { FieldValueSelection } from '../field_value_selection'; @@ -31,16 +30,14 @@ export default { (Story: ComponentType) => ( - - {}} - selectedValue={[]} - loading={false} - setQuery={() => {}} - /> - + {}} + selectedValue={[]} + loading={false} + setQuery={() => {}} + /> ), diff --git a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx index 86922b045c742..ef3ded61492c7 100644 --- a/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx +++ b/x-pack/plugins/observability/public/pages/landing/landing.stories.tsx @@ -6,7 +6,6 @@ */ import React, { ComponentType } from 'react'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { PluginContext, PluginContextValue } from '../../context/plugin_context'; import { LandingPage } from './'; @@ -27,9 +26,7 @@ export default { } as unknown) as PluginContextValue; return ( - - - + ); }, diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index dd424cf221d15..2982333235331 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -10,7 +10,6 @@ import { storiesOf } from '@storybook/react'; import { AppMountParameters, CoreStart } from 'kibana/public'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { EuiThemeProvider } from '../../../../../../src/plugins/kibana_react/common'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/public'; import { HasDataContextProvider } from '../../context/has_data_context'; import { PluginContext } from '../../context/plugin_context'; @@ -65,9 +64,7 @@ const withCore = makeDecorator({ ObservabilityPageTemplate: KibanaPageTemplate, }} > - - {storyFn(context)} - + {storyFn(context)} ); From 583b867d44b926fc1f19fa02f76bc2f83f99440b Mon Sep 17 00:00:00 2001 From: "Devin W. Hurley" Date: Fri, 9 Jul 2021 10:15:04 -0400 Subject: [PATCH 06/36] [RAC] [RBAC] Fix hyperlinks in typedocs for alerts client (#104975) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/rule_registry/docs/README.md | 2 +- .../alerts_client/classes/alertsclient.md | 32 +++++++++---------- .../interfaces/constructoroptions.md | 8 ++--- .../alerts_client/interfaces/updateoptions.md | 8 ++--- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/rule_registry/docs/README.md b/x-pack/plugins/rule_registry/docs/README.md index a22dc1ab7e864..0eb2463005193 100644 --- a/x-pack/plugins/rule_registry/docs/README.md +++ b/x-pack/plugins/rule_registry/docs/README.md @@ -19,7 +19,7 @@ yarn global add typedoc typedoc-plugin-markdown ```bash cd x-pack/plugins/rule_registry/docs -npx typedoc --options alerts_client_typedoc.json +npx typedoc --gitRemote upstream --options alerts_client_typedoc.json ``` After running the above commands the files in the `server` directory will be updated to match the new tsdocs. diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md b/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md index 9b639829a9f5f..359834bf9c2e7 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/classes/alertsclient.md @@ -41,7 +41,7 @@ on alerts as data. #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) +[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) ## Properties @@ -51,7 +51,7 @@ on alerts as data. #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:57](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L57) +[rule_registry/server/alert_data_client/alerts_client.ts:57](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L57) ___ @@ -61,7 +61,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:58](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L58) +[rule_registry/server/alert_data_client/alerts_client.ts:58](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L58) ___ @@ -71,7 +71,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) +[rule_registry/server/alert_data_client/alerts_client.ts:59](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L59) ___ @@ -81,13 +81,13 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:56](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L56) +[rule_registry/server/alert_data_client/alerts_client.ts:56](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L56) ## Methods ### fetchAlert -▸ `Private` **fetchAlert**(`__namedParameters`): `Promise` +▸ `Private` **fetchAlert**(`__namedParameters`): `Promise`\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> #### Parameters @@ -97,17 +97,17 @@ ___ #### Returns -`Promise` +`Promise`\>, ``"kibana.rac.alert.owner"`` \| ``"rule.id"``\> & { `kibana.rac.alert.owner`: `string` ; `rule.id`: `string` } & { `_version`: `undefined` \| `string` }\> #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:79](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L79) +[rule_registry/server/alert_data_client/alerts_client.ts:79](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L79) ___ ### get -▸ **get**(`__namedParameters`): `Promise`\>\> +▸ **get**(`__namedParameters`): `Promise`\>\> #### Parameters @@ -117,11 +117,11 @@ ___ #### Returns -`Promise`\>\> +`Promise`\>\> #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:108](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L108) +[rule_registry/server/alert_data_client/alerts_client.ts:115](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L115) ___ @@ -142,7 +142,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:68](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L68) +[rule_registry/server/alert_data_client/alerts_client.ts:68](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L68) ___ @@ -162,13 +162,13 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:200](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L200) +[rule_registry/server/alert_data_client/alerts_client.ts:219](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L219) ___ ### update -▸ **update**(`__namedParameters`): `Promise`<`Object`\> +▸ **update**(`__namedParameters`): `Promise` #### Type parameters @@ -184,8 +184,8 @@ ___ #### Returns -`Promise`<`Object`\> +`Promise` #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:146](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L146) +[rule_registry/server/alert_data_client/alerts_client.ts:160](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L160) diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md index e3dbc6b2c2354..051a5affc0379 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md @@ -19,7 +19,7 @@ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:34](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L34) +[rule_registry/server/alert_data_client/alerts_client.ts:34](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L34) ___ @@ -29,7 +29,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:33](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L33) +[rule_registry/server/alert_data_client/alerts_client.ts:33](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L33) ___ @@ -39,7 +39,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:35](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L35) +[rule_registry/server/alert_data_client/alerts_client.ts:35](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L35) ___ @@ -49,4 +49,4 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:32](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L32) +[rule_registry/server/alert_data_client/alerts_client.ts:32](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L32) diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md index fbc0991635000..10e793155c196 100644 --- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md +++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md @@ -25,7 +25,7 @@ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:41](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L41) +[rule_registry/server/alert_data_client/alerts_client.ts:41](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L41) ___ @@ -35,7 +35,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:39](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L39) +[rule_registry/server/alert_data_client/alerts_client.ts:39](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L39) ___ @@ -45,7 +45,7 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:42](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L42) +[rule_registry/server/alert_data_client/alerts_client.ts:42](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L42) ___ @@ -55,4 +55,4 @@ ___ #### Defined in -[rule_registry/server/alert_data_client/alerts_client.ts:40](https://github.com/dhurley14/kibana/blob/d2173f5090e/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L40) +[rule_registry/server/alert_data_client/alerts_client.ts:40](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L40) From 3cbea1bcaabeaaf3399047b3da12cc6533f12a47 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 9 Jul 2021 15:19:27 +0100 Subject: [PATCH 07/36] skip flaky suite (#105016) --- x-pack/test/functional/apps/lens/formula.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/lens/formula.ts b/x-pack/test/functional/apps/lens/formula.ts index 8b87db21a1ffe..6148215d8b6d2 100644 --- a/x-pack/test/functional/apps/lens/formula.ts +++ b/x-pack/test/functional/apps/lens/formula.ts @@ -16,7 +16,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const fieldEditor = getService('fieldEditor'); - describe('lens formula', () => { + // FLAKY: https://github.com/elastic/kibana/issues/105016 + describe.skip('lens formula', () => { it('should transition from count to formula', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await listingTable.searchForItemWithName('lnsXYvis'); From 8c366faf8f5e729216ea4b5f9b4c2d774d664742 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 9 Jul 2021 18:32:34 +0300 Subject: [PATCH 08/36] Fix of the `ja-JP.json not found` error. (#105023) * Fixed `i18nrc not found ja-JP.json file` at expression_reveal_image. * Fixed `i18nrc not found ja-JP.json file` at `screenshotMode`. * Fixed `i18nrc not found ja-JP.json file` at `x-pack/plugins/timelines`. --- src/plugins/expression_reveal_image/.i18nrc.json | 7 ------- src/plugins/screenshot_mode/.i18nrc.json | 7 ------- x-pack/plugins/timelines/.i18nrc.json | 7 ------- 3 files changed, 21 deletions(-) delete mode 100755 src/plugins/expression_reveal_image/.i18nrc.json delete mode 100644 src/plugins/screenshot_mode/.i18nrc.json delete mode 100644 x-pack/plugins/timelines/.i18nrc.json diff --git a/src/plugins/expression_reveal_image/.i18nrc.json b/src/plugins/expression_reveal_image/.i18nrc.json deleted file mode 100755 index 5b073e4374519..0000000000000 --- a/src/plugins/expression_reveal_image/.i18nrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "prefix": "expressionRevealImage", - "paths": { - "expressionRevealImage": "." - }, - "translations": ["translations/ja-JP.json"] -} diff --git a/src/plugins/screenshot_mode/.i18nrc.json b/src/plugins/screenshot_mode/.i18nrc.json deleted file mode 100644 index 79643fbb63d30..0000000000000 --- a/src/plugins/screenshot_mode/.i18nrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "prefix": "screenshotMode", - "paths": { - "screenshotMode": "." - }, - "translations": ["translations/ja-JP.json"] -} diff --git a/x-pack/plugins/timelines/.i18nrc.json b/x-pack/plugins/timelines/.i18nrc.json deleted file mode 100644 index 4fe01ccc7bc69..0000000000000 --- a/x-pack/plugins/timelines/.i18nrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "prefix": "timelines", - "paths": { - "timelines": "." - }, - "translations": ["translations/ja-JP.json"] -} From dacb5949a2639932296347a09a14388c9e340af9 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Fri, 9 Jul 2021 18:03:52 +0200 Subject: [PATCH 09/36] Bump fast-safe-stringify to v2.0.8 (#105066) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c64f4e1c4b281..227301eed8dd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13489,9 +13489,9 @@ fast-redact@^3.0.0: integrity sha512-a/S/Hp6aoIjx7EmugtzLqXmcNsyFszqbt6qQ99BdG61QjBZF6shNis0BYR6TsZOQ1twYc0FN2Xdhwwbv6+KD0w== fast-safe-stringify@2.x.x, fast-safe-stringify@^2.0.4, fast-safe-stringify@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" - integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + version "2.0.8" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.8.tgz#dc2af48c46cf712b683e849b2bbd446b32de936f" + integrity sha512-lXatBjf3WPjmWD6DpIZxkeSsCOwqI0maYMpgDlx8g4U2qi4lbjA9oH/HD2a87G+KfsUmo5WbJFmqBZlPxtptag== fast-shallow-equal@^1.0.0: version "1.0.0" From 48fa754042e7597b65b69b2c98fb2c6aa2057c4d Mon Sep 17 00:00:00 2001 From: Vadim Yakhin Date: Fri, 9 Jul 2021 13:11:14 -0300 Subject: [PATCH 10/36] Replace cmd with bash as EuiCodeBlock language (#105065) cmd is no longer supported by 3rd party library used by EuiCodeBlock --- .../components/shared/status_item/status_item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx index 79455ccc1d90d..35ac8f1b85c05 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/status_item/status_item.tsx @@ -44,7 +44,7 @@ export const StatusItem: React.FC = ({ details }) => { const infoPopover = ( Date: Fri, 9 Jul 2021 09:23:18 -0700 Subject: [PATCH 11/36] [App Search] Relevance Tuning: Fix unsaved changes bug (#104951) * Fix unsavedChanges false positive when MultiInputRows is present - The fix for this is to change MultiInputRows from useEffect to useUpdateEffect, which prevents onChange from firing on initial mount/render (triggering updateBoostValue->unsavedChanges) @see https://github.com/streamich/react-use/blob/master/docs/useUpdateEffect.md * Fix precision tuner not triggering unsavedChanges --- .../multi_input_rows/multi_input_rows.test.tsx | 12 ++++++++++-- .../components/multi_input_rows/multi_input_rows.tsx | 5 +++-- .../relevance_tuning/relevance_tuning_logic.test.ts | 3 ++- .../relevance_tuning/relevance_tuning_logic.ts | 1 + 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx index 3b8e1c96ff504..63952bc2a6de7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.test.tsx @@ -10,7 +10,7 @@ import '../../../__mocks__/shallow_useeffect.mock'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { rerender } from '../../../test_helpers'; @@ -162,10 +162,18 @@ describe('MultiInputRows', () => { }); describe('onChange', () => { + let wrapper: ShallowWrapper; const onChange = jest.fn(); + beforeEach(() => { + wrapper = shallow(); + }); + + it('does not call on change on mount', () => { + expect(onChange).not.toHaveBeenCalled(); + }); + it('returns the current values dynamically on change', () => { - const wrapper = shallow(); setMockValues({ ...values, values: ['updated'] }); rerender(wrapper); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx index ac61e69eb44c4..257f4b637f3e0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows.tsx @@ -5,9 +5,10 @@ * 2.0. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { useValues, useActions } from 'kea'; +import useUpdateEffect from 'react-use/lib/useUpdateEffect'; import { EuiForm, EuiButton, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; @@ -49,7 +50,7 @@ export const MultiInputRows: React.FC = ({ const { values, addedNewRow, hasEmptyValues, hasOnlyOneValue } = useValues(logic); const { addValue, editValue, deleteValue } = useActions(logic); - useEffect(() => { + useUpdateEffect(() => { if (onChange) { onChange(filterEmptyValues(values)); } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index 4233a7b300d15..e2493b6404f7d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -229,7 +229,7 @@ describe('RelevanceTuningLogic', () => { }); describe('updatePrecision', () => { - it('should set precision inside search settings', () => { + it('should set precision inside search settings and set unsavedChanges to true', () => { mount(); RelevanceTuningLogic.actions.updatePrecision(9); @@ -239,6 +239,7 @@ describe('RelevanceTuningLogic', () => { ...DEFAULT_VALUES.searchSettings, precision: 9, }, + unsavedChanges: true, }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts index 743bb1aa1502b..02903b4588ed4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.ts @@ -191,6 +191,7 @@ export const RelevanceTuningLogic = kea< unsavedChanges: [ false, { + updatePrecision: () => true, setSearchSettings: () => true, setSearchSettingsResponse: () => false, }, From 81f09a863de65a16d88bbc8763957190ae4165c8 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 9 Jul 2021 18:24:18 +0200 Subject: [PATCH 12/36] [Security Solution] [Endpoint] Allow filtering activity log with date range (#104085) * use date range in search query fixes elastic/security-team/issues/1137 * make any date selection fetch matching log fixes elastic/security-team/issues/1137 * use a single action for updating paging info and fetching data fixes elastic/security-team/issues/1137 * use consistent types for some reason TS was complaining earlier with `undefined` * reset date picker on tab load fixes elastic/security-team/issues/1137 * refactor date pickers into a component refs elastic/security-team/issues/1137 * clear dates on change of endpoint fixes elastic/security-team/issues/1137 * do not show empty state if date filtering results return empty data fixes elastic/security-team/issues/1137 * add tests fixes elastic/security-team/issues/1137 * review changes * update comment refs f551b67d661a330621fb77c435db26ec90288b81 * store invalidDateRange on redux store and decouple logic from the component review changes * fix test * fix lint * review changes * expand date picker to use the full width of the flyout review changes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/endpoint/schema/actions.ts | 2 + .../common/endpoint/types/actions.ts | 2 + .../pages/endpoint_hosts/store/action.ts | 20 +-- .../pages/endpoint_hosts/store/builders.ts | 3 + .../pages/endpoint_hosts/store/index.test.ts | 1 + .../pages/endpoint_hosts/store/middleware.ts | 56 ++++++-- .../pages/endpoint_hosts/store/reducer.ts | 25 ++-- .../management/pages/endpoint_hosts/types.ts | 5 +- .../pages/endpoint_hosts/utils.test.ts | 30 +++++ .../management/pages/endpoint_hosts/utils.ts | 16 +++ .../activity_log_date_range_picker/index.tsx | 127 ++++++++++++++++++ .../components/endpoint_details_tabs.tsx | 13 +- .../view/details/endpoint_activity_log.tsx | 34 +++-- .../pages/endpoint_hosts/view/index.test.tsx | 21 +++ .../pages/endpoint_hosts/view/translations.ts | 14 ++ .../endpoint/routes/actions/audit_log.test.ts | 46 +++++++ .../routes/actions/audit_log_handler.ts | 12 +- .../server/endpoint/services/actions.ts | 56 ++++++-- 18 files changed, 416 insertions(+), 67 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts index fd4d89540f0ce..98cb7729c9440 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/actions.ts @@ -23,6 +23,8 @@ export const EndpointActionLogRequestSchema = { query: schema.object({ page: schema.number({ defaultValue: 1, min: 1 }), page_size: schema.number({ defaultValue: 10, min: 1, max: 100 }), + start_date: schema.maybe(schema.string()), + end_date: schema.maybe(schema.string()), }), params: schema.object({ agent_id: schema.string(), 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 dfaad68e295eb..25fc831ca0aa4 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts @@ -60,6 +60,8 @@ export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse; export interface ActivityLog { page: number; pageSize: number; + startDate?: string; + endDate?: string; data: ActivityLogEntry[]; } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts index 42c16e151c45d..3fe6821abbcbe 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/action.ts @@ -146,13 +146,6 @@ export type EndpointIsolationRequestStateChange = Action<'endpointIsolationReque payload: EndpointState['isolationRequestState']; }; -export interface AppRequestedEndpointActivityLog { - type: 'appRequestedEndpointActivityLog'; - payload: { - page: number; - pageSize: number; - }; -} export type EndpointDetailsActivityLogChanged = Action<'endpointDetailsActivityLogChanged'> & { payload: EndpointState['endpointDetails']['activityLog']['logData']; }; @@ -165,9 +158,18 @@ export interface EndpointDetailsActivityLogUpdatePaging { type: 'endpointDetailsActivityLogUpdatePaging'; payload: { // disable paging when no more data after paging - disabled: boolean; + disabled?: boolean; page: number; pageSize: number; + startDate?: string; + endDate?: string; + }; +} + +export interface EndpointDetailsActivityLogUpdateIsInvalidDateRange { + type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange'; + payload: { + isInvalidDateRange?: boolean; }; } @@ -181,8 +183,8 @@ export type EndpointAction = | ServerFailedToReturnEndpointList | ServerReturnedEndpointDetails | ServerFailedToReturnEndpointDetails - | AppRequestedEndpointActivityLog | EndpointDetailsActivityLogUpdatePaging + | EndpointDetailsActivityLogUpdateIsInvalidDateRange | EndpointDetailsFlyoutTabChanged | EndpointDetailsActivityLogChanged | ServerReturnedEndpointPolicyResponse diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts index 5db861d18cd69..2a869095cac81 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/builders.ts @@ -25,6 +25,9 @@ export const initialEndpointPageState = (): Immutable => { disabled: false, page: 1, pageSize: 50, + startDate: undefined, + endDate: undefined, + isInvalidDateRange: false, }, logData: createUninitialisedResourceState(), }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts index 3bf625d726e5f..a9c65c74015c6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/index.test.ts @@ -48,6 +48,7 @@ describe('EndpointList store concerns', () => { disabled: false, page: 1, pageSize: 50, + isInvalidDateRange: false, }, logData: { type: 'UninitialisedResourceState' }, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index e34e9cf5a83f3..f233fbdec5415 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -65,6 +65,7 @@ import { resolvePathVariables } from '../../../../common/utils/resolve_path_vari import { EndpointPackageInfoStateChanged } from './action'; import { fetchPendingActionsByAgentId } from '../../../../common/lib/endpoint_pending_actions'; import { EndpointDetailsTabsTypes } from '../view/details/components/endpoint_details_tabs'; +import { getIsInvalidDateRange } from '../utils'; type EndpointPageStore = ImmutableMiddlewareAPI; @@ -400,21 +401,50 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory(getActivityLogData(getState())), - }); - + if ( + action.type === 'endpointDetailsActivityLogUpdatePaging' && + hasSelectedEndpoint(getState()) + ) { try { - const { page, pageSize } = getActivityLogDataPaging(getState()); + const { disabled, page, pageSize, startDate, endDate } = getActivityLogDataPaging( + getState() + ); + // don't page when paging is disabled or when date ranges are invalid + if (disabled) { + return; + } + if (getIsInvalidDateRange({ startDate, endDate })) { + dispatch({ + type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange', + payload: { + isInvalidDateRange: true, + }, + }); + return; + } + + dispatch({ + type: 'endpointDetailsActivityLogUpdateIsInvalidDateRange', + payload: { + isInvalidDateRange: false, + }, + }); + dispatch({ + type: 'endpointDetailsActivityLogChanged', + // ts error to be fixed when AsyncResourceState is refactored (#830) + // @ts-expect-error + payload: createLoadingResourceState(getActivityLogData(getState())), + }); const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, { agent_id: selectedAgent(getState()), }); const activityLog = await coreStart.http.get(route, { - query: { page, page_size: pageSize }, + query: { + page, + page_size: pageSize, + start_date: startDate, + end_date: endDate, + }, }); const lastLoadedLogData = getLastLoadedActivityLogData(getState()); @@ -428,6 +458,8 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory 1 ? activityLog.page - 1 : 1, pageSize: activityLog.pageSize, + startDate: activityLog.startDate, + endDate: activityLog.endDate, }, }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 0981d621f26f3..1498ce08db8ab 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -41,6 +41,8 @@ const handleEndpointDetailsActivityLogChanged: CaseReducer; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts new file mode 100644 index 0000000000000..fa2aaaa16ae37 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.test.ts @@ -0,0 +1,30 @@ +/* + * 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 moment from 'moment'; +import { getIsInvalidDateRange } from './utils'; + +describe('utils', () => { + describe('getIsInvalidDateRange', () => { + it('should return FALSE when either dates are undefined', () => { + expect(getIsInvalidDateRange({})).toBe(false); + expect(getIsInvalidDateRange({ startDate: moment().subtract(1, 'd').toISOString() })).toBe( + false + ); + expect(getIsInvalidDateRange({ endDate: moment().toISOString() })).toBe(false); + }); + + it('should return TRUE when startDate is after endDate', () => { + expect( + getIsInvalidDateRange({ + startDate: moment().toISOString(), + endDate: moment().subtract(1, 'd').toISOString(), + }) + ).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts index 3e17992dd975f..e2d619743c83b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/utils.ts @@ -5,6 +5,7 @@ * 2.0. */ +import moment from 'moment'; import { HostInfo, HostMetadata } from '../../../../common/endpoint/types'; export const isPolicyOutOfDate = ( @@ -23,3 +24,18 @@ export const isPolicyOutOfDate = ( reported.endpoint_policy_version >= current.endpoint.revision ); }; + +export const getIsInvalidDateRange = ({ + startDate, + endDate, +}: { + startDate?: string; + endDate?: string; +}) => { + if (startDate && endDate) { + const start = moment(startDate); + const end = moment(endDate); + return start.isAfter(end); + } + return false; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx new file mode 100644 index 0000000000000..f11d2872e3d26 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/activity_log_date_range_picker/index.tsx @@ -0,0 +1,127 @@ +/* + * 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 { useDispatch } from 'react-redux'; +import React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; +import moment from 'moment'; +import { EuiFlexGroup, EuiFlexItem, EuiDatePicker, EuiDatePickerRange } from '@elastic/eui'; + +import * as i18 from '../../../translations'; +import { useEndpointSelector } from '../../../hooks'; +import { getActivityLogDataPaging } from '../../../../store/selectors'; + +const DatePickerWrapper = styled.div` + width: ${(props) => props.theme.eui.fractions.single.percentage}; + background: white; +`; +const StickyFlexItem = styled(EuiFlexItem)` + position: sticky; + top: ${(props) => props.theme.eui.euiSizeM}; + z-index: 1; +`; + +export const DateRangePicker = memo(() => { + const dispatch = useDispatch(); + const { page, pageSize, startDate, endDate, isInvalidDateRange } = useEndpointSelector( + getActivityLogDataPaging + ); + + const onClear = useCallback( + ({ clearStart = false, clearEnd = false }: { clearStart?: boolean; clearEnd?: boolean }) => { + dispatch({ + type: 'endpointDetailsActivityLogUpdatePaging', + payload: { + disabled: false, + page, + pageSize, + startDate: clearStart ? undefined : startDate, + endDate: clearEnd ? undefined : endDate, + }, + }); + }, + [dispatch, endDate, startDate, page, pageSize] + ); + + const onChangeStartDate = useCallback( + (date) => { + dispatch({ + type: 'endpointDetailsActivityLogUpdatePaging', + payload: { + disabled: false, + page, + pageSize, + startDate: date ? date?.toISOString() : undefined, + endDate: endDate ? endDate : undefined, + }, + }); + }, + [dispatch, endDate, page, pageSize] + ); + + const onChangeEndDate = useCallback( + (date) => { + dispatch({ + type: 'endpointDetailsActivityLogUpdatePaging', + payload: { + disabled: false, + page, + pageSize, + startDate: startDate ? startDate : undefined, + endDate: date ? date.toISOString() : undefined, + }, + }); + }, + [dispatch, startDate, page, pageSize] + ); + + return ( + + + + + onClear({ clearStart: true })} + placeholderText={i18.ACTIVITY_LOG.datePicker.startDate} + selected={startDate ? moment(startDate) : undefined} + showTimeSelect + startDate={startDate ? moment(startDate) : undefined} + /> + } + endDateControl={ + onClear({ clearEnd: true })} + placeholderText={i18.ACTIVITY_LOG.datePicker.endDate} + selected={endDate ? moment(endDate) : undefined} + showTimeSelect + startDate={startDate ? moment(startDate) : undefined} + /> + } + /> + + + + + ); +}); + +DateRangePicker.displayName = 'DateRangePicker'; diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx index aa1f56529657e..73a3734e4ca88 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/endpoint_details_tabs.tsx @@ -56,19 +56,14 @@ export const EndpointDetailsFlyoutTabs = memo( }, }); if (tab.id === EndpointDetailsTabsTypes.activityLog) { - const paging = { - page: 1, - pageSize, - }; - dispatch({ - type: 'appRequestedEndpointActivityLog', - payload: paging, - }); dispatch({ type: 'endpointDetailsActivityLogUpdatePaging', payload: { disabled: false, - ...paging, + page: 1, + pageSize, + startDate: undefined, + endDate: undefined, }, }); } diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx index 360d6e3842816..121f23fdb3a9e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { memo, useCallback, useEffect, useRef } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react'; import styled from 'styled-components'; import { @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { LogEntry } from './components/log_entry'; +import { DateRangePicker } from './components/activity_log_date_range_picker'; import * as i18 from '../translations'; import { Immutable, ActivityLog } from '../../../../../../common/endpoint/types'; import { AsyncResourceState } from '../../../../state'; @@ -31,12 +32,12 @@ import { getActivityLogRequestLoading, } from '../../store/selectors'; -const StyledEuiFlexGroup = styled(EuiFlexGroup)` - height: 85vh; +const StyledEuiFlexGroup = styled(EuiFlexGroup)<{ isShorter: boolean }>` + height: ${({ isShorter }) => (isShorter ? '25vh' : '85vh')}; `; const LoadMoreTrigger = styled.div` - height: 6px; - width: 100%; + height: ${(props) => props.theme.eui.euiSizeXS}; + width: ${(props) => props.theme.eui.fractions.single.percentage}; `; export const EndpointActivityLog = memo( @@ -48,25 +49,37 @@ export const EndpointActivityLog = memo( const activityLogSize = activityLogData.length; const activityLogError = useEndpointSelector(getActivityLogError); const dispatch = useDispatch<(action: EndpointAction) => void>(); - const { page, pageSize, disabled: isPagingDisabled } = useEndpointSelector( + const { page, pageSize, startDate, endDate, disabled: isPagingDisabled } = useEndpointSelector( getActivityLogDataPaging ); + const hasActiveDateRange = useMemo(() => !!startDate || !!endDate, [startDate, endDate]); + const showEmptyState = useMemo( + () => (activityLogLoaded && !activityLogSize && !hasActiveDateRange) || activityLogError, + [activityLogLoaded, activityLogSize, hasActiveDateRange, activityLogError] + ); + const isShorter = useMemo( + () => !!(hasActiveDateRange && isPagingDisabled && !activityLogLoading && !activityLogSize), + [hasActiveDateRange, isPagingDisabled, activityLogLoading, activityLogSize] + ); + const loadMoreTrigger = useRef(null); const getActivityLog = useCallback( (entries: IntersectionObserverEntry[]) => { const isTargetIntersecting = entries.some((entry) => entry.isIntersecting); if (isTargetIntersecting && activityLogLoaded && !isPagingDisabled) { dispatch({ - type: 'appRequestedEndpointActivityLog', + type: 'endpointDetailsActivityLogUpdatePaging', payload: { page: page + 1, pageSize, + startDate, + endDate, }, }); } }, - [activityLogLoaded, dispatch, isPagingDisabled, page, pageSize] + [activityLogLoaded, dispatch, isPagingDisabled, page, pageSize, startDate, endDate] ); useEffect(() => { @@ -82,8 +95,8 @@ export const EndpointActivityLog = memo( return ( <> - - {(activityLogLoaded && !activityLogSize) || activityLogError ? ( + + {showEmptyState ? ( ) : ( <> + {activityLogLoaded && activityLogData.map((logEntry) => ( diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index ee5ef52d00f18..aafac38accd89 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -889,6 +889,27 @@ describe('when on the endpoint list page', () => { const emptyState = await renderResult.queryByTestId('activityLogEmpty'); expect(emptyState).not.toBe(null); }); + + it('should not display empty state with no log data while date range filter is active', async () => { + const activityLogTab = await renderResult.findByTestId('activity_log'); + reactTestingLibrary.act(() => { + reactTestingLibrary.fireEvent.click(activityLogTab); + }); + await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged'); + reactTestingLibrary.act(() => { + dispatchEndpointDetailsActivityLogChanged('success', { + page: 1, + pageSize: 50, + startDate: new Date().toISOString(), + data: [], + }); + }); + + const emptyState = await renderResult.queryByTestId('activityLogEmpty'); + const dateRangePicker = await renderResult.queryByTestId('activityLogDateRangePicker'); + expect(emptyState).toBe(null); + expect(dateRangePicker).not.toBe(null); + }); }); describe('when showing host Policy Response panel', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts index 89ffd2d23807e..7759935aa840a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/translations.ts @@ -15,6 +15,20 @@ export const ACTIVITY_LOG = { tabTitle: i18n.translate('xpack.securitySolution.endpointDetails.activityLog', { defaultMessage: 'Activity Log', }), + datePicker: { + startDate: i18n.translate( + 'xpack.securitySolution.endpointDetails.activityLog.datePicker.startDate', + { + defaultMessage: 'Pick a start date', + } + ), + endDate: i18n.translate( + 'xpack.securitySolution.endpointDetails.activityLog.datePicker.endDate', + { + defaultMessage: 'Pick an end date', + } + ), + }, LogEntry: { endOfLog: i18n.translate( 'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.endOfLog', diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts index c7f07151f8724..d9069444a10d7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log.test.ts @@ -60,6 +60,37 @@ describe('Action Log API', () => { }).not.toThrow(); }); + it('should work with all query params', () => { + expect(() => { + EndpointActionLogRequestSchema.query.validate({ + page: 10, + page_size: 100, + start_date: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), // yesterday + end_date: new Date().toISOString(), // today + }); + }).not.toThrow(); + }); + + it('should work with just startDate', () => { + expect(() => { + EndpointActionLogRequestSchema.query.validate({ + page: 1, + page_size: 100, + start_date: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), // yesterday + }); + }).not.toThrow(); + }); + + it('should work with just endDate', () => { + expect(() => { + EndpointActionLogRequestSchema.query.validate({ + page: 1, + page_size: 100, + end_date: new Date().toISOString(), // today + }); + }).not.toThrow(); + }); + it('should not work without allowed page and page_size params', () => { expect(() => { EndpointActionLogRequestSchema.query.validate({ page_size: 101 }); @@ -176,5 +207,20 @@ describe('Action Log API', () => { expect(error.message).toEqual(`Error fetching actions log for agent_id ${mockID}`); } }); + + it('should return date ranges if present in the query', async () => { + havingActionsAndResponses([], []); + const startDate = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(); + const endDate = new Date().toISOString(); + const response = await getActivityLog({ + page: 1, + page_size: 50, + start_date: startDate, + end_date: endDate, + }); + expect(response.ok).toBeCalled(); + expect((response.ok.mock.calls[0][0]?.body as ActivityLog).startDate).toEqual(startDate); + expect((response.ok.mock.calls[0][0]?.body as ActivityLog).endDate).toEqual(endDate); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts index 5e9594f478b31..716c1ab833559 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/audit_log_handler.ts @@ -27,10 +27,18 @@ export const actionsLogRequestHandler = ( return async (context, req, res) => { const { params: { agent_id: elasticAgentId }, - query: { page, page_size: pageSize }, + query: { page, page_size: pageSize, start_date: startDate, end_date: endDate }, } = req; - const body = await getAuditLogResponse({ elasticAgentId, page, pageSize, context, logger }); + const body = await getAuditLogResponse({ + elasticAgentId, + page, + pageSize, + startDate, + endDate, + context, + logger, + }); return res.ok({ body, }); diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts index 9d8db5b9a2154..89f088e322ffa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/actions.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/actions.ts @@ -19,28 +19,37 @@ export const getAuditLogResponse = async ({ elasticAgentId, page, pageSize, + startDate, + endDate, context, logger, }: { elasticAgentId: string; page: number; pageSize: number; + startDate?: string; + endDate?: string; context: SecuritySolutionRequestHandlerContext; logger: Logger; -}): Promise<{ - page: number; - pageSize: number; - data: ActivityLog['data']; -}> => { +}): Promise => { const size = Math.floor(pageSize / 2); const from = page <= 1 ? 0 : page * size - size + 1; const esClient = context.core.elasticsearch.client.asCurrentUser; - - const data = await getActivityLog({ esClient, from, size, elasticAgentId, logger }); + const data = await getActivityLog({ + esClient, + from, + size, + startDate, + endDate, + elasticAgentId, + logger, + }); return { page, pageSize, + startDate, + endDate, data, }; }; @@ -49,6 +58,8 @@ const getActivityLog = async ({ esClient, size, from, + startDate, + endDate, elasticAgentId, logger, }: { @@ -56,6 +67,8 @@ const getActivityLog = async ({ elasticAgentId: string; size: number; from: number; + startDate?: string; + endDate?: string; logger: Logger; }) => { const options = { @@ -67,8 +80,22 @@ const getActivityLog = async ({ let actionsResult; let responsesResult; + const dateFilters = []; + if (startDate) { + dateFilters.push({ range: { '@timestamp': { gte: startDate } } }); + } + if (endDate) { + dateFilters.push({ range: { '@timestamp': { lte: endDate } } }); + } try { + // fetch actions with matching agent_id + const baseActionFilters = [ + { term: { agents: elasticAgentId } }, + { term: { input_type: 'endpoint' } }, + { term: { type: 'INPUT_ACTION' } }, + ]; + const actionsFilters = [...baseActionFilters, ...dateFilters]; actionsResult = await esClient.search( { index: AGENT_ACTIONS_INDEX, @@ -77,11 +104,8 @@ const getActivityLog = async ({ body: { query: { bool: { - filter: [ - { term: { agents: elasticAgentId } }, - { term: { input_type: 'endpoint' } }, - { term: { type: 'INPUT_ACTION' } }, - ], + // @ts-ignore + filter: actionsFilters, }, }, sort: [ @@ -99,6 +123,12 @@ const getActivityLog = async ({ (e) => (e._source as EndpointAction).action_id ); + // fetch responses with matching `action_id`s + const baseResponsesFilter = [ + { term: { agent_id: elasticAgentId } }, + { terms: { action_id: actionIds } }, + ]; + const responsesFilters = [...baseResponsesFilter, ...dateFilters]; responsesResult = await esClient.search( { index: AGENT_ACTIONS_RESULTS_INDEX, @@ -106,7 +136,7 @@ const getActivityLog = async ({ body: { query: { bool: { - filter: [{ term: { agent_id: elasticAgentId } }, { terms: { action_id: actionIds } }], + filter: responsesFilters, }, }, }, From 357264db09fd9c6ce1e5440a52837cd0da798220 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Fri, 9 Jul 2021 09:34:27 -0700 Subject: [PATCH 13/36] [DOCS] Fixes formatting in search sessions doc (#105077) --- docs/discover/search-sessions.asciidoc | 31 ++++++++++++++++++-------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/docs/discover/search-sessions.asciidoc b/docs/discover/search-sessions.asciidoc index b503e8cfba3b4..652583db785ad 100644 --- a/docs/discover/search-sessions.asciidoc +++ b/docs/discover/search-sessions.asciidoc @@ -72,15 +72,28 @@ behaves differently: [float] ==== Limitations -Certain visualization features do not fully support background search sessions yet. If a dashboard using these features gets restored, -all panels using unsupported features won't load immediately, but instead send out additional data requests which can take a while to complete. -In this case a warning *Your search session is still running* will be shown. +Certain visualization features do not fully support background search sessions. If a dashboard +using these features is restored, +all panels using unsupported features won't load immediately, but instead send out additional +data requests, which can take a while to complete. +The warning *Your search session is still running* is shown. -You can either wait for these additional requests to complete or come back to the dashboard later when all data requests have been finished. +You can either wait for these additional requests to complete or come back to the dashboard later +when all data requests have finished. A panel on a dashboard can behave like this if one of the following features is used: -* *Lens* - A *top values* dimension with an enabled setting *Group other values as "Other"* (configurable in the *Advanced* section of the dimension) -* *Lens* - An *intervals* dimension is used -* *Aggregation based* visualizations - A *terms* aggregation is used with an enabled setting *Group other values in separate bucket* -* *Aggregation based* visualizations - A *histogram* aggregation is used -* *Maps* - Layers using joins, blended layers or tracks layers are used + +**Lens** + +* A *top values* dimension with an enabled *Group other values as "Other"* setting. +This is configurable in the *Advanced* section of the dimension. +* An *intervals* dimension. + +**Aggregation based** visualizations + +* A *terms* aggregation with an enabled *Group other values in separate bucket* setting. +* A *histogram* aggregation. + +**Maps** + +* Layers using joins, blended layers, or tracks layers. From 0809d5d15fab7a3013b89f4e8cdb571385aa1ee6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Fri, 9 Jul 2021 18:53:20 +0200 Subject: [PATCH 14/36] [ML] Add integration tests for `trained_models` API (#104819) * [ML] api integration tests for get trained models endpoint * [ML] delete ingest pipelines after tests execution * [ML] deleteIngestPipeline method * [ML] test for unauthorized user * [ML] tests for model stats * [ML] delete trained model tests * [ML] fix typo * [ML] fix expect package path * [ML] get model pipelines tests * [ML] test for aliases * [ML] add tests for a 404 response * [ML] fix typo * [ML] fix typo --- x-pack/test/api_integration/apis/ml/index.ts | 1 + .../apis/ml/trained_models/delete_model.ts | 67 ++++++++++++++ .../ml/trained_models/get_model_pipelines.ts | 51 ++++++++++ .../apis/ml/trained_models/get_model_stats.ts | 54 +++++++++++ .../apis/ml/trained_models/get_models.ts | 88 ++++++++++++++++++ .../apis/ml/trained_models/index.ts | 17 ++++ x-pack/test/functional/services/ml/api.ts | 92 +++++++++++++++++++ .../functional/services/ml/trained_models.ts | 36 +------- 8 files changed, 371 insertions(+), 35 deletions(-) create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/get_models.ts create mode 100644 x-pack/test/api_integration/apis/ml/trained_models/index.ts diff --git a/x-pack/test/api_integration/apis/ml/index.ts b/x-pack/test/api_integration/apis/ml/index.ts index 7154debc3e195..394672ac07fc5 100644 --- a/x-pack/test/api_integration/apis/ml/index.ts +++ b/x-pack/test/api_integration/apis/ml/index.ts @@ -82,5 +82,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./results')); loadTestFile(require.resolve('./saved_objects')); loadTestFile(require.resolve('./system')); + loadTestFile(require.resolve('./trained_models')); }); } diff --git a/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts new file mode 100644 index 0000000000000..3848330a95fb9 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/delete_model.ts @@ -0,0 +1,67 @@ +/* + * 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'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('DELETE trained_models', () => { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.api.createdTestTrainedModels('regression', 2); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('deletes trained model by id', async () => { + const { body: deleteResponseBody } = await supertest + .delete(`/api/ml/trained_models/dfa_regression_model_n_0`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(deleteResponseBody).to.eql({ acknowledged: true }); + + // verify that model is actually deleted + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('returns 404 if requested trained model does not exist', async () => { + await supertest + .delete(`/api/ml/trained_models/not_existing_model`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('does not allow to delete trained model if the user does not have required permissions', async () => { + await supertest + .delete(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + + // verify that model has not been deleted + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts new file mode 100644 index 0000000000000..cc347056f02a3 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_pipelines.ts @@ -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'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET trained_models/pipelines', () => { + let testModelIds: string[] = []; + + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + testModelIds = await ml.api.createdTestTrainedModels('regression', 2, true); + }); + + after(async () => { + // delete all created ingest pipelines + await Promise.all(testModelIds.map((modelId) => ml.api.deleteIngestPipeline(modelId))); + await ml.api.cleanMlIndices(); + }); + + it('returns trained model pipelines by id', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/pipelines`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.length).to.eql(1); + expect(body[0].model_id).to.eql('dfa_regression_model_n_0'); + expect(Object.keys(body[0].pipelines).length).to.eql(1); + }); + + it('returns an error in case user does not have required permission', async () => { + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/pipelines`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts new file mode 100644 index 0000000000000..76f108836996f --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_model_stats.ts @@ -0,0 +1,54 @@ +/* + * 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'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET trained_models/_stats', () => { + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + await ml.api.createdTestTrainedModels('regression', 2); + }); + + after(async () => { + await ml.api.cleanMlIndices(); + }); + + it('returns trained model stats by id', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/_stats`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + + expect(body.count).to.eql(1); + expect(body.trained_model_stats[0].model_id).to.eql('dfa_regression_model_n_0'); + }); + + it('returns 404 if requested trained model does not exist', async () => { + await supertest + .get(`/api/ml/trained_models/not_existing_model/_stats`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('returns an error for unauthorized user', async () => { + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_0/_stats`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts new file mode 100644 index 0000000000000..604dff6a98a9a --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/get_models.ts @@ -0,0 +1,88 @@ +/* + * 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'; +import { FtrProviderContext } from '../../../ftr_provider_context'; +import { USER } from '../../../../functional/services/ml/security_common'; +import { COMMON_REQUEST_HEADERS } from '../../../../functional/services/ml/common_api'; + +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertestWithoutAuth'); + const ml = getService('ml'); + + describe('GET trained_models', () => { + let testModelIds: string[] = []; + + before(async () => { + await ml.testResources.setKibanaTimeZoneToUTC(); + testModelIds = await ml.api.createdTestTrainedModels('regression', 5, true); + await ml.api.createModelAlias('dfa_regression_model_n_0', 'dfa_regression_model_alias'); + await ml.api.createIngestPipeline('dfa_regression_model_alias'); + }); + + after(async () => { + // delete created ingest pipelines + await Promise.all( + ['dfa_regression_model_alias', ...testModelIds].map((modelId) => + ml.api.deleteIngestPipeline(modelId) + ) + ); + await ml.api.cleanMlIndices(); + }); + + it('returns all trained models with associated pipelines including aliases', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models?with_pipelines=true`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + // Created models + system model + expect(body.length).to.eql(6); + + const sampleModel = body.find((v: any) => v.model_id === 'dfa_regression_model_n_0'); + expect(Object.keys(sampleModel.pipelines).length).to.eql(2); + }); + + it('returns models without pipeline in case user does not have required permission', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models?with_pipelines=true`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + // Created models + system model + expect(body.length).to.eql(6); + const sampleModel = body.find((v: any) => v.model_id === 'dfa_regression_model_n_0'); + expect(sampleModel.pipelines).to.eql(undefined); + }); + + it('returns trained model by id', async () => { + const { body } = await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER)) + .set(COMMON_REQUEST_HEADERS) + .expect(200); + expect(body.length).to.eql(1); + expect(body[0].model_id).to.eql('dfa_regression_model_n_1'); + }); + + it('returns 404 if requested trained model does not exist', async () => { + await supertest + .get(`/api/ml/trained_models/not_existing_model`) + .auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER)) + .set(COMMON_REQUEST_HEADERS) + .expect(404); + }); + + it('returns an error for unauthorized user', async () => { + await supertest + .get(`/api/ml/trained_models/dfa_regression_model_n_1`) + .auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED)) + .set(COMMON_REQUEST_HEADERS) + .expect(403); + }); + }); +}; diff --git a/x-pack/test/api_integration/apis/ml/trained_models/index.ts b/x-pack/test/api_integration/apis/ml/trained_models/index.ts new file mode 100644 index 0000000000000..d1812dc188b00 --- /dev/null +++ b/x-pack/test/api_integration/apis/ml/trained_models/index.ts @@ -0,0 +1,17 @@ +/* + * 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 { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('trained models', function () { + loadTestFile(require.resolve('./get_models')); + loadTestFile(require.resolve('./get_model_stats')); + loadTestFile(require.resolve('./get_model_pipelines')); + loadTestFile(require.resolve('./delete_model')); + }); +} diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index 728e3ff8fc8e6..ec5ca4c661157 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -8,6 +8,8 @@ import { estypes } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; import { ProvidedType } from '@kbn/test'; +import fs from 'fs'; +import path from 'path'; import { Calendar } from '../../../../plugins/ml/server/models/calendar/index'; import { Annotation } from '../../../../plugins/ml/common/types/annotations'; import { DataFrameAnalyticsConfig } from '../../../../plugins/ml/public/application/data_frame_analytics/common'; @@ -25,6 +27,8 @@ import { import { COMMON_REQUEST_HEADERS } from '../../../functional/services/ml/common_api'; import { PutTrainedModelConfig } from '../../../../plugins/ml/common/types/trained_models'; +type ModelType = 'regression' | 'classification'; + export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const es = getService('es'); const log = getService('log'); @@ -943,5 +947,93 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { log.debug('> Trained model crated'); return model; }, + + async createdTestTrainedModels( + modelType: ModelType, + count: number = 10, + withIngestPipelines = false + ) { + const compressedDefinition = this.getCompressedModelDefinition(modelType); + + const modelIds = new Array(count).fill(null).map((v, i) => `dfa_${modelType}_model_n_${i}`); + + const models = modelIds.map((id) => { + return { + model_id: id, + body: { + compressed_definition: compressedDefinition, + inference_config: { + [modelType]: {}, + }, + input: { + field_names: ['common_field'], + }, + } as PutTrainedModelConfig, + }; + }); + + for (const model of models) { + await this.createTrainedModel(model.model_id, model.body); + if (withIngestPipelines) { + await this.createIngestPipeline(model.model_id); + } + } + + return modelIds; + }, + + /** + * Retrieves compressed model definition from the test resources. + * @param modelType + */ + getCompressedModelDefinition(modelType: ModelType) { + return fs.readFileSync( + path.resolve( + __dirname, + 'resources', + 'trained_model_definitions', + `minimum_valid_config_${modelType}.json.gz.b64` + ), + 'utf-8' + ); + }, + + async createModelAlias(modelId: string, modelAlias: string) { + log.debug(`Creating alias for model "${modelId}"`); + await esSupertest + .put(`/_ml/trained_models/${modelId}/model_aliases/${modelAlias}`) + .expect(200); + log.debug('> Model alias created'); + }, + + /** + * Creates ingest pipelines for trained model + * @param modelId + */ + async createIngestPipeline(modelId: string) { + log.debug(`Creating ingest pipeline for trained model with id "${modelId}"`); + const ingestPipeline = await esSupertest + .put(`/_ingest/pipeline/pipeline_${modelId}`) + .send({ + processors: [ + { + inference: { + model_id: modelId, + }, + }, + ], + }) + .expect(200) + .then((res) => res.body); + + log.debug('> Ingest pipeline crated'); + return ingestPipeline; + }, + + async deleteIngestPipeline(modelId: string) { + log.debug(`Deleting ingest pipeline for trained model with id "${modelId}"`); + await esSupertest.delete(`/_ingest/pipeline/pipeline_${modelId}`).expect(200); + log.debug('> Ingest pipeline deleted'); + }, }; } diff --git a/x-pack/test/functional/services/ml/trained_models.ts b/x-pack/test/functional/services/ml/trained_models.ts index ae799efbbd30c..7a1fa1714ca14 100644 --- a/x-pack/test/functional/services/ml/trained_models.ts +++ b/x-pack/test/functional/services/ml/trained_models.ts @@ -5,12 +5,9 @@ * 2.0. */ -import fs from 'fs'; -import path from 'path'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { MlApi } from './api'; -import { PutTrainedModelConfig } from '../../../../plugins/ml/common/types/trained_models'; import { MlCommonUI } from './common_ui'; type ModelType = 'regression' | 'classification'; @@ -24,38 +21,7 @@ export function TrainedModelsProvider( return { async createdTestTrainedModels(modelType: ModelType, count: number = 10) { - const compressedDefinition = this.getCompressedModelDefinition(modelType); - - const models = new Array(count).fill(null).map((v, i) => { - return { - model_id: `dfa_${modelType}_model_n_${i}`, - body: { - compressed_definition: compressedDefinition, - inference_config: { - [modelType]: {}, - }, - input: { - field_names: ['common_field'], - }, - } as PutTrainedModelConfig, - }; - }); - - for (const model of models) { - await mlApi.createTrainedModel(model.model_id, model.body); - } - }, - - getCompressedModelDefinition(modelType: ModelType) { - return fs.readFileSync( - path.resolve( - __dirname, - 'resources', - 'trained_model_definitions', - `minimum_valid_config_${modelType}.json.gz.b64` - ), - 'utf-8' - ); + await mlApi.createdTestTrainedModels(modelType, count); }, async assertStats(expectedTotalCount: number) { From 95008cdb61b16090166115be8e6e906b4bd47242 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Fri, 9 Jul 2021 13:04:52 -0400 Subject: [PATCH 15/36] [Fleet] Fix add host url validation in fleet server setup (#105072) --- .../components/fleet_server_on_prem_instructions.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx index 48ff51f1a25e8..0fc3821d2e3f7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_requirements_page/components/fleet_server_on_prem_instructions.tsx @@ -452,6 +452,8 @@ export const AddFleetServerHostStepContent = ({ await addFleetServerHost(fleetServerHost); setCalloutHost(fleetServerHost); setFleetServerHost(''); + } else { + setCalloutHost(''); } } finally { setIsLoading(false); From 5b207d8484cee16375068791eb4e81b914d9022e Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Fri, 9 Jul 2021 10:18:25 -0700 Subject: [PATCH 16/36] [Reporting] Add `handleSIGHUP: false` to puppeteer LaunchOptions (#104992) --- .../browsers/chromium/driver_factory/index.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index 1141437eae0ef..eb2abf4036c03 100644 --- a/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -26,6 +26,18 @@ import { HeadlessChromiumDriver } from '../driver'; import { args } from './args'; import { Metrics, getMetrics } from './metrics'; +// Puppeteer type definitions do not match the documentation. +// See https://pptr.dev/#?product=Puppeteer&version=v8.0.0&show=api-puppeteerlaunchoptions +interface ReportingLaunchOptions extends puppeteer.LaunchOptions { + userDataDir?: string; + ignoreHTTPSErrors?: boolean; + args?: string[]; +} + +declare module 'puppeteer' { + function launch(options: ReportingLaunchOptions): Promise; +} + type BrowserConfig = CaptureConfig['browser']['chromium']; type ViewportConfig = CaptureConfig['viewport']; @@ -85,11 +97,12 @@ export class HeadlessChromiumDriverFactory { userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, + handleSIGHUP: false, args: chromiumArgs, env: { TZ: browserTimezone, }, - } as puppeteer.LaunchOptions); + }); page = await browser.newPage(); devTools = await page.target().createCDPSession(); From d2ce8d52235248b03e69531c23a72f376e082263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Fri, 9 Jul 2021 13:46:52 -0400 Subject: [PATCH 17/36] [APM] Make fleet plugin dependency optional (#104967) * fixing tutorial when fleet plugin is disabled * addressing PR comments --- x-pack/plugins/apm/kibana.json | 11 +- x-pack/plugins/apm/public/plugin.ts | 33 +- .../config_agent/config_agent.stories.tsx | 7 + .../config_agent/get_policy_options.test.ts | 8 + .../tutorial/config_agent/index.test.tsx | 346 ++++++++++++++---- .../public/tutorial/config_agent/index.tsx | 51 ++- .../tutorial/config_agent/policy_selector.tsx | 10 +- .../tutorial_fleet_instructions/index.tsx | 1 + x-pack/plugins/apm/server/plugin.ts | 33 +- x-pack/plugins/apm/server/routes/fleet.ts | 10 +- .../apm/server/tutorial/envs/on_prem.ts | 19 +- x-pack/plugins/apm/server/tutorial/index.ts | 4 +- 12 files changed, 384 insertions(+), 149 deletions(-) diff --git a/x-pack/plugins/apm/kibana.json b/x-pack/plugins/apm/kibana.json index ae4510b10acd4..9f661f13a491e 100644 --- a/x-pack/plugins/apm/kibana.json +++ b/x-pack/plugins/apm/kibana.json @@ -7,7 +7,6 @@ "data", "embeddable", "features", - "fleet", "infra", "licensing", "observability", @@ -24,11 +23,15 @@ "security", "spaces", "taskManager", - "usageCollection" + "usageCollection", + "fleet" ], "server": true, "ui": true, - "configPath": ["xpack", "apm"], + "configPath": [ + "xpack", + "apm" + ], "requiredBundles": [ "fleet", "home", @@ -38,4 +41,4 @@ "ml", "observability" ] -} +} \ No newline at end of file diff --git a/x-pack/plugins/apm/public/plugin.ts b/x-pack/plugins/apm/public/plugin.ts index 0cd5009570613..91b045b8db46f 100644 --- a/x-pack/plugins/apm/public/plugin.ts +++ b/x-pack/plugins/apm/public/plugin.ts @@ -74,7 +74,7 @@ export interface ApmPluginStartDeps { ml?: MlPluginStart; triggersActionsUi: TriggersAndActionsUIPublicPluginStart; observability: ObservabilityPublicStart; - fleet: FleetStart; + fleet?: FleetStart; } export class ApmPlugin implements Plugin { @@ -311,20 +311,21 @@ export class ApmPlugin implements Plugin { } public start(core: CoreStart, plugins: ApmPluginStartDeps) { const { fleet } = plugins; - - const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData(); - - fleet.registerExtension({ - package: 'apm', - view: 'agent-enrollment-flyout', - title: agentEnrollmentExtensionData.title, - Component: agentEnrollmentExtensionData.Component, - }); - - fleet.registerExtension({ - package: 'apm', - view: 'package-detail-assets', - Component: LazyApmCustomAssetsExtension, - }); + if (fleet) { + const agentEnrollmentExtensionData = getApmEnrollmentFlyoutData(); + + fleet.registerExtension({ + package: 'apm', + view: 'agent-enrollment-flyout', + title: agentEnrollmentExtensionData.title, + Component: agentEnrollmentExtensionData.Component, + }); + + fleet.registerExtension({ + package: 'apm', + view: 'package-detail-assets', + Component: LazyApmCustomAssetsExtension, + }); + } } } diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx index 33f171ab88247..0d4d3748422ea 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx @@ -18,6 +18,7 @@ interface Args { onPrem: boolean; hasFleetPoliciesWithApmIntegration: boolean; hasCloudPolicyWithApmIntegration: boolean; + isFleetEnabled: boolean; } const policyElasticAgentOnCloudAgent: APIResponseType['fleetAgents'][0] = { @@ -47,6 +48,7 @@ function Wrapper({ apmAgent, onPrem, hasCloudPolicyWithApmIntegration, + isFleetEnabled, }: Args) { const http = ({ get: () => ({ @@ -56,6 +58,7 @@ function Wrapper({ ? [policyElasticAgentOnCloudAgent] : []), ], + isFleetEnabled, cloudStandaloneSetup: { apmServerUrl: 'cloud_url', secretToken: 'foo', @@ -80,6 +83,7 @@ Integration.args = { onPrem: true, hasFleetPoliciesWithApmIntegration: false, hasCloudPolicyWithApmIntegration: false, + isFleetEnabled: true, }; export default { @@ -113,5 +117,8 @@ export default { hasCloudPolicyWithApmIntegration: { control: { type: 'boolean', options: [true, false] }, }, + isFleetEnabled: { + control: { type: 'boolean', options: [true, false], defaultValue: true }, + }, }, }; diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts index 90c9aab80f6f5..c6dc7265f3d3e 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts +++ b/x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts @@ -41,6 +41,7 @@ describe('getPolicyOptions', () => { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -65,6 +66,7 @@ describe('getPolicyOptions', () => { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -109,6 +111,7 @@ describe('getPolicyOptions', () => { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -151,6 +154,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents: [], cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -173,6 +177,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents, cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -213,6 +218,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents: [policyElasticAgentOnCloudAgent, ...fleetAgents], cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: true, @@ -256,6 +262,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents: [], cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: false, @@ -278,6 +285,7 @@ describe('getPolicyOptions', () => { const data: APIResponseType = { fleetAgents, cloudStandaloneSetup: undefined, + isFleetEnabled: true, }; const options = getPolicyOptions({ isCloudEnabled: false, diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx index 8f8afe58506a6..cb49cee108bd1 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx @@ -7,6 +7,10 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { HttpStart } from 'kibana/public'; import React from 'react'; +import { + expectTextsInDocument, + expectTextsNotInDocument, +} from '../../utils/testHelpers'; import TutorialConfigAgent from './'; const policyElasticAgentOnCloudAgent = { @@ -32,68 +36,32 @@ const fleetAgents = [ ]; describe('TutorialConfigAgent', () => { - it('renders loading component while API is being called', () => { - const component = render( - - ); - expect(component.getByTestId('loading')).toBeInTheDocument(); + beforeAll(() => { + // Mocks console.error so it won't polute tests output when testing the api throwing error + jest.spyOn(console, 'error').mockImplementation(() => null); }); - it('updates commands when a different policy is selected', async () => { - const component = render( - - ); - expect( - await screen.findByText('Default Standalone configuration') - ).toBeInTheDocument(); - let commands = component.getByTestId('commands').innerHTML; - expect(commands).not.toEqual(''); - expect(commands).toMatchInlineSnapshot(` - "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ - -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=http://localhost:8200 \\\\ - -Delastic.apm.secret_token= \\\\ - -Delastic.apm.environment=production \\\\ - -Delastic.apm.application_packages=org.example \\\\ - -jar my-application.jar" - `); - fireEvent.click(component.getByTestId('comboBoxToggleListButton')); - fireEvent.click(component.getByText('agent foo')); - commands = component.getByTestId('commands').innerHTML; - expect(commands).not.toEqual(''); - expect(commands).toMatchInlineSnapshot(` - "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ - -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=foo \\\\ - -Delastic.apm.secret_token=foo \\\\ - -Delastic.apm.environment=production \\\\ - -Delastic.apm.application_packages=org.example \\\\ - -jar my-application.jar" - `); + afterAll(() => { + jest.restoreAllMocks(); }); - describe('running on prem', () => { - it('selects defaul standalone by defauls', async () => { + + describe('when fleet plugin is enabled', () => { + it('renders loading component while API is being called', () => { + const component = render( + + ); + expect(component.getByTestId('loading')).toBeInTheDocument(); + }); + it('updates commands when a different policy is selected', async () => { const component = render( { get: jest.fn().mockReturnValue({ cloudStandaloneSetup: undefined, fleetAgents, + isFleetEnabled: true, }), } as unknown) as HttpStart } @@ -112,10 +81,7 @@ describe('TutorialConfigAgent', () => { expect( await screen.findByText('Default Standalone configuration') ).toBeInTheDocument(); - expect( - component.getByTestId('policySelector_onPrem') - ).toBeInTheDocument(); - const commands = component.getByTestId('commands').innerHTML; + let commands = component.getByTestId('commands').innerHTML; expect(commands).not.toEqual(''); expect(commands).toMatchInlineSnapshot(` "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ @@ -126,21 +92,238 @@ describe('TutorialConfigAgent', () => { -Delastic.apm.application_packages=org.example \\\\ -jar my-application.jar" `); + + fireEvent.click(component.getByTestId('comboBoxToggleListButton')); + fireEvent.click(component.getByText('agent foo')); + commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=foo \\\\ + -Delastic.apm.secret_token=foo \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + describe('running on prem', () => { + it('selects defaul standalone by defauls', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + it('shows get started with fleet link when there are no fleet agents', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + expectTextsInDocument(component, ['Get started with fleet']); + }); + }); + describe('running on cloud', () => { + it('selects defaul standalone by defauls', async () => { + const component = render( + + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_cloud') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=cloud_url \\\\ + -Delastic.apm.secret_token=cloud_token \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + it('selects policy elastic agent on cloud when available by default', async () => { + const component = render( + + ); + expect( + await screen.findByText('Elastic Cloud agent policy') + ).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_policy-elastic-agent-on-cloud') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=apm_cloud_url \\\\ + -Delastic.apm.secret_token=apm_cloud_token \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); + + it('shows default standalone option when api throws an error', async () => { + const component = render( + { + throw new Error('Boom'); + }, + } as unknown) as HttpStart + } + basePath="http://localhost:5601" + isCloudEnabled + /> + ); + expect( + await screen.findByText('Default Standalone configuration') + ).toBeInTheDocument(); + const commands = component.getByTestId('commands').innerHTML; + expect(commands).not.toEqual(''); + expect(commands).toMatchInlineSnapshot(` + "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ + -Delastic.apm.service_name=my-application \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ + -Delastic.apm.environment=production \\\\ + -Delastic.apm.application_packages=org.example \\\\ + -jar my-application.jar" + `); + }); }); }); - describe('running on cloud', () => { - it('selects defaul standalone by defauls', async () => { + describe('when fleet plugin is disabled', () => { + it('hides fleet links', async () => { const component = render( + ); + + expectTextsNotInDocument(component, [ + 'Get started with fleet', + 'Manage fleet policies', + ]); + }); + it('shows default standalone on prem', async () => { + const component = render( + { expect( await screen.findByText('Default Standalone configuration') ).toBeInTheDocument(); - expect(component.getByTestId('policySelector_cloud')).toBeInTheDocument(); + expect( + component.getByTestId('policySelector_onPrem') + ).toBeInTheDocument(); const commands = component.getByTestId('commands').innerHTML; expect(commands).not.toEqual(''); expect(commands).toMatchInlineSnapshot(` "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=cloud_url \\\\ - -Delastic.apm.secret_token=cloud_token \\\\ + -Delastic.apm.server_urls=http://localhost:8200 \\\\ + -Delastic.apm.secret_token= \\\\ -Delastic.apm.environment=production \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-application.jar" `); }); - it('selects policy elastic agent on cloud when available by default', async () => { + it('shows default standalone on cloud', async () => { const component = render( { apmServerUrl: 'cloud_url', secretToken: 'cloud_token', }, - fleetAgents: [...fleetAgents, policyElasticAgentOnCloudAgent], + fleetAgents: [], + isFleetEnabled: false, }), } as unknown) as HttpStart } @@ -184,18 +370,16 @@ describe('TutorialConfigAgent', () => { /> ); expect( - await screen.findByText('Elastic Cloud agent policy') - ).toBeInTheDocument(); - expect( - component.getByTestId('policySelector_policy-elastic-agent-on-cloud') + await screen.findByText('Default Standalone configuration') ).toBeInTheDocument(); + expect(component.getByTestId('policySelector_cloud')).toBeInTheDocument(); const commands = component.getByTestId('commands').innerHTML; expect(commands).not.toEqual(''); expect(commands).toMatchInlineSnapshot(` "java -javaagent:/path/to/elastic-apm-agent-<version>.jar \\\\ -Delastic.apm.service_name=my-application \\\\ - -Delastic.apm.server_urls=apm_cloud_url \\\\ - -Delastic.apm.secret_token=apm_cloud_token \\\\ + -Delastic.apm.server_urls=cloud_url \\\\ + -Delastic.apm.secret_token=cloud_token \\\\ -Delastic.apm.environment=production \\\\ -Delastic.apm.application_packages=org.example \\\\ -jar my-application.jar" diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx index 755c3eca55868..d38d51f01c67b 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/index.tsx @@ -46,16 +46,43 @@ interface Props { isCloudEnabled: boolean; } +const INITIAL_STATE = { + fleetAgents: [], + cloudStandaloneSetup: undefined, + isFleetEnabled: false, +}; + +function getFleetLink({ + isFleetEnabled, + hasFleetAgents, + basePath, +}: { + isFleetEnabled: boolean; + hasFleetAgents: boolean; + basePath: string; +}) { + if (!isFleetEnabled) { + return; + } + + return hasFleetAgents + ? { + label: MANAGE_FLEET_POLICIES_LABEL, + href: `${basePath}/app/fleet#/policies`, + } + : { + label: GET_STARTED_WITH_FLEET_LABEL, + href: `${basePath}/app/integrations#/detail/apm-0.3.0/overview`, + }; +} + function TutorialConfigAgent({ variantId, http, basePath, isCloudEnabled, }: Props) { - const [data, setData] = useState({ - fleetAgents: [], - cloudStandaloneSetup: undefined, - }); + const [data, setData] = useState(INITIAL_STATE); const [isLoading, setIsLoading] = useState(true); const [selectedOption, setSelectedOption] = useState(); @@ -68,6 +95,7 @@ function TutorialConfigAgent({ setData(response as APIResponseType); } } catch (e) { + setIsLoading(false); console.error('Error while fetching fleet agents.', e); } } @@ -105,15 +133,6 @@ function TutorialConfigAgent({ }); const hasFleetAgents = !!data.fleetAgents.length; - const fleetLink = hasFleetAgents - ? { - label: MANAGE_FLEET_POLICIES_LABEL, - href: `${basePath}/app/fleet#/policies`, - } - : { - label: GET_STARTED_WITH_FLEET_LABEL, - href: `${basePath}/app/integrations#/detail/apm-0.3.0/overview`, - }; return ( <> @@ -125,7 +144,11 @@ function TutorialConfigAgent({ onChange={(newSelectedOption) => setSelectedOption(newSelectedOption) } - fleetLink={fleetLink} + fleetLink={getFleetLink({ + isFleetEnabled: data.isFleetEnabled, + hasFleetAgents, + basePath, + })} /> diff --git a/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx b/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx index 3a0c6d70db82b..25ce7042c4c97 100644 --- a/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx +++ b/x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx @@ -21,7 +21,7 @@ interface Props { options: PolicyOption[]; selectedOption?: PolicyOption; onChange: (selectedOption?: PolicyOption) => void; - fleetLink: { + fleetLink?: { label: string; href: string; }; @@ -58,9 +58,11 @@ export function PolicySelector({ { defaultMessage: 'Choose policy' } )} labelAppend={ - - {fleetLink.label} - + fleetLink && ( + + {fleetLink.label} + + ) } helpText={i18n.translate( 'xpack.apm.tutorial.agent_config.choosePolicy.helper', diff --git a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx index 8a81b7a994e76..6fcf13345538f 100644 --- a/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx +++ b/x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/index.tsx @@ -42,6 +42,7 @@ function TutorialFleetInstructions({ http, basePath, isDarkTheme }: Props) { const response = await http.get('/api/apm/fleet/has_data'); setData(response as APIResponseType); } catch (e) { + setIsLoading(false); console.error('Error while fetching fleet details.', e); } setIsLoading(false); diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index f260971c3bdcb..3a7eb738dd3b2 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -15,7 +15,7 @@ import { Plugin, PluginInitializerContext, } from 'src/core/server'; -import { mapValues, once } from 'lodash'; +import { isEmpty, mapValues, once } from 'lodash'; import { TECHNICAL_COMPONENT_TEMPLATE_NAME } from '../../rule_registry/common/assets'; import { mappingFromFieldMap } from '../../rule_registry/common/mapping_from_field_map'; import { APMConfig, APMXPackConfig, APM_SERVER_FEATURE_ID } from '.'; @@ -104,21 +104,6 @@ export class APMPlugin }); } - plugins.home?.tutorials.registerTutorial( - tutorialProvider({ - isEnabled: this.currentConfig['xpack.apm.ui.enabled'], - indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], - cloud: plugins.cloud, - indices: { - errorIndices: this.currentConfig['apm_oss.errorIndices'], - metricsIndices: this.currentConfig['apm_oss.metricsIndices'], - onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], - sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], - transactionIndices: this.currentConfig['apm_oss.transactionIndices'], - }, - }) - ); - plugins.features.registerKibanaFeature(APM_FEATURE); registerFeaturesUsage({ licensingPlugin: plugins.licensing }); @@ -206,6 +191,22 @@ export class APMPlugin }; }) as APMRouteHandlerResources['plugins']; + plugins.home?.tutorials.registerTutorial( + tutorialProvider({ + isEnabled: this.currentConfig['xpack.apm.ui.enabled'], + indexPatternTitle: this.currentConfig['apm_oss.indexPattern'], + cloud: plugins.cloud, + isFleetPluginEnabled: !isEmpty(resourcePlugins.fleet), + indices: { + errorIndices: this.currentConfig['apm_oss.errorIndices'], + metricsIndices: this.currentConfig['apm_oss.metricsIndices'], + onboardingIndices: this.currentConfig['apm_oss.onboardingIndices'], + sourcemapIndices: this.currentConfig['apm_oss.sourcemapIndices'], + transactionIndices: this.currentConfig['apm_oss.transactionIndices'], + }, + }) + ); + const telemetryUsageCounter = resourcePlugins.usageCollection?.setup.createUsageCounter( APM_SERVER_FEATURE_ID ); diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index b83bfd54b93cd..6628d29b256f7 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -32,7 +32,7 @@ const hasFleetDataRoute = createApmServerRoute({ handler: async ({ core, plugins }) => { const fleetPluginStart = await plugins.fleet?.start(); if (!fleetPluginStart) { - throw Boom.internal(FLEET_REQUIRED_MESSAGE); + return { hasData: false }; } const packagePolicies = await getApmPackgePolicies({ core, @@ -56,7 +56,7 @@ const fleetAgentsRoute = createApmServerRoute({ const fleetPluginStart = await plugins.fleet?.start(); if (!fleetPluginStart) { - throw Boom.internal(FLEET_REQUIRED_MESSAGE); + return { cloudStandaloneSetup, fleetAgents: [], isFleetEnabled: false }; } // fetches package policies that contains APM integrations const packagePolicies = await getApmPackgePolicies({ @@ -75,6 +75,7 @@ const fleetAgentsRoute = createApmServerRoute({ return { cloudStandaloneSetup, + isFleetEnabled: true, fleetAgents: fleetAgents.map((agent) => { const packagePolicy = policiesGroupedById[agent.id]; const packagePolicyVars = packagePolicy.inputs[0]?.vars; @@ -190,11 +191,6 @@ export const apmFleetRouteRepository = createApmServerRouteRepository() .add(getMigrationCheckRoute) .add(createCloudApmPackagePolicyRoute); -const FLEET_REQUIRED_MESSAGE = i18n.translate( - 'xpack.apm.fleet_has_data.fleetRequired', - { defaultMessage: `Fleet plugin is required` } -); - const FLEET_SECURITY_REQUIRED_MESSAGE = i18n.translate( 'xpack.apm.api.fleet.fleetSecurityRequired', { defaultMessage: `Fleet and Security plugins are required` } 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 882d45c4c21db..400da79e3d2d0 100644 --- a/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts +++ b/x-pack/plugins/apm/server/tutorial/envs/on_prem.ts @@ -38,12 +38,14 @@ export function onPremInstructions({ metricsIndices, sourcemapIndices, onboardingIndices, + isFleetPluginEnabled, }: { errorIndices: string; transactionIndices: string; metricsIndices: string; sourcemapIndices: string; onboardingIndices: string; + isFleetPluginEnabled: boolean; }): InstructionsSchema { const EDIT_CONFIG = createEditConfig(); const START_SERVER_UNIX = createStartServerUnix(); @@ -69,12 +71,17 @@ export function onPremInstructions({ iconType: 'alert', }, instructionVariants: [ - { - id: INSTRUCTION_VARIANT.FLEET, - instructions: [ - { customComponentName: 'TutorialFleetInstructions' }, - ], - }, + // hides fleet section when plugin is disabled + ...(isFleetPluginEnabled + ? [ + { + id: INSTRUCTION_VARIANT.FLEET, + instructions: [ + { customComponentName: 'TutorialFleetInstructions' }, + ], + }, + ] + : []), { id: INSTRUCTION_VARIANT.OSX, instructions: [ diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index 9118c30b845d0..edf056a6d1be4 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -28,6 +28,7 @@ export const tutorialProvider = ({ indexPatternTitle, indices, cloud, + isFleetPluginEnabled, }: { isEnabled: boolean; indexPatternTitle: string; @@ -39,6 +40,7 @@ export const tutorialProvider = ({ sourcemapIndices: string; onboardingIndices: string; }; + isFleetPluginEnabled: boolean; }) => () => { const savedObjects = [ { @@ -104,7 +106,7 @@ It allows you to monitor the performance of thousands of applications in real ti euiIconType: 'apmApp', artifacts, customStatusCheckName: 'apm_fleet_server_status_check', - onPrem: onPremInstructions(indices), + onPrem: onPremInstructions({ ...indices, isFleetPluginEnabled }), elasticCloud: createElasticCloudInstructions(cloud), previewImagePath: '/plugins/apm/assets/apm.png', savedObjects, From fa03028688aa145bb1483ed5be812e3d7a139353 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Fri, 9 Jul 2021 13:42:50 -0500 Subject: [PATCH 18/36] Enable CSS-in-JS styling with `emotion` (#98157) * emotion deps * kbn-babel * kbn-test * examples * babel-plugin-styled-components config * css prop type fixes * type context * declaration location * some emotion types resolved * clean up * emotion v10 accomodations * types * kbn-crypto * kbn-telemetry-tools * bazel * eslint rule; shared file regex array * update paths * Update packages/kbn-eslint-plugin-eslint/rules/module_migration.js Co-authored-by: Spencer * remove placeholder styles * doc api changes * snapshot updates * storybook comments * use constant * bump new deps * condense versions Co-authored-by: Spencer --- package.json | 3 + .../elastic-eslint-config-kibana/.eslintrc.js | 10 +- packages/kbn-babel-preset/BUILD.bazel | 1 + packages/kbn-babel-preset/webpack_preset.js | 34 +++- packages/kbn-crypto/BUILD.bazel | 3 +- packages/kbn-dev-utils/src/babel.ts | 11 ++ .../rules/module_migration.js | 23 ++- packages/kbn-storybook/lib/default_config.ts | 20 +- packages/kbn-storybook/lib/theme_switcher.tsx | 1 + packages/kbn-telemetry-tools/BUILD.bazel | 3 +- packages/kbn-test/jest-preset.js | 1 + packages/kbn-ui-shared-deps/BUILD.bazel | 1 + packages/kbn-ui-shared-deps/src/entry.js | 1 + packages/kbn-ui-shared-deps/src/index.js | 1 + .../collapsible_nav.test.tsx.snap | 20 +- src/plugins/data/public/public.api.md | 4 +- .../__snapshots__/data_view.test.tsx.snap | 2 - src/plugins/embeddable/public/public.api.md | 5 +- .../tsconfig.json | 3 +- test/tsconfig.json | 2 +- tsconfig.base.json | 8 +- typings/@emotion/index.d.ts | 12 ++ .../report_listing.test.tsx.snap | 27 --- ...screen_capture_panel_content.test.tsx.snap | 12 +- yarn.lock | 187 ++++++++++++++---- 25 files changed, 280 insertions(+), 115 deletions(-) create mode 100644 typings/@emotion/index.d.ts diff --git a/package.json b/package.json index 8f56d80c584ea..22eedde59c5e7 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "@elastic/safer-lodash-set": "link:bazel-bin/packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.6.0", "@elastic/ui-ace": "0.2.3", + "@emotion/react": "^11.4.0", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.1", "@hapi/cookie": "^11.0.2", @@ -454,6 +455,8 @@ "@elastic/eslint-plugin-eui": "0.0.2", "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", + "@emotion/babel-preset-css-prop": "^11.2.0", + "@emotion/jest": "^11.3.0", "@istanbuljs/schema": "^0.1.2", "@jest/reporters": "^26.6.2", "@kbn/babel-code-parser": "link:bazel-bin/packages/kbn-babel-code-parser", diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js index 3220a01184004..d3cf7cf964a60 100644 --- a/packages/elastic-eslint-config-kibana/.eslintrc.js +++ b/packages/elastic-eslint-config-kibana/.eslintrc.js @@ -1,3 +1,5 @@ +const { USES_STYLED_COMPONENTS } = require('@kbn/dev-utils'); + module.exports = { extends: [ './javascript.js', @@ -79,7 +81,13 @@ module.exports = { from: 'react-intl', to: '@kbn/i18n/react', disallowedMessage: `import from @kbn/i18n/react instead` - } + }, + { + from: 'styled-components', + to: false, + exclude: USES_STYLED_COMPONENTS, + disallowedMessage: `Prefer using @emotion/react instead. To use styled-components, ensure you plugin is enabled in @kbn/dev-utils/src/babel.ts.` + }, ], ], }, diff --git a/packages/kbn-babel-preset/BUILD.bazel b/packages/kbn-babel-preset/BUILD.bazel index f5ebc153b9e1a..11eae8bc55ca9 100644 --- a/packages/kbn-babel-preset/BUILD.bazel +++ b/packages/kbn-babel-preset/BUILD.bazel @@ -32,6 +32,7 @@ DEPS = [ "@npm//@babel/preset-env", "@npm//@babel/preset-react", "@npm//@babel/preset-typescript", + "@npm//@emotion/babel-preset-css-prop", "@npm//babel-plugin-add-module-exports", "@npm//babel-plugin-styled-components", "@npm//babel-plugin-transform-react-remove-prop-types", diff --git a/packages/kbn-babel-preset/webpack_preset.js b/packages/kbn-babel-preset/webpack_preset.js index ca7ea40ff0fe1..186ce87478828 100644 --- a/packages/kbn-babel-preset/webpack_preset.js +++ b/packages/kbn-babel-preset/webpack_preset.js @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +const { USES_STYLED_COMPONENTS } = require.resolve('@kbn/dev-utils'); + module.exports = () => { return { presets: [ @@ -21,14 +23,6 @@ module.exports = () => { ], require('./common_preset'), ], - plugins: [ - [ - require.resolve('babel-plugin-styled-components'), - { - fileName: false, - }, - ], - ], env: { production: { plugins: [ @@ -42,5 +36,29 @@ module.exports = () => { ], }, }, + overrides: [ + { + include: USES_STYLED_COMPONENTS, + plugins: [ + [ + require.resolve('babel-plugin-styled-components'), + { + fileName: false, + }, + ], + ], + }, + { + exclude: USES_STYLED_COMPONENTS, + presets: [ + [ + require.resolve('@emotion/babel-preset-css-prop'), + { + labelFormat: '[local]', + }, + ], + ], + }, + ], }; }; diff --git a/packages/kbn-crypto/BUILD.bazel b/packages/kbn-crypto/BUILD.bazel index 20793e27de629..bf1ed3f778975 100644 --- a/packages/kbn-crypto/BUILD.bazel +++ b/packages/kbn-crypto/BUILD.bazel @@ -38,7 +38,8 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/node-forge", "@npm//@types/testing-library__jest-dom", - "@npm//resize-observer-polyfill" + "@npm//resize-observer-polyfill", + "@npm//@emotion/react", ] DEPS = SRC_DEPS + TYPES_DEPS diff --git a/packages/kbn-dev-utils/src/babel.ts b/packages/kbn-dev-utils/src/babel.ts index 9daa7d9fe8d7a..5570055a21d15 100644 --- a/packages/kbn-dev-utils/src/babel.ts +++ b/packages/kbn-dev-utils/src/babel.ts @@ -46,3 +46,14 @@ export async function transformFileWithBabel(file: File) { file.extname = '.js'; transformedFiles.add(file); } + +/** + * Synchronized regex list of files that use `styled-components`. + * Used by `kbn-babel-preset` and `elastic-eslint-config-kibana`. + */ +export const USES_STYLED_COMPONENTS = [ + /packages[\/\\]kbn-ui-shared-deps[\/\\]/, + /src[\/\\]plugins[\/\\](data|kibana_react)[\/\\]/, + /x-pack[\/\\]plugins[\/\\](apm|beats_management|cases|fleet|infra|lists|observability|osquery|security_solution|timelines|uptime)[\/\\]/, + /x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/, +]; diff --git a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js index 87a1bae8eac1a..3175210eccb10 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/module_migration.js +++ b/packages/kbn-eslint-plugin-eslint/rules/module_migration.js @@ -78,6 +78,12 @@ module.exports = { disallowedMessage: { type: 'string', }, + include: { + type: 'array', + }, + exclude: { + type: 'array', + }, }, anyOf: [ { @@ -95,7 +101,22 @@ module.exports = { ], }, create: (context) => { - const mappings = context.options[0]; + const filename = path.relative(KIBANA_ROOT, context.getFilename()); + + const mappings = context.options[0].filter((mapping) => { + // exclude mapping rule if it is explicitly excluded from this file + if (mapping.exclude && mapping.exclude.some((p) => p.test(filename))) { + return false; + } + + // if this mapping rule is only included in specific files, optionally include it + if (mapping.include) { + return mapping.include.some((p) => p.test(filename)); + } + + // include all mapping rules by default + return true; + }); return { ImportDeclaration(node) { diff --git a/packages/kbn-storybook/lib/default_config.ts b/packages/kbn-storybook/lib/default_config.ts index e194c9789daab..989f707b06fed 100644 --- a/packages/kbn-storybook/lib/default_config.ts +++ b/packages/kbn-storybook/lib/default_config.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ +import * as path from 'path'; import { StorybookConfig } from '@storybook/core/types'; +import { REPO_ROOT } from './constants'; +const toPath = (_path: string) => path.join(REPO_ROOT, _path); export const defaultConfig: StorybookConfig = { addons: ['@kbn/storybook/preset', '@storybook/addon-a11y', '@storybook/addon-essentials'], stories: ['../**/*.stories.tsx'], @@ -22,6 +25,21 @@ export const defaultConfig: StorybookConfig = { config.node = { fs: 'empty' }; - return config; + // Remove when @storybook has moved to @emotion v11 + // https://github.com/storybookjs/storybook/issues/13145 + const emotion11CompatibleConfig = { + ...config, + resolve: { + ...config.resolve, + alias: { + ...config.resolve?.alias, + '@emotion/core': toPath('node_modules/@emotion/react'), + '@emotion/styled': toPath('node_modules/@emotion/styled'), + 'emotion-theming': toPath('node_modules/@emotion/react'), + }, + }, + }; + + return emotion11CompatibleConfig; }, }; diff --git a/packages/kbn-storybook/lib/theme_switcher.tsx b/packages/kbn-storybook/lib/theme_switcher.tsx index da62bc7010c4b..24ddec1fdf51c 100644 --- a/packages/kbn-storybook/lib/theme_switcher.tsx +++ b/packages/kbn-storybook/lib/theme_switcher.tsx @@ -54,6 +54,7 @@ export function ThemeSwitcher() { closeOnClick tooltip={({ onHide }) => } > + {/* @ts-ignore Remove when @storybook has moved to @emotion v11 */} diff --git a/packages/kbn-telemetry-tools/BUILD.bazel b/packages/kbn-telemetry-tools/BUILD.bazel index d394b0c93d45f..ef1316cec75a3 100644 --- a/packages/kbn-telemetry-tools/BUILD.bazel +++ b/packages/kbn-telemetry-tools/BUILD.bazel @@ -47,7 +47,8 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/normalize-path", "@npm//@types/testing-library__jest-dom", - "@npm//resize-observer-polyfill" + "@npm//resize-observer-polyfill", + "@npm//@emotion/react", ] DEPS = SRC_DEPS + TYPES_DEPS diff --git a/packages/kbn-test/jest-preset.js b/packages/kbn-test/jest-preset.js index c84fe3f7a55b0..abc5cfa8efaa8 100644 --- a/packages/kbn-test/jest-preset.js +++ b/packages/kbn-test/jest-preset.js @@ -66,6 +66,7 @@ module.exports = { snapshotSerializers: [ '/src/plugins/kibana_react/public/util/test_helpers/react_mount_serializer.ts', '/node_modules/enzyme-to-json/serializer', + '/node_modules/@emotion/jest/serializer', ], // The test environment that will be used for testing diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel index 9096905a2586b..f92049292f373 100644 --- a/packages/kbn-ui-shared-deps/BUILD.bazel +++ b/packages/kbn-ui-shared-deps/BUILD.bazel @@ -40,6 +40,7 @@ SRC_DEPS = [ "@npm//@elastic/charts", "@npm//@elastic/eui", "@npm//@elastic/numeral", + "@npm//@emotion/react", "@npm//abortcontroller-polyfill", "@npm//angular", "@npm//babel-loader", diff --git a/packages/kbn-ui-shared-deps/src/entry.js b/packages/kbn-ui-shared-deps/src/entry.js index 0e91c45ae6392..20e26ca6a2864 100644 --- a/packages/kbn-ui-shared-deps/src/entry.js +++ b/packages/kbn-ui-shared-deps/src/entry.js @@ -18,6 +18,7 @@ export const KbnI18n = require('@kbn/i18n'); export const KbnI18nAngular = require('@kbn/i18n/angular'); export const KbnI18nReact = require('@kbn/i18n/react'); export const Angular = require('angular'); +export const EmotionReact = require('@emotion/react'); export const Moment = require('moment'); export const MomentTimezone = require('moment-timezone/moment-timezone'); export const KbnMonaco = require('@kbn/monaco'); diff --git a/packages/kbn-ui-shared-deps/src/index.js b/packages/kbn-ui-shared-deps/src/index.js index 36c2e6b02879e..291c7c471d27c 100644 --- a/packages/kbn-ui-shared-deps/src/index.js +++ b/packages/kbn-ui-shared-deps/src/index.js @@ -57,6 +57,7 @@ exports.externals = { '@kbn/i18n': '__kbnSharedDeps__.KbnI18n', '@kbn/i18n/angular': '__kbnSharedDeps__.KbnI18nAngular', '@kbn/i18n/react': '__kbnSharedDeps__.KbnI18nReact', + '@emotion/react': '__kbnSharedDeps__.EmotionReact', jquery: '__kbnSharedDeps__.Jquery', moment: '__kbnSharedDeps__.Moment', 'moment-timezone': '__kbnSharedDeps__.MomentTimezone', diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 82353a96dc33c..6e33e39b148c4 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -746,9 +746,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1021,9 +1019,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1315,9 +1311,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1570,9 +1564,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
@@ -1786,9 +1778,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` onResize={[Function]} >
-
+
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 66d81d058fc77..b8af7c12d57fc 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -17,7 +17,6 @@ import { CoreSetup } from 'src/core/public'; import { CoreSetup as CoreSetup_2 } from 'kibana/public'; import { CoreStart } from 'kibana/public'; import { CoreStart as CoreStart_2 } from 'src/core/public'; -import * as CSS from 'csstype'; import { Datatable as Datatable_2 } from 'src/plugins/expressions'; import { Datatable as Datatable_3 } from 'src/plugins/expressions/common'; import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; @@ -72,13 +71,12 @@ import { Plugin } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import { PopoverAnchorPosition } from '@elastic/eui'; -import * as PropTypes from 'prop-types'; import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import { RangeFilter as RangeFilter_2 } from 'src/plugins/data/public'; import React from 'react'; -import * as React_3 from 'react'; +import * as React_2 from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; import { Request as Request_2 } from '@hapi/hapi'; import { RequestAdapter } from 'src/plugins/inspector/common'; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap index 0ab3f8a4e3466..1e7b59d8a9e76 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ b/src/plugins/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap @@ -1169,7 +1169,6 @@ exports[`Inspector Data View component should render single table without select > { - // Warning: (ae-forgotten-export) The symbol "React" needs to be exported by the entry point index.d.ts - constructor(getFactory: EmbeddableStart_2['getEmbeddableFactory'], getAllFactories: EmbeddableStart_2['getEmbeddableFactories'], overlays: OverlayStart_2, notifications: NotificationsStart_2, SavedObjectFinder: React_2.ComponentType, reportUiCounter?: ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined); + constructor(getFactory: EmbeddableStart_2['getEmbeddableFactory'], getAllFactories: EmbeddableStart_2['getEmbeddableFactories'], overlays: OverlayStart_2, notifications: NotificationsStart_2, SavedObjectFinder: React.ComponentType, reportUiCounter?: ((appName: string, type: import("@kbn/analytics").UiCounterMetricType, eventNames: string | string[], count?: number | undefined) => void) | undefined); // (undocumented) execute(context: ActionExecutionContext_2): Promise; // (undocumented) diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json index b704274a58aa4..e92dc717ae25e 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/tsconfig.json @@ -6,7 +6,8 @@ "types": [ "node", "jest", - "react" + "react", + "@emotion/react/types/css-prop" ] }, "include": [ diff --git a/test/tsconfig.json b/test/tsconfig.json index 8cf33d93a4067..dccbe8d715c51 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -6,7 +6,7 @@ "emitDeclarationOnly": true, "declaration": true, "declarationMap": true, - "types": ["node", "resize-observer-polyfill"] + "types": ["node", "resize-observer-polyfill", "@emotion/react/types/css-prop"] }, "include": [ "**/*", diff --git a/tsconfig.base.json b/tsconfig.base.json index cc8b66848a394..0c8fec7c88cda 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -12,7 +12,10 @@ // Allows for importing from `kibana` package for the exported types. "kibana": ["./kibana"], "kibana/public": ["src/core/public"], - "kibana/server": ["src/core/server"] + "kibana/server": ["src/core/server"], + "@emotion/core": [ + "typings/@emotion" + ], }, // Support .tsx files and transform JSX into calls to React.createElement "jsx": "react", @@ -62,7 +65,8 @@ "flot", "jest-styled-components", "@testing-library/jest-dom", - "resize-observer-polyfill" + "resize-observer-polyfill", + "@emotion/react/types/css-prop" ] } } diff --git a/typings/@emotion/index.d.ts b/typings/@emotion/index.d.ts new file mode 100644 index 0000000000000..2a5e63a3e29ef --- /dev/null +++ b/typings/@emotion/index.d.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +// Stub @emotion/core +// Remove when @storybook has moved to @emotion v11 +// https://github.com/storybookjs/storybook/issues/13145 +export {}; diff --git a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap index 744a3b2d405c3..8007acad93e4b 100644 --- a/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/management/__snapshots__/report_listing.test.tsx.snap @@ -424,7 +424,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -481,7 +480,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -523,7 +521,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -1491,7 +1487,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -1533,7 +1528,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -2515,7 +2508,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -2557,7 +2549,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -3586,7 +3576,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -3628,7 +3617,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -4690,7 +4677,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -4732,7 +4718,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -5761,7 +5745,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -5803,7 +5786,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -6832,7 +6813,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -6874,7 +6854,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -7903,7 +7881,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -7945,7 +7922,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -8974,7 +8949,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
@@ -9016,7 +8990,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` className="euiTableCellContent euiTableCellContent--overflowingContent" >
-
+
@@ -752,9 +750,7 @@ exports[`ScreenCapturePanelContent properly renders a view with "print" layout o onResize={[Function]} >
-
+
@@ -1064,9 +1060,7 @@ exports[`ScreenCapturePanelContent renders the default view properly 1`] = ` onResize={[Function]} >
-
+
diff --git a/yarn.lock b/yarn.lock index 227301eed8dd6..dccbd8f91a429 100644 --- a/yarn.lock +++ b/yarn.lock @@ -209,6 +209,13 @@ dependencies: "@babel/types" "^7.12.5" +"@babel/helper-module-imports@^7.7.0": + version "7.13.12" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== + dependencies: + "@babel/types" "^7.13.12" + "@babel/helper-module-transforms@^7.12.1": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz#7954fec71f5b32c48e4b303b437c34453fd7247c" @@ -548,6 +555,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.10.4" +"@babel/plugin-syntax-jsx@^7.2.0": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.13.tgz#044fb81ebad6698fe62c478875575bcbb9b70f15" + integrity sha512-d4HM23Q1K7oq/SLNmG6mRt85l2csmQ0cHRaxRXjKW0YFdEXqlZ5kzFQKH5Uc3rDJECgu+yCRgPkG04Mm98R/1g== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -1154,6 +1168,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.13.10": + version "7.13.17" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.17.tgz#8966d1fc9593bf848602f0662d6b4d0069e3a7ec" + integrity sha512-NCdgJEelPTSh+FEFylhnP1ylq848l1z9t9N0j1Lfbcw0+KXGjsTvUmkxy+voLLXB5SOKMbLLx4jxYliGrYQseA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.12.7", "@babel/template@^7.3.3", "@babel/template@^7.4.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" @@ -1187,6 +1208,15 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.13.12": + version "7.13.14" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" + integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@base2/pretty-print-object@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" @@ -1592,6 +1622,41 @@ resolved "https://registry.yarnpkg.com/@elastic/ui-ace/-/ui-ace-0.2.3.tgz#5281aed47a79b7216c55542b0675e435692f20cd" integrity sha512-Nti5s2dplBPhSKRwJxG9JXTMOev4jVOWcnTJD1TOkJr1MUBYKVZcNcJtIVMSvahWGmP0B/UfO9q9lyRqdivkvQ== +"@emotion/babel-plugin-jsx-pragmatic@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin-jsx-pragmatic/-/babel-plugin-jsx-pragmatic-0.1.5.tgz#27debfe9c27c4d83574d509787ae553bf8a34d7e" + integrity sha512-y+3AJ0SItMDaAgGPVkQBC/S/BaqaPACkQ6MyCI2CUlrjTxKttTVfD3TMtcs7vLEcLxqzZ1xiG0vzwCXjhopawQ== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@emotion/babel-plugin@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.2.0.tgz#f25c6df8ec045dad5ae6ca63df0791673b98c920" + integrity sha512-lsnQBnl3l4wu/FJoyHnYRpHJeIPNkOBMbtDUIXcO8luulwRKZXPvA10zd2eXVN6dABIWNX4E34en/jkejIg/yA== + dependencies: + "@babel/helper-module-imports" "^7.7.0" + "@babel/plugin-syntax-jsx" "^7.12.1" + "@babel/runtime" "^7.7.2" + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.5" + "@emotion/serialize" "^1.0.0" + babel-plugin-macros "^2.6.1" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "^4.0.3" + +"@emotion/babel-preset-css-prop@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-preset-css-prop/-/babel-preset-css-prop-11.2.0.tgz#c7e945f56b2610b438f0dc8ae5253fc55488de0e" + integrity sha512-9XLQm2eLPYTho+Cx1LQTDA1rATjoAaB4O+ds55XDvoAa+Z16Hhg8y5Vihj3C8E6+ilDM8SV5A9Z6z+yj0YIRBg== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.12.1" + "@babel/runtime" "^7.7.2" + "@emotion/babel-plugin" "^11.2.0" + "@emotion/babel-plugin-jsx-pragmatic" "^0.1.5" + "@emotion/babel-utils@^0.6.4": version "0.6.10" resolved "https://registry.yarnpkg.com/@emotion/babel-utils/-/babel-utils-0.6.10.tgz#83dbf3dfa933fae9fc566e54fbb45f14674c6ccc" @@ -1614,6 +1679,17 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" +"@emotion/cache@^11.4.0": + version "11.4.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0" + integrity sha512-Zx70bjE7LErRO9OaZrhf22Qye1y4F7iDl+ITjet0J+i+B88PrAOBkKvaAWhxsZf72tDLajwCgfCjJ2dvH77C3g== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.0.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "^4.0.3" + "@emotion/core@^10.0.9", "@emotion/core@^10.1.1": version "10.1.1" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.1.1.tgz#c956c1365f2f2481960064bcb8c4732e5fb612c3" @@ -1626,6 +1702,14 @@ "@emotion/sheet" "0.9.4" "@emotion/utils" "0.11.3" +"@emotion/css-prettifier@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/css-prettifier/-/css-prettifier-1.0.0.tgz#3ed4240d93c9798c001cedf27dd0aa960bdddd1a" + integrity sha512-efxSrRTiTqHTQVKW15Gz5H4pNAw8OqcG8NaiwkJIkqIdNXTD4Qr1zC1Ou6r2acd1oJJ2s56nb1ClnXMiWoj6gQ== + dependencies: + "@emotion/memoize" "^0.7.4" + stylis "^4.0.3" + "@emotion/css@^10.0.27", "@emotion/css@^10.0.9": version "10.0.27" resolved "https://registry.yarnpkg.com/@emotion/css/-/css-10.0.27.tgz#3a7458198fbbebb53b01b2b87f64e5e21241e14c" @@ -1635,7 +1719,7 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" -"@emotion/hash@0.8.0": +"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== @@ -1652,6 +1736,17 @@ dependencies: "@emotion/memoize" "0.7.4" +"@emotion/jest@^11.3.0": + version "11.3.0" + resolved "https://registry.yarnpkg.com/@emotion/jest/-/jest-11.3.0.tgz#43bed6dcb47c8691b346cee231861ebc8f9b0016" + integrity sha512-LZqYc3yerhic1IvAcEwBLRs1DsUt3oY7Oz6n+e+HU32iYOK/vpfzlhgmQURE94BHfv6eCOj6DV38f3jSnIkBkQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/css-prettifier" "^1.0.0" + chalk "^4.1.0" + specificity "^0.4.1" + stylis "^4.0.3" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" @@ -1662,6 +1757,24 @@ resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b" integrity sha512-h4t4jFjtm1YV7UirAFuSuFGyLa+NNxjdkq6DpFLANNQY5rHueFZHVY+8Cu1HYVP6DrheB0kv4m5xPjo7eKT7yQ== +"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.4.0": + version "11.4.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.0.tgz#2465ad7b073a691409b88dfd96dc17097ddad9b7" + integrity sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/cache" "^11.4.0" + "@emotion/serialize" "^1.0.2" + "@emotion/sheet" "^1.0.1" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + "@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": version "0.11.16" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" @@ -1683,11 +1796,27 @@ "@emotion/unitless" "^0.6.7" "@emotion/utils" "^0.8.2" +"@emotion/serialize@^1.0.0", "@emotion/serialize@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.2.tgz#77cb21a0571c9f68eb66087754a65fa97bfcd965" + integrity sha512-95MgNJ9+/ajxU7QIAruiOAdYNjxZX7G2mhgrtDWswA21VviYIRP1R5QilZ/bDY42xiKsaktP4egJb3QdYQZi1A== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + "@emotion/sheet@0.9.4": version "0.9.4" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== +"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698" + integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g== + "@emotion/styled-base@^10.0.27": version "10.0.31" resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.0.31.tgz#940957ee0aa15c6974adc7d494ff19765a2f742a" @@ -1716,7 +1845,7 @@ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.7.1.tgz#50f63225e712d99e2b2b39c19c70fff023793ca5" integrity sha512-/SLmSIkN13M//53TtNxgxo57mcJk/UJIDFRKwOiLIBEyBHEcipgR6hNMQ/59Sl4VjCJ0Z/3zeAZyvnSLPG/1HQ== -"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4": +"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== @@ -1736,7 +1865,12 @@ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.8.2.tgz#576ff7fb1230185b619a75d258cbc98f0867a8dc" integrity sha512-rLu3wcBWH4P5q1CGoSSH/i9hrXs7SlbRLkoq9IGuoPYNGQvDJ3pt/wmOM+XgYjIDRMVIdkUWt0RsfzF50JfnCw== -"@emotion/weak-memoize@0.2.5": +"@emotion/utils@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af" + integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA== + +"@emotion/weak-memoize@0.2.5", "@emotion/weak-memoize@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== @@ -7790,7 +7924,7 @@ babel-plugin-jest-hoist@^26.6.2: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0: +babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.6.1, babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== @@ -9487,15 +9621,6 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -clipboard@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d" - integrity sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ== - dependencies: - good-listener "^1.2.2" - select "^1.1.2" - tiny-emitter "^2.0.0" - cliui@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" @@ -10718,6 +10843,11 @@ csstype@^2.5.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.14.tgz#004822a4050345b55ad4dcc00be1d9cf2f4296de" integrity sha512-2mSc+VEpGPblzAxyeR+vZhJKgYg0Og0nnRi7pmRXFYYxSfnOnW8A5wwQb4n4cE2nIOzqKOAzLCaEX6aBmNEv8A== +csstype@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b" + integrity sha512-KxnUB0ZMlnUWCsx2Z8MUsr6qV6ja1w9ArPErJaJaF8a5SOWoHLIszeCTKGRGRgtLgYrs1E8CHkNSP1VZTTPc9g== + cucumber-expressions@^5.0.13: version "5.0.18" resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-5.0.18.tgz#6c70779efd3aebc5e9e7853938b1110322429596" @@ -11589,11 +11719,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegate@^3.1.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" - integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== - delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -14789,13 +14914,6 @@ gonzales-pe@^4.3.0: dependencies: minimist "^1.2.5" -good-listener@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" - integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= - dependencies: - delegate "^3.1.2" - got@5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/got/-/got-5.6.0.tgz#bb1d7ee163b78082bbc8eb836f3f395004ea6fbf" @@ -15410,7 +15528,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -22172,8 +22290,6 @@ prismjs@1.24.0, prismjs@^1.22.0, prismjs@~1.23.0: version "1.24.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.0.tgz#0409c30068a6c52c89ef7f1089b3ca4de56be2ac" integrity sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ== - optionalDependencies: - clipboard "^2.0.0" private@^0.1.8, private@~0.1.5: version "0.1.8" @@ -24791,11 +24907,6 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= -select@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" - integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= - selenium-webdriver@^4.0.0-alpha.7: version "4.0.0-alpha.7" resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.0.0-alpha.7.tgz#e3879d8457fd7ad8e4424094b7dc0540d99e6797" @@ -26207,6 +26318,11 @@ stylis@^3.5.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== +stylis@^4.0.3: + version "4.0.7" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904" + integrity sha512-OFFeUXFgwnGOKvEXaSv0D0KQ5ADP0n6g3SVONx6I/85JzNZ3u50FRwB3lVIk1QO2HNdI75tbVzc4Z66Gdp9voA== + subarg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" @@ -26828,11 +26944,6 @@ timsort@^0.3.0, timsort@~0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -tiny-emitter@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" - integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== - tiny-inflate@^1.0.0, tiny-inflate@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" From 65bc4a9c0e486f14b40b99d961644b2dd501ac16 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Fri, 9 Jul 2021 14:53:15 -0500 Subject: [PATCH 19/36] [Workplace Search] Remove users from groups views (#105108) * Remove user list from groups table * Remove users table from group overview * Lint --- .../groups/components/group_overview.tsx | 25 ++++++++----------- .../groups/components/group_row.test.tsx | 15 +---------- .../views/groups/components/group_row.tsx | 19 +------------- .../views/groups/components/groups_table.tsx | 7 ------ .../translations/translations/ja-JP.json | 2 -- .../translations/translations/zh-CN.json | 2 -- 6 files changed, 13 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index e7dfd6ddf1389..5714cc965827e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -33,24 +33,23 @@ import { NAV, CANCEL_BUTTON } from '../../../constants'; import { USERS_AND_ROLES_PATH } from '../../../routes'; import { GroupLogic, MAX_NAME_LENGTH } from '../group_logic'; -import { GroupUsersTable } from './group_users_table'; - export const EMPTY_SOURCES_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription', { defaultMessage: 'No content sources are shared with this group.', } ); -const GROUP_USERS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription', +const USERS_SECTION_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.overview.usersSectionTitle', { - defaultMessage: 'Members will be able to search over the group’s sources.', + defaultMessage: 'Group users', } ); -export const EMPTY_USERS_DESCRIPTION = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.overview.emptyUsersDescription', +const GROUP_USERS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription', { - defaultMessage: 'There are no users in this group.', + defaultMessage: + "Users assigned to this group gain access to the sources' data and content defined above. User assignments for this group can be managed in the Users and Roles area.", } ); const MANAGE_SOURCES_BUTTON_TEXT = i18n.translate( @@ -118,7 +117,7 @@ export const GroupOverview: React.FC = () => { onGroupNameInputChange, } = useActions(GroupLogic); const { - group: { name, contentSources, users, canDeleteGroup }, + group: { name, contentSources, canDeleteGroup }, groupNameInputValue, dataLoading, confirmDeleteModalVisible, @@ -158,7 +157,6 @@ export const GroupOverview: React.FC = () => { ); const hasContentSources = contentSources?.length > 0; - const hasUsers = users?.length > 0; const manageSourcesButton = ( @@ -199,12 +197,11 @@ export const GroupOverview: React.FC = () => { const usersSection = ( - {hasUsers && } + {manageUsersButton} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx index f98b873aed5bb..770bf8a51efd3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.test.tsx @@ -14,8 +14,7 @@ import moment from 'moment'; import { EuiTableRow } from '@elastic/eui'; -import { GroupRow, NO_USERS_MESSAGE, NO_SOURCES_MESSAGE } from './group_row'; -import { GroupUsers } from './group_users'; +import { GroupRow, NO_SOURCES_MESSAGE } from './group_row'; describe('GroupRow', () => { it('renders', () => { @@ -24,12 +23,6 @@ describe('GroupRow', () => { expect(wrapper.find(EuiTableRow)).toHaveLength(1); }); - it('renders group users', () => { - const wrapper = shallow(); - - expect(wrapper.find(GroupUsers)).toHaveLength(1); - }); - it('renders fromNow date string when in range', () => { const wrapper = shallow( @@ -44,12 +37,6 @@ describe('GroupRow', () => { expect(wrapper.find('small').text()).toEqual('Last updated January 1, 2020.'); }); - it('renders empty users message when no users present', () => { - const wrapper = shallow(); - - expect(wrapper.find('.user-group__accounts').text()).toEqual(NO_USERS_MESSAGE); - }); - it('renders empty sources message when no sources present', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx index 94d44fde57aed..d079eb34fbf89 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_row.tsx @@ -19,7 +19,6 @@ import { Group } from '../../../types'; import { MAX_NAME_LENGTH } from '../group_logic'; import { GroupSources } from './group_sources'; -import { GroupUsers } from './group_users'; const DAYS_CUTOFF = 8; export const NO_SOURCES_MESSAGE = i18n.translate( @@ -40,14 +39,7 @@ const dateDisplay = (date: string) => ? moment(date).fromNow() : moment(date).format('MMMM D, YYYY'); -export const GroupRow: React.FC = ({ - id, - name, - updatedAt, - contentSources, - users, - usersCount, -}) => { +export const GroupRow: React.FC = ({ id, name, updatedAt, contentSources }) => { const GROUP_UPDATED_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText', { @@ -76,15 +68,6 @@ export const GroupRow: React.FC = ({ )}
- -
- {usersCount > 0 ? ( - - ) : ( - NO_USERS_MESSAGE - )} -
-
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx index cfb3ed8044235..45175e489f94a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/groups_table.tsx @@ -36,12 +36,6 @@ const SOURCES_TABLE_HEADER = i18n.translate( defaultMessage: 'Content sources', } ); -const USERS_TABLE_HEADER = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader', - { - defaultMessage: 'Users', - } -); export const GroupsTable: React.FC<{}> = () => { const { setActivePage } = useActions(GroupsLogic); @@ -77,7 +71,6 @@ export const GroupsTable: React.FC<{}> = () => { {GROUP_TABLE_HEADER} {SOURCES_TABLE_HEADER} - {USERS_TABLE_HEADER} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 11770d2d2f386..b7cde09da6319 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8105,7 +8105,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "共有コンテンツソースが正常に更新されました。", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "グループ", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "コンテンツソース", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader": "ユーザー", "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "前回更新日時{updatedAt}。", "xpack.enterpriseSearch.workplaceSearch.groups.heading": "グループを管理", "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "ユーザーを招待", @@ -8118,7 +8117,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "グループはWorkplace Searchから削除されます。{name}を削除してよろしいですか?", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "確認", "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "コンテンツソースはこのグループと共有されていません。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptyUsersDescription": "このグループにはユーザーがありません。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription": "「{name}」グループのすべてのユーザーによって検索可能です。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "グループコンテンツソース", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "メンバーはグループのソースを検索できます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 9704070feb8ab..ac43983a75ccf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8173,7 +8173,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.groupSourcesUpdated": "已成功更新共享内容源。", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.groupTableHeader": "组", "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.sourcesTableHeader": "内容源", - "xpack.enterpriseSearch.workplaceSearch.groups.groupsTable.usersTableHeader": "用户", "xpack.enterpriseSearch.workplaceSearch.groups.groupUpdatedText": "上次更新于 {updatedAt}。", "xpack.enterpriseSearch.workplaceSearch.groups.heading": "管理组", "xpack.enterpriseSearch.workplaceSearch.groups.inviteUsers.action": "邀请用户", @@ -8186,7 +8185,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "您的组将从 Workplace Search 中删除。确定要移除 {name}?", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "确认", "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptySourcesDescription": "未与此组共享任何内容源。", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.emptyUsersDescription": "此组中没有用户。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription": "可按“{name}”组中的所有用户搜索。", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle": "组内容源", "xpack.enterpriseSearch.workplaceSearch.groups.overview.groupUsersDescription": "成员将可以对该组的源进行搜索。", From f3228a38f41e7f946883030f2a718637ffd3c514 Mon Sep 17 00:00:00 2001 From: Spencer Date: Fri, 9 Jul 2021 13:26:26 -0700 Subject: [PATCH 20/36] [kbn/client/ui-settings] support using uiSettings in a specific space (#105116) Co-authored-by: spalger --- .../kbn_client/kbn_client_requester.test.ts | 35 +++++++++++++++++++ .../src/kbn_client/kbn_client_requester.ts | 13 +++++++ .../src/kbn_client/kbn_client_ui_settings.ts | 25 +++++++------ 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts new file mode 100644 index 0000000000000..bb2f923ad1f01 --- /dev/null +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester.test.ts @@ -0,0 +1,35 @@ +/* + * 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 { pathWithSpace } from './kbn_client_requester'; + +describe('pathWithSpace()', () => { + it('adds a space to the path', () => { + expect(pathWithSpace('hello')`/foo/bar`).toMatchInlineSnapshot(`"/s/hello/foo/bar"`); + }); + + it('ignores the space when it is empty', () => { + expect(pathWithSpace(undefined)`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); + expect(pathWithSpace('')`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); + }); + + it('ignores the space when it is the default space', () => { + expect(pathWithSpace('default')`/foo/bar`).toMatchInlineSnapshot(`"/foo/bar"`); + }); + + it('uriencodes variables in the path', () => { + expect(pathWithSpace('space')`hello/${'funky/username🏴‍☠️'}`).toMatchInlineSnapshot( + `"/s/space/hello/funky%2Fusername%F0%9F%8F%B4%E2%80%8D%E2%98%A0%EF%B8%8F"` + ); + }); + + it('ensures the path always starts with a slash', () => { + expect(pathWithSpace('foo')`hello/world`).toMatchInlineSnapshot(`"/s/foo/hello/world"`); + expect(pathWithSpace()`hello/world`).toMatchInlineSnapshot(`"/hello/world"`); + }); +}); diff --git a/packages/kbn-test/src/kbn_client/kbn_client_requester.ts b/packages/kbn-test/src/kbn_client/kbn_client_requester.ts index a194b593b3863..c2e4247df1ab0 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_requester.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_requester.ts @@ -23,6 +23,19 @@ const isIgnorableError = (error: any, ignorableErrors: number[] = []) => { return isAxiosResponseError(error) && ignorableErrors.includes(error.response.status); }; +/** + * Creates a template literal tag which will uriencode the variables in a template literal + * as well as prefix the path with a specific space if one is defined + */ +export const pathWithSpace = (space?: string) => { + const prefix = !space || space === 'default' ? '' : uriencode`/s/${space}`; + + return (strings: TemplateStringsArray, ...args: Array) => { + const path = uriencode(strings, ...args); + return path.startsWith('/') || path === '' ? `${prefix}${path}` : `${prefix}/${path}`; + }; +}; + export const uriencode = ( strings: TemplateStringsArray, ...values: Array diff --git a/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts b/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts index 78155098ef038..7ea685667d48b 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_ui_settings.ts @@ -8,7 +8,7 @@ import { ToolingLog } from '@kbn/dev-utils'; -import { KbnClientRequester, uriencode } from './kbn_client_requester'; +import { KbnClientRequester, pathWithSpace } from './kbn_client_requester'; export type UiSettingValues = Record; interface UiSettingsApiResponse { @@ -27,8 +27,8 @@ export class KbnClientUiSettings { private readonly defaults?: UiSettingValues ) {} - async get(setting: string) { - const all = await this.getAll(); + async get(setting: string, { space }: { space?: string } = {}) { + const all = await this.getAll({ space }); const value = all[setting]?.userValue; this.log.verbose('uiSettings.value: %j', value); @@ -45,9 +45,9 @@ export class KbnClientUiSettings { /** * Unset a uiSetting */ - async unset(setting: string) { + async unset(setting: string, { space }: { space?: string } = {}) { const { data } = await this.requester.request({ - path: uriencode`/api/kibana/settings/${setting}`, + path: pathWithSpace(space)`/api/kibana/settings/${setting}`, method: 'DELETE', }); return data; @@ -57,7 +57,10 @@ export class KbnClientUiSettings { * Replace all uiSettings with the `doc` values, `doc` is merged * with some defaults */ - async replace(doc: UiSettingValues, { retries = 5 }: { retries?: number } = {}) { + async replace( + doc: UiSettingValues, + { retries = 5, space }: { retries?: number; space?: string } = {} + ) { this.log.debug('replacing kibana config doc: %j', doc); const changes: Record = { @@ -73,7 +76,7 @@ export class KbnClientUiSettings { await this.requester.request({ method: 'POST', - path: '/api/kibana/settings', + path: pathWithSpace(space)`/api/kibana/settings`, body: { changes }, retries, }); @@ -82,11 +85,11 @@ export class KbnClientUiSettings { /** * Add fields to the config doc (like setting timezone and defaultIndex) */ - async update(updates: UiSettingValues) { + async update(updates: UiSettingValues, { space }: { space?: string } = {}) { this.log.debug('applying update to kibana config: %j', updates); await this.requester.request({ - path: '/api/kibana/settings', + path: pathWithSpace(space)`/api/kibana/settings`, method: 'POST', body: { changes: updates, @@ -95,9 +98,9 @@ export class KbnClientUiSettings { }); } - private async getAll() { + private async getAll({ space }: { space?: string } = {}) { const { data } = await this.requester.request({ - path: '/api/kibana/settings', + path: pathWithSpace(space)`/api/kibana/settings`, method: 'GET', }); From 36bf7f71208d1c03eb4f06fa61aa75f0095fc298 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell Date: Fri, 9 Jul 2021 14:28:02 -0600 Subject: [PATCH 21/36] [Maps] Fix tracking threshold alerts improper handling of elasticsearch epoch millis strings (#105010) --- .../alert_types/geo_containment/es_query_builder.ts | 9 ++++++++- .../alert_types/geo_containment/geo_containment.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts index 1e26ea09618d5..37e0a293b03a0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/es_query_builder.ts @@ -151,7 +151,14 @@ export async function executeEsQueryFactory( }, }, ], - docvalue_fields: [entity, dateField, geoField], + docvalue_fields: [ + entity, + { + field: dateField, + format: 'strict_date_optional_time', + }, + geoField, + ], _source: false, }, }, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts index 754af920b009e..21a536dd474ba 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts @@ -103,7 +103,7 @@ export function getActiveEntriesAndGenerateAlerts( locationsArr.forEach(({ location, shapeLocationId, dateInShape, docId }) => { const context = { entityId: entityName, - entityDateTime: dateInShape ? new Date(dateInShape).toISOString() : null, + entityDateTime: dateInShape || null, entityDocumentId: docId, detectionDateTime: new Date(currIntervalEndTime).toISOString(), entityLocation: `POINT (${location[0]} ${location[1]})`, From f7b87a5f65da7ed8538a962ccc8f2ca48aa18799 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Fri, 9 Jul 2021 15:33:20 -0500 Subject: [PATCH 22/36] [ML] Fix Single Metric Viewer & Explorer annotation table actions overflow and annotations count not matching (#104955) * Fix annotations * Fix translations * Fix onclick open * Fix label/aggregations mismatch * Fix title --- .../annotations_table/annotations_table.js | 136 ++++++++---------- .../public/application/explorer/explorer.js | 36 +++-- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 4 files changed, 89 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index ed603357206ad..39d4dd1a71dd9 100644 --- a/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -18,13 +18,13 @@ import React, { Component, Fragment, useContext } from 'react'; import memoizeOne from 'memoize-one'; import { EuiBadge, - EuiButtonEmpty, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiLink, EuiLoadingSpinner, + EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -52,6 +52,19 @@ import { timeFormatter } from '../../../../../common/util/date_utils'; import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context'; import { DatafeedChartFlyout } from '../../../jobs/jobs_list/components/datafeed_chart_flyout'; +const editAnnotationsText = ( + +); +const viewDataFeedText = ( + +); + const CURRENT_SERIES = 'current_series'; /** * Table component for rendering the lists of annotations for an ML job. @@ -463,82 +476,67 @@ class AnnotationsTableUI extends Component { const actions = []; actions.push({ - render: (annotation) => { - // find the original annotation because the table might not show everything + name: editAnnotationsText, + description: editAnnotationsText, + icon: 'pencil', + type: 'icon', + onClick: (annotation) => { const annotationId = annotation._id; const originalAnnotation = annotations.find((d) => d._id === annotationId); - const editAnnotationsText = ( - - ); - const editAnnotationsAriaLabelText = i18n.translate( - 'xpack.ml.annotationsTable.editAnnotationsTooltipAriaLabel', - { defaultMessage: 'Edit annotation' } - ); - return ( - annotationUpdatesService.setValue(originalAnnotation ?? annotation)} - > - {editAnnotationsText} - - ); + + annotationUpdatesService.setValue(originalAnnotation ?? annotation); }, }); if (this.state.jobId && this.props.jobs[0].analysis_config.bucket_span) { // add datafeed modal action actions.push({ - render: (annotation) => { - const viewDataFeedText = ( - - ); - const viewDataFeedTooltipAriaLabelText = i18n.translate( - 'xpack.ml.annotationsTable.datafeedChartTooltipAriaLabel', - { defaultMessage: 'Datafeed chart' } - ); - return ( - - this.setState({ - datafeedFlyoutVisible: true, - datafeedEnd: annotation.end_timestamp, - }) - } - > - {viewDataFeedText} - - ); + name: viewDataFeedText, + description: viewDataFeedText, + icon: 'visAreaStacked', + type: 'icon', + onClick: (annotation) => { + this.setState({ + datafeedFlyoutVisible: true, + datafeedEnd: annotation.end_timestamp, + }); }, }); } if (isSingleMetricViewerLinkVisible) { actions.push({ - render: (annotation) => { + name: (annotation) => { const isDrillDownAvailable = isTimeSeriesViewJob(this.getJob(annotation.job_id)); - const openInSingleMetricViewerTooltipText = isDrillDownAvailable ? ( - - ) : ( - + + if (isDrillDownAvailable) { + return ( + + ); + } + return ( + + } + > + + ); - const openInSingleMetricViewerAriaLabelText = isDrillDownAvailable + }, + description: (annotation) => { + const isDrillDownAvailable = isTimeSeriesViewJob(this.getJob(annotation.job_id)); + + return isDrillDownAvailable ? i18n.translate('xpack.ml.annotationsTable.openInSingleMetricViewerAriaLabel', { defaultMessage: 'Open in Single Metric Viewer', }) @@ -546,19 +544,11 @@ class AnnotationsTableUI extends Component { 'xpack.ml.annotationsTable.jobConfigurationNotSupportedInSingleMetricViewerAriaLabel', { defaultMessage: 'Job configuration not supported in Single Metric Viewer' } ); - - return ( - this.openSingleMetricView(annotation)} - > - {openInSingleMetricViewerTooltipText} - - ); }, + enabled: (annotation) => isTimeSeriesViewJob(this.getJob(annotation.job_id)), + icon: 'visLine', + type: 'icon', + onClick: (annotation) => this.openSingleMetricView(annotation), }); } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index a11a42b9b65b2..31058b62af7fe 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -261,6 +261,30 @@ export class ExplorerUI extends React.Component { } = this.props.explorerState; const { annotationsData, aggregations, error: annotationsError } = annotations; + const annotationsCnt = Array.isArray(annotationsData) ? annotationsData.length : 0; + const allAnnotationsCnt = Array.isArray(aggregations?.event?.buckets) + ? aggregations.event.buckets.reduce((acc, v) => acc + v.doc_count, 0) + : annotationsCnt; + + const badge = + allAnnotationsCnt > annotationsCnt ? ( + + + + ) : ( + + + + ); + const jobSelectorProps = { dateFormatTz: getDateFormatTz(), }; @@ -404,7 +428,7 @@ export class ExplorerUI extends React.Component { {loading === false && tableData.anomalies?.length ? ( ) : null} - {annotationsData.length > 0 && ( + {annotationsCnt > 0 && ( <> - - - ), + badge, }} /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b7cde09da6319..387547295f047 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13639,7 +13639,6 @@ "xpack.ml.annotationsTable.byColumnSMVName": "グループ基準", "xpack.ml.annotationsTable.detectorColumnName": "検知器", "xpack.ml.annotationsTable.editAnnotationsTooltip": "注釈を編集します", - "xpack.ml.annotationsTable.editAnnotationsTooltipAriaLabel": "注釈を編集します", "xpack.ml.annotationsTable.eventColumnName": "イベント", "xpack.ml.annotationsTable.fromColumnName": "開始:", "xpack.ml.annotationsTable.howToCreateAnnotationDescription": "注釈を作成するには、{linkToSingleMetricView} を開きます", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ac43983a75ccf..d142969022c81 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13818,7 +13818,6 @@ "xpack.ml.annotationsTable.byColumnSMVName": "依据", "xpack.ml.annotationsTable.detectorColumnName": "检测工具", "xpack.ml.annotationsTable.editAnnotationsTooltip": "编辑注释", - "xpack.ml.annotationsTable.editAnnotationsTooltipAriaLabel": "编辑注释", "xpack.ml.annotationsTable.eventColumnName": "事件", "xpack.ml.annotationsTable.fromColumnName": "自", "xpack.ml.annotationsTable.howToCreateAnnotationDescription": "要创建注释,请打开 {linkToSingleMetricView}", From ce48b73dc8f215eb7761f896b1bdaa4d584e5121 Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 13:59:56 -0700 Subject: [PATCH 23/36] skip suites failing es promotion (#104466) --- test/functional/apps/discover/_data_grid_field_data.ts | 3 ++- test/functional/apps/discover/_field_data.ts | 7 ++++--- .../apps/discover/_field_data_with_fields_api.ts | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index 94e8e942f86ba..cf8a6fc1bd60f 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; const dataGrid = getService('dataGrid'); - describe('discover data grid field data tests', function describeIndexTests() { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover data grid field data tests', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index 5ab6495686726..6471b751945a8 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - describe('discover tab', function describeIndexTests() { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); @@ -33,8 +34,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await PageObjects.common.navigateToApp('discover'); }); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('field data', function () { + + describe('field data', function () { it('search php should show the correct hit count', async function () { const expectedHitCount = '445'; await retry.try(async function () { diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 110e255d18c75..7c6867e935063 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - describe('discover tab with new fields API', function describeIndexTests() { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover tab with new fields API', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); From fdd37e3cfb0116772c48fc45c75e8fe61cd55579 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 9 Jul 2021 17:14:37 -0400 Subject: [PATCH 24/36] Fixed description on Ent Home (#105122) --- .../product_selector/product_selector.tsx | 10 ++++++---- .../applications/enterprise_search/index.scss | 14 -------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx index a7eb2424e797a..0dd2b0988b3f4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/components/product_selector/product_selector.tsx @@ -19,6 +19,7 @@ import { EuiFlexItem, EuiSpacer, EuiTitle, + EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -59,14 +60,15 @@ export const ProductSelector: React.FC = ({ access }) => { -

+

{i18n.translate('xpack.enterpriseSearch.overview.heading', { defaultMessage: 'Welcome to Elastic Enterprise Search', })}

- -

+ + +

{config.host ? i18n.translate('xpack.enterpriseSearch.overview.subheading', { defaultMessage: 'Select a product to get started.', @@ -75,7 +77,7 @@ export const ProductSelector: React.FC = ({ access }) => { defaultMessage: 'Choose a product to set up and get started.', })}

-
+
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss index 45bf37def1121..4be8d7322b4c8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search/index.scss @@ -21,21 +21,7 @@ &__header { text-align: center; margin: auto; - } - - &__heading { - @include euiBreakpoint('xs', 's') { - font-size: $euiFontSizeXL; - line-height: map-get(map-get($euiTitles, 'm'), 'line-height'); - } - } - - &__subheading { - color: $euiColorMediumShade; - font-size: $euiFontSize; - @include euiBreakpoint('m', 'l', 'xl') { - font-size: $euiFontSizeL; margin-bottom: $euiSizeL; } } From b7f50c279bf9e348096f2d7b9e289ec1acb5ae47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Fri, 9 Jul 2021 17:17:48 -0400 Subject: [PATCH 25/36] [CTI] updates overview cti panel copy (#105074) --- .../overview/components/overview_cti_links/translations.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts index 663ec3a75c902..91abd48eb2b7e 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/translations.ts @@ -51,9 +51,10 @@ export const DANGER_TITLE = i18n.translate( ); export const DANGER_BODY = i18n.translate( - 'xpack.securitySolution.overview.ctiDashboardDangerPanelBody', + 'xpack.securitySolution.overview.ctiDashboardEnableThreatIntel', { - defaultMessage: 'You need to enable module in order to view data from different sources.', + defaultMessage: + 'You need to enable the filebeat threatintel module in order to view data from different sources.', } ); From bcc8ee2532133c39e663f73303187a2345ef24a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Fri, 9 Jul 2021 17:28:15 -0400 Subject: [PATCH 26/36] [CTI] makes dashboard links external (#104979) * [CTI] makes dashboard links external --- .../cti_disabled_module.tsx | 2 +- .../threat_intel_panel_view.tsx | 28 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx index e22fec1861f8b..21a4beca72f3b 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/cti_disabled_module.tsx @@ -25,7 +25,7 @@ export const CtiDisabledModuleComponent = () => { title={i18n.DANGER_TITLE} body={i18n.DANGER_BODY} button={ - + {i18n.DANGER_BUTTON} } diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx index 4565c16bc2bf6..b34f6e657d39a 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_cti_links/threat_intel_panel_view.tsx @@ -22,7 +22,6 @@ import { InspectButtonContainer } from '../../../common/components/inspect'; import { HeaderSection } from '../../../common/components/header_section'; import { ID as CTIEventCountQueryId } from '../../containers/overview_cti_links/use_cti_event_counts'; import { CtiListItem } from '../../containers/overview_cti_links/helpers'; -import { LinkButton } from '../../../common/components/links'; import { useKibana } from '../../../common/lib/kibana'; import { CtiInnerPanel } from './cti_inner_panel'; import * as i18n from './translations'; @@ -36,7 +35,7 @@ const DashboardLinkItems = styled(EuiFlexGroup)` `; const Title = styled(EuiFlexItem)` - min-width: 140px; + min-width: 110px; `; const List = styled.ul` @@ -45,12 +44,11 @@ const List = styled.ul` const DashboardRightSideElement = styled(EuiFlexItem)` align-items: flex-end; - max-width: 160px; `; const RightSideLink = styled(EuiLink)` text-align: right; - min-width: 140px; + min-width: 180px; `; interface ThreatIntelPanelViewProps { @@ -96,12 +94,12 @@ export const ThreatIntelPanelView: React.FC = ({ const button = useMemo( () => ( - + - + ), [buttonHref] ); @@ -117,7 +115,11 @@ export const ThreatIntelPanelView: React.FC = ({ color={'primary'} title={i18n.INFO_TITLE} body={i18n.INFO_BODY} - button={{i18n.INFO_BUTTON}} + button={ + + {i18n.INFO_BUTTON} + + } /> ) : null, [isDashboardPluginDisabled, threatIntelDashboardDocLink] @@ -149,9 +151,7 @@ export const ThreatIntelPanelView: React.FC = ({ gutterSize="l" justifyContent="spaceBetween" > - - {title} - + {title} = ({ alignItems="center" justifyContent="flexEnd" > - + {count} - + {path ? ( - {linkCopy} + + {linkCopy} + ) : ( {linkCopy} From 1739b55f94df04b4b99fbc11233cc16d5cef863b Mon Sep 17 00:00:00 2001 From: Constance Date: Fri, 9 Jul 2021 14:31:01 -0700 Subject: [PATCH 27/36] [Enterprise Search] Fix Error Connecting view not displaying for auth issues (#105125) * Fix ent-search authentication to show the error connecting screen Missed this in #103555 * [Misc] updoot handleConnectionError order/spacing to match - why? because i've lost control of my life, probably --- .../server/lib/enterprise_search_request_handler.test.ts | 2 +- .../server/lib/enterprise_search_request_handler.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts index d0e74f3234c14..f9756119b336c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.test.ts @@ -403,7 +403,7 @@ describe('EnterpriseSearchRequestHandler', () => { expect(responseMock.customError).toHaveBeenCalledWith({ statusCode: 502, body: 'Cannot authenticate Enterprise Search user', - headers: mockExpectedResponseHeaders, + headers: { ...mockExpectedResponseHeaders, [ERROR_CONNECTING_HEADER]: 'true' }, }); expect(mockLogger.error).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts index 8031fc724f7b3..57b91c2b30c73 100644 --- a/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts +++ b/x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts @@ -283,12 +283,11 @@ export class EnterpriseSearchRequestHandler { handleConnectionError(response: KibanaResponseFactory, e: Error) { const errorMessage = `Error connecting to Enterprise Search: ${e?.message || e.toString()}`; + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; this.log.error(errorMessage); if (e instanceof Error) this.log.debug(e.stack as string); - const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; - return response.customError({ statusCode: 502, headers, body: errorMessage }); } @@ -298,9 +297,10 @@ export class EnterpriseSearchRequestHandler { */ handleAuthenticationError(response: KibanaResponseFactory) { const errorMessage = 'Cannot authenticate Enterprise Search user'; + const headers = { ...this.headers, [ERROR_CONNECTING_HEADER]: 'true' }; this.log.error(errorMessage); - return response.customError({ statusCode: 502, headers: this.headers, body: errorMessage }); + return response.customError({ statusCode: 502, headers, body: errorMessage }); } /** From a05853ab2ad26e5f8fe8654a138c805ba45422be Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Fri, 9 Jul 2021 17:52:17 -0400 Subject: [PATCH 28/36] [APM] Support for data streams index patterns for cloud migration (#105015) * [APM] Support for data streams index patterns for cloud migration (#101095) * [APM] Update apm package version to 0.3.0 (elastic/apm-server/#5579) --- x-pack/plugins/apm/server/index.test.ts | 2 +- x-pack/plugins/apm/server/index.ts | 6 ++- .../get_apm_package_policy_definition.ts | 2 +- .../create_static_index_pattern.ts | 5 ++- x-pack/plugins/apm/server/routes/fleet.ts | 41 ++++++++++++++----- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/apm/server/index.test.ts b/x-pack/plugins/apm/server/index.test.ts index 6052ec921f9f9..56c9825db5a5c 100644 --- a/x-pack/plugins/apm/server/index.test.ts +++ b/x-pack/plugins/apm/server/index.test.ts @@ -30,7 +30,7 @@ describe('mergeConfigs', () => { expect(mergeConfigs(apmOssConfig, apmConfig)).toEqual({ 'apm_oss.errorIndices': 'logs-apm*,apm-*-error-*', - 'apm_oss.indexPattern': 'apm-*', + 'apm_oss.indexPattern': 'traces-apm*,logs-apm*,metrics-apm*,apm-*', 'apm_oss.metricsIndices': 'metrics-apm*,apm-*-metric-*', 'apm_oss.spanIndices': 'traces-apm*,apm-*-span-*', 'apm_oss.transactionIndices': 'traces-apm*,apm-*-transaction-*', diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index f14894a76edb4..9031f454f4a7f 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -74,7 +74,7 @@ export function mergeConfigs( 'apm_oss.metricsIndices': apmOssConfig.metricsIndices, 'apm_oss.sourcemapIndices': apmOssConfig.sourcemapIndices, 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, - 'apm_oss.indexPattern': apmOssConfig.indexPattern, // TODO: add data stream indices: traces-apm*,logs-apm*,metrics-apm*. Blocked by https://github.com/elastic/kibana/issues/87851 + 'apm_oss.indexPattern': apmOssConfig.indexPattern, /* eslint-enable @typescript-eslint/naming-convention */ 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled, 'xpack.apm.serviceMapFingerprintBucketSize': @@ -119,6 +119,10 @@ export function mergeConfigs( 'apm_oss.metricsIndices' ] = `metrics-apm*,${mergedConfig['apm_oss.metricsIndices']}`; + mergedConfig[ + 'apm_oss.indexPattern' + ] = `traces-apm*,logs-apm*,metrics-apm*,${mergedConfig['apm_oss.indexPattern']}`; + return mergedConfig; } diff --git a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts index 82e85e7da9bb3..291b2fa2af99d 100644 --- a/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts +++ b/x-pack/plugins/apm/server/lib/fleet/get_apm_package_policy_definition.ts @@ -34,7 +34,7 @@ export function getApmPackagePolicyDefinition( ], package: { name: APM_PACKAGE_NAME, - version: '0.3.0-dev.1', + version: '0.3.0', title: 'Elastic APM', }, }; diff --git a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts index 607a7e6227a9d..a2944d6241d2d 100644 --- a/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts +++ b/x-pack/plugins/apm/server/lib/index_pattern/create_static_index_pattern.ts @@ -19,7 +19,8 @@ export async function createStaticIndexPattern( setup: Setup, config: APMRouteHandlerResources['config'], savedObjectsClient: InternalSavedObjectsClient, - spaceId: string | undefined + spaceId: string | undefined, + overwrite = false ): Promise { return withApmSpan('create_static_index_pattern', async () => { // don't autocreate APM index pattern if it's been disabled via the config @@ -45,7 +46,7 @@ export async function createStaticIndexPattern( }, { id: APM_STATIC_INDEX_PATTERN_ID, - overwrite: false, + overwrite, namespace: spaceId, } ) diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts index 6628d29b256f7..b760014d6af89 100644 --- a/x-pack/plugins/apm/server/routes/fleet.ts +++ b/x-pack/plugins/apm/server/routes/fleet.ts @@ -25,6 +25,8 @@ import { createCloudApmPackgePolicy } from '../lib/fleet/create_cloud_apm_packag import { getUnsupportedApmServerSchema } from '../lib/fleet/get_unsupported_apm_server_schema'; import { isSuperuser } from '../lib/fleet/is_superuser'; import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client'; +import { setupRequest } from '../lib/helpers/setup_request'; +import { createStaticIndexPattern } from '../lib/index_pattern/create_static_index_pattern'; const hasFleetDataRoute = createApmServerRoute({ endpoint: 'GET /api/apm/fleet/has_data', @@ -154,7 +156,7 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ endpoint: 'POST /api/apm/fleet/cloud_apm_package_policy', options: { tags: ['access:apm', 'access:apm_write'] }, handler: async (resources) => { - const { plugins, context, config, request, logger } = resources; + const { plugins, context, config, request, logger, core } = resources; const cloudApmMigrationEnabled = config['xpack.apm.agent.migrations.enabled']; if (!plugins.fleet || !plugins.security) { @@ -171,15 +173,34 @@ const createCloudApmPackagePolicyRoute = createApmServerRoute({ if (!hasRequiredRole || !cloudApmMigrationEnabled) { throw Boom.forbidden(CLOUD_SUPERUSER_REQUIRED_MESSAGE); } - return { - cloud_apm_package_policy: await createCloudApmPackgePolicy({ - cloudPluginSetup, - fleetPluginStart, - savedObjectsClient, - esClient, - logger, - }), - }; + + const cloudApmAackagePolicy = await createCloudApmPackgePolicy({ + cloudPluginSetup, + fleetPluginStart, + savedObjectsClient, + esClient, + logger, + }); + + const [setup, internalSavedObjectsClient] = await Promise.all([ + setupRequest(resources), + core + .start() + .then(({ savedObjects }) => savedObjects.createInternalRepository()), + ]); + + const spaceId = plugins.spaces?.setup.spacesService.getSpaceId(request); + + // force update the index pattern title with data streams + await createStaticIndexPattern( + setup, + config, + internalSavedObjectsClient, + spaceId, + true + ); + + return { cloud_apm_package_policy: cloudApmAackagePolicy }; }, }); From 937a4f381a592f605b1edc6a898de02e4831aadf Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Fri, 9 Jul 2021 17:20:10 -0500 Subject: [PATCH 29/36] [Security Solution] Enrichment details UI cleanup (#104995) * Remove the "view threat intel data" button from the alert summary This can be accomplished by clicking the tab itself; there's no real need for this button. * Remove section title from alert summary view This made sense when we had both alert and threat sections, but we no longer do. Removes the corresponding translation, and the analogously unused title from the defunct threat summary view. * Smaller spacer on alert summary tab This is distractingly large as compared to other tabs. * Move "no enrichments" panel below our threat details table * Remove old import * Move inspect button inline with rest of header * Add HR separator to top of NoEnrichmentsPanel This should arguably be added a level above so as to keep this panel context-agnostic, but it's currently only used in one place and will always require the HR, so YAGNI for now. * Adds more space between title and description on "no data" panel It has been suggested that the NoEnrichmentsPanel should be following the guidelines of the EuiEmptyPrompt. If we end up needing e.g. centered text, we're better off rewriting NoEnrichmentsPanelView in terms of an EuiEmptyPrompt. * StyledEuiInMemoryTable has no header row height We have never provided column names to this component. However, there is default padding on the thead tds such that even without content they take up vertical height. This has resulted in some extra top-margin on historical uses of this table (which are just the Alert Details views). However, the addition of a sibling table (ThreatSummaryView) made the extra margin noticable, since it made the two tables appear disjointed even though they're right up against each other. This fixes the issue by removing the padding, allowing the thead to take no height. And now that that space isn't taken up by the table header, we need to add a little bit of space between the header and table on the Threat Details view. * Move test to appropriate location The ThreatDetailsView is no longer responsible for displaying the "no data" components, that's now a level above in EventDetails. * Prune unused translations These have been changed in the latest designs. * Only add HR if panel is preceded by enrichments We do not want an HR if there's nothing above the panel. --- .../event_details/alert_summary_view.tsx | 2 +- .../empty_threat_details_view.test.tsx | 48 ---------- .../cti_details/empty_threat_details_view.tsx | 51 ----------- .../cti_details/no_enrichments_panel.test.tsx | 57 ++++++++++++ .../cti_details/no_enrichments_panel.tsx | 88 +++++++++++++++++++ .../cti_details/threat_details_view.test.tsx | 9 -- .../cti_details/threat_details_view.tsx | 18 ++-- .../event_details/cti_details/translations.ts | 34 ++++++- .../event_details/event_details.test.tsx | 7 ++ .../event_details/event_details.tsx | 53 +++++------ .../components/event_details/summary_view.tsx | 7 +- .../components/event_details/translations.ts | 12 --- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - 14 files changed, 222 insertions(+), 170 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 3a1a29b63eadf..329b8e32f057d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -213,7 +213,7 @@ const AlertSummaryViewComponent: React.FC<{ return ( <> - + { - const mount = useMountAppended(); - const mockTheme = getMockTheme({ - eui: { - euiBreakpoints: { - l: '1200px', - }, - paddingSizes: { - m: '8px', - xl: '32px', - }, - }, - }); - - test('renders correct items', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - - test('renders link to docs', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('a').exists()).toEqual(true); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx deleted file mode 100644 index d7e1c4d7754ec..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/empty_threat_details_view.tsx +++ /dev/null @@ -1,51 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiLink, EuiSpacer, EuiTitle } from '@elastic/eui'; -import React from 'react'; -import styled from 'styled-components'; - -import { useKibana } from '../../../lib/kibana'; -import * as i18n from './translations'; - -const EmptyThreatDetailsViewContainer = styled.div` - display: flex; - flex-direction: column; - align-items: center; -`; - -const Span = styled.span` - color: ${({ theme }) => theme.eui.euiColorDarkShade}; - line-height: 1.8em; - text-align: center; - padding: ${({ theme }) => `${theme.eui.paddingSizes.m} ${theme.eui.paddingSizes.xl}`}; -`; - -const EmptyThreatDetailsViewComponent: React.FC<{}> = () => { - const threatIntelDocsUrl = `${ - useKibana().services.docLinks.links.filebeat.base - }/filebeat-module-threatintel.html`; - - return ( - - - -

{i18n.NO_ENRICHMENT_FOUND}

-
- - {i18n.IF_CTI_NOT_ENABLED} - - {i18n.CHECK_DOCS} - - -
- ); -}; - -EmptyThreatDetailsViewComponent.displayName = 'EmptyThreatDetailsView'; - -export const EmptyThreatDetailsView = React.memo(EmptyThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx new file mode 100644 index 0000000000000..819c666bd7267 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx @@ -0,0 +1,57 @@ +/* + * 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 React from 'react'; +import { mount } from 'enzyme'; + +import { NoEnrichmentsPanel } from './no_enrichments_panel'; +import * as i18n from './translations'; + +jest.mock('../../../lib/kibana'); + +describe('NoEnrichmentsPanelView', () => { + it('renders a qualified container', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true); + }); + + it('renders nothing when all enrichments are present', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(false); + }); + + it('renders expected text when no enrichments are present', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_ENRICHMENTS_FOUND_TITLE + ); + }); + + it('renders expected text when existing enrichments are absent', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_INDICATOR_ENRICHMENTS_TITLE + ); + }); + + it('renders expected text when investigation enrichments are absent', () => { + const wrapper = mount( + + ); + expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain( + i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx new file mode 100644 index 0000000000000..b521c3ba92c4d --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx @@ -0,0 +1,88 @@ +/* + * 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 { EuiHorizontalRule, EuiLink, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { useKibana } from '../../../lib/kibana'; + +import * as i18n from './translations'; + +const Container = styled(EuiPanel)` + display: flex; + flex-direction: column; +`; + +const NoEnrichmentsPanelView: React.FC<{ + title: React.ReactNode; + description: React.ReactNode; +}> = ({ title, description }) => { + return ( + + {title} + + + {description} + + + ); +}; + +NoEnrichmentsPanelView.displayName = 'NoEnrichmentsPanelView'; + +export const NoEnrichmentsPanel: React.FC<{ + existingEnrichmentsCount: number; + investigationEnrichmentsCount: number; +}> = ({ existingEnrichmentsCount, investigationEnrichmentsCount }) => { + const threatIntelDocsUrl = `${ + useKibana().services.docLinks.links.filebeat.base + }/filebeat-module-threatintel.html`; + const noIndicatorEnrichmentsDescription = ( + <> + {i18n.IF_CTI_NOT_ENABLED} + + {i18n.CHECK_DOCS} + + + ); + + if (existingEnrichmentsCount === 0 && investigationEnrichmentsCount === 0) { + return ( + {i18n.NO_ENRICHMENTS_FOUND_TITLE}} + description={ + <> +

{noIndicatorEnrichmentsDescription}

+

{i18n.NO_INVESTIGATION_ENRICHMENTS_DESCRIPTION}

+ + } + /> + ); + } else if (existingEnrichmentsCount === 0) { + return ( + <> + + {i18n.NO_INDICATOR_ENRICHMENTS_TITLE}} + description={noIndicatorEnrichmentsDescription} + /> + + ); + } else if (investigationEnrichmentsCount === 0) { + return ( + <> + + {i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE}} + description={i18n.NO_INVESTIGATION_ENRICHMENTS_DESCRIPTION} + /> + + ); + } else { + return null; + } +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx index 0113dde96a4b6..c25457a5e5e88 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.test.tsx @@ -31,15 +31,6 @@ describe('ThreatDetailsView', () => { ); }); - it('renders an empty view if there are no enrichments', () => { - const wrapper = mount( - - - - ); - expect(wrapper.find('[data-test-subj="empty-threat-details-view"]').exists()).toEqual(true); - }); - it('renders anchor links for event.url and event.reference', () => { const enrichments = [ buildEventEnrichmentMock({ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx index d5e985c5757a6..b6b8a47c1dd8c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/threat_details_view.tsx @@ -20,7 +20,6 @@ import React, { Fragment } from 'react'; import { StyledEuiInMemoryTable } from '../summary_view'; import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from '../helpers'; -import { EmptyThreatDetailsView } from './empty_threat_details_view'; import { FIRSTSEEN, EVENT_URL, EVENT_REFERENCE } from '../../../../../common/cti/constants'; import { DEFAULT_INDICATOR_SOURCE_PATH } from '../../../../../common/constants'; import { getFirstElement } from '../../../../../common/utils/data_retrieval'; @@ -70,15 +69,13 @@ const ThreatDetailsHeader: React.FC<{ + {isInvestigationTimeEnrichment(type) && ( + + + + )}
- {isInvestigationTimeEnrichment(type) && ( - - - - - - )} ); @@ -131,10 +128,6 @@ const buildThreatDetailsItems = (enrichment: CtiEnrichment) => const ThreatDetailsViewComponent: React.FC<{ enrichments: CtiEnrichment[]; }> = ({ enrichments }) => { - if (enrichments.length < 1) { - return ; - } - const sortedEnrichments = enrichments.sort((a, b) => getFirstSeen(b) - getFirstSeen(a)); return ( @@ -146,6 +139,7 @@ const ThreatDetailsViewComponent: React.FC<{ return ( + { ).toEqual('Summary'); }); }); + + describe('threat intel tab', () => { + it('renders a "no enrichments" panel view if there are no enrichments', () => { + alertsWrapper.find('[data-test-subj="threatIntelTab"]').first().simulate('click'); + expect(alertsWrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 9afaaef61b17a..7074212dcdb4c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -9,9 +9,6 @@ import { EuiTabbedContent, EuiTabbedContentTab, EuiSpacer, - EuiButton, - EuiFlexGroup, - EuiFlexItem, EuiLoadingContent, EuiLoadingSpinner, } from '@elastic/eui'; @@ -34,6 +31,7 @@ import { parseExistingEnrichments, timelineDataToEnrichment, } from './cti_details/helpers'; +import { NoEnrichmentsPanel } from './cti_details/no_enrichments_panel'; type EventViewTab = EuiTabbedContentTab; @@ -100,9 +98,6 @@ const EventDetailsComponent: React.FC = ({ (tab: EuiTabbedContentTab) => setSelectedTabId(tab.id as EventViewId), [setSelectedTabId] ); - const viewThreatIntelTab = useCallback(() => setSelectedTabId(EventsViewType.threatIntelView), [ - setSelectedTabId, - ]); const eventFields = useMemo(() => getEnrichmentFields(data), [data]); const existingEnrichments = useMemo( @@ -118,6 +113,9 @@ const EventDetailsComponent: React.FC = ({ loading: enrichmentsLoading, result: enrichmentsResponse, } = useInvestigationTimeEnrichment(eventFields); + const investigationEnrichments = useMemo(() => enrichmentsResponse?.enrichments ?? [], [ + enrichmentsResponse?.enrichments, + ]); const allEnrichments = useMemo(() => { if (enrichmentsLoading || !enrichmentsResponse?.enrichments) { return existingEnrichments; @@ -140,29 +138,20 @@ const EventDetailsComponent: React.FC = ({ eventId: id, browserFields, timelineId, - title: i18n.ALERT_SUMMARY, }} /> + {enrichmentCount > 0 && ( + + )} {enrichmentsLoading && ( <> )} - {enrichmentCount > 0 && ( - <> - - - - - {i18n.VIEW_CTI_DATA} - - - - )} ), } @@ -176,7 +165,6 @@ const EventDetailsComponent: React.FC = ({ enrichmentsLoading, enrichmentCount, allEnrichments, - viewThreatIntelTab, ] ); @@ -192,10 +180,25 @@ const EventDetailsComponent: React.FC = ({ {enrichmentsLoading ? : `(${enrichmentCount})`} ), - content: , + content: ( + <> + + + + ), } : undefined, - [allEnrichments, enrichmentCount, enrichmentsLoading, isAlert] + [ + allEnrichments, + enrichmentCount, + enrichmentsLoading, + existingEnrichments.length, + investigationEnrichments.length, + isAlert, + ] ); const tableTab = useMemo( diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 0e846f3f6f699..961860ed6d8b9 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -13,12 +13,13 @@ import { SummaryRow } from './helpers'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` - .euiTableHeaderCell { - border: none; - } + .euiTableHeaderCell, .euiTableRowCell { border: none; } + .euiTableHeaderCell .euiTableCellContent { + padding: 0; + } `; const StyledEuiTitle = styled(EuiTitle)` diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index a17ca5e434ace..c632f5d6332e0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -11,22 +11,10 @@ export const SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.summa defaultMessage: 'Summary', }); -export const ALERT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.alertSummary', { - defaultMessage: 'Alert Summary', -}); - export const THREAT_INTEL = i18n.translate('xpack.securitySolution.alertDetails.threatIntel', { defaultMessage: 'Threat Intel', }); -export const THREAT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.threatSummary', { - defaultMessage: 'Threat Summary', -}); - -export const VIEW_CTI_DATA = i18n.translate('xpack.securitySolution.alertDetails.threatIntelCta', { - defaultMessage: 'View threat intel data', -}); - export const INVESTIGATION_GUIDE = i18n.translate( 'xpack.securitySolution.alertDetails.summary.investigationGuide', { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 387547295f047..2b1088d8c11ae 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -18562,16 +18562,13 @@ "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", "xpack.securitySolution.administration.os.windows": "Windows", - "xpack.securitySolution.alertDetails.alertSummary": "アラート概要", "xpack.securitySolution.alertDetails.checkDocs": "マニュアルをご確認ください。", "xpack.securitySolution.alertDetails.ifCtiNotEnabled": "脅威インテリジェンスソースを有効にしていない場合で、この機能について関心がある場合は、", - "xpack.securitySolution.alertDetails.noEnrichmentFound": "Threat Intel Enrichmentが見つかりません", "xpack.securitySolution.alertDetails.summary": "まとめ", "xpack.securitySolution.alertDetails.summary.investigationGuide": "調査ガイド", "xpack.securitySolution.alertDetails.summary.readLess": "表示を減らす", "xpack.securitySolution.alertDetails.summary.readMore": "続きを読む", "xpack.securitySolution.alertDetails.threatIntel": "Threat Intel", - "xpack.securitySolution.alertDetails.threatSummary": "脅威概要", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "このルールで生成されたすべてのアラートのリスクスコアを選択します。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "デフォルトリスクスコア", "xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel": "ソースイベント値を使用して、デフォルトリスクスコアを上書きします。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d142969022c81..04394a1ac1704 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -18826,16 +18826,13 @@ "xpack.securitySolution.administration.os.linux": "Linux", "xpack.securitySolution.administration.os.macos": "Mac", "xpack.securitySolution.administration.os.windows": "Windows", - "xpack.securitySolution.alertDetails.alertSummary": "告警摘要", "xpack.securitySolution.alertDetails.checkDocs": "请查看我们的文档。", "xpack.securitySolution.alertDetails.ifCtiNotEnabled": "如果尚未启用任何威胁情报来源,并希望更多了解此功能,", - "xpack.securitySolution.alertDetails.noEnrichmentFound": "未找到威胁情报扩充", "xpack.securitySolution.alertDetails.summary": "摘要", "xpack.securitySolution.alertDetails.summary.investigationGuide": "调查指南", "xpack.securitySolution.alertDetails.summary.readLess": "阅读更少内容", "xpack.securitySolution.alertDetails.summary.readMore": "阅读更多内容", "xpack.securitySolution.alertDetails.threatIntel": "威胁情报", - "xpack.securitySolution.alertDetails.threatSummary": "威胁摘要", "xpack.securitySolution.alerts.riskScoreMapping.defaultDescriptionLabel": "选择此规则生成的所有告警的风险分数。", "xpack.securitySolution.alerts.riskScoreMapping.defaultRiskScoreTitle": "默认风险分数", "xpack.securitySolution.alerts.riskScoreMapping.mappingDescriptionLabel": "使用源事件值覆盖默认风险分数。", From c07f51e5be9b16c58a4988491d8ae2ce021b4aba Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 9 Jul 2021 16:23:00 -0600 Subject: [PATCH 30/36] [Security Detections] Fixes ip on threshold preview button when selecting an ip data type such as source.ip (#105126) ## Summary See https://github.com/elastic/kibana/issues/100433 for details and test instructions. This is considered critical and a small fix for 7.14.0 has been requested. * Wrote Cypress test that exercises the bug * Fixed mutation in one part of the Cypress Test * Decided to remove the "missing" that we were telling users was "others" since missing is not the same as others. It no longer errors, but some users might be asking why we don't show "others" anymore. The reality is that we only showed "missing" which isn't adding value to the preview of what detections will end up looking like. * Later if we want a true "others" we should implement it as a larger feature request and not a bug fix IMHO Before you would get errors in your network panel: ![errors_threshold](https://user-images.githubusercontent.com/1151048/125126681-b0380e00-e0b8-11eb-9f2c-a75e2909754c.png) After you now get the `source.ip` without errors: Screen Shot 2021-07-09 at 1 28 24 PM ### 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 --- .../detection_rules/threshold_rule.spec.ts | 27 ++++++++++++++++--- .../cypress/tasks/create_new_rule.ts | 2 +- .../components/rules/query_preview/index.tsx | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index ad71d54eb2a7a..ce00c9b40aead 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -6,7 +6,7 @@ */ import { formatMitreAttackDescription } from '../../helpers/rules'; -import { indexPatterns, newRule, newThresholdRule } from '../../objects/rule'; +import { indexPatterns, newRule, newThresholdRule, ThresholdRule } from '../../objects/rule'; import { ALERT_RULE_METHOD, @@ -180,9 +180,9 @@ describe('Detection rules, threshold', () => { cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', rule.riskScore); }); - it('Preview results', () => { - const previewRule = { ...newThresholdRule }; - previewRule.index!.push('.siem-signals*'); + it('Preview results of keyword using "host.name"', () => { + const previewRule: ThresholdRule = { ...newThresholdRule }; + previewRule.index = [...previewRule.index, '.siem-signals*']; createCustomRuleActivated(newRule); goToManageAlertsDetectionRules(); @@ -194,4 +194,23 @@ describe('Detection rules, threshold', () => { cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits'); }); + + it('Preview results of "ip" using "source.ip"', () => { + const previewRule: ThresholdRule = { + ...newThresholdRule, + thresholdField: 'source.ip', + threshold: '1', + }; + previewRule.index = [...previewRule.index, '.siem-signals*']; + + createCustomRuleActivated(newRule); + goToManageAlertsDetectionRules(); + waitForRulesTableToBeLoaded(); + goToCreateNewRule(); + selectThresholdRuleType(); + fillDefineThresholdRule(previewRule); + previewResults(); + + cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '10 unique hits'); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 9b74110f0ef77..1b420cd6d1520 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -275,7 +275,7 @@ export const fillDefineThresholdRule = (rule: ThresholdRule) => { cy.get(TIMELINE(rule.timeline.id!)).click(); cy.get(COMBO_BOX_CLEAR_BTN).click(); - rule.index!.forEach((index) => { + rule.index.forEach((index) => { cy.get(COMBO_BOX_INPUT).first().type(`${index}{enter}`); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx index 6342d468f5962..45b66058a04fb 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx @@ -118,6 +118,7 @@ export const PreviewQuery = ({ startDate: toTime, filterQuery: queryFilter, indexNames: index, + includeMissingData: false, histogramType: MatrixHistogramType.events, stackByField: 'event.category', threshold: ruleType === 'threshold' ? threshold : undefined, From b40fc09dfca3c2e7072ba33f65c9c4ee7474764c Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 16:40:13 -0700 Subject: [PATCH 31/36] skip another suite blocking es promotion (#104466) --- test/functional/apps/discover/_large_string.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index de3f0f2c40ae1..ea219881c7a95 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -19,7 +19,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover']); - describe('test large strings', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); From d776c0940ee47098bef21a129a03445e5fbf3eda Mon Sep 17 00:00:00 2001 From: spalger Date: Fri, 9 Jul 2021 18:01:11 -0700 Subject: [PATCH 32/36] skip all discover functional tests to unblock es promotion (#104466) --- test/functional/apps/discover/_data_grid_field_data.ts | 3 +-- test/functional/apps/discover/_field_data.ts | 3 +-- test/functional/apps/discover/_field_data_with_fields_api.ts | 3 +-- test/functional/apps/discover/_large_string.ts | 3 +-- test/functional/apps/discover/index.ts | 3 ++- 5 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index cf8a6fc1bd60f..94e8e942f86ba 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const defaultSettings = { defaultIndex: 'logstash-*', 'doc_table:legacy': false }; const dataGrid = getService('dataGrid'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover data grid field data tests', function describeIndexTests() { + describe('discover data grid field data tests', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index 6471b751945a8..ec9f9cf65e0fa 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover tab', function describeIndexTests() { + describe('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_field_data_with_fields_api.ts b/test/functional/apps/discover/_field_data_with_fields_api.ts index 7c6867e935063..110e255d18c75 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -20,8 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'header', 'discover', 'visualize', 'timePicker']); const find = getService('find'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('discover tab with new fields API', function describeIndexTests() { + describe('discover tab with new fields API', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index ea219881c7a95..de3f0f2c40ae1 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -19,8 +19,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings', 'discover']); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 - describe.skip('test large strings', function () { + describe('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index b396f172f6961..a17bf53e7f478 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -12,7 +12,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('discover app', function () { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/104466 + describe.skip('discover app', function () { this.tags('ciGroup6'); before(function () { From c0fec48104a7d026802673723ffb8796211bc2e0 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Fri, 9 Jul 2021 20:55:20 -0600 Subject: [PATCH 33/36] [Security Solutions][Detection Engine] Fixes button group alignments in machine learning and tags (#105166) ## Summary See: https://github.com/elastic/kibana/issues/104055 For more issue details. This is deemed embarrassing enough to be critical for a fix for 7.14.0 before shipping. EUI looks to have updated its self and added a new attribute that it wants us to use called `numFIlters` which when set will show the total number of filter items before they are selected. Once selected the number and look and feel change. ```ts numFilters ``` Before: Screen Shot 2021-07-09 at 5 45 08 PM After before selections: Screen Shot 2021-07-09 at 5 48 43 PM After once you have selections: Screen Shot 2021-07-09 at 5 49 44 PM Before: Screen Shot 2021-07-09 at 5 42 01 PM After before selections: Screen Shot 2021-07-09 at 5 42 27 PM After once you have selections: Screen Shot 2021-07-09 at 5 49 36 PM ### 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 --- .../filters/__snapshots__/groups_filter_popover.test.tsx.snap | 1 + .../ml_popover/jobs_table/filters/groups_filter_popover.tsx | 1 + .../rules/all/rules_table_filters/tags_filter_popover.tsx | 2 ++ 3 files changed, 4 insertions(+) diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap index 22805d34d2ee1..410fb7f3ae793 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap @@ -10,6 +10,7 @@ exports[`GroupsFilterPopover renders correctly against snapshot 1`] = ` iconType="arrowDown" isSelected={false} numActiveFilters={0} + numFilters={3} onClick={[Function]} > Groups diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index b7425a62f6773..249dc0dfccdbb 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -59,6 +59,7 @@ export const GroupsFilterPopoverComponent = ({ iconType="arrowDown" onClick={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} isSelected={isGroupPopoverOpen} + numFilters={uniqueGroups.length} hasActiveFilters={selectedGroups.length > 0} numActiveFilters={selectedGroups.length} > diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx index 45ce5bc18361c..c5262caf6c776 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/tags_filter_popover.tsx @@ -102,9 +102,11 @@ const TagsFilterPopoverComponent = ({ ownFocus button={ setIsTagPopoverOpen(!isTagPopoverOpen)} + numFilters={tags.length} isSelected={isTagPopoverOpen} hasActiveFilters={selectedTags.length > 0} numActiveFilters={selectedTags.length} From 857dc9f2e1e1edd86c9cf7cd5f157e3e8b82a14a Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Sat, 10 Jul 2021 09:27:07 +0200 Subject: [PATCH 34/36] [APM] Don't log error if request was aborted (#105024) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/register_routes/index.test.ts | 23 ++++-- .../server/routes/register_routes/index.ts | 72 ++++++++++++------- 2 files changed, 64 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts index 158d7ee7e76a3..b9dece866fae5 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.test.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.test.ts @@ -109,6 +109,11 @@ const initApi = ( params: {}, query: {}, body: null, + events: { + aborted$: { + toPromise: () => new Promise(() => {}), + }, + }, ...request, }, responseMock @@ -202,7 +207,7 @@ describe('createApi', () => { describe('when validating', () => { describe('_inspect', () => { it('allows _inspect=true', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -234,7 +239,7 @@ describe('createApi', () => { }); it('rejects _inspect=1', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, @@ -267,7 +272,7 @@ describe('createApi', () => { }); it('allows omitting _inspect', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, @@ -297,7 +302,11 @@ describe('createApi', () => { simulateRequest, mocks: { response }, } = initApi([ - { endpoint: 'GET /foo', options: { tags: [] }, handler: jest.fn() }, + { + endpoint: 'GET /foo', + options: { tags: [] }, + handler: jest.fn().mockResolvedValue({}), + }, ]); await simulateRequest({ @@ -328,7 +337,7 @@ describe('createApi', () => { }); it('validates path parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -402,7 +411,7 @@ describe('createApi', () => { }); it('validates body parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, @@ -448,7 +457,7 @@ describe('createApi', () => { }); it('validates query parameters', async () => { - const handlerMock = jest.fn(); + const handlerMock = jest.fn().mockResolvedValue({}); const { simulateRequest, mocks: { response }, diff --git a/x-pack/plugins/apm/server/routes/register_routes/index.ts b/x-pack/plugins/apm/server/routes/register_routes/index.ts index 136f3c73d8046..8e6070de722be 100644 --- a/x-pack/plugins/apm/server/routes/register_routes/index.ts +++ b/x-pack/plugins/apm/server/routes/register_routes/index.ts @@ -29,6 +29,13 @@ const inspectRt = t.exact( }) ); +const CLIENT_CLOSED_REQUEST = { + statusCode: 499, + body: { + message: 'Client closed request', + }, +}; + export const inspectableEsQueriesMap = new WeakMap< KibanaRequest, InspectResponse @@ -89,23 +96,40 @@ export function registerRoutes({ runtimeType ); - const data: Record | undefined | null = (await handler({ - request, - context, - config, - logger, - core, - plugins, - params: merge( - { - query: { - _inspect: false, + const { aborted, data } = await Promise.race([ + handler({ + request, + context, + config, + logger, + core, + plugins, + params: merge( + { + query: { + _inspect: false, + }, }, - }, - validatedParams - ), - ruleDataClient, - })) as any; + validatedParams + ), + ruleDataClient, + }).then((value) => { + return { + aborted: false, + data: value as Record | undefined | null, + }; + }), + request.events.aborted$.toPromise().then(() => { + return { + aborted: true, + data: undefined, + }; + }), + ]); + + if (aborted) { + return response.custom(CLIENT_CLOSED_REQUEST); + } if (Array.isArray(data)) { throw new Error('Return type cannot be an array'); @@ -118,9 +142,6 @@ export function registerRoutes({ } : { ...data }; - // cleanup - inspectableEsQueriesMap.delete(request); - if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, @@ -131,6 +152,7 @@ export function registerRoutes({ return response.ok({ body }); } catch (error) { logger.error(error); + if (!options.disableTelemetry && telemetryUsageCounter) { telemetryUsageCounter.incrementCounter({ counterName: `${method.toUpperCase()} ${pathname}`, @@ -147,16 +169,18 @@ export function registerRoutes({ }, }; - if (Boom.isBoom(error)) { - opts.statusCode = error.output.statusCode; + if (error instanceof RequestAbortedError) { + return response.custom(merge(opts, CLIENT_CLOSED_REQUEST)); } - if (error instanceof RequestAbortedError) { - opts.statusCode = 499; - opts.body.message = 'Client closed request'; + if (Boom.isBoom(error)) { + opts.statusCode = error.output.statusCode; } return response.custom(opts); + } finally { + // cleanup + inspectableEsQueriesMap.delete(request); } } ); From e4ba52928c289547d97706b083bed1202be1176d Mon Sep 17 00:00:00 2001 From: "Joey F. Poon" Date: Sat, 10 Jul 2021 18:34:03 -0500 Subject: [PATCH 35/36] [Security Solution] remove query strategy v1 (#104196) --- .../common/endpoint/types/index.ts | 14 - .../management/pages/endpoint_hosts/mocks.ts | 4 - .../pages/endpoint_hosts/store/builders.ts | 1 - .../pages/endpoint_hosts/store/middleware.ts | 3 +- .../store/mock_endpoint_result_list.ts | 16 +- .../pages/endpoint_hosts/store/reducer.ts | 2 - .../pages/endpoint_hosts/store/selectors.ts | 7 - .../management/pages/endpoint_hosts/types.ts | 3 - .../pages/endpoint_hosts/view/index.test.tsx | 34 +- .../pages/endpoint_hosts/view/index.tsx | 9 +- .../endpoint/endpoint_app_context_services.ts | 54 --- .../endpoint/routes/actions/isolation.ts | 2 +- .../routes/metadata/enrichment.test.ts | 86 +--- .../endpoint/routes/metadata/handlers.ts | 85 +--- .../server/endpoint/routes/metadata/index.ts | 27 +- .../endpoint/routes/metadata/metadata.test.ts | 107 +--- .../routes/metadata/metadata_v1.test.ts | 456 ------------------ .../routes/metadata/query_builders.test.ts | 73 ++- .../routes/metadata/query_builders.ts | 24 +- .../routes/metadata/query_builders_v1.test.ts | 188 -------- .../metadata/support/query_strategies.ts | 123 ++--- .../routes/metadata/support/test_support.ts | 56 --- .../server/endpoint/services/metadata.ts | 13 +- .../server/endpoint/types.ts | 15 +- .../factory/hosts/details/helpers.ts | 14 +- .../apis/index.ts | 1 - .../apis/metadata.ts | 9 - .../apis/metadata_v1.ts | 290 ----------- 28 files changed, 124 insertions(+), 1592 deletions(-) delete mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts delete mode 100644 x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts delete mode 100644 x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index 1e0d798cf7f07..cf8c33d38f8fa 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -178,8 +178,6 @@ export interface HostResultList { request_page_size: number; /* the page index requested */ request_page_index: number; - /* the version of the query strategy */ - query_strategy_version: MetadataQueryStrategyVersions; /* policy IDs and versions */ policy_info?: HostInfo['policy_info']; } @@ -404,21 +402,11 @@ export enum HostStatus { INACTIVE = 'inactive', } -export enum MetadataQueryStrategyVersions { - VERSION_1 = 'v1', - VERSION_2 = 'v2', -} - export type PolicyInfo = Immutable<{ revision: number; id: string; }>; -export interface HostMetadataInfo { - metadata: HostMetadata; - query_strategy_version: MetadataQueryStrategyVersions; -} - export type HostInfo = Immutable<{ metadata: HostMetadata; host_status: HostStatus; @@ -438,8 +426,6 @@ export type HostInfo = Immutable<{ */ endpoint: PolicyInfo; }; - /* the version of the query strategy */ - query_strategy_version: MetadataQueryStrategyVersions; }>; // HostMetadataDetails is now just HostMetadata diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts index d6b24fa3cbdfc..76de52222bbd3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/mocks.ts @@ -15,7 +15,6 @@ import { HostPolicyResponse, HostResultList, HostStatus, - MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { @@ -54,7 +53,6 @@ export const endpointMetadataHttpMocks = httpHandlerMockFactory => { agentsWithEndpointsTotalError: undefined, endpointsTotal: 0, endpointsTotalError: undefined, - queryStrategyVersion: undefined, policyVersionInfo: undefined, hostStatus: undefined, isolationRequestState: createUninitialisedResourceState(), diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts index f233fbdec5415..922f10cee2f8b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/middleware.ts @@ -28,7 +28,6 @@ import { nonExistingPolicies, patterns, searchBarQuery, - isTransformEnabled, getIsIsolationRequestPending, getCurrentIsolationRequestState, getActivityLogData, @@ -180,7 +179,7 @@ export const endpointMiddlewareFactory: ImmutableMiddlewareFactory HostResultList = (options = {}) => { const { total = 1, request_page_size: requestPageSize = 10, request_page_index: requestPageIndex = 0, - query_strategy_version: queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, } = options; // Skip any that are before the page we're on @@ -58,7 +55,6 @@ export const mockEndpointResultList: (options?: { hosts.push({ metadata: generator.generateHostMetadata(), host_status: HostStatus.UNHEALTHY, - query_strategy_version: queryStrategyVersion, }); } const mock: HostResultList = { @@ -66,7 +62,6 @@ export const mockEndpointResultList: (options?: { total, request_page_size: requestPageSize, request_page_index: requestPageIndex, - query_strategy_version: queryStrategyVersion, }; return mock; }; @@ -78,7 +73,6 @@ export const mockEndpointDetailsApiResult = (): HostInfo => { return { metadata: generator.generateHostMetadata(), host_status: HostStatus.UNHEALTHY, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_2, }; }; @@ -92,7 +86,6 @@ const endpointListApiPathHandlerMocks = ({ endpointPackagePolicies = [], policyResponse = generator.generatePolicyResponse(), agentPolicy = generator.generateAgentPolicy(), - queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, totalAgentsUsingEndpoint = 0, }: { /** route handlers will be setup for each individual host in this array */ @@ -101,7 +94,6 @@ const endpointListApiPathHandlerMocks = ({ endpointPackagePolicies?: GetPolicyListResponse['items']; policyResponse?: HostPolicyResponse; agentPolicy?: GetAgentPoliciesResponseItem; - queryStrategyVersion?: MetadataQueryStrategyVersions; totalAgentsUsingEndpoint?: number; } = {}) => { const apiHandlers = { @@ -119,7 +111,6 @@ const endpointListApiPathHandlerMocks = ({ request_page_size: 10, request_page_index: 0, total: endpointsResults?.length || 0, - query_strategy_version: queryStrategyVersion, }; }, @@ -192,16 +183,11 @@ export const setEndpointListApiMockImplementation: ( apiResponses?: Parameters[0] ) => void = ( mockedHttpService, - { - endpointsResults = mockEndpointResultList({ total: 3 }).hosts, - queryStrategyVersion = MetadataQueryStrategyVersions.VERSION_2, - ...pathHandlersOptions - } = {} + { endpointsResults = mockEndpointResultList({ total: 3 }).hosts, ...pathHandlersOptions } = {} ) => { const apiHandlers = endpointListApiPathHandlerMocks({ ...pathHandlersOptions, endpointsResults, - queryStrategyVersion, }); mockedHttpService.post diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts index 1498ce08db8ab..c6bf13a3b5715 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts @@ -89,7 +89,6 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta total, request_page_size: pageSize, request_page_index: pageIndex, - query_strategy_version: queryStrategyVersion, policy_info: policyVersionInfo, } = action.payload; return { @@ -98,7 +97,6 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta total, pageSize, pageIndex, - queryStrategyVersion, policyVersionInfo, loading: false, error: undefined, diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts index 5771fbac957d8..4287cf9a109ea 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts @@ -15,7 +15,6 @@ import { HostPolicyResponseAppliedAction, HostPolicyResponseConfiguration, HostPolicyResponseActionStatus, - MetadataQueryStrategyVersions, HostStatus, ActivityLog, HostMetadata, @@ -90,17 +89,11 @@ export const agentsWithEndpointsTotalError = (state: Immutable) = state.agentsWithEndpointsTotalError; export const endpointsTotalError = (state: Immutable) => state.endpointsTotalError; -const queryStrategyVersion = (state: Immutable) => state.queryStrategyVersion; export const endpointPackageVersion = createSelector(endpointPackageInfo, (info) => isLoadedResourceState(info) ? info.data.version : undefined ); -export const isTransformEnabled = createSelector( - queryStrategyVersion, - (version) => version !== MetadataQueryStrategyVersions.VERSION_1 -); - /** * Returns the index patterns for the SearchBar to use for autosuggest */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts index 144cc7a64d6cb..875841cb55b73 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts @@ -13,7 +13,6 @@ import { HostPolicyResponse, AppLocation, PolicyData, - MetadataQueryStrategyVersions, HostStatus, HostIsolationResponse, EndpointPendingActions, @@ -96,8 +95,6 @@ export interface EndpointState { endpointsTotal: number; /** api error for total, actual Endpoints */ endpointsTotalError?: ServerApiError; - /** The query strategy version that informs whether the transform for KQL is enabled or not */ - queryStrategyVersion?: MetadataQueryStrategyVersions; /** The policy IDs and revision number of the corresponding agent, and endpoint. May be more recent than what's running */ policyVersionInfo?: HostInfo['policy_info']; /** The status of the host, which is mapped to the Elastic Agent status in Fleet */ diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index aafac38accd89..26d0d53e39982 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -23,7 +23,6 @@ import { HostPolicyResponseActionStatus, HostPolicyResponseAppliedAction, HostStatus, - MetadataQueryStrategyVersions, } from '../../../../../common/endpoint/types'; import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_data'; import { POLICY_STATUS_TO_TEXT } from './host_constants'; @@ -167,31 +166,6 @@ describe('when on the endpoint list page', () => { }); }); - describe('when loading data with the query_strategy_version is `v1`', () => { - beforeEach(() => { - reactTestingLibrary.act(() => { - const mockedEndpointListData = mockEndpointResultList({ - total: 4, - query_strategy_version: MetadataQueryStrategyVersions.VERSION_1, - }); - setEndpointListApiMockImplementation(coreStart.http, { - endpointsResults: mockedEndpointListData.hosts, - queryStrategyVersion: mockedEndpointListData.query_strategy_version, - }); - }); - }); - afterEach(() => { - jest.clearAllMocks(); - }); - it('should not display the KQL bar', async () => { - const renderResult = render(); - await reactTestingLibrary.act(async () => { - await middlewareSpy.waitForAction('serverReturnedEndpointList'); - }); - expect(renderResult.queryByTestId('adminSearchBar')).toBeNull(); - }); - }); - describe('when determining when to show the enrolling message', () => { afterEach(() => { jest.clearAllMocks(); @@ -268,7 +242,6 @@ describe('when on the endpoint list page', () => { reactTestingLibrary.act(() => { const mockedEndpointData = mockEndpointResultList({ total: 5 }); const hostListData = mockedEndpointData.hosts; - const queryStrategyVersion = mockedEndpointData.query_strategy_version; firstPolicyID = hostListData[0].metadata.Endpoint.policy.applied.id; firstPolicyRev = hostListData[0].metadata.Endpoint.policy.applied.endpoint_policy_version; @@ -329,7 +302,6 @@ describe('when on the endpoint list page', () => { hostListData[index].metadata.Endpoint.policy.applied, setup.policy ), - query_strategy_version: queryStrategyVersion, }; }); hostListData.forEach((item, index) => { @@ -535,8 +507,6 @@ describe('when on the endpoint list page', () => { // eslint-disable-next-line @typescript-eslint/naming-convention host_status, metadata: { agent, Endpoint, ...details }, - // eslint-disable-next-line @typescript-eslint/naming-convention - query_strategy_version, } = mockEndpointDetailsApiResult(); hostDetails = { @@ -555,7 +525,6 @@ describe('when on the endpoint list page', () => { id: '1', }, }, - query_strategy_version, }; const policy = docGenerator.generatePolicyPackagePolicy(); @@ -1198,7 +1167,7 @@ describe('when on the endpoint list page', () => { let renderResult: ReturnType; const mockEndpointListApi = () => { - const { hosts, query_strategy_version: queryStrategyVersion } = mockEndpointResultList(); + const { hosts } = mockEndpointResultList(); hostInfo = { host_status: hosts[0].host_status, metadata: { @@ -1222,7 +1191,6 @@ describe('when on the endpoint list page', () => { version: '7.14.0', }, }, - query_strategy_version: queryStrategyVersion, }; const packagePolicy = docGenerator.generatePolicyPackagePolicy(); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx index 0ee345431055b..c78d4ca6af634 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx @@ -120,7 +120,6 @@ export const EndpointList = () => { areEndpointsEnrolling, agentsWithEndpointsTotalError, endpointsTotalError, - isTransformEnabled, } = useEndpointSelector(selector); const { search } = useFormatUrl(SecurityPageName.administration); const { getAppUrl } = useAppUrl(); @@ -476,8 +475,8 @@ export const EndpointList = () => { const hasListData = listData && listData.length > 0; const refreshStyle = useMemo(() => { - return { display: endpointsExist && isTransformEnabled ? 'flex' : 'none', maxWidth: 200 }; - }, [endpointsExist, isTransformEnabled]); + return { display: endpointsExist ? 'flex' : 'none', maxWidth: 200 }; + }, [endpointsExist]); const refreshIsPaused = useMemo(() => { return !endpointsExist ? false : hasSelectedEndpoint ? true : !isAutoRefreshEnabled; @@ -492,8 +491,8 @@ export const EndpointList = () => { }, [endpointsTotalError, agentsWithEndpointsTotalError]); const shouldShowKQLBar = useMemo(() => { - return endpointsExist && !patternsError && isTransformEnabled; - }, [endpointsExist, patternsError, isTransformEnabled]); + return endpointsExist && !patternsError; + }, [endpointsExist, patternsError]); return ( ; -} - -export const createMetadataService = (packageService: PackageService): MetadataService => { - return { - async queryStrategy( - savedObjectsClient: SavedObjectsClientContract, - version?: MetadataQueryStrategyVersions - ): Promise { - if (version === MetadataQueryStrategyVersions.VERSION_1) { - return metadataQueryStrategyV1(); - } - if (!packageService) { - throw new Error('package service is uninitialized'); - } - - if (version === MetadataQueryStrategyVersions.VERSION_2 || !version) { - const assets = - (await packageService.getInstallation({ savedObjectsClient, pkgName: 'endpoint' })) - ?.installed_es ?? []; - const expectedTransformAssets = assets.filter( - (ref) => - ref.type === ElasticsearchAssetType.transform && - ref.id.startsWith(metadataTransformPrefix) - ); - if (expectedTransformAssets && expectedTransformAssets.length === 1) { - return metadataQueryStrategyV2(); - } - return metadataQueryStrategyV1(); - } - return metadataQueryStrategyV1(); - }, - }; -}; - export type EndpointAppContextServiceStartContract = Partial< Pick< FleetStartContract, @@ -114,7 +66,6 @@ export class EndpointAppContextService { private packagePolicyService: PackagePolicyServiceInterface | undefined; private agentPolicyService: AgentPolicyServiceInterface | undefined; private savedObjectsStart: SavedObjectsServiceStart | undefined; - private metadataService: MetadataService | undefined; private config: ConfigType | undefined; private license: LicenseService | undefined; public security: SecurityPluginStart | undefined; @@ -128,7 +79,6 @@ export class EndpointAppContextService { this.agentPolicyService = dependencies.agentPolicyService; this.manifestManager = dependencies.manifestManager; this.savedObjectsStart = dependencies.savedObjectsStart; - this.metadataService = createMetadataService(dependencies.packageService!); this.config = dependencies.config; this.license = dependencies.licenseService; this.security = dependencies.security; @@ -176,10 +126,6 @@ export class EndpointAppContextService { return this.agentPolicyService; } - public getMetadataService(): MetadataService | undefined { - return this.metadataService; - } - public getManifestManager(): ManifestManager | undefined { return this.manifestManager; } 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 45063ca92e2b0..fceb45b17c258 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 @@ -83,7 +83,7 @@ export const isolationRequestHandler = function ( // fetch the Agent IDs to send the commands to const endpointIDs = [...new Set(req.body.endpoint_ids)]; // dedupe - const endpointData = await getMetadataForEndpoints(endpointIDs, context, endpointContext); + const endpointData = await getMetadataForEndpoints(endpointIDs, context); const casesClient = await endpointContext.service.getCasesClient(req); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts index 960f3abda8195..39aa0bf2d8cf7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/enrichment.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { HostStatus, MetadataQueryStrategyVersions } from '../../../../common/endpoint/types'; +import { HostStatus } from '../../../../common/endpoint/types'; import { createMockMetadataRequestContext } from '../../mocks'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { enrichHostMetadata, MetadataRequestContext } from './handlers'; @@ -18,30 +18,6 @@ describe('test document enrichment', () => { metaReqCtx = createMockMetadataRequestContext(); }); - // verify query version passed through - describe('metadata query strategy enrichment', () => { - it('should match v1 strategy when directed', async () => { - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_1 - ); - expect(enrichedHostList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - it('should match v2 strategy when directed', async () => { - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); - expect(enrichedHostList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); - }); - }); - describe('host status enrichment', () => { let statusFn: jest.Mock; @@ -57,77 +33,49 @@ describe('test document enrichment', () => { it('should return host healthy for online agent', async () => { statusFn.mockImplementation(() => 'online'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.HEALTHY); }); it('should return host offline for offline agent', async () => { statusFn.mockImplementation(() => 'offline'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.OFFLINE); }); it('should return host updating for unenrolling agent', async () => { statusFn.mockImplementation(() => 'unenrolling'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UPDATING); }); it('should return host unhealthy for degraded agent', async () => { statusFn.mockImplementation(() => 'degraded'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); it('should return host unhealthy for erroring agent', async () => { statusFn.mockImplementation(() => 'error'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); it('should return host unhealthy for warning agent', async () => { statusFn.mockImplementation(() => 'warning'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); it('should return host unhealthy for invalid agent', async () => { statusFn.mockImplementation(() => 'asliduasofb'); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.host_status).toEqual(HostStatus.UNHEALTHY); }); }); @@ -164,11 +112,7 @@ describe('test document enrichment', () => { }; }); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.policy_info).toBeDefined(); expect(enrichedHostList.policy_info!.agent.applied.id).toEqual(policyID); expect(enrichedHostList.policy_info!.agent.applied.revision).toEqual(policyRev); @@ -184,11 +128,7 @@ describe('test document enrichment', () => { }; }); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.policy_info).toBeDefined(); expect(enrichedHostList.policy_info!.agent.configured.id).toEqual(policyID); expect(enrichedHostList.policy_info!.agent.configured.revision).toEqual(policyRev); @@ -209,11 +149,7 @@ describe('test document enrichment', () => { }; }); - const enrichedHostList = await enrichHostMetadata( - docGen.generateHostMetadata(), - metaReqCtx, - MetadataQueryStrategyVersions.VERSION_2 - ); + const enrichedHostList = await enrichHostMetadata(docGen.generateHostMetadata(), metaReqCtx); expect(enrichedHostList.policy_info).toBeDefined(); expect(enrichedHostList.policy_info!.endpoint.id).toEqual(policyID); expect(enrichedHostList.policy_info!.endpoint.revision).toEqual(policyRev); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts index 815f30e6e7426..2ceca170881e3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/handlers.ts @@ -17,10 +17,8 @@ import { import { HostInfo, HostMetadata, - HostMetadataInfo, HostResultList, HostStatus, - MetadataQueryStrategyVersions, } from '../../../../common/endpoint/types'; import type { SecuritySolutionRequestHandlerContext } from '../../../types'; @@ -33,6 +31,10 @@ import { findAllUnenrolledAgentIds } from './support/unenroll'; import { findAgentIDsByStatus } from './support/agent_status'; import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { fleetAgentStatusToEndpointHostStatus } from '../../utils'; +import { + queryResponseToHostListResult, + queryResponseToHostResult, +} from './support/query_strategies'; export interface MetadataRequestContext { esClient?: IScopedClusterClient; @@ -58,8 +60,7 @@ export const getLogger = (endpointAppContext: EndpointAppContext): Logger => { export const getMetadataListRequestHandler = function ( endpointAppContext: EndpointAppContext, - logger: Logger, - queryStrategyVersion?: MetadataQueryStrategyVersions + logger: Logger ): RequestHandler< unknown, unknown, @@ -96,24 +97,15 @@ export const getMetadataListRequestHandler = function ( ) : undefined; - const queryStrategy = await endpointAppContext.service - ?.getMetadataService() - ?.queryStrategy(context.core.savedObjects.client, queryStrategyVersion); - - const queryParams = await kibanaRequestToMetadataListESQuery( - request, - endpointAppContext, - queryStrategy!, - { - unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), - statusAgentIDs: statusIDs, - } - ); + const queryParams = await kibanaRequestToMetadataListESQuery(request, endpointAppContext, { + unenrolledAgentIds: unenrolledAgentIds.concat(IGNORED_ELASTIC_AGENT_IDS), + statusAgentIDs: statusIDs, + }); const result = await context.core.elasticsearch.client.asCurrentUser.search( queryParams ); - const hostListQueryResult = queryStrategy!.queryResponseToHostListResult(result.body); + const hostListQueryResult = queryResponseToHostListResult(result.body); return response.ok({ body: await mapToHostResultList(queryParams, hostListQueryResult, metadataRequestContext), }); @@ -122,8 +114,7 @@ export const getMetadataListRequestHandler = function ( export const getMetadataRequestHandler = function ( endpointAppContext: EndpointAppContext, - logger: Logger, - queryStrategyVersion?: MetadataQueryStrategyVersions + logger: Logger ): RequestHandler< TypeOf, unknown, @@ -145,11 +136,7 @@ export const getMetadataRequestHandler = function ( }; try { - const doc = await getHostData( - metadataRequestContext, - request?.params?.id, - queryStrategyVersion - ); + const doc = await getHostData(metadataRequestContext, request?.params?.id); if (doc) { return response.ok({ body: doc }); } @@ -169,9 +156,8 @@ export const getMetadataRequestHandler = function ( export async function getHostMetaData( metadataRequestContext: MetadataRequestContext, - id: string, - queryStrategyVersion?: MetadataQueryStrategyVersions -): Promise { + id: string +): Promise { if ( !metadataRequestContext.esClient && !metadataRequestContext.requestHandlerContext?.core.elasticsearch.client @@ -190,32 +176,23 @@ export async function getHostMetaData( metadataRequestContext.requestHandlerContext?.core.elasticsearch .client) as IScopedClusterClient; - const esSavedObjectClient = - metadataRequestContext?.savedObjectsClient ?? - (metadataRequestContext.requestHandlerContext?.core.savedObjects - .client as SavedObjectsClientContract); - - const queryStrategy = await metadataRequestContext.endpointAppContextService - ?.getMetadataService() - ?.queryStrategy(esSavedObjectClient, queryStrategyVersion); - const query = getESQueryHostMetadataByID(id, queryStrategy!); + const query = getESQueryHostMetadataByID(id); const response = await esClient.asCurrentUser.search(query); - const hostResult = queryStrategy!.queryResponseToHostResult(response.body); + const hostResult = queryResponseToHostResult(response.body); const hostMetadata = hostResult.result; if (!hostMetadata) { return undefined; } - return { metadata: hostMetadata, query_strategy_version: hostResult.queryStrategyVersion }; + return hostMetadata; } export async function getHostData( metadataRequestContext: MetadataRequestContext, - id: string, - queryStrategyVersion?: MetadataQueryStrategyVersions + id: string ): Promise { if (!metadataRequestContext.savedObjectsClient) { throw Boom.badRequest('savedObjectsClient not found'); @@ -228,25 +205,21 @@ export async function getHostData( throw Boom.badRequest('esClient not found'); } - const hostResult = await getHostMetaData(metadataRequestContext, id, queryStrategyVersion); + const hostMetadata = await getHostMetaData(metadataRequestContext, id); - if (!hostResult) { + if (!hostMetadata) { return undefined; } - const agent = await findAgent(metadataRequestContext, hostResult.metadata); + const agent = await findAgent(metadataRequestContext, hostMetadata); if (agent && !agent.active) { throw Boom.badRequest('the requested endpoint is unenrolled'); } - const metadata = await enrichHostMetadata( - hostResult.metadata, - metadataRequestContext, - hostResult.query_strategy_version - ); + const metadata = await enrichHostMetadata(hostMetadata, metadataRequestContext); - return { ...metadata, query_strategy_version: hostResult.query_strategy_version }; + return metadata; } async function findAgent( @@ -293,15 +266,10 @@ export async function mapToHostResultList( request_page_index: queryParams.from, hosts: await Promise.all( hostListQueryResult.resultList.map(async (entry) => - enrichHostMetadata( - entry, - metadataRequestContext, - hostListQueryResult.queryStrategyVersion - ) + enrichHostMetadata(entry, metadataRequestContext) ) ), total: totalNumberOfHosts, - query_strategy_version: hostListQueryResult.queryStrategyVersion, }; } else { return { @@ -309,15 +277,13 @@ export async function mapToHostResultList( request_page_index: queryParams.from, total: totalNumberOfHosts, hosts: [], - query_strategy_version: hostListQueryResult.queryStrategyVersion, }; } } export async function enrichHostMetadata( hostMetadata: HostMetadata, - metadataRequestContext: MetadataRequestContext, - metadataQueryStrategyVersion: MetadataQueryStrategyVersions + metadataRequestContext: MetadataRequestContext ): Promise { let hostStatus = HostStatus.UNHEALTHY; let elasticAgentId = hostMetadata?.elastic?.agent?.id; @@ -413,6 +379,5 @@ export async function enrichHostMetadata( metadata: hostMetadata, host_status: hostStatus, policy_info: policyInfo, - query_strategy_version: metadataQueryStrategyVersion, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts index b4784c1ff5ed4..d9c3e6c195307 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts @@ -7,19 +7,15 @@ import { schema } from '@kbn/config-schema'; -import { HostStatus, MetadataQueryStrategyVersions } from '../../../../common/endpoint/types'; +import { HostStatus } from '../../../../common/endpoint/types'; import { EndpointAppContext } from '../../types'; import { getLogger, getMetadataListRequestHandler, getMetadataRequestHandler } from './handlers'; import type { SecuritySolutionPluginRouter } from '../../../types'; import { - BASE_ENDPOINT_ROUTE, HOST_METADATA_GET_ROUTE, HOST_METADATA_LIST_ROUTE, } from '../../../../common/endpoint/constants'; -export const METADATA_REQUEST_V1_ROUTE = `${BASE_ENDPOINT_ROUTE}/v1/metadata`; -export const GET_METADATA_REQUEST_V1_ROUTE = `${METADATA_REQUEST_V1_ROUTE}/{id}`; - /* Filters that can be applied to the endpoint fetch route */ export const endpointFilters = schema.object({ kql: schema.nullable(schema.string()), @@ -69,18 +65,6 @@ export function registerEndpointRoutes( endpointAppContext: EndpointAppContext ) { const logger = getLogger(endpointAppContext); - router.post( - { - path: `${METADATA_REQUEST_V1_ROUTE}`, - validate: GetMetadataListRequestSchema, - options: { authRequired: true, tags: ['access:securitySolution'] }, - }, - getMetadataListRequestHandler( - endpointAppContext, - logger, - MetadataQueryStrategyVersions.VERSION_1 - ) - ); router.post( { @@ -91,15 +75,6 @@ export function registerEndpointRoutes( getMetadataListRequestHandler(endpointAppContext, logger) ); - router.get( - { - path: `${GET_METADATA_REQUEST_V1_ROUTE}`, - validate: GetMetadataRequestSchema, - options: { authRequired: true, tags: ['access:securitySolution'] }, - }, - getMetadataRequestHandler(endpointAppContext, logger, MetadataQueryStrategyVersions.VERSION_1) - ); - router.get( { path: `${HOST_METADATA_GET_ROUTE}`, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 5250f7c49d6ad..1e56f79aa0b32 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -19,12 +19,7 @@ import { loggingSystemMock, savedObjectsClientMock, } from '../../../../../../../src/core/server/mocks'; -import { - HostInfo, - HostResultList, - HostStatus, - MetadataQueryStrategyVersions, -} from '../../../../common/endpoint/types'; +import { HostInfo, HostResultList, HostStatus } from '../../../../common/endpoint/types'; import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; import { registerEndpointRoutes } from './index'; import { @@ -39,7 +34,7 @@ import { import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; import { Agent, ElasticsearchAssetType } from '../../../../../fleet/common/types/models'; -import { createV1SearchResponse, createV2SearchResponse } from './support/test_support'; +import { createV2SearchResponse } from './support/test_support'; import { PackageService } from '../../../../../fleet/server/services'; import { HOST_METADATA_LIST_ROUTE, @@ -98,94 +93,6 @@ describe('test endpoint route', () => { ); }); - describe('with no transform package', () => { - beforeEach(() => { - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue(Promise.resolve(undefined)); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - - it('test find the latest of all endpoints', async () => { - const mockRequest = httpServerMock.createKibanaRequest({}); - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${HOST_METADATA_LIST_ROUTE}`) - )!; - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(0); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - it('should return a single endpoint with status healthy', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('online'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${HOST_METADATA_LIST_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result).toHaveProperty('metadata.Endpoint'); - expect(result.host_status).toEqual(HostStatus.HEALTHY); - expect(result.query_strategy_version).toEqual(MetadataQueryStrategyVersions.VERSION_1); - }); - }); - describe('with new transform package', () => { beforeEach(() => { endpointAppContextService = new EndpointAppContextService(); @@ -254,9 +161,6 @@ describe('test endpoint route', () => { expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(0); expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); }); it('test find the latest of all endpoints with paging properties', async () => { @@ -311,9 +215,6 @@ describe('test endpoint route', () => { expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); }); it('test find the latest of all endpoints with paging and filters properties', async () => { @@ -405,9 +306,6 @@ describe('test endpoint route', () => { expect(endpointResultList.total).toEqual(1); expect(endpointResultList.request_page_index).toEqual(10); expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_2 - ); }); describe('Endpoint Details route', () => { @@ -475,7 +373,6 @@ describe('test endpoint route', () => { const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; expect(result).toHaveProperty('metadata.Endpoint'); expect(result.host_status).toEqual(HostStatus.HEALTHY); - expect(result.query_strategy_version).toEqual(MetadataQueryStrategyVersions.VERSION_2); }); it('should return a single endpoint with status unhealthy when AgentService throw 404', async () => { diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts deleted file mode 100644 index 29b2c231cc4a5..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ /dev/null @@ -1,456 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { - KibanaResponseFactory, - RequestHandler, - RouteConfig, - SavedObjectsClientContract, - SavedObjectsErrorHelpers, -} from '../../../../../../../src/core/server'; -import { - ClusterClientMock, - ScopedClusterClientMock, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../../src/core/server/elasticsearch/client/mocks'; -import { - elasticsearchServiceMock, - httpServerMock, - httpServiceMock, - loggingSystemMock, - savedObjectsClientMock, -} from '../../../../../../../src/core/server/mocks'; -import { - HostInfo, - HostResultList, - HostStatus, - MetadataQueryStrategyVersions, -} from '../../../../common/endpoint/types'; -import { registerEndpointRoutes, METADATA_REQUEST_V1_ROUTE } from './index'; -import { - createMockEndpointAppContextServiceStartContract, - createMockPackageService, - createRouteHandlerContext, -} from '../../mocks'; -import { - EndpointAppContextService, - EndpointAppContextServiceStartContract, -} from '../../endpoint_app_context_services'; -import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; -import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data'; -import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; -import { Agent } from '../../../../../fleet/common/types/models'; -import { createV1SearchResponse } from './support/test_support'; -import { PackageService } from '../../../../../fleet/server/services'; -import type { SecuritySolutionPluginRouter } from '../../../types'; -import { PackagePolicyServiceInterface } from '../../../../../fleet/server'; - -describe('test endpoint route v1', () => { - let routerMock: jest.Mocked; - let mockResponse: jest.Mocked; - let mockClusterClient: ClusterClientMock; - let mockScopedClient: ScopedClusterClientMock; - let mockSavedObjectClient: jest.Mocked; - let mockPackageService: jest.Mocked; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let routeHandler: RequestHandler; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let routeConfig: RouteConfig; - // tests assume that fleet is enabled, and thus agentService is available - let mockAgentService: Required< - ReturnType - >['agentService']; - let endpointAppContextService: EndpointAppContextService; - let startContract: EndpointAppContextServiceStartContract; - const noUnenrolledAgent = { - agents: [], - total: 0, - page: 1, - perPage: 1, - }; - - beforeEach(() => { - mockClusterClient = elasticsearchServiceMock.createClusterClient(); - mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); - mockSavedObjectClient = savedObjectsClientMock.create(); - mockClusterClient.asScoped.mockReturnValue(mockScopedClient); - routerMock = httpServiceMock.createRouter(); - mockResponse = httpServerMock.createResponseFactory(); - endpointAppContextService = new EndpointAppContextService(); - mockPackageService = createMockPackageService(); - mockPackageService.getInstallation.mockReturnValue(Promise.resolve(undefined)); - startContract = createMockEndpointAppContextServiceStartContract(); - endpointAppContextService.start({ ...startContract, packageService: mockPackageService }); - mockAgentService = startContract.agentService!; - - (startContract.packagePolicyService as jest.Mocked).list.mockImplementation( - () => { - return Promise.resolve({ - items: [], - total: 0, - page: 1, - perPage: 1000, - }); - } - ); - - registerEndpointRoutes(routerMock, { - logFactory: loggingSystemMock.create(), - service: endpointAppContextService, - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }); - }); - - afterEach(() => endpointAppContextService.stop()); - - it('test find the latest of all endpoints', async () => { - const mockRequest = httpServerMock.createKibanaRequest({}); - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(0); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - it('test find the latest of all endpoints with paging properties', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 1, - }, - ], - }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ - body: createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()), - }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect( - (mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool - .must_not - ).toContainEqual({ - terms: { - 'elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }); - expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(10); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - it('test find the latest of all endpoints with paging and filters properties', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 1, - }, - ], - - filters: { kql: 'not host.ip:10.140.73.246' }, - }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.listAgents = jest.fn().mockReturnValue(noUnenrolledAgent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ - body: createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()), - }) - ); - [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toBeCalled(); - // needs to have the KQL filter passed through - expect( - (mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must - ).toContainEqual({ - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - }); - // and unenrolled should be filtered out. - expect( - (mockScopedClient.asCurrentUser.search as jest.Mock).mock.calls[0][0]?.body?.query.bool.must - ).toContainEqual({ - bool: { - must_not: [ - { - terms: { - 'elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }, - { - terms: { - // we actually don't care about HostDetails in v1 queries, but - // harder to set up the expectation to ignore its inclusion succinctly - 'HostDetails.elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }, - ], - }, - }); - expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); - expect(mockResponse.ok).toBeCalled(); - const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as HostResultList; - expect(endpointResultList.hosts.length).toEqual(1); - expect(endpointResultList.total).toEqual(1); - expect(endpointResultList.request_page_index).toEqual(10); - expect(endpointResultList.request_page_size).toEqual(10); - expect(endpointResultList.query_strategy_version).toEqual( - MetadataQueryStrategyVersions.VERSION_1 - ); - }); - - describe('Endpoint Details route', () => { - it('should return 404 on no results', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ params: { id: 'BADID' } }); - - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: createV1SearchResponse() }) - ); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('error'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.notFound).toBeCalled(); - const message = mockResponse.notFound.mock.calls[0][0]?.body; - expect(message).toEqual('Endpoint Not Found'); - }); - - it('should return a single endpoint with status healthy', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('online'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result).toHaveProperty('metadata.Endpoint'); - expect(result.host_status).toEqual(HostStatus.HEALTHY); - }); - - it('should return a single endpoint with status unhealthy when AgentService throw 404', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockImplementation(() => { - SavedObjectsErrorHelpers.createGenericNotFoundError(); - }); - - mockAgentService.getAgent = jest.fn().mockImplementation(() => { - SavedObjectsErrorHelpers.createGenericNotFoundError(); - }); - - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result.host_status).toEqual(HostStatus.UNHEALTHY); - }); - - it('should return a single endpoint with status unhealthy when status is not offline, online or enrolling', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - - mockAgentService.getAgentStatusById = jest.fn().mockReturnValue('warning'); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: true, - } as unknown) as Agent); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(routeConfig.options).toEqual({ - authRequired: true, - tags: ['access:securitySolution'], - }); - expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; - expect(result.host_status).toEqual(HostStatus.UNHEALTHY); - }); - - it('should throw error when endpoint agent is not active', async () => { - const response = createV1SearchResponse(new EndpointDocGenerator().generateHostMetadata()); - - const mockRequest = httpServerMock.createKibanaRequest({ - params: { id: response.hits.hits[0]._id }, - }); - (mockScopedClient.asCurrentUser.search as jest.Mock).mockImplementationOnce(() => - Promise.resolve({ body: response }) - ); - mockAgentService.getAgent = jest.fn().mockReturnValue(({ - active: false, - } as unknown) as Agent); - - [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) => - path.startsWith(`${METADATA_REQUEST_V1_ROUTE}`) - )!; - - await routeHandler( - createRouteHandlerContext(mockScopedClient, mockSavedObjectClient), - mockRequest, - mockResponse - ); - - expect(mockScopedClient.asCurrentUser.search).toHaveBeenCalledTimes(1); - expect(mockResponse.customError).toBeCalled(); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts index e790c1de1a5b8..87de5a540ea99 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts @@ -11,38 +11,29 @@ import { EndpointAppContextService } from '../../endpoint_app_context_services'; import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; import { metadataCurrentIndexPattern } from '../../../../common/endpoint/constants'; import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; -import { metadataQueryStrategyV2 } from './support/query_strategies'; import { get } from 'lodash'; describe('query builder', () => { describe('MetadataListESQuery', () => { it('queries the correct index', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.index).toEqual(metadataCurrentIndexPattern); }); it('sorts using *event.created', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.body.sort).toContainEqual({ 'event.created': { order: 'desc', @@ -61,16 +52,12 @@ describe('query builder', () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {}, }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.body.query).toHaveProperty('match_all'); }); @@ -87,7 +74,6 @@ describe('query builder', () => { config: () => Promise.resolve(createMockConfig()), experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), }, - metadataQueryStrategyV2(), { unenrolledAgentIds: [unenrolledElasticAgentId], } @@ -111,16 +97,12 @@ describe('query builder', () => { filters: { kql: 'not host.ip:10.140.73.246' }, }, }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV2() - ); + const query = await kibanaRequestToMetadataListESQuery(mockRequest, { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), + }); expect(query.body.query.bool.must).toContainEqual({ bool: { @@ -160,7 +142,6 @@ describe('query builder', () => { createMockConfig().enableExperimental ), }, - metadataQueryStrategyV2(), { unenrolledAgentIds: [unenrolledElasticAgentId], } @@ -197,13 +178,13 @@ describe('query builder', () => { describe('MetadataGetQuery', () => { it('searches the correct index', () => { - const query = getESQueryHostMetadataByID('nonsense-id', metadataQueryStrategyV2()); + const query = getESQueryHostMetadataByID('nonsense-id'); expect(query.index).toEqual(metadataCurrentIndexPattern); }); it('searches for the correct ID', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2()); + const query = getESQueryHostMetadataByID(mockID); expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({ term: { 'agent.id': mockID }, @@ -212,7 +193,7 @@ describe('query builder', () => { it('supports HostDetails in schema for backwards compat', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2()); + const query = getESQueryHostMetadataByID(mockID); expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({ term: { 'HostDetails.agent.id': mockID }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts index f0950e5fb79ba..99ec1d1022747 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts @@ -6,9 +6,10 @@ */ import type { estypes } from '@elastic/elasticsearch'; +import { metadataCurrentIndexPattern } from '../../../../common/endpoint/constants'; import { KibanaRequest } from '../../../../../../../src/core/server'; import { esKuery } from '../../../../../../../src/plugins/data/server'; -import { EndpointAppContext, MetadataQueryStrategy } from '../../types'; +import { EndpointAppContext } from '../../types'; export interface QueryBuilderOptions { unenrolledAgentIds?: string[]; @@ -39,7 +40,6 @@ export async function kibanaRequestToMetadataListESQuery( // eslint-disable-next-line @typescript-eslint/no-explicit-any request: KibanaRequest, endpointAppContext: EndpointAppContext, - metadataQueryStrategy: MetadataQueryStrategy, queryBuilderOptions?: QueryBuilderOptions // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise> { @@ -49,16 +49,15 @@ export async function kibanaRequestToMetadataListESQuery( body: { query: buildQueryBody( request, - metadataQueryStrategy, queryBuilderOptions?.unenrolledAgentIds!, queryBuilderOptions?.statusAgentIDs! ), - ...metadataQueryStrategy.extraBodyProperties, + track_total_hits: true, sort: MetadataSortMethod, }, from: pagingProperties.pageIndex * pagingProperties.pageSize, size: pagingProperties.pageSize, - index: metadataQueryStrategy.index, + index: metadataCurrentIndexPattern, }; } @@ -86,7 +85,6 @@ async function getPagingProperties( function buildQueryBody( // eslint-disable-next-line @typescript-eslint/no-explicit-any request: KibanaRequest, - metadataQueryStrategy: MetadataQueryStrategy, unerolledAgentIds: string[] | undefined, statusAgentIDs: string[] | undefined // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -144,10 +142,7 @@ function buildQueryBody( }; } -export function getESQueryHostMetadataByID( - agentID: string, - metadataQueryStrategy: MetadataQueryStrategy -): estypes.SearchRequest { +export function getESQueryHostMetadataByID(agentID: string): estypes.SearchRequest { return { body: { query: { @@ -167,14 +162,11 @@ export function getESQueryHostMetadataByID( sort: MetadataSortMethod, size: 1, }, - index: metadataQueryStrategy.index, + index: metadataCurrentIndexPattern, }; } -export function getESQueryHostMetadataByIDs( - agentIDs: string[], - metadataQueryStrategy: MetadataQueryStrategy -) { +export function getESQueryHostMetadataByIDs(agentIDs: string[]) { return { body: { query: { @@ -193,6 +185,6 @@ export function getESQueryHostMetadataByIDs( }, sort: MetadataSortMethod, }, - index: metadataQueryStrategy.index, + index: metadataCurrentIndexPattern, }; } diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts deleted file mode 100644 index c18c585cd3d34..0000000000000 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts +++ /dev/null @@ -1,188 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { httpServerMock, loggingSystemMock } from '../../../../../../../src/core/server/mocks'; -import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; -import { EndpointAppContextService } from '../../endpoint_app_context_services'; -import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__'; -import { metadataIndexPattern } from '../../../../common/endpoint/constants'; -import { parseExperimentalConfigValue } from '../../../../common/experimental_features'; -import { metadataQueryStrategyV1 } from './support/query_strategies'; -import { get } from 'lodash'; - -describe('query builder v1', () => { - describe('MetadataListESQuery', () => { - it('test default query params for all endpoints metadata when no params or body is provided', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: {}, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV1() - ); - - expect(query.body.query).toHaveProperty('match_all'); // no filtering - expect(query.body.collapse).toEqual({ - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }); - expect(query.body.aggs).toEqual({ - total: { - cardinality: { - field: 'agent.id', - }, - }, - }); - expect(query.index).toEqual(metadataIndexPattern); - }); - - it( - 'test default query params for all endpoints metadata when no params or body is provided ' + - 'with unenrolled host ids excluded', - async () => { - const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; - const mockRequest = httpServerMock.createKibanaRequest({ - body: {}, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue( - createMockConfig().enableExperimental - ), - }, - metadataQueryStrategyV1(), - { - unenrolledAgentIds: [unenrolledElasticAgentId], - } - ); - expect(Object.keys(query.body.query.bool)).toEqual(['must_not']); // only filtering out unenrolled - expect(query.body.query.bool.must_not).toContainEqual({ - terms: { 'elastic.agent.id': [unenrolledElasticAgentId] }, - }); - } - ); - }); - - describe('test query builder with kql filter', () => { - it('test default query params for all endpoints metadata when body filter is provided', async () => { - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - filters: { kql: 'not host.ip:10.140.73.246' }, - }, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue(createMockConfig().enableExperimental), - }, - metadataQueryStrategyV1() - ); - expect(query.body.query.bool.must).toHaveLength(1); // should not be any other filtering happening - expect(query.body.query.bool.must).toContainEqual({ - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - }); - }); - - it( - 'test default query params for all endpoints endpoint metadata excluding unerolled endpoint ' + - 'and when body filter is provided', - async () => { - const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; - const mockRequest = httpServerMock.createKibanaRequest({ - body: { - filters: { kql: 'not host.ip:10.140.73.246' }, - }, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - experimentalFeatures: parseExperimentalConfigValue( - createMockConfig().enableExperimental - ), - }, - metadataQueryStrategyV1(), - { - unenrolledAgentIds: [unenrolledElasticAgentId], - } - ); - - expect(query.body.query.bool.must.length).toBeGreaterThan(1); - // unenrollment filter should be there - expect(query.body.query.bool.must).toContainEqual({ - bool: { - must_not: [ - { terms: { 'elastic.agent.id': [unenrolledElasticAgentId] } }, - // below is not actually necessary behavior for v1, but hard to structure the test to ignore it - { terms: { 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId] } }, - ], - }, - }); - // and KQL should also be there - expect(query.body.query.bool.must).toContainEqual({ - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, - }, - }); - } - ); - }); - - describe('MetadataGetQuery', () => { - it('searches for the correct ID', () => { - const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; - const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV1()); - - expect(get(query, 'body.query.bool.filter.0.bool.should')).toContainEqual({ - term: { 'agent.id': mockID }, - }); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts index 506c02fc2f1ec..2d7bff4a53f3f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts @@ -6,102 +6,39 @@ */ import { SearchResponse } from '@elastic/elasticsearch/api/types'; -import { - metadataCurrentIndexPattern, - metadataIndexPattern, -} from '../../../../../common/endpoint/constants'; -import { HostMetadata, MetadataQueryStrategyVersions } from '../../../../../common/endpoint/types'; -import { HostListQueryResult, HostQueryResult, MetadataQueryStrategy } from '../../../types'; +import { HostMetadata } from '../../../../../common/endpoint/types'; +import { HostListQueryResult, HostQueryResult } from '../../../types'; -export function metadataQueryStrategyV1(): MetadataQueryStrategy { - return { - index: metadataIndexPattern, - extraBodyProperties: { - collapse: { - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }, - aggs: { - total: { - cardinality: { - field: 'agent.id', - }, - }, - }, - }, - queryResponseToHostListResult: ( - searchResponse: SearchResponse - ): HostListQueryResult => { - const response = searchResponse as SearchResponse; - return { - resultLength: - ((response?.aggregations?.total as unknown) as { value?: number; relation: string }) - ?.value || 0, - resultList: response.hits.hits - .map((hit) => hit.inner_hits?.most_recent.hits.hits) - .flatMap((data) => data) - .map((entry) => (entry?._source ?? {}) as HostMetadata), - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_1, - }; - }, - queryResponseToHostResult: (searchResponse: SearchResponse): HostQueryResult => { - const response = searchResponse as SearchResponse; - return { - resultLength: response.hits.hits.length, - result: response.hits.hits.length > 0 ? response.hits.hits[0]._source : undefined, - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_1, - }; - }, - }; +// remove the top-level 'HostDetails' property if found, from previous schemas +function stripHostDetails(host: HostMetadata | { HostDetails: HostMetadata }): HostMetadata { + return 'HostDetails' in host ? host.HostDetails : host; } -export function metadataQueryStrategyV2(): MetadataQueryStrategy { +export const queryResponseToHostResult = ( + searchResponse: SearchResponse +): HostQueryResult => { + const response = searchResponse as SearchResponse; return { - index: metadataCurrentIndexPattern, - extraBodyProperties: { - track_total_hits: true, - }, - queryResponseToHostListResult: ( - searchResponse: SearchResponse - ): HostListQueryResult => { - const response = searchResponse as SearchResponse< - HostMetadata | { HostDetails: HostMetadata } - >; - const list = - response.hits.hits.length > 0 - ? response.hits.hits.map((entry) => stripHostDetails(entry?._source as HostMetadata)) - : []; - - return { - resultLength: - ((response.hits?.total as unknown) as { value: number; relation: string }).value || 0, - resultList: list, - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2, - }; - }, - queryResponseToHostResult: ( - searchResponse: SearchResponse - ): HostQueryResult => { - const response = searchResponse as SearchResponse< - HostMetadata | { HostDetails: HostMetadata } - >; - return { - resultLength: response.hits.hits.length, - result: - response.hits.hits.length > 0 - ? stripHostDetails(response.hits.hits[0]._source as HostMetadata) - : undefined, - queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2, - }; - }, + resultLength: response.hits.hits.length, + result: + response.hits.hits.length > 0 + ? stripHostDetails(response.hits.hits[0]._source as HostMetadata) + : undefined, }; -} +}; -// remove the top-level 'HostDetails' property if found, from previous schemas -function stripHostDetails(host: HostMetadata | { HostDetails: HostMetadata }): HostMetadata { - return 'HostDetails' in host ? host.HostDetails : host; -} +export const queryResponseToHostListResult = ( + searchResponse: SearchResponse +): HostListQueryResult => { + const response = searchResponse as SearchResponse; + const list = + response.hits.hits.length > 0 + ? response.hits.hits.map((entry) => stripHostDetails(entry?._source as HostMetadata)) + : []; + + return { + resultLength: + ((response.hits?.total as unknown) as { value: number; relation: string }).value || 0, + resultList: list, + }; +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts index bc23c253c4347..a0530590f5f9f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts @@ -8,62 +8,6 @@ import { SearchResponse } from 'elasticsearch'; import { HostMetadata } from '../../../../../common/endpoint/types'; -export function createV1SearchResponse(hostMetadata?: HostMetadata): SearchResponse { - return ({ - took: 15, - timed_out: false, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - hits: { - total: { - value: 5, - relation: 'eq', - }, - max_score: null, - hits: hostMetadata - ? [ - { - _index: 'metrics-endpoint.metadata-default', - _id: '8FhM0HEBYyRTvb6lOQnw', - _score: null, - _source: hostMetadata, - sort: [1588337587997], - inner_hits: { - most_recent: { - hits: { - total: { - value: 2, - relation: 'eq', - }, - max_score: null, - hits: [ - { - _index: 'metrics-endpoint.metadata-default', - _id: 'W6Vo1G8BYQH1gtPUgYkC', - _score: null, - _source: hostMetadata, - sort: [1579816615336], - }, - ], - }, - }, - }, - }, - ] - : [], - }, - aggregations: { - total: { - value: 1, - }, - }, - } as unknown) as SearchResponse; -} - export function createV2SearchResponse(hostMetadata?: HostMetadata): SearchResponse { return ({ took: 15, diff --git a/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts index 0ca1983aa68d5..1a5515d8122f1 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/metadata.ts @@ -10,20 +10,15 @@ import { SearchResponse } from 'elasticsearch'; import { HostMetadata } from '../../../common/endpoint/types'; import { SecuritySolutionRequestHandlerContext } from '../../types'; import { getESQueryHostMetadataByIDs } from '../routes/metadata/query_builders'; -import { EndpointAppContext } from '../types'; +import { queryResponseToHostListResult } from '../routes/metadata/support/query_strategies'; export async function getMetadataForEndpoints( endpointIDs: string[], - requestHandlerContext: SecuritySolutionRequestHandlerContext, - endpointAppContext: EndpointAppContext + requestHandlerContext: SecuritySolutionRequestHandlerContext ): Promise { - const queryStrategy = await endpointAppContext.service - ?.getMetadataService() - ?.queryStrategy(requestHandlerContext.core.savedObjects.client); - - const query = getESQueryHostMetadataByIDs(endpointIDs, queryStrategy!); + const query = getESQueryHostMetadataByIDs(endpointIDs); const esClient = requestHandlerContext.core.elasticsearch.client.asCurrentUser; const { body } = await esClient.search(query as SearchRequest); - const hosts = queryStrategy!.queryResponseToHostListResult(body as SearchResponse); + const hosts = queryResponseToHostListResult(body as SearchResponse); return hosts.resultList; } diff --git a/x-pack/plugins/security_solution/server/endpoint/types.ts b/x-pack/plugins/security_solution/server/endpoint/types.ts index 6076aa9af635b..bc52b759b9f0a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/types.ts @@ -7,11 +7,9 @@ import { LoggerFactory } from 'kibana/server'; -import { SearchResponse } from '@elastic/elasticsearch/api/types'; -import { JsonObject } from '@kbn/common-utils'; import { ConfigType } from '../config'; import { EndpointAppContextService } from './endpoint_app_context_services'; -import { HostMetadata, MetadataQueryStrategyVersions } from '../../common/endpoint/types'; +import { HostMetadata } from '../../common/endpoint/types'; import { ExperimentalFeatures } from '../../common/experimental_features'; /** @@ -31,20 +29,9 @@ export interface EndpointAppContext { export interface HostListQueryResult { resultLength: number; resultList: HostMetadata[]; - queryStrategyVersion: MetadataQueryStrategyVersions; } export interface HostQueryResult { resultLength: number; result: HostMetadata | undefined; - queryStrategyVersion: MetadataQueryStrategyVersions; -} - -export interface MetadataQueryStrategy { - index: string; - extraBodyProperties?: JsonObject; - queryResponseToHostListResult: ( - searchResponse: SearchResponse - ) => HostListQueryResult; - queryResponseToHostResult: (searchResponse: SearchResponse) => HostQueryResult; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index f4d942f733c1d..9b9f49a167397 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -199,10 +199,10 @@ export const getHostEndpoint = async ( }; const endpointData = id != null && metadataRequestContext.endpointAppContextService.getAgentService() != null - ? await getHostMetaData(metadataRequestContext, id, undefined) + ? await getHostMetaData(metadataRequestContext, id) : null; - const fleetAgentId = endpointData?.metadata.elastic.agent.id; + const fleetAgentId = endpointData?.elastic.agent.id; const [fleetAgentStatus, pendingActions] = !fleetAgentId ? [undefined, {}] : await Promise.all([ @@ -214,13 +214,13 @@ export const getHostEndpoint = async ( }), ]); - return endpointData != null && endpointData.metadata + return endpointData != null && endpointData ? { - endpointPolicy: endpointData.metadata.Endpoint.policy.applied.name, - policyStatus: endpointData.metadata.Endpoint.policy.applied.status, - sensorVersion: endpointData.metadata.agent.version, + endpointPolicy: endpointData.Endpoint.policy.applied.name, + policyStatus: endpointData.Endpoint.policy.applied.status, + sensorVersion: endpointData.agent.version, elasticAgentStatus: fleetAgentStatusToEndpointHostStatus(fleetAgentStatus!), - isolation: endpointData.metadata.Endpoint.state?.isolation ?? false, + isolation: endpointData.Endpoint.state?.isolation ?? false, pendingActions, } : null; diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts index 1a52bd18f80af..e1763b6ad4404 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/index.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/index.ts @@ -29,7 +29,6 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider }); loadTestFile(require.resolve('./resolver/index')); loadTestFile(require.resolve('./metadata')); - loadTestFile(require.resolve('./metadata_v1')); loadTestFile(require.resolve('./policy')); loadTestFile(require.resolve('./package')); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index b5d98c115d194..1f57cd1b6db34 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -12,7 +12,6 @@ import { deleteAllDocsFromMetadataIndex, deleteMetadataStream, } from './data_stream_helper'; -import { MetadataQueryStrategyVersions } from '../../../plugins/security_solution/common/endpoint/types'; import { HOST_METADATA_LIST_ROUTE } from '../../../plugins/security_solution/common/endpoint/constants'; /** @@ -88,7 +87,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(1); expect(body.request_page_size).to.eql(1); expect(body.request_page_index).to.eql(1); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); /* test that when paging properties produces no result, the total should reflect the actual number of metadata @@ -113,7 +111,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(0); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(30); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return 400 when pagingProperties is below boundaries.', async () => { @@ -148,7 +145,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(2); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return page based on filters and paging passed.', async () => { @@ -186,7 +182,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(2); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return page based on host.os.Ext.variant filter.', async () => { @@ -208,7 +203,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(2); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return the latest event for all the events for an endpoint', async () => { @@ -231,7 +225,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(1); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return the latest event for all the events where policy status is not success', async () => { @@ -275,7 +268,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(1); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); it('metadata api should return all hosts when filter is empty string', async () => { @@ -292,7 +284,6 @@ export default function ({ getService }: FtrProviderContext) { expect(body.hosts.length).to.eql(numberOfHostsInFixture); expect(body.request_page_size).to.eql(10); expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_2); }); }); }); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts deleted file mode 100644 index d8cf1a11fac0a..0000000000000 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata_v1.ts +++ /dev/null @@ -1,290 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../ftr_provider_context'; -import { deleteMetadataStream } from './data_stream_helper'; -import { METADATA_REQUEST_V1_ROUTE } from '../../../plugins/security_solution/server/endpoint/routes/metadata'; -import { MetadataQueryStrategyVersions } from '../../../plugins/security_solution/common/endpoint/types'; - -/** - * The number of host documents in the es archive. - */ -const numberOfHostsInFixture = 3; - -export default function ({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - describe('test metadata api v1', () => { - describe(`POST ${METADATA_REQUEST_V1_ROUTE} when index is empty`, () => { - it('metadata api should return empty result when index is empty', async () => { - // the endpoint uses data streams and es archiver does not support deleting them at the moment so we need - // to do it manually - await deleteMetadataStream(getService); - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send() - .expect(200); - expect(body.total).to.eql(0); - expect(body.hosts.length).to.eql(0); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - }); - - describe(`POST ${METADATA_REQUEST_V1_ROUTE} when index is not empty`, () => { - before( - async () => - await esArchiver.load( - 'x-pack/test/functional/es_archives/endpoint/metadata/api_feature', - { useCreate: true } - ) - ); - // the endpoint uses data streams and es archiver does not support deleting them at the moment so we need - // to do it manually - after(async () => await deleteMetadataStream(getService)); - it('metadata api should return one entry for each host with default paging', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send() - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(numberOfHostsInFixture); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return page based on paging properties passed.', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 1, - }, - { - page_index: 1, - }, - ], - }) - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(1); - expect(body.request_page_size).to.eql(1); - expect(body.request_page_index).to.eql(1); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - /* test that when paging properties produces no result, the total should reflect the actual number of metadata - in the index. - */ - it('metadata api should return accurate total metadata if page index produces no result', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 3, - }, - ], - }) - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(0); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(30); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return 400 when pagingProperties is below boundaries.', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 0, - }, - { - page_index: 1, - }, - ], - }) - .expect(400); - expect(body.message).to.contain('Value must be equal to or greater than [1]'); - }); - - it('metadata api should return page based on filters passed.', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: 'not host.ip:10.46.229.234', - }, - }) - .expect(200); - expect(body.total).to.eql(2); - expect(body.hosts.length).to.eql(2); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return page based on filters and paging passed.', async () => { - const notIncludedIp = '10.46.229.234'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - paging_properties: [ - { - page_size: 10, - }, - { - page_index: 0, - }, - ], - filters: { - kql: `not host.ip:${notIncludedIp}`, - }, - }) - .expect(200); - expect(body.total).to.eql(2); - const resultIps: string[] = [].concat( - ...body.hosts.map((hostInfo: Record) => hostInfo.metadata.host.ip) - ); - expect(resultIps).to.eql([ - '10.192.213.130', - '10.70.28.129', - '10.101.149.26', - '2606:a000:ffc0:39:11ef:37b9:3371:578c', - ]); - expect(resultIps).not.include.eql(notIncludedIp); - expect(body.hosts.length).to.eql(2); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return page based on host.os.Ext.variant filter.', async () => { - const variantValue = 'Windows Pro'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `host.os.Ext.variant:${variantValue}`, - }, - }) - .expect(200); - expect(body.total).to.eql(2); - const resultOsVariantValue: Set = new Set( - body.hosts.map((hostInfo: Record) => hostInfo.metadata.host.os.Ext.variant) - ); - expect(Array.from(resultOsVariantValue)).to.eql([variantValue]); - expect(body.hosts.length).to.eql(2); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return the latest event for all the events for an endpoint', async () => { - const targetEndpointIp = '10.46.229.234'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `host.ip:${targetEndpointIp}`, - }, - }) - .expect(200); - expect(body.total).to.eql(1); - const resultIp: string = body.hosts[0].metadata.host.ip.filter( - (ip: string) => ip === targetEndpointIp - ); - expect(resultIp).to.eql([targetEndpointIp]); - expect(body.hosts[0].metadata.event.created).to.eql(1618841405309); - expect(body.hosts.length).to.eql(1); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return the latest event for all the events where policy status is not success', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `not Endpoint.policy.applied.status:success`, - }, - }) - .expect(200); - const statuses: Set = new Set( - body.hosts.map( - (hostInfo: Record) => hostInfo.metadata.Endpoint.policy.applied.status - ) - ); - expect(statuses.size).to.eql(1); - expect(Array.from(statuses)).to.eql(['failure']); - }); - - it('metadata api should return the endpoint based on the elastic agent id, and status should be unhealthy', async () => { - const targetEndpointId = 'fc0ff548-feba-41b6-8367-65e8790d0eaf'; - const targetElasticAgentId = '023fa40c-411d-4188-a941-4147bfadd095'; - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: `elastic.agent.id:${targetElasticAgentId}`, - }, - }) - .expect(200); - expect(body.total).to.eql(1); - const resultHostId: string = body.hosts[0].metadata.host.id; - const resultElasticAgentId: string = body.hosts[0].metadata.elastic.agent.id; - expect(resultHostId).to.eql(targetEndpointId); - expect(resultElasticAgentId).to.eql(targetElasticAgentId); - expect(body.hosts[0].metadata.event.created).to.eql(1618841405309); - expect(body.hosts[0].host_status).to.eql('unhealthy'); - expect(body.hosts.length).to.eql(1); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - - it('metadata api should return all hosts when filter is empty string', async () => { - const { body } = await supertest - .post(`${METADATA_REQUEST_V1_ROUTE}`) - .set('kbn-xsrf', 'xxx') - .send({ - filters: { - kql: '', - }, - }) - .expect(200); - expect(body.total).to.eql(numberOfHostsInFixture); - expect(body.hosts.length).to.eql(numberOfHostsInFixture); - expect(body.request_page_size).to.eql(10); - expect(body.request_page_index).to.eql(0); - expect(body.query_strategy_version).to.eql(MetadataQueryStrategyVersions.VERSION_1); - }); - }); - }); -} From b2b57a240423e9503d1307075d34c73eb8b2a3c9 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Mon, 12 Jul 2021 00:02:20 -0700 Subject: [PATCH 36/36] [dev-docs] Add debugging tutorial (#104468) Signed-off-by: Tyler Smalley Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/tutorials/debugging.mdx | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 dev_docs/tutorials/debugging.mdx diff --git a/dev_docs/tutorials/debugging.mdx b/dev_docs/tutorials/debugging.mdx new file mode 100644 index 0000000000000..c0efd249be066 --- /dev/null +++ b/dev_docs/tutorials/debugging.mdx @@ -0,0 +1,61 @@ +--- +id: kibDevTutorialDebugging +slug: /kibana-dev-docs/tutorial/debugging +title: Debugging in development +summary: Learn how to debug Kibana while running from source +date: 2021-04-26 +tags: ['kibana', 'onboarding', 'dev', 'tutorials', 'debugging'] +--- + +There are multiple ways to go about debugging Kibana when running from source. + +## Debugging using Chrome DevTools + +You will need to run Node using `--inspect` or `--inspect-brk` in order to enable the inspector. Additional information can be found in the [Node.js docs](https://nodejs.org/en/docs/guides/debugging-getting-started/). + +Once Node is running with the inspector enabled, you can open `chrome://inspect` in your Chrome browser. You should see a remote target for the inspector running. Click "inspect". You can now begin using the debugger. + +Next we will go over how to exactly enable the inspector for different aspects of the codebase. + +### Jest Unit Tests + +You will need to run Jest directly from the Node script: + +`node --inspect-brk scripts/jest [TestPathPattern]` + +### Functional Test Runner + +`node --inspect-brk scripts/functional_test_runner` + +### Development Server + +`node --inspect-brk scripts/kibana` + +## Debugging using logging + +When running Kibana, it's sometimes helpful to enable verbose logging. + +`yarn start --verbose` + +Using verbose logging usually results in much more information than you're interested in. The [logging documentation](https://www.elastic.co/guide/en/kibana/current/logging-settings.html) covers ways to change the log level of certain types. + +In the following example of a configuration stored in `config/kibana.dev.yml` we are logging all Elasticsearch queries and any logs created by the Management plugin. + +``` +logging: + appenders: + console: + type: console + layout: + type: pattern + highlight: true + root: + appenders: [default, console] + level: info + + loggers: + - name: plugins.management + level: debug + - name: elasticsearch.query + level: debug +``` \ No newline at end of file