From 4a8067bd58fe520a3ede059ca1f426f61cbc2215 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Wed, 21 Apr 2021 07:55:09 -0700 Subject: [PATCH 01/65] [DOCS] Updates notifications lifetime advanced settings descriptions (#97692) --- docs/management/advanced-options.asciidoc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 02cb25078cc926..853180ec816e97 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -321,20 +321,19 @@ https://help.github.com/en/articles/basic-writing-and-formatting-syntax[Markdown [[notifications-lifetime-banner]]`notifications:lifetime:banner`:: The duration, in milliseconds, for banner notification displays. The default -value is 3000000. Set this field to `Infinity` to disable banner notifications. +value is 3000000. [[notificatios-lifetime-error]]`notifications:lifetime:error`:: The duration, in milliseconds, for error notification displays. The default -value is 300000. Set this field to `Infinity` to disable error notifications. +value is 300000. [[notifications-lifetime-info]]`notifications:lifetime:info`:: The duration, in milliseconds, for information notification displays. The -default value is 5000. Set this field to `Infinity` to disable information -notifications. +default value is 5000. [[notifications-lifetime-warning]]`notifications:lifetime:warning`:: The duration, in milliseconds, for warning notification displays. The default -value is 10000. Set this field to `Infinity` to disable warning notifications. +value is 10000. [float] From 103fea456a411bdfc26614e9bcd6ee2a74416e3f Mon Sep 17 00:00:00 2001 From: Pete Hampton Date: Wed, 21 Apr 2021 16:09:02 +0100 Subject: [PATCH 02/65] [7.13][Security Solution] Fetch detection adoption metrics (#97789) * pushing initial experiments. * Add name, version tags. * Get alert count. * Include rule type. * Fetch cases count. * Get all data sources working together. * Stage work. * Add detection adoption metrics. * Add usage collector schema. * Add usage collector schema. * Update telemetry schema. * Use let instead of const * Fix spelling on array key. * Update telemetry schema. * Add unit tests. * Fix type. * Move types to index. * Bug fix * Update telemetry schema. * Pass in signals index. * Opps. Broke tests. * Update. * Fix types. * Reflect @FrankHassanabad feedback in PR. * Separate metric / usage telemetry code for complexity reduction. * Add first e2e jest test. * Add some additional tests for custom cases. * Fix up type error. * Update telemetry schema. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security_solution/server/plugin.ts | 1 + .../server/usage/collector.ts | 163 +++++++- .../dectections_metrics_helpers.test.ts | 147 +++++++ .../detections/detection_telemetry_helpers.ts | 46 +++ .../usage/detections/detections.mocks.ts | 176 ++++++++ .../usage/detections/detections.test.ts | 301 +++++++++++++- .../detections/detections_metrics_helpers.ts | 378 ++++++++++++++++++ ...helpers.ts => detections_usage_helpers.ts} | 122 +----- .../server/usage/detections/index.ts | 78 +++- .../security_solution/server/usage/types.ts | 1 + .../schema/xpack_plugins.json | 274 ++++++++++++- 11 files changed, 1551 insertions(+), 136 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/usage/detections/dectections_metrics_helpers.test.ts create mode 100644 x-pack/plugins/security_solution/server/usage/detections/detection_telemetry_helpers.ts create mode 100644 x-pack/plugins/security_solution/server/usage/detections/detections_metrics_helpers.ts rename x-pack/plugins/security_solution/server/usage/detections/{detections_helpers.ts => detections_usage_helpers.ts} (51%) diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index d0b7e6500c42bd..2b5a25ec1b3165 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -174,6 +174,7 @@ export class Plugin implements IPlugin { @@ -65,6 +66,163 @@ export const registerCollector: RegisterCollector = ({ }, }, detectionMetrics: { + detection_rules: { + detection_rule_usage: { + query: { + enabled: { type: 'long', _meta: { description: 'Number of query rules enabled' } }, + disabled: { type: 'long', _meta: { description: 'Number of query rules disabled' } }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by query rules' }, + }, + cases: { + type: 'long', + _meta: { description: 'Number of cases attached to query detection rule alerts' }, + }, + }, + threshold: { + enabled: { + type: 'long', + _meta: { description: 'Number of threshold rules enabled' }, + }, + disabled: { + type: 'long', + _meta: { description: 'Number of threshold rules disabled' }, + }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by threshold rules' }, + }, + cases: { + type: 'long', + _meta: { + description: 'Number of cases attached to threshold detection rule alerts', + }, + }, + }, + eql: { + enabled: { type: 'long', _meta: { description: 'Number of eql rules enabled' } }, + disabled: { type: 'long', _meta: { description: 'Number of eql rules disabled' } }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by eql rules' }, + }, + cases: { + type: 'long', + _meta: { description: 'Number of cases attached to eql detection rule alerts' }, + }, + }, + machine_learning: { + enabled: { + type: 'long', + _meta: { description: 'Number of machine_learning rules enabled' }, + }, + disabled: { + type: 'long', + _meta: { description: 'Number of machine_learning rules disabled' }, + }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by machine_learning rules' }, + }, + cases: { + type: 'long', + _meta: { + description: 'Number of cases attached to machine_learning detection rule alerts', + }, + }, + }, + threat_match: { + enabled: { + type: 'long', + _meta: { description: 'Number of threat_match rules enabled' }, + }, + disabled: { + type: 'long', + _meta: { description: 'Number of threat_match rules disabled' }, + }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by threat_match rules' }, + }, + cases: { + type: 'long', + _meta: { + description: 'Number of cases attached to threat_match detection rule alerts', + }, + }, + }, + elastic_total: { + enabled: { type: 'long', _meta: { description: 'Number of elastic rules enabled' } }, + disabled: { + type: 'long', + _meta: { description: 'Number of elastic rules disabled' }, + }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by elastic rules' }, + }, + cases: { + type: 'long', + _meta: { description: 'Number of cases attached to elastic detection rule alerts' }, + }, + }, + custom_total: { + enabled: { type: 'long', _meta: { description: 'Number of custom rules enabled' } }, + disabled: { type: 'long', _meta: { description: 'Number of custom rules disabled' } }, + alerts: { + type: 'long', + _meta: { description: 'Number of alerts generated by custom rules' }, + }, + cases: { + type: 'long', + _meta: { description: 'Number of cases attached to custom detection rule alerts' }, + }, + }, + }, + detection_rule_detail: { + type: 'array', + items: { + rule_name: { + type: 'keyword', + _meta: { description: 'The name of the detection rule' }, + }, + rule_id: { + type: 'keyword', + _meta: { description: 'The UUID id of the detection rule' }, + }, + rule_type: { + type: 'keyword', + _meta: { description: 'The type of detection rule. ie eql, query...' }, + }, + rule_version: { type: 'long', _meta: { description: 'The version of the rule' } }, + enabled: { + type: 'boolean', + _meta: { description: 'If the detection rule has been enabled by the user' }, + }, + elastic_rule: { + type: 'boolean', + _meta: { description: 'If the detection rule has been authored by Elastic' }, + }, + created_on: { + type: 'keyword', + _meta: { description: 'When the detection rule was created on the cluster' }, + }, + updated_on: { + type: 'keyword', + _meta: { description: 'When the detection rule was updated on the cluster' }, + }, + alert_count_daily: { + type: 'long', + _meta: { description: 'The number of daily alerts generated by a rule' }, + }, + cases_count_daily: { + type: 'long', + _meta: { description: 'The number of daily cases generated by a rule' }, + }, + }, + }, + }, ml_jobs: { type: 'array', items: { @@ -89,7 +247,6 @@ export const registerCollector: RegisterCollector = ({ peak_model_bytes: { type: 'long' }, }, timing_stats: { - average_bucket_processing_time_ms: { type: 'long' }, bucket_count: { type: 'long' }, exponential_average_bucket_processing_time_ms: { type: 'long' }, exponential_average_bucket_processing_time_per_hour_ms: { type: 'long' }, @@ -132,13 +289,13 @@ export const registerCollector: RegisterCollector = ({ }, }, }, - isReady: () => kibanaIndex.length > 0, + isReady: () => true, fetch: async ({ esClient }: CollectorFetchContext): Promise => { const internalSavedObjectsClient = await getInternalSavedObjectsClient(core); const savedObjectsClient = (internalSavedObjectsClient as unknown) as SavedObjectsClientContract; const [detections, detectionMetrics, endpoints] = await Promise.allSettled([ fetchDetectionsUsage(kibanaIndex, esClient, ml, savedObjectsClient), - fetchDetectionsMetrics(ml, savedObjectsClient), + fetchDetectionsMetrics(kibanaIndex, signalsIndex, esClient, ml, savedObjectsClient), getEndpointTelemetryFromFleet(savedObjectsClient, endpointAppContext, esClient), ]); diff --git a/x-pack/plugins/security_solution/server/usage/detections/dectections_metrics_helpers.test.ts b/x-pack/plugins/security_solution/server/usage/detections/dectections_metrics_helpers.test.ts new file mode 100644 index 00000000000000..bd470ccabbfed2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/detections/dectections_metrics_helpers.test.ts @@ -0,0 +1,147 @@ +/* + * 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 { initialDetectionRulesUsage, updateDetectionRuleUsage } from './detections_metrics_helpers'; +import { DetectionRuleMetric, DetectionRulesTypeUsage } from './index'; +import { v4 as uuid } from 'uuid'; + +const createStubRule = ( + ruleType: string, + enabled: boolean, + elasticRule: boolean, + alertCount: number, + caseCount: number +): DetectionRuleMetric => ({ + rule_name: uuid(), + rule_id: uuid(), + rule_type: ruleType, + enabled, + elastic_rule: elasticRule, + created_on: uuid(), + updated_on: uuid(), + alert_count_daily: alertCount, + cases_count_daily: caseCount, +}); + +describe('Detections Usage and Metrics', () => { + describe('Update metrics with rule information', () => { + it('Should update elastic and eql rule metric total', async () => { + const initialUsage: DetectionRulesTypeUsage = initialDetectionRulesUsage; + const stubRule = createStubRule('eql', true, true, 1, 1); + const usage = updateDetectionRuleUsage(stubRule, initialUsage); + + expect(usage).toEqual( + expect.objectContaining({ + custom_total: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + elastic_total: { + alerts: 1, + cases: 1, + disabled: 0, + enabled: 1, + }, + eql: { + alerts: 1, + cases: 1, + disabled: 0, + enabled: 1, + }, + machine_learning: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + query: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + threat_match: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + threshold: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + }) + ); + }); + + it('Should update based on multiple metrics', async () => { + const initialUsage: DetectionRulesTypeUsage = initialDetectionRulesUsage; + const stubEqlRule = createStubRule('eql', true, true, 1, 1); + const stubQueryRuleOne = createStubRule('query', true, true, 5, 2); + const stubQueryRuleTwo = createStubRule('query', true, false, 5, 2); + const stubMachineLearningOne = createStubRule('machine_learning', false, false, 0, 10); + const stubMachineLearningTwo = createStubRule('machine_learning', true, true, 22, 44); + + let usage = updateDetectionRuleUsage(stubEqlRule, initialUsage); + usage = updateDetectionRuleUsage(stubQueryRuleOne, usage); + usage = updateDetectionRuleUsage(stubQueryRuleTwo, usage); + usage = updateDetectionRuleUsage(stubMachineLearningOne, usage); + usage = updateDetectionRuleUsage(stubMachineLearningTwo, usage); + + expect(usage).toEqual( + expect.objectContaining({ + custom_total: { + alerts: 5, + cases: 12, + disabled: 1, + enabled: 1, + }, + elastic_total: { + alerts: 28, + cases: 47, + disabled: 0, + enabled: 3, + }, + eql: { + alerts: 1, + cases: 1, + disabled: 0, + enabled: 1, + }, + machine_learning: { + alerts: 22, + cases: 54, + disabled: 1, + enabled: 1, + }, + query: { + alerts: 10, + cases: 4, + disabled: 0, + enabled: 2, + }, + threat_match: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + threshold: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/usage/detections/detection_telemetry_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detection_telemetry_helpers.ts new file mode 100644 index 00000000000000..bc1e734e4cc3af --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/detections/detection_telemetry_helpers.ts @@ -0,0 +1,46 @@ +/* + * 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 { INTERNAL_IMMUTABLE_KEY } from '../../../common/constants'; + +export const isElasticRule = (tags: string[] = []) => + tags.includes(`${INTERNAL_IMMUTABLE_KEY}:true`); + +interface RuleSearchBody { + query: { + bool: { + filter: { + term: { [key: string]: string }; + }; + }; + }; +} + +export interface RuleSearchParams { + body: RuleSearchBody; + filterPath: string[]; + ignoreUnavailable: boolean; + index: string; + size: number; +} + +export interface RuleSearchResult { + alert: { + name: string; + enabled: boolean; + tags: string[]; + createdAt: string; + updatedAt: string; + params: DetectionRuleParms; + }; +} + +interface DetectionRuleParms { + ruleId: string; + version: string; + type: string; +} diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections.mocks.ts b/x-pack/plugins/security_solution/server/usage/detections/detections.mocks.ts index f7fa59958abae4..f90841ff4e596c 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections.mocks.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections.mocks.ts @@ -302,3 +302,179 @@ export const getMockMlDatafeedStatsResponse = () => ({ }, ], }); + +export const getMockRuleSearchResponse = (immutableTag: string = '__internal_immutable:true') => ({ + took: 2, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 1093, + relation: 'eq', + }, + max_score: 0, + hits: [ + { + _index: '.kibanaindex', + _id: 'alert:6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + _score: 0, + _source: { + alert: { + name: 'Azure Diagnostic Settings Deletion', + tags: [ + 'Elastic', + 'Cloud', + 'Azure', + 'Continuous Monitoring', + 'SecOps', + 'Monitoring', + '__internal_rule_id:5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de', + `${immutableTag}`, + ], + alertTypeId: 'siem.signals', + consumer: 'siem', + params: { + author: ['Elastic'], + description: + 'Identifies the deletion of diagnostic settings in Azure, which send platform logs and metrics to different destinations. An adversary may delete diagnostic settings in an attempt to evade defenses.', + ruleId: '5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de', + index: ['filebeat-*', 'logs-azure*'], + falsePositives: [ + 'Deletion of diagnostic settings may be done by a system or network administrator. Verify whether the username, hostname, and/or resource name should be making changes in your environment. Diagnostic settings deletion from unfamiliar users or hosts should be investigated. If known behavior is causing false positives, it can be exempted from the rule.', + ], + from: 'now-25m', + immutable: true, + query: + 'event.dataset:azure.activitylogs and azure.activitylogs.operation_name:"MICROSOFT.INSIGHTS/DIAGNOSTICSETTINGS/DELETE" and event.outcome:(Success or success)', + language: 'kuery', + license: 'Elastic License v2', + outputIndex: '.siem-signals', + maxSignals: 100, + riskScore: 47, + timestampOverride: 'event.ingested', + to: 'now', + type: 'query', + references: [ + 'https://docs.microsoft.com/en-us/azure/azure-monitor/platform/diagnostic-settings', + ], + note: 'The Azure Filebeat module must be enabled to use this rule.', + version: 4, + exceptionsList: [], + }, + schedule: { + interval: '5m', + }, + enabled: false, + actions: [], + throttle: null, + notifyWhen: 'onActiveAlert', + apiKeyOwner: null, + apiKey: null, + createdBy: 'user', + updatedBy: 'user', + createdAt: '2021-03-23T17:15:59.634Z', + updatedAt: '2021-03-23T17:15:59.634Z', + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'pending', + lastExecutionDate: '2021-03-23T17:15:59.634Z', + error: null, + }, + meta: { + versionApiKeyLastmodified: '8.0.0', + }, + }, + type: 'alert', + references: [], + migrationVersion: { + alert: '7.13.0', + }, + coreMigrationVersion: '8.0.0', + updated_at: '2021-03-23T17:15:59.634Z', + }, + }, + ], + }, +}); + +export const getMockRuleAlertsResponse = (docCount: number) => ({ + took: 7, + timed_out: false, + _shards: { + total: 1, + successful: 1, + skipped: 0, + failed: 0, + }, + hits: { + total: { + value: 7322, + relation: 'eq', + }, + max_score: null, + hits: [], + }, + aggregations: { + detectionAlerts: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + doc_count: docCount, + }, + ], + }, + }, +}); + +export const getMockAlertCasesResponse = () => ({ + page: 1, + per_page: 10000, + total: 4, + saved_objects: [ + { + type: 'cases-comments', + id: '3bb5cc10-9249-11eb-85b7-254c8af1a983', + attributes: { + associationType: 'case', + type: 'alert', + alertId: '54802763917f521249c9f68d0d4be0c26cc538404c26dfed1ae7dcfa94ea2226', + index: '.siem-signals-default-000001', + rule: { + id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + name: 'Azure Diagnostic Settings Deletion', + }, + created_at: '2021-03-31T17:47:59.449Z', + created_by: { + email: '', + full_name: '', + username: '', + }, + pushed_at: null, + pushed_by: null, + updated_at: null, + updated_by: null, + }, + references: [ + { + type: 'cases', + name: 'associated-cases', + id: '3a3a4fa0-9249-11eb-85b7-254c8af1a983', + }, + ], + migrationVersion: {}, + coreMigrationVersion: '8.0.0', + updated_at: '2021-03-31T17:47:59.818Z', + version: 'WzI3MDIyODMsNF0=', + namespaces: ['default'], + score: 0, + }, + ], +}); diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts b/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts index 64a33068ad6869..9241186bb6d9c0 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections.test.ts @@ -5,8 +5,11 @@ * 2.0. */ -import { ElasticsearchClient, SavedObjectsClientContract } from '../../../../../../src/core/server'; -import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; +import { ElasticsearchClient } from '../../../../../../src/core/server'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, +} from '../../../../../../src/core/server/mocks'; import { mlServicesMock } from '../../lib/machine_learning/mocks'; import { getMockJobSummaryResponse, @@ -15,12 +18,16 @@ import { getMockMlJobDetailsResponse, getMockMlJobStatsResponse, getMockMlDatafeedStatsResponse, + getMockRuleSearchResponse, + getMockRuleAlertsResponse, + getMockAlertCasesResponse, } from './detections.mocks'; import { fetchDetectionsUsage, fetchDetectionsMetrics } from './index'; +const savedObjectsClient = savedObjectsClientMock.create(); + describe('Detections Usage and Metrics', () => { let esClientMock: jest.Mocked; - let savedObjectsClientMock: jest.Mocked; let mlMock: ReturnType; describe('fetchDetectionsUsage()', () => { @@ -30,7 +37,7 @@ describe('Detections Usage and Metrics', () => { }); it('returns zeroed counts if both calls are empty', async () => { - const result = await fetchDetectionsUsage('', esClientMock, mlMock, savedObjectsClientMock); + const result = await fetchDetectionsUsage('', esClientMock, mlMock, savedObjectsClient); expect(result).toEqual({ detection_rules: { @@ -59,7 +66,7 @@ describe('Detections Usage and Metrics', () => { it('tallies rules data given rules results', async () => { (esClientMock.search as jest.Mock).mockResolvedValue({ body: getMockRulesResponse() }); - const result = await fetchDetectionsUsage('', esClientMock, mlMock, savedObjectsClientMock); + const result = await fetchDetectionsUsage('', esClientMock, mlMock, savedObjectsClient); expect(result).toEqual( expect.objectContaining({ @@ -87,7 +94,7 @@ describe('Detections Usage and Metrics', () => { jobsSummary: mockJobSummary, }); - const result = await fetchDetectionsUsage('', esClientMock, mlMock, savedObjectsClientMock); + const result = await fetchDetectionsUsage('', esClientMock, mlMock, savedObjectsClient); expect(result).toEqual( expect.objectContaining({ @@ -106,8 +113,285 @@ describe('Detections Usage and Metrics', () => { }); }); + describe('getDetectionRuleMetrics()', () => { + beforeEach(() => { + esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser; + mlMock = mlServicesMock.createSetupContract(); + }); + + it('returns zeroed counts if calls are empty', async () => { + const result = await fetchDetectionsMetrics('', '', esClientMock, mlMock, savedObjectsClient); + + expect(result).toEqual( + expect.objectContaining({ + detection_rules: { + detection_rule_detail: [], + detection_rule_usage: { + query: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + threshold: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + eql: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + machine_learning: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + threat_match: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + elastic_total: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + custom_total: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + }, + }, + ml_jobs: [], + }) + ); + }); + + it('returns information with rule, alerts and cases', async () => { + (esClientMock.search as jest.Mock) + .mockReturnValueOnce({ body: getMockRuleSearchResponse() }) + .mockReturnValue({ body: getMockRuleAlertsResponse(3400) }); + (savedObjectsClient.find as jest.Mock).mockReturnValue(getMockAlertCasesResponse()); + + const result = await fetchDetectionsMetrics('', '', esClientMock, mlMock, savedObjectsClient); + + expect(result).toEqual( + expect.objectContaining({ + detection_rules: { + detection_rule_detail: [ + { + alert_count_daily: 3400, + cases_count_daily: 1, + created_on: '2021-03-23T17:15:59.634Z', + elastic_rule: true, + enabled: false, + rule_id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + rule_name: 'Azure Diagnostic Settings Deletion', + rule_type: 'query', + rule_version: 4, + updated_on: '2021-03-23T17:15:59.634Z', + }, + ], + detection_rule_usage: { + custom_total: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + elastic_total: { + alerts: 3400, + cases: 1, + disabled: 1, + enabled: 0, + }, + eql: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + machine_learning: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + query: { + alerts: 3400, + cases: 1, + disabled: 1, + enabled: 0, + }, + threat_match: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + threshold: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + }, + }, + ml_jobs: [], + }) + ); + }); + + it('returns information with on non elastic prebuilt rule', async () => { + (esClientMock.search as jest.Mock) + .mockReturnValueOnce({ body: getMockRuleSearchResponse('not_immutable') }) + .mockReturnValue({ body: getMockRuleAlertsResponse(800) }); + (savedObjectsClient.find as jest.Mock).mockReturnValue(getMockAlertCasesResponse()); + + const result = await fetchDetectionsMetrics('', '', esClientMock, mlMock, savedObjectsClient); + + expect(result).toEqual( + expect.objectContaining({ + detection_rules: { + detection_rule_detail: [], // *should not* contain custom detection rule details + detection_rule_usage: { + custom_total: { + alerts: 800, + cases: 1, + disabled: 1, + enabled: 0, + }, + elastic_total: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + eql: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + machine_learning: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + query: { + alerts: 800, + cases: 1, + disabled: 1, + enabled: 0, + }, + threat_match: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + threshold: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + }, + }, + ml_jobs: [], + }) + ); + }); + + it('returns information with rule, no alerts and no cases', async () => { + (esClientMock.search as jest.Mock) + .mockReturnValueOnce({ body: getMockRuleSearchResponse() }) + .mockReturnValue({ body: getMockRuleAlertsResponse(0) }); + (savedObjectsClient.find as jest.Mock).mockReturnValue(getMockAlertCasesResponse()); + + const result = await fetchDetectionsMetrics('', '', esClientMock, mlMock, savedObjectsClient); + + expect(result).toEqual( + expect.objectContaining({ + detection_rules: { + detection_rule_detail: [ + { + alert_count_daily: 0, + cases_count_daily: 1, + created_on: '2021-03-23T17:15:59.634Z', + elastic_rule: true, + enabled: false, + rule_id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + rule_name: 'Azure Diagnostic Settings Deletion', + rule_type: 'query', + rule_version: 4, + updated_on: '2021-03-23T17:15:59.634Z', + }, + ], + detection_rule_usage: { + custom_total: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + elastic_total: { + alerts: 0, + cases: 1, + disabled: 1, + enabled: 0, + }, + eql: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + machine_learning: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + query: { + alerts: 0, + cases: 1, + disabled: 1, + enabled: 0, + }, + threat_match: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + threshold: { + alerts: 0, + cases: 0, + disabled: 0, + enabled: 0, + }, + }, + }, + ml_jobs: [], + }) + ); + }); + }); + describe('fetchDetectionsMetrics()', () => { beforeEach(() => { + esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser; mlMock = mlServicesMock.createSetupContract(); }); @@ -116,7 +400,7 @@ describe('Detections Usage and Metrics', () => { jobs: null, jobStats: null, } as unknown) as ReturnType); - const result = await fetchDetectionsMetrics(mlMock, savedObjectsClientMock); + const result = await fetchDetectionsMetrics('', '', esClientMock, mlMock, savedObjectsClient); expect(result).toEqual( expect.objectContaining({ @@ -138,7 +422,7 @@ describe('Detections Usage and Metrics', () => { datafeedStats: mockDatafeedStatsResponse, } as unknown) as ReturnType); - const result = await fetchDetectionsMetrics(mlMock, savedObjectsClientMock); + const result = await fetchDetectionsMetrics('', '', esClientMock, mlMock, savedObjectsClient); expect(result).toEqual( expect.objectContaining({ @@ -177,7 +461,6 @@ describe('Detections Usage and Metrics', () => { datafeed_id: 'datafeed-high_distinct_count_error_message', state: 'stopped', timing_stats: { - average_search_time_per_bucket_ms: 360.7927310729215, bucket_count: 8612, exponential_average_search_time_per_hour_ms: 86145.39799630083, search_count: 7202, diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections_metrics_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detections_metrics_helpers.ts new file mode 100644 index 00000000000000..97fc8c28842897 --- /dev/null +++ b/x-pack/plugins/security_solution/server/usage/detections/detections_metrics_helpers.ts @@ -0,0 +1,378 @@ +/* + * 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 { + ElasticsearchClient, + KibanaRequest, + SavedObjectsClientContract, +} from '../../../../../../src/core/server'; +import { + AlertsAggregationResponse, + CasesSavedObject, + DetectionRulesTypeUsage, + DetectionRuleMetric, + DetectionRuleAdoption, + MlJobMetric, +} from './index'; +import { SIGNALS_ID } from '../../../common/constants'; +import { DatafeedStats, Job, MlPluginSetup } from '../../../../ml/server'; +import { isElasticRule, RuleSearchParams, RuleSearchResult } from './detection_telemetry_helpers'; + +/** + * Default detection rule usage count, split by type + elastic/custom + */ +export const initialDetectionRulesUsage: DetectionRulesTypeUsage = { + query: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + threshold: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + eql: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + machine_learning: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + threat_match: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + elastic_total: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, + custom_total: { + enabled: 0, + disabled: 0, + alerts: 0, + cases: 0, + }, +}; + +/* eslint-disable complexity */ +export const updateDetectionRuleUsage = ( + detectionRuleMetric: DetectionRuleMetric, + usage: DetectionRulesTypeUsage +): DetectionRulesTypeUsage => { + let updatedUsage = usage; + + if (detectionRuleMetric.rule_type === 'query') { + updatedUsage = { + ...usage, + query: { + ...usage.query, + enabled: detectionRuleMetric.enabled ? usage.query.enabled + 1 : usage.query.enabled, + disabled: !detectionRuleMetric.enabled ? usage.query.disabled + 1 : usage.query.disabled, + alerts: usage.query.alerts + detectionRuleMetric.alert_count_daily, + cases: usage.query.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } else if (detectionRuleMetric.rule_type === 'threshold') { + updatedUsage = { + ...usage, + threshold: { + ...usage.threshold, + enabled: detectionRuleMetric.enabled + ? usage.threshold.enabled + 1 + : usage.threshold.enabled, + disabled: !detectionRuleMetric.enabled + ? usage.threshold.disabled + 1 + : usage.threshold.disabled, + alerts: usage.threshold.alerts + detectionRuleMetric.alert_count_daily, + cases: usage.threshold.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } else if (detectionRuleMetric.rule_type === 'eql') { + updatedUsage = { + ...usage, + eql: { + ...usage.eql, + enabled: detectionRuleMetric.enabled ? usage.eql.enabled + 1 : usage.eql.enabled, + disabled: !detectionRuleMetric.enabled ? usage.eql.disabled + 1 : usage.eql.disabled, + alerts: usage.eql.alerts + detectionRuleMetric.alert_count_daily, + cases: usage.eql.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } else if (detectionRuleMetric.rule_type === 'machine_learning') { + updatedUsage = { + ...usage, + machine_learning: { + ...usage.machine_learning, + enabled: detectionRuleMetric.enabled + ? usage.machine_learning.enabled + 1 + : usage.machine_learning.enabled, + disabled: !detectionRuleMetric.enabled + ? usage.machine_learning.disabled + 1 + : usage.machine_learning.disabled, + alerts: usage.machine_learning.alerts + detectionRuleMetric.alert_count_daily, + cases: usage.machine_learning.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } else if (detectionRuleMetric.rule_type === 'threat_match') { + updatedUsage = { + ...usage, + threat_match: { + ...usage.threat_match, + enabled: detectionRuleMetric.enabled + ? usage.threat_match.enabled + 1 + : usage.threat_match.enabled, + disabled: !detectionRuleMetric.enabled + ? usage.threat_match.disabled + 1 + : usage.threat_match.disabled, + alerts: usage.threat_match.alerts + detectionRuleMetric.alert_count_daily, + cases: usage.threat_match.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } + + if (detectionRuleMetric.elastic_rule) { + updatedUsage = { + ...updatedUsage, + elastic_total: { + ...updatedUsage.elastic_total, + enabled: detectionRuleMetric.enabled + ? updatedUsage.elastic_total.enabled + 1 + : updatedUsage.elastic_total.enabled, + disabled: !detectionRuleMetric.enabled + ? updatedUsage.elastic_total.disabled + 1 + : updatedUsage.elastic_total.disabled, + alerts: updatedUsage.elastic_total.alerts + detectionRuleMetric.alert_count_daily, + cases: updatedUsage.elastic_total.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } else { + updatedUsage = { + ...updatedUsage, + custom_total: { + ...updatedUsage.custom_total, + enabled: detectionRuleMetric.enabled + ? updatedUsage.custom_total.enabled + 1 + : updatedUsage.custom_total.enabled, + disabled: !detectionRuleMetric.enabled + ? updatedUsage.custom_total.disabled + 1 + : updatedUsage.custom_total.disabled, + alerts: updatedUsage.custom_total.alerts + detectionRuleMetric.alert_count_daily, + cases: updatedUsage.custom_total.cases + detectionRuleMetric.cases_count_daily, + }, + }; + } + + return updatedUsage; +}; + +export const getDetectionRuleMetrics = async ( + kibanaIndex: string, + signalsIndex: string, + esClient: ElasticsearchClient, + savedObjectClient: SavedObjectsClientContract +): Promise => { + let rulesUsage: DetectionRulesTypeUsage = initialDetectionRulesUsage; + const ruleSearchOptions: RuleSearchParams = { + body: { query: { bool: { filter: { term: { 'alert.alertTypeId': SIGNALS_ID } } } } }, + filterPath: [], + ignoreUnavailable: true, + index: kibanaIndex, + size: 1, + }; + + try { + const { body: ruleResults } = await esClient.search(ruleSearchOptions); + const { body: detectionAlertsResp } = (await esClient.search({ + index: `${signalsIndex}*`, + size: 0, + body: { + aggs: { + detectionAlerts: { + terms: { field: 'signal.rule.id.keyword' }, + }, + }, + query: { + bool: { + filter: [ + { + range: { + '@timestamp': { + gte: 'now-24h', + lte: 'now', + }, + }, + }, + ], + }, + }, + }, + })) as { body: AlertsAggregationResponse }; + + const cases = await savedObjectClient.find({ + type: 'cases-comments', + fields: [], + page: 1, + perPage: 10_000, + filter: 'cases-comments.attributes.type: alert', + }); + + const casesCache = cases.saved_objects.reduce((cache, { attributes: casesObject }) => { + const ruleId = casesObject.rule.id; + + const cacheCount = cache.get(ruleId); + if (cacheCount === undefined) { + cache.set(ruleId, 1); + } else { + cache.set(ruleId, cacheCount + 1); + } + return cache; + }, new Map()); + + const alertBuckets = detectionAlertsResp.aggregations?.detectionAlerts?.buckets ?? []; + + const alertsCache = new Map(); + alertBuckets.map((bucket) => alertsCache.set(bucket.key, bucket.doc_count)); + + if (ruleResults.hits?.hits?.length > 0) { + const ruleObjects = ruleResults.hits.hits.map((hit) => { + const ruleId = hit._id.split(':')[1]; + const isElastic = isElasticRule(hit._source?.alert.tags); + return { + rule_name: hit._source?.alert.name, + rule_id: ruleId, + rule_type: hit._source?.alert.params.type, + rule_version: hit._source?.alert.params.version, + enabled: hit._source?.alert.enabled, + elastic_rule: isElastic, + created_on: hit._source?.alert.createdAt, + updated_on: hit._source?.alert.updatedAt, + alert_count_daily: alertsCache.get(ruleId) || 0, + cases_count_daily: casesCache.get(ruleId) || 0, + } as DetectionRuleMetric; + }); + + // Only bring back rule detail on elastic prepackaged detection rules + const elasticRuleObjects = ruleObjects.filter((hit) => hit.elastic_rule === true); + + rulesUsage = ruleObjects.reduce((usage, rule) => { + return updateDetectionRuleUsage(rule, usage); + }, rulesUsage); + + return { + detection_rule_detail: elasticRuleObjects, + detection_rule_usage: rulesUsage, + }; + } + } catch (e) { + // ignore failure, usage will be zeroed + } + + return { + detection_rule_detail: [], + detection_rule_usage: rulesUsage, + }; +}; + +export const getMlJobMetrics = async ( + ml: MlPluginSetup | undefined, + savedObjectClient: SavedObjectsClientContract +): Promise => { + if (ml) { + try { + const fakeRequest = { headers: {} } as KibanaRequest; + const jobsType = 'security'; + const securityJobStats = await ml + .anomalyDetectorsProvider(fakeRequest, savedObjectClient) + .jobStats(jobsType); + + const jobDetails = await ml + .anomalyDetectorsProvider(fakeRequest, savedObjectClient) + .jobs(jobsType); + + const jobDetailsCache = new Map(); + jobDetails.jobs.forEach((detail) => jobDetailsCache.set(detail.job_id, detail)); + + const datafeedStats = await ml + .anomalyDetectorsProvider(fakeRequest, savedObjectClient) + .datafeedStats(); + + const datafeedStatsCache = new Map(); + datafeedStats.datafeeds.forEach((datafeedStat) => + datafeedStatsCache.set(`${datafeedStat.datafeed_id}`, datafeedStat) + ); + + return securityJobStats.jobs.map((stat) => { + const jobId = stat.job_id; + const jobDetail = jobDetailsCache.get(stat.job_id); + const datafeed = datafeedStatsCache.get(`datafeed-${jobId}`); + + return { + job_id: jobId, + open_time: stat.open_time, + create_time: jobDetail?.create_time, + finished_time: jobDetail?.finished_time, + state: stat.state, + data_counts: { + bucket_count: stat.data_counts.bucket_count, + empty_bucket_count: stat.data_counts.empty_bucket_count, + input_bytes: stat.data_counts.input_bytes, + input_record_count: stat.data_counts.input_record_count, + last_data_time: stat.data_counts.last_data_time, + processed_record_count: stat.data_counts.processed_record_count, + }, + model_size_stats: { + bucket_allocation_failures_count: + stat.model_size_stats.bucket_allocation_failures_count, + memory_status: stat.model_size_stats.memory_status, + model_bytes: stat.model_size_stats.model_bytes, + model_bytes_exceeded: stat.model_size_stats.model_bytes_exceeded, + model_bytes_memory_limit: stat.model_size_stats.model_bytes_memory_limit, + peak_model_bytes: stat.model_size_stats.peak_model_bytes, + }, + timing_stats: { + average_bucket_processing_time_ms: stat.timing_stats.average_bucket_processing_time_ms, + bucket_count: stat.timing_stats.bucket_count, + exponential_average_bucket_processing_time_ms: + stat.timing_stats.exponential_average_bucket_processing_time_ms, + exponential_average_bucket_processing_time_per_hour_ms: + stat.timing_stats.exponential_average_bucket_processing_time_per_hour_ms, + maximum_bucket_processing_time_ms: stat.timing_stats.maximum_bucket_processing_time_ms, + minimum_bucket_processing_time_ms: stat.timing_stats.minimum_bucket_processing_time_ms, + total_bucket_processing_time_ms: stat.timing_stats.total_bucket_processing_time_ms, + }, + datafeed: { + datafeed_id: datafeed?.datafeed_id, + state: datafeed?.state, + timing_stats: { + bucket_count: datafeed?.timing_stats.bucket_count, + exponential_average_search_time_per_hour_ms: + datafeed?.timing_stats.exponential_average_search_time_per_hour_ms, + search_count: datafeed?.timing_stats.search_count, + total_search_time_ms: datafeed?.timing_stats.total_search_time_ms, + }, + }, + } as MlJobMetric; + }); + } catch (e) { + // ignore failure, usage will be zeroed + } + } + + return []; +}; diff --git a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detections_usage_helpers.ts similarity index 51% rename from x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts rename to x-pack/plugins/security_solution/server/usage/detections/detections_usage_helpers.ts index 211c477027eec9..3c666d4d217809 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detections_helpers.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detections_usage_helpers.ts @@ -7,42 +7,21 @@ import { ElasticsearchClient, - SavedObjectsClientContract, KibanaRequest, + SavedObjectsClientContract, } from '../../../../../../src/core/server'; -import { MlPluginSetup } from '../../../../ml/server'; -import { SIGNALS_ID, INTERNAL_IMMUTABLE_KEY } from '../../../common/constants'; -import { DetectionRulesUsage, MlJobsUsage, MlJobMetric } from './index'; +import { SIGNALS_ID } from '../../../common/constants'; import { isJobStarted } from '../../../common/machine_learning/helpers'; import { isSecurityJob } from '../../../common/machine_learning/is_security_job'; +import { MlPluginSetup } from '../../../../ml/server'; +import { DetectionRulesUsage, MlJobsUsage } from './index'; +import { isElasticRule, RuleSearchParams, RuleSearchResult } from './detection_telemetry_helpers'; interface DetectionsMetric { isElastic: boolean; isEnabled: boolean; } -interface RuleSearchBody { - query: { - bool: { - filter: { - term: { [key: string]: string }; - }; - }; - }; -} -interface RuleSearchParams { - body: RuleSearchBody; - filterPath: string[]; - ignoreUnavailable: boolean; - index: string; - size: number; -} -interface RuleSearchResult { - alert: { enabled: boolean; tags: string[] }; -} - -const isElasticRule = (tags: string[]) => tags.includes(`${INTERNAL_IMMUTABLE_KEY}:true`); - /** * Default detection rule usage count */ @@ -170,7 +149,6 @@ export const getRulesUsage = async ( if (ruleResults.hits?.hits?.length > 0) { rulesUsage = ruleResults.hits.hits.reduce((usage, hit) => { - // @ts-expect-error _source is optional const isElastic = isElasticRule(hit._source?.alert.tags); const isEnabled = Boolean(hit._source?.alert.enabled); @@ -211,93 +189,3 @@ export const getMlJobsUsage = async ( return jobsUsage; }; - -export const getMlJobMetrics = async ( - ml: MlPluginSetup | undefined, - savedObjectClient: SavedObjectsClientContract -): Promise => { - if (ml) { - try { - const fakeRequest = { headers: {} } as KibanaRequest; - const jobsType = 'security'; - const securityJobStats = await ml - .anomalyDetectorsProvider(fakeRequest, savedObjectClient) - .jobStats(jobsType); - - const jobDetails = await ml - .anomalyDetectorsProvider(fakeRequest, savedObjectClient) - .jobs(jobsType); - - const jobDetailsCache = new Map(); - jobDetails.jobs.forEach((detail) => jobDetailsCache.set(detail.job_id, detail)); - - const datafeedStats = await ml - .anomalyDetectorsProvider(fakeRequest, savedObjectClient) - .datafeedStats(); - - const datafeedStatsCache = new Map(); - datafeedStats.datafeeds.forEach((datafeedStat) => - datafeedStatsCache.set(`${datafeedStat.datafeed_id}`, datafeedStat) - ); - - return securityJobStats.jobs.map((stat) => { - const jobId = stat.job_id; - const jobDetail = jobDetailsCache.get(stat.job_id); - const datafeed = datafeedStatsCache.get(`datafeed-${jobId}`); - - return { - job_id: jobId, - open_time: stat.open_time, - create_time: jobDetail?.create_time, - finished_time: jobDetail?.finished_time, - state: stat.state, - data_counts: { - bucket_count: stat.data_counts.bucket_count, - empty_bucket_count: stat.data_counts.empty_bucket_count, - input_bytes: stat.data_counts.input_bytes, - input_record_count: stat.data_counts.input_record_count, - last_data_time: stat.data_counts.last_data_time, - processed_record_count: stat.data_counts.processed_record_count, - }, - model_size_stats: { - bucket_allocation_failures_count: - stat.model_size_stats.bucket_allocation_failures_count, - memory_status: stat.model_size_stats.memory_status, - model_bytes: stat.model_size_stats.model_bytes, - model_bytes_exceeded: stat.model_size_stats.model_bytes_exceeded, - model_bytes_memory_limit: stat.model_size_stats.model_bytes_memory_limit, - peak_model_bytes: stat.model_size_stats.peak_model_bytes, - }, - timing_stats: { - average_bucket_processing_time_ms: stat.timing_stats.average_bucket_processing_time_ms, - bucket_count: stat.timing_stats.bucket_count, - exponential_average_bucket_processing_time_ms: - stat.timing_stats.exponential_average_bucket_processing_time_ms, - exponential_average_bucket_processing_time_per_hour_ms: - stat.timing_stats.exponential_average_bucket_processing_time_per_hour_ms, - maximum_bucket_processing_time_ms: stat.timing_stats.maximum_bucket_processing_time_ms, - minimum_bucket_processing_time_ms: stat.timing_stats.minimum_bucket_processing_time_ms, - total_bucket_processing_time_ms: stat.timing_stats.total_bucket_processing_time_ms, - }, - datafeed: { - datafeed_id: datafeed?.datafeed_id, - state: datafeed?.state, - timing_stats: { - average_search_time_per_bucket_ms: - datafeed?.timing_stats.average_search_time_per_bucket_ms, - bucket_count: datafeed?.timing_stats.bucket_count, - exponential_average_search_time_per_hour_ms: - datafeed?.timing_stats.exponential_average_search_time_per_hour_ms, - search_count: datafeed?.timing_stats.search_count, - total_search_time_ms: datafeed?.timing_stats.total_search_time_ms, - }, - }, - } as MlJobMetric; - }); - } catch (e) { - // ignore failure, usage will be zeroed - } - } - - return []; -}; diff --git a/x-pack/plugins/security_solution/server/usage/detections/index.ts b/x-pack/plugins/security_solution/server/usage/detections/index.ts index 39c8f3159fe037..cc831b0b3b366f 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/index.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/index.ts @@ -8,11 +8,15 @@ import { ElasticsearchClient, SavedObjectsClientContract } from '../../../../../../src/core/server'; import { getMlJobsUsage, - getMlJobMetrics, getRulesUsage, initialRulesUsage, initialMlJobsUsage, -} from './detections_helpers'; +} from './detections_usage_helpers'; +import { + getMlJobMetrics, + getDetectionRuleMetrics, + initialDetectionRulesUsage, +} from './detections_metrics_helpers'; import { MlPluginSetup } from '../../../../ml/server'; interface FeatureUsage { @@ -20,6 +24,23 @@ interface FeatureUsage { disabled: number; } +interface FeatureTypeUsage { + enabled: number; + disabled: number; + alerts: number; + cases: number; +} + +export interface DetectionRulesTypeUsage { + query: FeatureTypeUsage; + threshold: FeatureTypeUsage; + eql: FeatureTypeUsage; + machine_learning: FeatureTypeUsage; + threat_match: FeatureTypeUsage; + elastic_total: FeatureTypeUsage; + custom_total: FeatureTypeUsage; +} + export interface DetectionRulesUsage { custom: FeatureUsage; elastic: FeatureUsage; @@ -37,6 +58,7 @@ export interface DetectionsUsage { export interface DetectionMetrics { ml_jobs: MlJobMetric[]; + detection_rules: DetectionRuleAdoption; } export interface MlJobDataCount { @@ -58,7 +80,6 @@ export interface MlJobModelSize { } export interface MlTimingStats { - average_bucket_processing_time_ms: number; bucket_count: number; exponential_average_bucket_processing_time_ms: number; exponential_average_bucket_processing_time_per_hour_ms: number; @@ -76,6 +97,45 @@ export interface MlJobMetric { timing_stats: MlTimingStats; } +export interface DetectionRuleMetric { + rule_name: string; + rule_id: string; + rule_type: string; + enabled: boolean; + elastic_rule: boolean; + created_on: string; + updated_on: string; + alert_count_daily: number; + cases_count_daily: number; +} + +export interface DetectionRuleAdoption { + detection_rule_detail: DetectionRuleMetric[]; + detection_rule_usage: DetectionRulesTypeUsage; +} + +export interface AlertsAggregationResponse { + hits: { + total: { value: number }; + }; + aggregations: { + [aggName: string]: { + buckets: Array<{ key: string; doc_count: number }>; + }; + }; +} + +export interface CasesSavedObject { + associationType: string; + type: string; + alertId: string; + index: string; + rule: { + id: string; + name: string; + }; +} + export const defaultDetectionsUsage = { detection_rules: initialRulesUsage, ml_jobs: initialMlJobsUsage, @@ -99,12 +159,22 @@ export const fetchDetectionsUsage = async ( }; export const fetchDetectionsMetrics = async ( + kibanaIndex: string, + signalsIndex: string, + esClient: ElasticsearchClient, ml: MlPluginSetup | undefined, savedObjectClient: SavedObjectsClientContract ): Promise => { - const [mlJobMetrics] = await Promise.allSettled([getMlJobMetrics(ml, savedObjectClient)]); + const [mlJobMetrics, detectionRuleMetrics] = await Promise.allSettled([ + getMlJobMetrics(ml, savedObjectClient), + getDetectionRuleMetrics(kibanaIndex, signalsIndex, esClient, savedObjectClient), + ]); return { ml_jobs: mlJobMetrics.status === 'fulfilled' ? mlJobMetrics.value : [], + detection_rules: + detectionRuleMetrics.status === 'fulfilled' + ? detectionRuleMetrics.value + : { detection_rule_detail: [], detection_rule_usage: initialDetectionRulesUsage }, }; }; diff --git a/x-pack/plugins/security_solution/server/usage/types.ts b/x-pack/plugins/security_solution/server/usage/types.ts index c06c8a4722cd7f..4e1e647952a72b 100644 --- a/x-pack/plugins/security_solution/server/usage/types.ts +++ b/x-pack/plugins/security_solution/server/usage/types.ts @@ -11,6 +11,7 @@ import { SetupPlugins } from '../plugin'; export type CollectorDependencies = { kibanaIndex: string; + signalsIndex: string; core: CoreSetup; endpointAppContext: EndpointAppContext; } & Pick; diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index b8c8c5121d7d78..50933335710da2 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -4578,6 +4578,277 @@ }, "detectionMetrics": { "properties": { + "detection_rules": { + "properties": { + "detection_rule_usage": { + "properties": { + "query": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of query rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of query rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by query rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to query detection rule alerts" + } + } + } + }, + "threshold": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of threshold rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of threshold rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by threshold rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to threshold detection rule alerts" + } + } + } + }, + "eql": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of eql rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of eql rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by eql rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to eql detection rule alerts" + } + } + } + }, + "machine_learning": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of machine_learning rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by machine_learning rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to machine_learning detection rule alerts" + } + } + } + }, + "threat_match": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of threat_match rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by threat_match rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to threat_match detection rule alerts" + } + } + } + }, + "elastic_total": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of elastic rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of elastic rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by elastic rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to elastic detection rule alerts" + } + } + } + }, + "custom_total": { + "properties": { + "enabled": { + "type": "long", + "_meta": { + "description": "Number of custom rules enabled" + } + }, + "disabled": { + "type": "long", + "_meta": { + "description": "Number of custom rules disabled" + } + }, + "alerts": { + "type": "long", + "_meta": { + "description": "Number of alerts generated by custom rules" + } + }, + "cases": { + "type": "long", + "_meta": { + "description": "Number of cases attached to custom detection rule alerts" + } + } + } + } + } + }, + "detection_rule_detail": { + "type": "array", + "items": { + "properties": { + "rule_name": { + "type": "keyword", + "_meta": { + "description": "The name of the detection rule" + } + }, + "rule_id": { + "type": "keyword", + "_meta": { + "description": "The UUID id of the detection rule" + } + }, + "rule_type": { + "type": "keyword", + "_meta": { + "description": "The type of detection rule. ie eql, query..." + } + }, + "rule_version": { + "type": "long", + "_meta": { + "description": "The version of the rule" + } + }, + "enabled": { + "type": "boolean", + "_meta": { + "description": "If the detection rule has been enabled by the user" + } + }, + "elastic_rule": { + "type": "boolean", + "_meta": { + "description": "If the detection rule has been authored by Elastic" + } + }, + "created_on": { + "type": "keyword", + "_meta": { + "description": "When the detection rule was created on the cluster" + } + }, + "updated_on": { + "type": "keyword", + "_meta": { + "description": "When the detection rule was updated on the cluster" + } + }, + "alert_count_daily": { + "type": "long", + "_meta": { + "description": "The number of daily alerts generated by a rule" + } + }, + "cases_count_daily": { + "type": "long", + "_meta": { + "description": "The number of daily cases generated by a rule" + } + } + } + } + } + } + }, "ml_jobs": { "type": "array", "items": { @@ -4640,9 +4911,6 @@ }, "timing_stats": { "properties": { - "average_bucket_processing_time_ms": { - "type": "long" - }, "bucket_count": { "type": "long" }, From e01d5fd255d7c3e0bab610a25325b5b623971ec7 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 21 Apr 2021 08:15:38 -0700 Subject: [PATCH 03/65] [FTR] Disables GeoIP downloader (#97813) A temporary workaround to the increased segment memory issues we have in the new ES snapshots Signed-off-by: Tyler Smalley --- test/common/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/config.js b/test/common/config.js index 84848347f94cda..b44f2de5042eb6 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -21,7 +21,7 @@ export default function () { servers, esTestCluster: { - serverArgs: ['xpack.security.enabled=false'], + serverArgs: ['xpack.security.enabled=false', 'geoip.downloader.enabled=false'], }, kbnTestServer: { From c27245b201879c32ee04861b0dd77567b68684df Mon Sep 17 00:00:00 2001 From: ymao1 Date: Wed, 21 Apr 2021 11:28:58 -0400 Subject: [PATCH 04/65] Removing unnecessary hit count check from es query alert (#97735) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/alert_types/es_query/alert_type.ts | 79 ++++++++++--------- .../builtin_alert_types/es_query/alert.ts | 44 +++++++++-- 2 files changed, 79 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts index 990ab9c1f60027..ece193e07d776a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/alert_type.ts @@ -216,46 +216,47 @@ export function getAlertType( const { body: searchResult } = await esClient.search(query); - if (searchResult.hits.hits.length > 0) { - const numMatches = (searchResult.hits.total as estypes.TotalHits).value; - logger.debug(`alert ${ES_QUERY_ID}:${alertId} "${name}" query has ${numMatches} matches`); - - // apply the alert condition - const conditionMet = compareFn(numMatches, params.threshold); - - if (conditionMet) { - const humanFn = i18n.translate( - 'xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription', - { - defaultMessage: `Number of matching documents is {thresholdComparator} {threshold}`, - values: { - thresholdComparator: getHumanReadableComparator(params.thresholdComparator), - threshold: params.threshold.join(' and '), - }, - } - ); - - const baseContext: EsQueryAlertActionContext = { - date: new Date().toISOString(), - value: numMatches, - conditions: humanFn, - hits: searchResult.hits.hits, - }; - - const actionContext = addMessages(options, baseContext, params); - const alertInstance = options.services.alertInstanceFactory(ConditionMetAlertInstanceId); - alertInstance - // store the params we would need to recreate the query that led to this alert instance - .replaceState({ latestTimestamp: timestamp, dateStart, dateEnd }) - .scheduleActions(ActionGroupId, actionContext); - - // update the timestamp based on the current search results - const firstValidTimefieldSort = getValidTimefieldSort( - searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort - ); - if (firstValidTimefieldSort) { - timestamp = firstValidTimefieldSort; + logger.debug( + `alert ${ES_QUERY_ID}:${alertId} "${name}" result - ${JSON.stringify(searchResult)}` + ); + + const numMatches = (searchResult.hits.total as estypes.TotalHits).value; + + // apply the alert condition + const conditionMet = compareFn(numMatches, params.threshold); + + if (conditionMet) { + const humanFn = i18n.translate( + 'xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription', + { + defaultMessage: `Number of matching documents is {thresholdComparator} {threshold}`, + values: { + thresholdComparator: getHumanReadableComparator(params.thresholdComparator), + threshold: params.threshold.join(' and '), + }, } + ); + + const baseContext: EsQueryAlertActionContext = { + date: new Date().toISOString(), + value: numMatches, + conditions: humanFn, + hits: searchResult.hits.hits, + }; + + const actionContext = addMessages(options, baseContext, params); + const alertInstance = options.services.alertInstanceFactory(ConditionMetAlertInstanceId); + alertInstance + // store the params we would need to recreate the query that led to this alert instance + .replaceState({ latestTimestamp: timestamp, dateStart, dateEnd }) + .scheduleActions(ActionGroupId, actionContext); + + // update the timestamp based on the current search results + const firstValidTimefieldSort = getValidTimefieldSort( + searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort + ); + if (firstValidTimefieldSort) { + timestamp = firstValidTimefieldSort; } } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts index 8511bcdf89d3bf..ebc03ffb0e952f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/es_query/alert.ts @@ -53,9 +53,6 @@ export default function alertTests({ getService }: FtrProviderContext) { // write documents in the future, figure out the end date const endDateMillis = Date.now() + (ALERT_INTERVALS_TO_WRITE - 1) * ALERT_INTERVAL_MILLIS; endDate = new Date(endDateMillis).toISOString(); - - // write documents from now to the future end date in groups - createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); }); afterEach(async () => { @@ -65,6 +62,9 @@ export default function alertTests({ getService }: FtrProviderContext) { }); it('runs correctly: threshold on hit count < >', async () => { + // write documents from now to the future end date in groups + createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); + await createAlert({ name: 'never fire', esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, @@ -104,6 +104,9 @@ export default function alertTests({ getService }: FtrProviderContext) { }); it('runs correctly with query: threshold on hit count < >', async () => { + // write documents from now to the future end date in groups + createEsDocumentsInGroups(ES_GROUPS_TO_WRITE); + const rangeQuery = (rangeThreshold: number) => { return { query: { @@ -126,8 +129,8 @@ export default function alertTests({ getService }: FtrProviderContext) { name: 'never fire', esQuery: JSON.stringify(rangeQuery(ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE + 1)), size: 100, - thresholdComparator: '>=', - threshold: [0], + thresholdComparator: '<', + threshold: [-1], }); await createAlert({ @@ -154,6 +157,37 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); + it('runs correctly: no matches', async () => { + await createAlert({ + name: 'always fire', + esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`, + size: 100, + thresholdComparator: '<', + threshold: [1], + }); + + const docs = await waitForDocs(1); + for (let i = 0; i < docs.length; i++) { + const doc = docs[i]; + const { previousTimestamp, hits } = doc._source; + const { name, title, message } = doc._source.params; + + expect(name).to.be('always fire'); + expect(title).to.be(`alert 'always fire' matched query`); + const messagePattern = /alert 'always fire' is active:\n\n- Value: 0+\n- Conditions Met: Number of matching documents is less than 1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + expect(hits).to.be.empty(); + + // during the first execution, the latestTimestamp value should be empty + // since this alert always fires, the latestTimestamp value should be updated each execution + if (!i) { + expect(previousTimestamp).to.be.empty(); + } else { + expect(previousTimestamp).not.to.be.empty(); + } + } + }); + async function createEsDocumentsInGroups(groups: number) { await createEsDocuments( es, From d66147fa1839c813f3a3a8b44a464e9148b77dce Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 21 Apr 2021 11:32:36 -0400 Subject: [PATCH 05/65] [Fleet] Update open API spec with removed agent routes (#97811) --- .../plugins/fleet/common/openapi/bundled.json | 409 +----------------- .../plugins/fleet/common/openapi/bundled.yaml | 269 +----------- .../components/schemas/agent_event.yaml | 9 - .../components/schemas/new_agent_event.yaml | 44 -- .../fleet/common/openapi/entrypoint.yaml | 8 - .../fleet/common/openapi/paths/README.md | 3 - .../paths/agents@{agent_id}@unenroll.yaml | 2 + 7 files changed, 7 insertions(+), 737 deletions(-) delete mode 100644 x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml delete mode 100644 x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json index 388aebed9a85b4..b121095c8b91b3 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.json +++ b/x-pack/plugins/fleet/common/openapi/bundled.json @@ -446,234 +446,6 @@ ] } }, - "/agents/{agentId}/acks": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "agentId", - "in": "path", - "required": true - } - ], - "post": { - "summary": "Fleet - Agent - Acks", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "acks" - ] - } - }, - "required": [ - "action" - ] - } - } - } - } - }, - "operationId": "post-fleet-agents-agentId-acks", - "parameters": [ - { - "$ref": "#/paths/~1setup/post/parameters/0" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - } - } - } - }, - "/agents/{agentId}/checkin": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "agentId", - "in": "path", - "required": true - } - ], - "post": { - "summary": "Fleet - Agent - Check In", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "action": { - "type": "string", - "enum": [ - "checkin" - ] - }, - "actions": { - "type": "array", - "items": { - "type": "object", - "properties": { - "agent_id": { - "type": "string" - }, - "data": { - "type": "object" - }, - "id": { - "type": "string" - }, - "created_at": { - "type": "string", - "format": "date-time" - }, - "type": { - "type": "string" - } - }, - "required": [ - "agent_id", - "data", - "id", - "created_at", - "type" - ] - } - } - } - } - } - } - } - }, - "operationId": "post-fleet-agents-agentId-checkin", - "parameters": [ - { - "$ref": "#/paths/~1setup/post/parameters/0" - } - ], - "security": [ - { - "Access API Key": [] - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "local_metadata": { - "title": "AgentMetadata", - "type": "object" - }, - "events": { - "type": "array", - "items": { - "title": "NewAgentEvent", - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "STATE", - "ERROR", - "ACTION_RESULT", - "ACTION" - ] - }, - "subtype": { - "type": "string", - "enum": [ - "RUNNING", - "STARTING", - "IN_PROGRESS", - "CONFIG", - "FAILED", - "STOPPING", - "STOPPED", - "DEGRADED", - "DATA_DUMP", - "ACKNOWLEDGED", - "UNKNOWN" - ] - }, - "timestamp": { - "type": "string" - }, - "message": { - "type": "string" - }, - "payload": { - "type": "string" - }, - "agent_id": { - "type": "string" - }, - "policy_id": { - "type": "string" - }, - "stream_id": { - "type": "string" - }, - "action_id": { - "type": "string" - } - }, - "required": [ - "type", - "subtype", - "timestamp", - "message", - "agent_id" - ] - } - } - } - } - } - } - } - } - }, - "/agents/{agentId}/events": { - "parameters": [ - { - "schema": { - "type": "string" - }, - "name": "agentId", - "in": "path", - "required": true - } - ], - "get": { - "summary": "Fleet - Agent - Events", - "tags": [], - "responses": {}, - "operationId": "get-fleet-agents-agentId-events" - } - }, "/agents/{agentId}/unenroll": { "parameters": [ { @@ -701,6 +473,9 @@ "schema": { "type": "object", "properties": { + "revoke": { + "type": "boolean" + }, "force": { "type": "boolean" } @@ -895,184 +670,6 @@ } } }, - "/agents/enroll": { - "post": { - "summary": "Fleet - Agent - Enroll", - "tags": [], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "action": { - "type": "string" - }, - "item": { - "title": "Agent", - "type": "object", - "properties": { - "type": { - "type": "string", - "title": "AgentType", - "enum": [ - "PERMANENT", - "EPHEMERAL", - "TEMPORARY" - ] - }, - "active": { - "type": "boolean" - }, - "enrolled_at": { - "type": "string" - }, - "unenrolled_at": { - "type": "string" - }, - "unenrollment_started_at": { - "type": "string" - }, - "shared_id": { - "type": "string", - "deprecated": true - }, - "access_api_key_id": { - "type": "string" - }, - "default_api_key_id": { - "type": "string" - }, - "policy_id": { - "type": "string" - }, - "policy_revision": { - "type": "number" - }, - "last_checkin": { - "type": "string" - }, - "user_provided_metadata": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" - }, - "local_metadata": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" - }, - "id": { - "type": "string" - }, - "current_error_events": { - "type": "array", - "items": { - "title": "AgentEvent", - "allOf": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - } - }, - "required": [ - "id" - ] - }, - { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/events/items" - } - ] - } - }, - "access_api_key": { - "type": "string" - }, - "status": { - "type": "string", - "title": "AgentStatus", - "enum": [ - "offline", - "error", - "online", - "inactive", - "warning" - ] - }, - "default_api_key": { - "type": "string" - } - }, - "required": [ - "type", - "active", - "enrolled_at", - "id", - "current_error_events", - "status" - ] - } - } - } - } - } - } - }, - "operationId": "post-fleet-agents-enroll", - "parameters": [ - { - "$ref": "#/paths/~1setup/post/parameters/0" - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "PERMANENT", - "EPHEMERAL", - "TEMPORARY" - ] - }, - "shared_id": { - "type": "string", - "deprecated": true - }, - "metadata": { - "type": "object", - "required": [ - "local", - "user_provided" - ], - "properties": { - "local": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" - }, - "user_provided": { - "$ref": "#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata" - } - } - } - }, - "required": [ - "type", - "metadata" - ] - } - } - } - }, - "security": [ - { - "Enrollment API Key": [] - } - ] - } - }, "/agents/setup": { "get": { "summary": "Agents setup - Info", diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml index 227faffdac489f..537ef136c76110 100644 --- a/x-pack/plugins/fleet/common/openapi/bundled.yaml +++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml @@ -277,156 +277,6 @@ paths: operationId: get-fleet-agents security: - basicAuth: [] - '/agents/{agentId}/acks': - parameters: - - schema: - type: string - name: agentId - in: path - required: true - post: - summary: Fleet - Agent - Acks - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - action: - type: string - enum: - - acks - required: - - action - operationId: post-fleet-agents-agentId-acks - parameters: - - $ref: '#/paths/~1setup/post/parameters/0' - requestBody: - content: - application/json: - schema: - type: object - properties: {} - '/agents/{agentId}/checkin': - parameters: - - schema: - type: string - name: agentId - in: path - required: true - post: - summary: Fleet - Agent - Check In - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - action: - type: string - enum: - - checkin - actions: - type: array - items: - type: object - properties: - agent_id: - type: string - data: - type: object - id: - type: string - created_at: - type: string - format: date-time - type: - type: string - required: - - agent_id - - data - - id - - created_at - - type - operationId: post-fleet-agents-agentId-checkin - parameters: - - $ref: '#/paths/~1setup/post/parameters/0' - security: - - Access API Key: [] - requestBody: - content: - application/json: - schema: - type: object - properties: - local_metadata: - title: AgentMetadata - type: object - events: - type: array - items: - title: NewAgentEvent - type: object - properties: - type: - type: string - enum: - - STATE - - ERROR - - ACTION_RESULT - - ACTION - subtype: - type: string - enum: - - RUNNING - - STARTING - - IN_PROGRESS - - CONFIG - - FAILED - - STOPPING - - STOPPED - - DEGRADED - - DATA_DUMP - - ACKNOWLEDGED - - UNKNOWN - timestamp: - type: string - message: - type: string - payload: - type: string - agent_id: - type: string - policy_id: - type: string - stream_id: - type: string - action_id: - type: string - required: - - type - - subtype - - timestamp - - message - - agent_id - '/agents/{agentId}/events': - parameters: - - schema: - type: string - name: agentId - in: path - required: true - get: - summary: Fleet - Agent - Events - tags: [] - responses: {} - operationId: get-fleet-agents-agentId-events '/agents/{agentId}/unenroll': parameters: - schema: @@ -447,6 +297,8 @@ paths: schema: type: object properties: + revoke: + type: boolean force: type: boolean '/agents/{agentId}/upgrade': @@ -558,123 +410,6 @@ paths: required: - version - agents - /agents/enroll: - post: - summary: Fleet - Agent - Enroll - tags: [] - responses: - '200': - description: OK - content: - application/json: - schema: - type: object - properties: - action: - type: string - item: - title: Agent - type: object - properties: - type: - type: string - title: AgentType - enum: - - PERMANENT - - EPHEMERAL - - TEMPORARY - active: - type: boolean - enrolled_at: - type: string - unenrolled_at: - type: string - unenrollment_started_at: - type: string - shared_id: - type: string - deprecated: true - access_api_key_id: - type: string - default_api_key_id: - type: string - policy_id: - type: string - policy_revision: - type: number - last_checkin: - type: string - user_provided_metadata: - $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' - local_metadata: - $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' - id: - type: string - current_error_events: - type: array - items: - title: AgentEvent - allOf: - - type: object - properties: - id: - type: string - required: - - id - - $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/events/items' - access_api_key: - type: string - status: - type: string - title: AgentStatus - enum: - - offline - - error - - online - - inactive - - warning - default_api_key: - type: string - required: - - type - - active - - enrolled_at - - id - - current_error_events - - status - operationId: post-fleet-agents-enroll - parameters: - - $ref: '#/paths/~1setup/post/parameters/0' - requestBody: - content: - application/json: - schema: - type: object - properties: - type: - type: string - enum: - - PERMANENT - - EPHEMERAL - - TEMPORARY - shared_id: - type: string - deprecated: true - metadata: - type: object - required: - - local - - user_provided - properties: - local: - $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' - user_provided: - $ref: '#/paths/~1agents~1%7BagentId%7D~1checkin/post/requestBody/content/application~1json/schema/properties/local_metadata' - required: - - type - - metadata - security: - - Enrollment API Key: [] /agents/setup: get: summary: Agents setup - Info diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml deleted file mode 100644 index ada709378a9b1b..00000000000000 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/agent_event.yaml +++ /dev/null @@ -1,9 +0,0 @@ -title: AgentEvent -allOf: - - type: object - properties: - id: - type: string - required: - - id - - $ref: ./new_agent_event.yaml diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml deleted file mode 100644 index ee4ddfb5f004dd..00000000000000 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/new_agent_event.yaml +++ /dev/null @@ -1,44 +0,0 @@ -title: NewAgentEvent -type: object -properties: - type: - type: string - enum: - - STATE - - ERROR - - ACTION_RESULT - - ACTION - subtype: - type: string - enum: - - RUNNING - - STARTING - - IN_PROGRESS - - CONFIG - - FAILED - - STOPPING - - STOPPED - - DEGRADED - - DATA_DUMP - - ACKNOWLEDGED - - UNKNOWN - timestamp: - type: string - message: - type: string - payload: - type: string - agent_id: - type: string - policy_id: - type: string - stream_id: - type: string - action_id: - type: string -required: - - type - - subtype - - timestamp - - message - - agent_id diff --git a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml index 791d3da56783e9..6ea8ae966bdca9 100644 --- a/x-pack/plugins/fleet/common/openapi/entrypoint.yaml +++ b/x-pack/plugins/fleet/common/openapi/entrypoint.yaml @@ -22,20 +22,12 @@ paths: $ref: paths/agent_status.yaml /agents: $ref: paths/agents.yaml - '/agents/{agentId}/acks': - $ref: 'paths/agents@{agent_id}@acks.yaml' - '/agents/{agentId}/checkin': - $ref: 'paths/agents@{agent_id}@checkin.yaml' - '/agents/{agentId}/events': - $ref: 'paths/agents@{agent_id}@events.yaml' '/agents/{agentId}/unenroll': $ref: 'paths/agents@{agent_id}@unenroll.yaml' '/agents/{agentId}/upgrade': $ref: 'paths/agents@{agent_id}@upgrade.yaml' /agents/bulk_upgrade: $ref: paths/agents@bulk_upgrade.yaml - /agents/enroll: - $ref: paths/agents@enroll.yaml /agents/setup: $ref: paths/agents@setup.yaml /enrollment-api-keys: diff --git a/x-pack/plugins/fleet/common/openapi/paths/README.md b/x-pack/plugins/fleet/common/openapi/paths/README.md index f5003e3e3473b1..e5bd80632c7ee1 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/README.md +++ b/x-pack/plugins/fleet/common/openapi/paths/README.md @@ -30,9 +30,6 @@ paths/ ├── agents@enroll.yaml ├── agents@setup.yaml ├── agents@{agent_id}.yaml -├── agents@{agent_id}@acks.yaml -├── agents@{agent_id}@checkin.yaml -├── agents@{agent_id}@events.yaml ├── agents@{agent_id}@unenroll.yaml ├── agents@{agent_id}@upgrade.yaml ├── enrollment_api_keys.yaml diff --git a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml index 00c9cdfbcf4ae7..eccbbbfc9b8cae 100644 --- a/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml +++ b/x-pack/plugins/fleet/common/openapi/paths/agents@{agent_id}@unenroll.yaml @@ -17,5 +17,7 @@ post: schema: type: object properties: + revoke: + type: boolean force: type: boolean From 07c45e1bcf9549a165a24c757942b09fe8d09a6a Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 21 Apr 2021 09:51:27 -0600 Subject: [PATCH 06/65] [Maps] convert LayerControl to TS (#97728) * [Maps] convert LayerControl to TS * layer_control --- .../maps/public/classes/layers/layer.tsx | 1 + ...st.js.snap => layer_control.test.tsx.snap} | 0 .../layer_control/{index.js => index.ts} | 13 ++-- .../{view.test.js => layer_control.test.tsx} | 14 ++-- .../{view.js => layer_control.tsx} | 25 +++++++- ...w.test.js.snap => layer_toc.test.tsx.snap} | 0 .../layer_toc/{index.js => index.ts} | 23 ++++--- .../{view.test.js => layer_toc.test.tsx} | 21 ++++-- .../layer_toc/{view.js => layer_toc.tsx} | 26 +++++--- ...w.test.js.snap => toc_entry.test.tsx.snap} | 22 +++++++ .../toc_entry/{index.js => index.ts} | 26 +++++--- .../{view.test.js => toc_entry.test.tsx} | 32 ++++++---- .../toc_entry/{view.js => toc_entry.tsx} | 64 +++++++++++++++---- .../toc_entry/toc_entry_button/index.ts | 8 ++- .../toc_entry_button/toc_entry_button.tsx | 4 +- 15 files changed, 196 insertions(+), 83 deletions(-) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/{view.test.js.snap => layer_control.test.tsx.snap} (100%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/{index.js => index.ts} (72%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/{view.test.js => layer_control.test.tsx} (90%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/{view.js => layer_control.tsx} (91%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/{view.test.js.snap => layer_toc.test.tsx.snap} (100%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/{index.js => index.ts} (52%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/{view.test.js => layer_toc.test.tsx} (68%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/{view.js => layer_toc.tsx} (78%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/{view.test.js.snap => toc_entry.test.tsx.snap} (92%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/{index.js => index.ts} (65%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/{view.test.js => toc_entry.test.tsx} (74%) rename x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/{view.js => toc_entry.tsx} (83%) diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx index de889608300bd2..5786b5fb194b80 100644 --- a/x-pack/plugins/maps/public/classes/layers/layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx @@ -54,6 +54,7 @@ export interface ILayer { supportsFitToBounds(): Promise; getAttributions(): Promise; getLabel(): string; + hasLegendDetails(): Promise; renderLegendDetails(): ReactElement | null; showAtZoomLevel(zoom: number): boolean; getMinZoom(): number; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/layer_control.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/view.test.js.snap rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/__snapshots__/layer_control.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.ts similarity index 72% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.ts index 7ed2fa006cc836..5f293dba05d9dd 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/index.ts @@ -5,8 +5,10 @@ * 2.0. */ +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; -import { LayerControl } from './view'; +import { LayerControl } from './layer_control'; import { FLYOUT_STATE } from '../../../reducers/ui'; import { setSelectedLayer, updateFlyout, setIsLayerTOCOpen } from '../../../actions'; @@ -16,8 +18,9 @@ import { getFlyoutDisplay, } from '../../../selectors/ui_selectors'; import { getLayerList } from '../../../selectors/map_selectors'; +import { MapStoreState } from '../../../reducers/store'; -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { isReadOnly: getIsReadOnly(state), isLayerTOCOpen: getIsLayerTOCOpen(state), @@ -26,7 +29,7 @@ function mapStateToProps(state = {}) { }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: ThunkDispatch) { return { showAddLayerWizard: async () => { await dispatch(setSelectedLayer(null)); @@ -41,5 +44,5 @@ function mapDispatchToProps(dispatch) { }; } -const connectedLayerControl = connect(mapStateToProps, mapDispatchToProps)(LayerControl); -export { connectedLayerControl as LayerControl }; +const connected = connect(mapStateToProps, mapDispatchToProps)(LayerControl); +export { connected as LayerControl }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.test.tsx similarity index 90% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.test.tsx index e4af1ad4f46cab..cde42f42362e08 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_control.test.tsx @@ -14,10 +14,12 @@ jest.mock('./layer_toc', () => ({ import React from 'react'; import { shallow } from 'enzyme'; -import { LayerControl } from './view'; +import { LayerControl } from './layer_control'; +import { ILayer } from '../../../classes/layers/layer'; const defaultProps = { - showAddLayerWizard: () => {}, + isReadOnly: false, + showAddLayerWizard: async () => {}, closeLayerTOC: () => {}, openLayerTOC: () => {}, isLayerTOCOpen: true, @@ -53,7 +55,7 @@ describe('LayerControl', () => { describe('spinner icon', () => { const isLayerLoading = true; let isVisible = true; - const mockLayerThatIsLoading = { + const mockLayerThatIsLoading = ({ hasErrors: () => { return false; }, @@ -63,7 +65,7 @@ describe('LayerControl', () => { isVisible: () => { return isVisible; }, - }; + } as unknown) as ILayer; test('Should render expand button with loading icon when layer is loading', () => { const component = shallow( { }); test('Should render expand button with error icon when layer has error', () => { - const mockLayerThatHasError = { + const mockLayerThatHasError = ({ hasErrors: () => { return true; }, isLayerLoading: () => { return false; }, - }; + } as unknown) as ILayer; const component = shallow( Promise; + closeLayerTOC: () => void; + openLayerTOC: () => void; +} -function renderExpandButton({ hasErrors, isLoading, onClick }) { +function renderExpandButton({ + hasErrors, + isLoading, + onClick, +}: { + hasErrors: boolean; + isLoading: boolean; + onClick: () => void; +}) { const expandLabel = i18n.translate('xpack.maps.layerControl.openLayerTOCButtonAriaLabel', { defaultMessage: 'Expand layers panel', }); @@ -59,7 +78,7 @@ export function LayerControl({ openLayerTOC, layerList, isFlyoutOpen, -}) { +}: Props) { if (!isLayerTOCOpen) { const hasErrors = layerList.some((layer) => { return layer.hasErrors(); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/view.test.js.snap rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/__snapshots__/layer_toc.test.tsx.snap diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.ts similarity index 52% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.ts index 311765da8b6f39..ab9b043b18d8b2 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/index.ts @@ -5,24 +5,27 @@ * 2.0. */ +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; -import { LayerTOC } from './view'; +import { LayerTOC } from './layer_toc'; import { updateLayerOrder } from '../../../../actions'; import { getLayerList } from '../../../../selectors/map_selectors'; import { getIsReadOnly } from '../../../../selectors/ui_selectors'; +import { MapStoreState } from '../../../../reducers/store'; -const mapDispatchToProps = { - updateLayerOrder: (newOrder) => updateLayerOrder(newOrder), -}; - -function mapStateToProps(state = {}) { +function mapStateToProps(state: MapStoreState) { return { isReadOnly: getIsReadOnly(state), layerList: getLayerList(state), }; } -const connectedLayerTOC = connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })( - LayerTOC -); -export { connectedLayerTOC as LayerTOC }; +function mapDispatchToProps(dispatch: ThunkDispatch) { + return { + updateLayerOrder: (newOrder: number[]) => dispatch(updateLayerOrder(newOrder)), + }; +} + +const connected = connect(mapStateToProps, mapDispatchToProps)(LayerTOC); +export { connected as LayerTOC }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.test.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.test.tsx similarity index 68% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.test.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.test.tsx index 4a036d8d70c8f3..8f0b62efbffac1 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.test.tsx @@ -13,38 +13,45 @@ jest.mock('./toc_entry', () => ({ import React from 'react'; import { shallow } from 'enzyme'; +import { ILayer } from '../../../../classes/layers/layer'; -import { LayerTOC } from './view'; +import { LayerTOC } from './layer_toc'; const mockLayers = [ - { + ({ getId: () => { return '1'; }, supportsFitToBounds: () => { return true; }, - }, - { + } as unknown) as ILayer, + ({ getId: () => { return '2'; }, supportsFitToBounds: () => { return false; }, - }, + } as unknown) as ILayer, ]; +const defaultProps = { + layerList: mockLayers, + isReadOnly: false, + updateLayerOrder: () => {}, +}; + describe('LayerTOC', () => { test('is rendered', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); describe('props', () => { test('isReadOnly', () => { - const component = shallow(); + const component = shallow(); expect(component).toMatchSnapshot(); }); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.tsx similarity index 78% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.tsx index 1ef718c0650e4d..1800f2dc33618a 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/layer_toc.tsx @@ -6,11 +6,18 @@ */ import _ from 'lodash'; -import React from 'react'; -import { EuiDragDropContext, EuiDroppable, EuiDraggable } from '@elastic/eui'; +import React, { Component } from 'react'; +import { DropResult, EuiDragDropContext, EuiDroppable, EuiDraggable } from '@elastic/eui'; import { TOCEntry } from './toc_entry'; +import { ILayer } from '../../../../classes/layers/layer'; -export class LayerTOC extends React.Component { +export interface Props { + isReadOnly: boolean; + layerList: ILayer[]; + updateLayerOrder: (newOrder: number[]) => void; +} + +export class LayerTOC extends Component { componentWillUnmount() { this._updateDebounced.cancel(); } @@ -22,14 +29,14 @@ export class LayerTOC extends React.Component { _updateDebounced = _.debounce(this.forceUpdate, 100); - _onDragEnd = ({ source, destination }) => { + _onDragEnd = ({ source, destination }: DropResult) => { // Dragging item out of EuiDroppable results in destination of null if (!destination) { return; } // Layer list is displayed in reverse order so index needs to reversed to get back to original reference. - const reverseIndex = (index) => { + const reverseIndex = (index: number) => { return this.props.layerList.length - index - 1; }; @@ -58,8 +65,8 @@ export class LayerTOC extends React.Component { return ( - {(provided, snapshot) => - reverseLayerList.map((layer, idx) => ( + {(droppableProvided, snapshot) => { + const tocEntries = reverseLayerList.map((layer, idx: number) => ( )} - )) - } + )); + return
{tocEntries}
; + }}
); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap similarity index 92% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap index b43d740e729075..3abc6801122fb8 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/view.test.js.snap +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/__snapshots__/toc_entry.test.tsx.snap @@ -13,6 +13,7 @@ exports[`TOCEntry is rendered 1`] = ` displayName="layer 1" editLayer={[Function]} escapedDisplayName="layer_1" + isEditButtonDisabled={false} layer={ Object { "getDisplayName": [Function], @@ -33,12 +34,15 @@ exports[`TOCEntry is rendered 1`] = ` @@ -46,6 +50,7 @@ exports[`TOCEntry is rendered 1`] = ` aria-label="Reorder layer" className="mapTocEntry__grab" iconType="grab" + key="reorder" title="Reorder layer" /> @@ -82,6 +87,7 @@ exports[`TOCEntry props Should shade background when not selected layer 1`] = ` displayName="layer 1" editLayer={[Function]} escapedDisplayName="layer_1" + isEditButtonDisabled={false} layer={ Object { "getDisplayName": [Function], @@ -102,12 +108,15 @@ exports[`TOCEntry props Should shade background when not selected layer 1`] = ` @@ -115,6 +124,7 @@ exports[`TOCEntry props Should shade background when not selected layer 1`] = ` aria-label="Reorder layer" className="mapTocEntry__grab" iconType="grab" + key="reorder" title="Reorder layer" /> @@ -151,6 +161,7 @@ exports[`TOCEntry props Should shade background when selected layer 1`] = ` displayName="layer 1" editLayer={[Function]} escapedDisplayName="layer_1" + isEditButtonDisabled={false} layer={ Object { "getDisplayName": [Function], @@ -171,12 +182,15 @@ exports[`TOCEntry props Should shade background when selected layer 1`] = ` @@ -184,6 +198,7 @@ exports[`TOCEntry props Should shade background when selected layer 1`] = ` aria-label="Reorder layer" className="mapTocEntry__grab" iconType="grab" + key="reorder" title="Reorder layer" /> @@ -220,6 +235,7 @@ exports[`TOCEntry props isReadOnly 1`] = ` displayName="layer 1" editLayer={[Function]} escapedDisplayName="layer_1" + isEditButtonDisabled={false} layer={ Object { "getDisplayName": [Function], @@ -240,6 +256,7 @@ exports[`TOCEntry props isReadOnly 1`] = ` @@ -277,6 +294,7 @@ exports[`TOCEntry props should display layer details when isLegendDetailsOpen is displayName="layer 1" editLayer={[Function]} escapedDisplayName="layer_1" + isEditButtonDisabled={false} layer={ Object { "getDisplayName": [Function], @@ -297,12 +315,15 @@ exports[`TOCEntry props should display layer details when isLegendDetailsOpen is @@ -310,6 +331,7 @@ exports[`TOCEntry props should display layer details when isLegendDetailsOpen is aria-label="Reorder layer" className="mapTocEntry__grab" iconType="grab" + key="reorder" title="Reorder layer" /> diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.ts similarity index 65% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.ts index ee7922a579c349..eaebc9099ada12 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/index.ts @@ -5,8 +5,11 @@ * 2.0. */ +import { AnyAction } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; import { connect } from 'react-redux'; -import { TOCEntry } from './view'; +import { TOCEntry, OwnProps, ReduxDispatchProps, ReduxStateProps } from './toc_entry'; +import { MapStoreState } from '../../../../../reducers/store'; import { FLYOUT_STATE } from '../../../../../reducers/ui'; import { getMapZoom, @@ -27,7 +30,7 @@ import { toggleLayerVisible, } from '../../../../../actions'; -function mapStateToProps(state = {}, ownProps) { +function mapStateToProps(state: MapStoreState, ownProps: OwnProps): ReduxStateProps { const flyoutDisplay = getFlyoutDisplay(state); return { isReadOnly: getIsReadOnly(state), @@ -40,26 +43,29 @@ function mapStateToProps(state = {}, ownProps) { }; } -function mapDispatchToProps(dispatch) { +function mapDispatchToProps(dispatch: ThunkDispatch) { return { - fitToBounds: (layerId) => { + fitToBounds: (layerId: string) => { dispatch(fitToLayerExtent(layerId)); }, - openLayerPanel: async (layerId) => { + openLayerPanel: async (layerId: string) => { await dispatch(setSelectedLayer(layerId)); dispatch(updateFlyout(FLYOUT_STATE.LAYER_PANEL)); }, - hideTOCDetails: (layerId) => { + hideTOCDetails: (layerId: string) => { dispatch(hideTOCDetails(layerId)); }, - showTOCDetails: (layerId) => { + showTOCDetails: (layerId: string) => { dispatch(showTOCDetails(layerId)); }, - toggleVisible: (layerId) => { + toggleVisible: (layerId: string) => { dispatch(toggleLayerVisible(layerId)); }, }; } -const connectedTOCEntry = connect(mapStateToProps, mapDispatchToProps)(TOCEntry); -export { connectedTOCEntry as TOCEntry }; +const connected = connect( + mapStateToProps, + mapDispatchToProps +)(TOCEntry); +export { connected as TOCEntry }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.test.tsx similarity index 74% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.test.tsx index ea7afd8480d10e..4d80f762b6a829 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.test.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.test.tsx @@ -6,7 +6,8 @@ */ import React from 'react'; -import { shallowWithIntl } from '@kbn/test/jest'; +import { shallow } from 'enzyme'; +import { ILayer } from '../../../../../classes/layers/layer'; jest.mock('../../../../../kibana_services', () => { return { @@ -16,11 +17,11 @@ jest.mock('../../../../../kibana_services', () => { }; }); -import { TOCEntry } from './view'; +import { TOCEntry } from './toc_entry'; const LAYER_ID = '1'; -const mockLayer = { +const mockLayer = ({ getId: () => { return LAYER_ID; }, @@ -45,22 +46,27 @@ const mockLayer = { hasLegendDetails: () => { return true; }, -}; +} as unknown) as ILayer; const defaultProps = { layer: mockLayer, - openLayerPanel: () => {}, + selectedLayer: undefined, + openLayerPanel: async () => {}, toggleVisible: () => {}, fitToBounds: () => {}, getSelectedLayerSelector: () => {}, - hasDirtyStateSelector: () => {}, + hasDirtyStateSelector: false, zoom: 0, isLegendDetailsOpen: false, + isReadOnly: false, + isEditButtonDisabled: false, + hideTOCDetails: () => {}, + showTOCDetails: () => {}, }; describe('TOCEntry', () => { test('is rendered', async () => { - const component = shallowWithIntl(); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -72,7 +78,7 @@ describe('TOCEntry', () => { describe('props', () => { test('isReadOnly', async () => { - const component = shallowWithIntl(); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -83,7 +89,7 @@ describe('TOCEntry', () => { }); test('should display layer details when isLegendDetailsOpen is true', async () => { - const component = shallowWithIntl(); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -94,7 +100,7 @@ describe('TOCEntry', () => { }); test('Should shade background when selected layer', async () => { - const component = shallowWithIntl(); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); @@ -105,13 +111,11 @@ describe('TOCEntry', () => { }); test('Should shade background when not selected layer', async () => { - const differentLayer = Object.create(mockLayer); + const differentLayer = (Object.create(mockLayer) as unknown) as ILayer; differentLayer.getId = () => { return 'foobar'; }; - const component = shallowWithIntl( - - ); + const component = shallow(); // Ensure all promises resolve await new Promise((resolve) => process.nextTick(resolve)); diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.tsx similarity index 83% rename from x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js rename to x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.tsx index b886dd21030baf..553d7b94006f4a 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/view.js +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry.tsx @@ -5,26 +5,62 @@ * 2.0. */ -import React from 'react'; +import React, { Component } from 'react'; import classNames from 'classnames'; +import type { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd'; import { EuiIcon, EuiButtonIcon, EuiConfirmModal } from '@elastic/eui'; -import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; import { i18n } from '@kbn/i18n'; +import { TOCEntryActionsPopover } from './toc_entry_actions_popover'; import { getVisibilityToggleIcon, getVisibilityToggleLabel, EDIT_LAYER_LABEL, FIT_TO_DATA_LABEL, } from './action_labels'; +import { ILayer } from '../../../../../classes/layers/layer'; + +function escapeLayerName(name: string) { + return name.split(' ').join('_'); +} + +export interface ReduxStateProps { + isReadOnly: boolean; + zoom: number; + selectedLayer: ILayer | undefined; + hasDirtyStateSelector: boolean; + isLegendDetailsOpen: boolean; + isEditButtonDisabled: boolean; +} -function escapeLayerName(name) { - return name ? name.split(' ').join('_') : ''; +export interface ReduxDispatchProps { + fitToBounds: (layerId: string) => void; + openLayerPanel: (layerId: string) => Promise; + hideTOCDetails: (layerId: string) => void; + showTOCDetails: (layerId: string) => void; + toggleVisible: (layerId: string) => void; } -export class TOCEntry extends React.Component { - state = { - displayName: null, +export interface OwnProps { + layer: ILayer; + dragHandleProps?: DraggableProvidedDragHandleProps; + isDragging?: boolean; + isDraggingOver?: boolean; +} + +type Props = ReduxStateProps & ReduxDispatchProps & OwnProps; + +interface State { + displayName: string; + hasLegendDetails: boolean; + shouldShowModal: boolean; + supportsFitToBounds: boolean; +} + +export class TOCEntry extends Component { + private _isMounted = false; + state: State = { + displayName: '', hasLegendDetails: false, shouldShowModal: false, supportsFitToBounds: false, @@ -72,13 +108,9 @@ export class TOCEntry extends React.Component { } async _updateDisplayName() { - const label = await this.props.layer.getDisplayName(); - if (this._isMounted) { - if (label !== this.state.displayName) { - this.setState({ - displayName: label, - }); - } + const displayName = await this.props.layer.getDisplayName(); + if (this._isMounted && displayName !== this.state.displayName) { + this.setState({ displayName }); } } @@ -141,6 +173,7 @@ export class TOCEntry extends React.Component { _renderQuickActions() { const quickActions = [ (mapStateToProps)(TOCEntryButton); +const connected = connect(mapStateToProps)( + TOCEntryButton +); export { connected as TOCEntryButton }; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx index 058a3b82224b98..41c2992c77d88d 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_button/toc_entry_button.tsx @@ -22,7 +22,7 @@ interface IconAndTooltipContent { footnotes: Footnote[]; } -export interface StateProps { +export interface ReduxStateProps { isUsingSearch: boolean; zoom: number; } @@ -34,7 +34,7 @@ export interface OwnProps { onClick: () => void; } -type Props = StateProps & OwnProps; +type Props = ReduxStateProps & OwnProps; interface State { isFilteredByGlobalTime: boolean; From 47d863b0ff7a6dbcdd6e0fced762b61de3825120 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Wed, 21 Apr 2021 11:16:46 -0500 Subject: [PATCH 07/65] [ML] Fix Anomaly Detection job model memory limit input editable when datafeed is open (#97723) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../edit_job_flyout/edit_job_flyout.js | 6 ++- .../edit_job_flyout/tabs/job_details.js | 38 ++++++++++++++----- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index b23bbedb7413a2..758e3fa472a0b8 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -32,7 +32,7 @@ import { toastNotificationServiceProvider } from '../../../../services/toast_not import { ml } from '../../../../services/ml_api_service'; import { withKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; import { collapseLiteralStrings } from '../../../../../../shared_imports'; -import { DATAFEED_STATE } from '../../../../../../common/constants/states'; +import { DATAFEED_STATE, JOB_STATE } from '../../../../../../common/constants/states'; export class EditJobFlyoutUI extends Component { _initialJobFormState = null; @@ -176,11 +176,13 @@ export class EditJobFlyoutUI extends Component { extractJob(job, hasDatafeed) { this.extractInitialJobFormState(job, hasDatafeed); const datafeedRunning = hasDatafeed && job.datafeed_config.state !== DATAFEED_STATE.STOPPED; + const jobClosed = job.state === JOB_STATE.CLOSED; this.setState({ job, hasDatafeed, datafeedRunning, + jobClosed, jobModelMemoryLimitValidationError: '', jobGroupsValidationError: '', ...cloneDeep(this._initialJobFormState), @@ -318,6 +320,7 @@ export class EditJobFlyoutUI extends Component { isValidJobDetails, isValidJobCustomUrls, datafeedRunning, + jobClosed, } = this.state; const tabs = [ @@ -328,6 +331,7 @@ export class EditJobFlyoutUI extends Component { }), content: ( ({ label: g, color: tabColor(g) })) : []; + const { datafeedRunning, jobClosed } = props; + + let mmlHelpText = null; + if (!jobClosed) { + mmlHelpText = ( + + ); + } + + if (datafeedRunning) { + mmlHelpText = ( + + ); + } + return { description: props.jobDescription, selectedGroups, mml: props.jobModelMemoryLimit, + mmlHelpText, mmlValidationError: props.jobModelMemoryLimitValidationError, groupsValidationError: props.jobGroupsValidationError, modelSnapshotRetentionDays: props.jobModelSnapshotRetentionDays, @@ -139,8 +161,11 @@ export class JobDetails extends Component { groupsValidationError, modelSnapshotRetentionDays, dailyModelSnapshotRetentionAfterDays, + mmlHelpText, } = this.state; - const { datafeedRunning } = this.props; + + const { datafeedRunning, jobClosed } = this.props; + return ( @@ -188,14 +213,7 @@ export class JobDetails extends Component { defaultMessage="Model memory limit" /> } - helpText={ - datafeedRunning ? ( - - ) : null - } + helpText={mmlHelpText} isInvalid={mmlValidationError !== ''} error={mmlValidationError} > @@ -204,7 +222,7 @@ export class JobDetails extends Component { onChange={this.onMmlChange} isInvalid={mmlValidationError !== ''} error={mmlValidationError} - disabled={datafeedRunning} + disabled={datafeedRunning || !jobClosed} /> Date: Wed, 21 Apr 2021 10:17:58 -0600 Subject: [PATCH 08/65] [QA] Switch tests to use importExport - discover (#94006) Switch es archiver for kbn archiver everywhere possible Partially closes #93797 --- .../kbn_client/kbn_client_import_export.ts | 9 +- test/functional/apps/discover/_data_grid.ts | 3 +- .../apps/discover/_data_grid_context.ts | 3 +- .../apps/discover/_data_grid_doc_table.ts | 3 +- .../apps/discover/_data_grid_field_data.ts | 3 +- test/functional/apps/discover/_discover.ts | 5 - .../apps/discover/_discover_fields_api.ts | 3 +- test/functional/apps/discover/_doc_table.ts | 3 +- test/functional/apps/discover/_errors.ts | 2 +- test/functional/apps/discover/_field_data.ts | 3 +- .../discover/_field_data_with_fields_api.ts | 3 +- .../apps/discover/_filter_editor.ts | 3 +- test/functional/apps/discover/_inspector.ts | 4 +- .../functional/apps/discover/_large_string.ts | 4 +- .../apps/discover/_saved_queries.ts | 3 +- .../functional/apps/discover/_shared_links.ts | 3 +- .../fixtures/es_archiver/hamlet/data.json.gz | Bin 80587 -> 80080 bytes .../fixtures/es_archiver/hamlet/mappings.json | 349 ------------------ .../kbn_archiver/testlargestring.json | 17 + 19 files changed, 54 insertions(+), 369 deletions(-) create mode 100644 test/functional/fixtures/kbn_archiver/testlargestring.json diff --git a/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts b/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts index 7f4d0160923bfe..fe67fbb70fa3ca 100644 --- a/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts +++ b/packages/kbn-test/src/kbn_client/kbn_client_import_export.ts @@ -80,7 +80,14 @@ export class KbnClientImportExport { if (resp.data.success) { this.log.success('import success'); } else { - throw createFailError(`failed to import all saved objects: ${inspect(resp.data)}`); + throw createFailError( + `failed to import all saved objects: ${inspect(resp.data, { + compact: false, + depth: 99, + breakLength: 80, + sorted: true, + })}` + ); } } diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts index a8b05c0d902885..366865b53fb0dc 100644 --- a/test/functional/apps/discover/_data_grid.ts +++ b/test/functional/apps/discover/_data_grid.ts @@ -23,7 +23,8 @@ export default function ({ const testSubjects = getService('testSubjects'); before(async function () { - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); await PageObjects.common.navigateToApp('discover'); diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts index ab826ec4f985fd..275ac011820bed 100644 --- a/test/functional/apps/discover/_data_grid_context.ts +++ b/test/functional/apps/discover/_data_grid_context.ts @@ -36,7 +36,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover data grid context tests', () => { before(async () => { - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update(defaultSettings); diff --git a/test/functional/apps/discover/_data_grid_doc_table.ts b/test/functional/apps/discover/_data_grid_doc_table.ts index f8406f4c8a8a9a..feecc7f5355190 100644 --- a/test/functional/apps/discover/_data_grid_doc_table.ts +++ b/test/functional/apps/discover/_data_grid_doc_table.ts @@ -27,7 +27,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover data grid doc table', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index 62c27c8d50dc4e..5d73192c1608cf 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -22,7 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover data grid field data tests', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); await kibanaServer.uiSettings.update(defaultSettings); diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index ab53eca0cedf5e..7bdc3490a959fa 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -11,7 +11,6 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const savedObjectInfo = getService('savedObjectInfo'); const browser = getService('browser'); const log = getService('log'); const retry = getService('retry'); @@ -30,11 +29,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); - await kibanaServer.savedObjects.clean({ types: ['search'] }); await kibanaServer.importExport.load('discover'); - log.info( - `\n### SAVED OBJECT TYPES IN index: [.kibana]: \n\t${await savedObjectInfo.types()}` - ); // and load a set of makelogs data await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/test/functional/apps/discover/_discover_fields_api.ts b/test/functional/apps/discover/_discover_fields_api.ts index 8ee8aabbfbbe07..0a6029a9f10e85 100644 --- a/test/functional/apps/discover/_discover_fields_api.ts +++ b/test/functional/apps/discover/_discover_fields_api.ts @@ -22,7 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover uses fields API test', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); log.debug('discover'); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index 1fd26b561195ee..7cb33e6a7c2b8c 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -28,7 +28,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { log.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); // and load a set of makelogs data await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/test/functional/apps/discover/_errors.ts b/test/functional/apps/discover/_errors.ts index fefa8665b0a573..8b8877016b103e 100644 --- a/test/functional/apps/discover/_errors.ts +++ b/test/functional/apps/discover/_errors.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); after(async function () { - await esArchiver.unload('invalid_scripted_field'); + await esArchiver.load('empty_kibana'); }); describe('invalid scripted field error', () => { diff --git a/test/functional/apps/discover/_field_data.ts b/test/functional/apps/discover/_field_data.ts index 492925cf6b2df8..265c39678ce9d4 100644 --- a/test/functional/apps/discover/_field_data.ts +++ b/test/functional/apps/discover/_field_data.ts @@ -23,8 +23,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover tab', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.load('discover'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', 'discover:searchFieldsFromSource': true, 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 c2705311950113..92d36a243370bb 100644 --- a/test/functional/apps/discover/_field_data_with_fields_api.ts +++ b/test/functional/apps/discover/_field_data_with_fields_api.ts @@ -23,8 +23,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover tab with new fields API', function describeIndexTests() { this.tags('includeFirefox'); before(async function () { + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.load('discover'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', 'discover:searchFieldsFromSource': false, diff --git a/test/functional/apps/discover/_filter_editor.ts b/test/functional/apps/discover/_filter_editor.ts index 903059fc54020f..b94ba3cda40440 100644 --- a/test/functional/apps/discover/_filter_editor.ts +++ b/test/functional/apps/discover/_filter_editor.ts @@ -24,7 +24,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('discover filter editor', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); - await esArchiver.loadIfNeeded('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); // and load a set of makelogs data await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/test/functional/apps/discover/_inspector.ts b/test/functional/apps/discover/_inspector.ts index 8516e202e2d531..ca8539df9a9260 100644 --- a/test/functional/apps/discover/_inspector.ts +++ b/test/functional/apps/discover/_inspector.ts @@ -32,8 +32,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('inspect', () => { before(async () => { + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); - await esArchiver.load('discover'); // delete .kibana index and update configDoc await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', diff --git a/test/functional/apps/discover/_large_string.ts b/test/functional/apps/discover/_large_string.ts index fcc36d11a1eb99..9383f8fdc8c77b 100644 --- a/test/functional/apps/discover/_large_string.ts +++ b/test/functional/apps/discover/_large_string.ts @@ -22,7 +22,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('test large strings', function () { before(async function () { await security.testUser.setRoles(['kibana_admin', 'kibana_large_strings']); - await esArchiver.load('empty_kibana'); + + await kibanaServer.importExport.load('testlargestring'); await esArchiver.loadIfNeeded('hamlet'); await kibanaServer.uiSettings.replace({ defaultIndex: 'testlargestring' }); }); @@ -73,6 +74,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await security.testUser.restoreDefaults(); await esArchiver.unload('hamlet'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); }); }); } diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts index a7374c81286306..acf78802fa18f7 100644 --- a/test/functional/apps/discover/_saved_queries.ts +++ b/test/functional/apps/discover/_saved_queries.ts @@ -29,7 +29,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('saved queries saved objects', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); // and load a set of makelogs data await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/test/functional/apps/discover/_shared_links.ts b/test/functional/apps/discover/_shared_links.ts index 9522b665dd6499..555d5ad2d94d27 100644 --- a/test/functional/apps/discover/_shared_links.ts +++ b/test/functional/apps/discover/_shared_links.ts @@ -38,7 +38,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); log.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); + await kibanaServer.savedObjects.clean({ types: ['search', 'index-pattern'] }); + await kibanaServer.importExport.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.replace({ diff --git a/test/functional/fixtures/es_archiver/hamlet/data.json.gz b/test/functional/fixtures/es_archiver/hamlet/data.json.gz index 822022489ae9d8351e86da9580bfceb89eb0f85f..add8df3a73f4a4b7aaa52bd664663776c95c4495 100644 GIT binary patch delta 78103 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
W+SQ@ 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 delta 78580 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 diff --git a/test/functional/fixtures/es_archiver/hamlet/mappings.json b/test/functional/fixtures/es_archiver/hamlet/mappings.json index bf051eb072be5c..fdfac3b5ad52c6 100644 --- a/test/functional/fixtures/es_archiver/hamlet/mappings.json +++ b/test/functional/fixtures/es_archiver/hamlet/mappings.json @@ -1,352 +1,3 @@ -{ - "type": "index", - "value": { - "aliases": { - ".kibana": { - } - }, - "index": ".kibana_1", - "mappings": { - "_meta": { - "migrationMappingPropertyHashes": { - "config": "87aca8fdb053154f11383fce3dbf3edf", - "dashboard": "eb3789e1af878e73f85304333240f65f", - "index-pattern": "66eccb05066c5a89924f48a9e9736499", - "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", - "migrationVersion": "4a1746014a75ade3a714e1db5763276f", - "namespace": "2f4316de49999235636386fe51dc06c1", - "references": "7997cf5a56cc02bdc9c93361bde732b0", - "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", - "search": "181661168bbadd1eff5902361e2a0d5c", - "server": "ec97f1c5da1a19609a60874e5af1100c", - "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", - "type": "2f4316de49999235636386fe51dc06c1", - "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", - "updated_at": "00da57df13e94e9d98437d13ace4bfe0", - "url": "c7f66a0df8b1b52f17c28c4adb111105", - "visualization": "52d7a13ad68a150c4525b292d23e12cc" - } - }, - "dynamic": "strict", - "properties": { - "config": { - "dynamic": "true", - "properties": { - "buildNum": { - "type": "keyword" - }, - "dateFormat:tz": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "defaultIndex": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "dashboard": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "optionsJSON": { - "type": "text" - }, - "panelsJSON": { - "type": "text" - }, - "refreshInterval": { - "properties": { - "display": { - "type": "keyword" - }, - "pause": { - "type": "boolean" - }, - "section": { - "type": "integer" - }, - "value": { - "type": "integer" - } - } - }, - "timeFrom": { - "type": "keyword" - }, - "timeRestore": { - "type": "boolean" - }, - "timeTo": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "index-pattern": { - "properties": { - "fieldFormatMap": { - "type": "text" - }, - "fields": { - "type": "text" - }, - "intervalName": { - "type": "keyword" - }, - "notExpandable": { - "type": "boolean" - }, - "sourceFilters": { - "type": "text" - }, - "timeFieldName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "type": { - "type": "keyword" - }, - "typeMeta": { - "type": "keyword" - } - } - }, - "kql-telemetry": { - "properties": { - "optInCount": { - "type": "long" - }, - "optOutCount": { - "type": "long" - } - } - }, - "migrationVersion": { - "dynamic": "true", - "properties": { - "index-pattern": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "namespace": { - "type": "keyword" - }, - "references": { - "properties": { - "id": { - "type": "keyword" - }, - "name": { - "type": "keyword" - }, - "type": { - "type": "keyword" - } - }, - "type": "nested" - }, - "sample-data-telemetry": { - "properties": { - "installCount": { - "type": "long" - }, - "unInstallCount": { - "type": "long" - } - } - }, - "search": { - "properties": { - "columns": { - "type": "keyword" - }, - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "sort": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "server": { - "properties": { - "uuid": { - "type": "keyword" - } - } - }, - "timelion-sheet": { - "properties": { - "description": { - "type": "text" - }, - "hits": { - "type": "integer" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "timelion_chart_height": { - "type": "integer" - }, - "timelion_columns": { - "type": "integer" - }, - "timelion_interval": { - "type": "keyword" - }, - "timelion_other_interval": { - "type": "keyword" - }, - "timelion_rows": { - "type": "integer" - }, - "timelion_sheet": { - "type": "text" - }, - "title": { - "type": "text" - }, - "version": { - "type": "integer" - } - } - }, - "type": { - "type": "keyword" - }, - "ui-metric": { - "properties": { - "count": { - "type": "integer" - } - } - }, - "updated_at": { - "type": "date" - }, - "url": { - "properties": { - "accessCount": { - "type": "long" - }, - "accessDate": { - "type": "date" - }, - "createDate": { - "type": "date" - }, - "url": { - "fields": { - "keyword": { - "ignore_above": 2048, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "visualization": { - "properties": { - "description": { - "type": "text" - }, - "kibanaSavedObjectMeta": { - "properties": { - "searchSourceJSON": { - "type": "text" - } - } - }, - "savedSearchRefName": { - "type": "keyword" - }, - "title": { - "type": "text" - }, - "uiStateJSON": { - "type": "text" - }, - "version": { - "type": "integer" - }, - "visState": { - "type": "text" - } - } - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} - { "type": "index", "value": { diff --git a/test/functional/fixtures/kbn_archiver/testlargestring.json b/test/functional/fixtures/kbn_archiver/testlargestring.json new file mode 100644 index 00000000000000..874e6adc983aa0 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/testlargestring.json @@ -0,0 +1,17 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fields": "[]", + "runtimeFieldMap": "{}", + "title": "testlargestring" + }, + "coreMigrationVersion": "8.0.0", + "id": "testlargestring", + "migrationVersion": { + "index-pattern": "7.11.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2021-04-14T20:35:29.121Z", + "version": "WzExLDJd" +} \ No newline at end of file From dfde5cbdf79c95bf914164fab3759982b2c87732 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 10:05:08 -0700 Subject: [PATCH 09/65] skip flaky suite (#97584) --- .../security_and_spaces/tests/generating_signals.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index ed758aa85bde90..7d69e006666cde 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -40,7 +40,8 @@ export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe('Generating signals from source indexes', () => { + // FLAKY: https://github.com/elastic/kibana/issues/97584 + describe.skip('Generating signals from source indexes', () => { beforeEach(async () => { await createSignalsIndex(supertest); }); From efaaa40b40231010e64530adc9f9b5fa6c277bc6 Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Wed, 21 Apr 2021 11:22:38 -0600 Subject: [PATCH 10/65] [Security Solution] [Cases] Remove dynamic mappings for 3rd party cases mappings (#97754) --- .../server/client/configure/utils.test.ts | 12 --- .../cases/server/client/configure/utils.ts | 73 ++----------------- 2 files changed, 5 insertions(+), 80 deletions(-) diff --git a/x-pack/plugins/cases/server/client/configure/utils.test.ts b/x-pack/plugins/cases/server/client/configure/utils.test.ts index 403854693e36cb..41d62f5a9b91f9 100644 --- a/x-pack/plugins/cases/server/client/configure/utils.test.ts +++ b/x-pack/plugins/cases/server/client/configure/utils.test.ts @@ -11,7 +11,6 @@ export { ServiceNowGetFieldsResponse, } from '../../../../actions/server/types'; import { createDefaultMapping, formatFields } from './utils'; -import { ConnectorTypes } from '../../../common/api/connectors'; import { mappings, formatFieldsTestData } from './mock'; describe('client/configure/utils', () => { @@ -30,16 +29,5 @@ describe('client/configure/utils', () => { expect(result).toEqual(mappings[type]); }); }); - it(`if the preferredField is not required and another field is, use the other field`, () => { - const result = createDefaultMapping( - [ - { id: 'summary', name: 'Summary', required: false, type: 'text' }, - { id: 'title', name: 'Title', required: true, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'text' }, - ], - ConnectorTypes.jira - ); - expect(result).toEqual(mappings[`${ConnectorTypes.jira}-alt`]); - }); }); }); diff --git a/x-pack/plugins/cases/server/client/configure/utils.ts b/x-pack/plugins/cases/server/client/configure/utils.ts index 80e6c7a3b886c6..10c3e1fd3c1a94 100644 --- a/x-pack/plugins/cases/server/client/configure/utils.ts +++ b/x-pack/plugins/cases/server/client/configure/utils.ts @@ -5,11 +5,7 @@ * 2.0. */ -import { - ConnectorField, - ConnectorMappingsAttributes, - ConnectorTypes, -} from '../../../common/api/connectors'; +import { ConnectorField, ConnectorMappingsAttributes, ConnectorTypes } from '../../../common/api'; import { JiraGetFieldsResponse, ResilientGetFieldsResponse, @@ -78,17 +74,6 @@ export const formatFields = (theData: unknown, theType: string): ConnectorField[ return []; } }; -const findTextField = (fields: ConnectorField[]): string => - ( - fields.find((field: ConnectorField) => field.type === 'text' && field.required) ?? - fields.find((field: ConnectorField) => field.type === 'text') - )?.id ?? ''; -const findTextAreaField = (fields: ConnectorField[]): string => - ( - fields.find((field: ConnectorField) => field.type === 'textarea' && field.required) ?? - fields.find((field: ConnectorField) => field.type === 'textarea') ?? - fields.find((field: ConnectorField) => field.type === 'text') - )?.id ?? ''; const getPreferredFields = (theType: string) => { let title: string = ''; @@ -115,73 +100,25 @@ const getPreferredFields = (theType: string) => { return { title, description, comments }; }; -const getRemainingFields = (fields: ConnectorField[], titleTarget: string) => - fields.filter((field: ConnectorField) => field.id !== titleTarget); - -const getDynamicFields = (fields: ConnectorField[], dynamicTitle = findTextField(fields)) => { - const remainingFields = getRemainingFields(fields, dynamicTitle); - const dynamicDescription = findTextAreaField(remainingFields); - return { - description: dynamicDescription, - title: dynamicTitle, - }; -}; - -const getField = (fields: ConnectorField[], fieldId: string) => - fields.find((field: ConnectorField) => field.id === fieldId); - -// if dynamic title is not required and preferred is, true -const shouldTargetBePreferred = ( - fields: ConnectorField[], - dynamic: string, - preferred: string -): boolean => { - if (dynamic !== preferred) { - const dynamicT = getField(fields, dynamic); - const preferredT = getField(fields, preferred); - return preferredT != null && !(dynamicT?.required && !preferredT.required); - } - return false; -}; export const createDefaultMapping = ( fields: ConnectorField[], theType: string ): ConnectorMappingsAttributes[] => { - const { description: dynamicDescription, title: dynamicTitle } = getDynamicFields(fields); - - const { - description: preferredDescription, - title: preferredTitle, - comments: preferredComments, - } = getPreferredFields(theType); - - let titleTarget = dynamicTitle; - let descriptionTarget = dynamicDescription; - - if (preferredTitle.length > 0 && preferredDescription.length > 0) { - if (shouldTargetBePreferred(fields, dynamicTitle, preferredTitle)) { - const { description: dynamicDescriptionOverwrite } = getDynamicFields(fields, preferredTitle); - titleTarget = preferredTitle; - descriptionTarget = dynamicDescriptionOverwrite; - } - if (shouldTargetBePreferred(fields, descriptionTarget, preferredDescription)) { - descriptionTarget = preferredDescription; - } - } + const { description, title, comments } = getPreferredFields(theType); return [ { source: 'title', - target: titleTarget, + target: title, action_type: 'overwrite', }, { source: 'description', - target: descriptionTarget, + target: description, action_type: 'overwrite', }, { source: 'comments', - target: preferredComments, + target: comments, action_type: 'append', }, ]; From 1985e0a1c0cdfb2cdd7e2bf04be5646a4d24243d Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 21 Apr 2021 10:28:25 -0700 Subject: [PATCH 11/65] Improve focus UX of MultiInputRows (#97695) --- .../multi_input_rows/input_row.test.tsx | 2 ++ .../components/multi_input_rows/input_row.tsx | 4 ++- .../multi_input_rows.test.tsx | 15 ++++++++++ .../multi_input_rows/multi_input_rows.tsx | 29 +++++++++++-------- .../multi_input_rows_logic.test.ts | 4 ++- .../multi_input_rows_logic.ts | 7 +++++ 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx index 03b0c0e4a0d91e..c999881fa9fed9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.test.tsx @@ -17,6 +17,7 @@ describe('InputRow', () => { const props = { value: 'some value', placeholder: 'Enter a value', + autoFocus: false, onChange: jest.fn(), onDelete: jest.fn(), disableDelete: false, @@ -33,6 +34,7 @@ describe('InputRow', () => { expect(wrapper.find(EuiFieldText)).toHaveLength(1); expect(wrapper.find(EuiFieldText).prop('value')).toEqual('some value'); expect(wrapper.find(EuiFieldText).prop('placeholder')).toEqual('Enter a value'); + expect(wrapper.find(EuiFieldText).prop('autoFocus')).toEqual(false); expect(wrapper.find('[data-test-subj="deleteInputRowButton"]').prop('title')).toEqual( 'Delete value' ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx index 5f2a82ae945edc..55ed5e9d905090 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/input_row.tsx @@ -12,6 +12,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFieldText, EuiButtonIcon } from '@elastic interface Props { value: string; placeholder: string; + autoFocus: boolean; onChange(newValue: string): void; onDelete(): void; disableDelete: boolean; @@ -23,6 +24,7 @@ import './input_row.scss'; export const InputRow: React.FC = ({ value, placeholder, + autoFocus, onChange, onDelete, disableDelete, @@ -35,7 +37,7 @@ export const InputRow: React.FC = ({ placeholder={placeholder} value={value} onChange={(e) => onChange(e.target.value)} - autoFocus + autoFocus={autoFocus} /> 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 f832ceb8c88427..221495ee2c658b 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 @@ -27,6 +27,7 @@ describe('MultiInputRows', () => { }; const values = { values: ['a', 'b', 'c'], + addedNewRow: false, hasEmptyValues: false, hasOnlyOneValue: false, }; @@ -56,6 +57,20 @@ describe('MultiInputRows', () => { expect(wrapper.find(InputRow).at(2).prop('value')).toEqual('c'); }); + it('focuses the first input row on load, but focuses new input rows on add', () => { + setMockValues({ ...values, addedNewRow: false }); + const wrapper = shallow(); + + expect(wrapper.find(InputRow).first().prop('autoFocus')).toEqual(true); + expect(wrapper.find(InputRow).last().prop('autoFocus')).toEqual(false); + + setMockValues({ ...values, addedNewRow: true }); + rerender(wrapper); + + expect(wrapper.find(InputRow).first().prop('autoFocus')).toEqual(false); + expect(wrapper.find(InputRow).last().prop('autoFocus')).toEqual(true); + }); + it('calls editValue when the InputRow value changes', () => { const wrapper = shallow(); wrapper.find(InputRow).at(0).simulate('change', 'new value'); 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 aa2f0977594c45..3c401fbbf953fb 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 @@ -44,7 +44,7 @@ export const MultiInputRows: React.FC = ({ inputPlaceholder = INPUT_ROW_PLACEHOLDER, }) => { const logic = MultiInputRowsLogic({ id, values: initialValues }); - const { values, hasEmptyValues, hasOnlyOneValue } = useValues(logic); + const { values, addedNewRow, hasEmptyValues, hasOnlyOneValue } = useValues(logic); const { addValue, editValue, deleteValue } = useActions(logic); useEffect(() => { @@ -55,17 +55,22 @@ export const MultiInputRows: React.FC = ({ return ( <> - {values.map((value: string, index: number) => ( - editValue(index, newValue)} - onDelete={() => deleteValue(index)} - disableDelete={hasOnlyOneValue} - deleteLabel={deleteRowLabel} - /> - ))} + {values.map((value: string, index: number) => { + const firstRow = index === 0; + const lastRow = index === values.length - 1; + return ( + editValue(index, newValue)} + onDelete={() => deleteValue(index)} + disableDelete={hasOnlyOneValue} + deleteLabel={deleteRowLabel} + /> + ); + })} { }; const DEFAULT_VALUES = { values: MOCK_VALUES, + addedNewRow: false, hasEmptyValues: false, hasOnlyOneValue: false, }; @@ -48,11 +49,12 @@ describe('MultiInputRowsLogic', () => { }); describe('addValue', () => { - it('appends an empty string to the values array', () => { + it('appends an empty string to the values array & sets addedNewRow to true', () => { logic.actions.addValue(); expect(logic.values).toEqual({ ...DEFAULT_VALUES, + addedNewRow: true, hasEmptyValues: true, values: ['a', 'b', 'c', ''], }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts index 6cc392598a61f2..d80dd1f3db7268 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/multi_input_rows/multi_input_rows_logic.ts @@ -9,6 +9,7 @@ import { kea, MakeLogicType } from 'kea'; interface MultiInputRowsValues { values: string[]; + addedNewRow: boolean; hasEmptyValues: boolean; hasOnlyOneValue: boolean; } @@ -51,6 +52,12 @@ export const MultiInputRowsLogic = kea< }, }, ], + addedNewRow: [ + false, + { + addValue: () => true, + }, + ], }), selectors: { hasEmptyValues: [(selectors) => [selectors.values], (values) => values.indexOf('') >= 0], From e6e36102f0c96120e61c0922f0d10a12f967b7d5 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Wed, 21 Apr 2021 11:02:00 -0700 Subject: [PATCH 12/65] [DOCS] Reorganizes concepts and discover sections (#97019) * [DOCS] Reorganizes concepts and discover sections * [DOCS] Updates time filter and saved query sections * Update docs/discover/save-search.asciidoc Co-authored-by: Wylie Conlon * [DOCS] Updates time filter and saved query doc * [DOCS] Updates images * [DOCS] Adds missing images * [DOCS] Minor edits Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Wylie Conlon --- docs/concepts/images/add-filter-popup.png | Bin 31465 -> 31707 bytes docs/concepts/images/refresh-every.png | Bin 8560 -> 8506 bytes docs/concepts/images/save-icon.png | Bin 841 -> 891 bytes docs/concepts/images/saved-query-popup.png | Bin 0 -> 230367 bytes docs/concepts/images/saved-query.png | Bin 0 -> 297987 bytes docs/concepts/images/time-filter-icon.png | Bin 0 -> 1053 bytes docs/concepts/images/time-filter.png | Bin 0 -> 77327 bytes docs/concepts/images/time-relative.png | Bin 0 -> 26440 bytes .../index-patterns.asciidoc | 6 +- docs/concepts/index.asciidoc | 16 ++++- docs/{discover => concepts}/kuery.asciidoc | 0 docs/concepts/lucene.asciidoc | 51 +++++++++++++++ docs/concepts/save-query.asciidoc | 58 ++++++++++-------- docs/concepts/set-time-filter.asciidoc | 31 ++++++++++ docs/discover/save-search.asciidoc | 38 ++++++++++++ docs/discover/search-sessions.asciidoc | 2 +- docs/discover/set-time-filter.asciidoc | 56 ----------------- docs/redirects.asciidoc | 5 ++ docs/user/discover.asciidoc | 14 ++--- 19 files changed, 181 insertions(+), 96 deletions(-) create mode 100644 docs/concepts/images/saved-query-popup.png create mode 100644 docs/concepts/images/saved-query.png create mode 100644 docs/concepts/images/time-filter-icon.png create mode 100644 docs/concepts/images/time-filter.png create mode 100644 docs/concepts/images/time-relative.png rename docs/{management => concepts}/index-patterns.asciidoc (98%) rename docs/{discover => concepts}/kuery.asciidoc (100%) create mode 100644 docs/concepts/lucene.asciidoc create mode 100644 docs/concepts/set-time-filter.asciidoc create mode 100644 docs/discover/save-search.asciidoc delete mode 100644 docs/discover/set-time-filter.asciidoc diff --git a/docs/concepts/images/add-filter-popup.png b/docs/concepts/images/add-filter-popup.png index f1b5b1ff3f6ca6deef2aa256902642878ddc8a22..f96c8746ef17adbde7a00967573cf4b7d13e8129 100644 GIT binary patch literal 31707 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 P*F#1^Q5-C4;QxOB6{%)W diff --git a/docs/concepts/images/save-icon.png b/docs/concepts/images/save-icon.png index 959c7ef8e1bb977c49e667902392f8d02ae1c910..7841cd58c9d6df5a7603cb3e08988e33206f4c2a 100644 GIT binary patch literal 891 zcmV->1BCpEP)Px&HAzH4R7ef2m0d`aQ5eVn@Al5Q>C~2P<;TMLMLG>-mWDS8C1zSghFMS=R743v zP(rBSMO_tL1VR!-ACN{D%>*&1i|#Tl!AvDW`VcXpW~g&tJh5b8+7DzUMj5 z`JMlH&htuMGHk-!ZVxs!A;Q>lYwtK-ef|ZvFS=48!9#3;c>K-l@uHzL73a!KAQl?` zzc_M-Z31^rSYS0JaCFo^_<*bXGq5Wqju!NYMNf%uX54=L6+eDE&|Z~Au;d=$ZXERr~h1NQSSeBxZrSkus~1yk_*c&FZJEXOV**gu?U@Sr!Zr8fRAjgu}W@=BP&D4 z0)2T@rui(E;l2AfEpf;psPL~hb%^+3`0m1q{TLlo}p>HIB$i8 zj+<2r0b$x_!_#Od=S8On^?Op#R++USgddP$xYjh3q(LJqaF$^19=G6F;dZg}=I3MR z|1^ufi^a$`#I7D)2nYwfIq(IS_NAjVJpt97gE+lA6`2MtU1eToV`DGgWt}wzO3J6p zJ)^KMI`RIy9d9P*V7p*NW>U;51V;=3Vfka#N=(fyiX>H#o+`cpkud~OGE6Ngp{48I z(Km^03DL08ahMZ>A=U+mQp%`@t-v={4e-ZImNH6uuXRsiGXI*qJ15 z3a1%=TWdlQ70JgY`<|ugdJHs`1o002ovPDHLkV1o6uj>Z50 literal 841 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

KXaH2AY>#WMB9?h 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

_^>J#PvAePn9L{DM%HZJr$(Cl_=L+&$_u&iyuuK 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 literal 0 HcmV?d00001 diff --git a/docs/concepts/images/time-filter-icon.png b/docs/concepts/images/time-filter-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f920453879d4de9b8849b4aab55118ed3115f939 GIT binary patch literal 1053 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

J%-eozp_F7jGQvFk%Ef*Guk}cd#XL= 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, which shows you how to put these concepts into action. * Go to <> for instructions on searching your data. + + +include::index-patterns.asciidoc[] + +include::set-time-filter.asciidoc[] + +include::kuery.asciidoc[] + +include::lucene.asciidoc[] + +include::save-query.asciidoc[] diff --git a/docs/discover/kuery.asciidoc b/docs/concepts/kuery.asciidoc similarity index 100% rename from docs/discover/kuery.asciidoc rename to docs/concepts/kuery.asciidoc diff --git a/docs/concepts/lucene.asciidoc b/docs/concepts/lucene.asciidoc new file mode 100644 index 00000000000000..589a03cef13185 --- /dev/null +++ b/docs/concepts/lucene.asciidoc @@ -0,0 +1,51 @@ +[[lucene-query]] +=== Lucene query syntax +Lucene query syntax is available to {kib} users who opt out of the <>. +Full documentation for this syntax is available as part of {es} +{ref}/query-dsl-query-string-query.html#query-string-syntax[query string syntax]. + +The main reason to use the Lucene query syntax in {kib} is for advanced +Lucene features, such as regular expressions or fuzzy term matching. However, +Lucene syntax is not able to search nested objects or scripted fields. + +To perform a free text search, simply enter a text string. For example, if +you're searching web server logs, you could enter `safari` to search all +fields: + +[source,yaml] +------------------- +safari +------------------- + +To search for a value in a specific field, prefix the value with the name +of the field: + +[source,yaml] +------------------- +status:200 +------------------- + +To search for a range of values, use the bracketed range syntax, +`[START_VALUE TO END_VALUE]`. For example, to find entries that have 4xx +status codes, you could enter `status:[400 TO 499]`. + +[source,yaml] +------------------- +status:[400 TO 499] +------------------- + +For an open range, use a wildcard: + +[source,yaml] +------------------- +status:[400 TO *] +------------------- + +To specify more complex search criteria, use the boolean operators +`AND`, `OR`, and `NOT`. For example, to find entries that have 4xx status +codes and have an extension of `php` or `html`: + +[source,yaml] +------------------- +status:[400 TO 499] AND (extension:php OR extension:html) +------------------- diff --git a/docs/concepts/save-query.asciidoc b/docs/concepts/save-query.asciidoc index 4f049d121bbef5..fa626f6eaa913e 100644 --- a/docs/concepts/save-query.asciidoc +++ b/docs/concepts/save-query.asciidoc @@ -1,39 +1,45 @@ [[save-load-delete-query]] -== Save a query -A saved query is a collection of query text and filters that you can -reuse in any app with a query bar, like <> and <>. Save a query when you want to: +=== Save a query -* Retrieve results from the same query at a later time without having to reenter the query text, add the filters or set the time filter -* View the results of the same query in multiple apps -* Share your query +Have you ever built a query that you wanted to reuse? +With saved queries, you can save your query text, filters, and time range for +reuse anywhere a query bar is present. -Saved queries don't include information specific to *Discover*, -such as the currently selected columns in the document table, the sort order, and the index pattern. -To save your current view of *Discover* for later retrieval and reuse, -create a <> instead. +For example, suppose you're in *Discover*, and you've put time into building +a query that includes query input text, multiple filters, and a specific time range. +Save this query, and you can embed the search results in dashboards, +use them as a foundation for building a visualization, +and share them in a link or CVS form. + +[role="screenshot"] +image:concepts/images/saved-query.png["Example of the saved query management popover with a list of saved queries"] -NOTE:: -If you have insufficient privileges to save queries, the *Save current query* -button isn't visible in the saved query management popover. +Saved queries are different than <>, +which include the *Discover* configuration—selected columns in the document table, sort order, and +index pattern—in addition to the query. +Saved searches are primarily used for adding search results to a dashboard. + +[role="xpack"] +==== Read-only access +If you have insufficient privileges to save queries, +the *Save* button isn't visible in the saved query management popover. For more information, see <> -. Click *#* in the query bar. +==== Save a query + +. Once you’ve built a query worth saving, click the save query icon image:concepts/images/save-icon.png["save query icon"]. . In the popover, click *Save current query*. -+ -[role="screenshot"] -image::discover/images/saved-query-management-component-all-privileges.png["Example of the saved query management popover with a list of saved queries with write access",width="80%"] -+ -. Enter a name, a description, and then select the filter options. +. Enter a unique name to identify the query and an optional description that will appear in a tooltip in the saved query popover. +. Choose whether to include or exclude filters and a time range. By default, filters are automatically included, but the time filter is not. + [role="screenshot"] -image::discover/images/saved-query-save-form-default-filters.png["Example of the saved query management save form with the filters option included and the time filter option excluded",width="80%"] +image:concepts/images/saved-query-popup.png["Example of the saved query management popover with a list of saved queries"] + . Click *Save*. -. To load a saved query into *Discover* or *Dashboard*, open the *Saved search* popover, and select the query. -. To manage your saved queries, use these actions in the popover: +. To load a saved query, select it in the *Saved query* popover. + -* Save as new: Save changes to the current query. -* Clear. Clear a query that is currently loaded in an app. -* Delete. You can’t recover a deleted query. -. To import and export saved queries, go to <>. +The query text, filters, and time range are updated and your data refreshed. +If you’re loading a saved query that did not include the filters or time range, those components remain as-is. +. To clear, modify, and delete saved queries, use the *Saved query* popover. diff --git a/docs/concepts/set-time-filter.asciidoc b/docs/concepts/set-time-filter.asciidoc new file mode 100644 index 00000000000000..7ab3c934e5770b --- /dev/null +++ b/docs/concepts/set-time-filter.asciidoc @@ -0,0 +1,31 @@ +[[set-time-filter]] +=== Set the time range +Display data within a +specified time range when your index contains time-based events, and a time-field is configured for the +selected <>. +The default time range is 15 minutes, but you can customize +it in <>. + +. Click image:concepts/images/time-filter-icon.png[clock icon]. + +. Choose one of the following: + +* *Quick select* to use a recent time range, then use the back and forward + arrows to move through the time ranges. + +* *Commonly used* to use a time range from options such as *Last 15 minutes*, + *Today*, and *Week to date*. + +* *Recently used date ranges* to use a previously selected data range. + +* *Refresh every* to specify an automatic refresh rate. ++ +[role="screenshot"] +image::concepts/images/time-filter.png[Time filter menu] + +. To set start and end times, click the bar next to the time filter. +In the popup, select *Absolute*, *Relative* or *Now*, then specify the required +options. ++ +[role="screenshot"] +image::concepts/images/time-relative.png[Time filter showing relative time] diff --git a/docs/discover/save-search.asciidoc b/docs/discover/save-search.asciidoc new file mode 100644 index 00000000000000..b2baf8ee646727 --- /dev/null +++ b/docs/discover/save-search.asciidoc @@ -0,0 +1,38 @@ +[[save-open-search]] +== Save a search +A saved search persists your current view of Discover for +later retrieval and reuse. You can reload a saved search into Discover, +add it to a dashboard, and use it as the basis for a visualization. + +A saved search includes the query text, filters, and optionally, the time filter. A saved search also includes the selected columns in the document table, the sort order, and the current index pattern. + +[role="xpack"] +[[discover-read-only-access]] +[float] +=== Read-only access +When you have insufficient privileges to save searches, the following indicator in Kibana will be +displayed and the *Save* button won't be visible. For more information on granting access to +Kibana see <>. + +[role="screenshot"] +image::discover/images/read-only-badge.png[Example of Discover's read only access indicator in Kibana's header] +[float] +=== Save a search +To save the current search: + +. Click *Save* in the toolbar. +. Enter a name for the search and click *Save*. + +To import, export, and delete saved searches, open the main menu, +then click *Stack Management > Saved Objects*. + +[float] +=== Open a saved search +To load a saved search into Discover: + +. Click *Open* in the toolbar. +. Select the search you want to open. + +If the saved search is associated with a different index pattern than is currently +selected, opening the saved search changes the selected index pattern. The query language +used for the saved search will also be automatically selected. diff --git a/docs/discover/search-sessions.asciidoc b/docs/discover/search-sessions.asciidoc index 0673b9b8f6562e..f7091d16f9cd3a 100644 --- a/docs/discover/search-sessions.asciidoc +++ b/docs/discover/search-sessions.asciidoc @@ -1,5 +1,5 @@ [[search-sessions]] -=== Run a search session in the background +== Run a search session in the background Sometimes you might need to search through large amounts of data no matter how long the search takes. While this might not happen often, diff --git a/docs/discover/set-time-filter.asciidoc b/docs/discover/set-time-filter.asciidoc deleted file mode 100644 index dcdc8ee791e836..00000000000000 --- a/docs/discover/set-time-filter.asciidoc +++ /dev/null @@ -1,56 +0,0 @@ -[[set-time-filter]] -== Set the time filter -If your index contains time-based events, and a time-field is configured for the -selected <>, set a time filter that displays only the data within the -specified time range. - -You can use the time filter to change the time range, or select a specific time -range in the histogram. - -[float] -[[use-time-filter]] -=== Use the time filter - -Use the time filter to change the time range. By default, the time filter is set -to the last 15 minutes. - -. Click image:images/time-filter-calendar.png[Calendar icon]. - -. Choose one of the following: - -* *Quick select* to use a recent time range, then use the back and forward - arrows to move through the time ranges. - -* *Commonly used* to use a time range from options such as *Last 15 minutes*, - *Today*, and *Week to date*. - -* *Recently used date ranges* to use a previously selected data range that - you recently used. - -* *Refresh every* to specify an automatic refresh rate. -+ -[role="screenshot"] -image::images/time-filter.png[Time filter menu] - -. To set the start and end times, click the bar next to the time filter. -In the popup, select *Absolute*, *Relative* or *Now*, then specify the required -options. -+ -[role="screenshot"] -image::images/time-filter-bar.png[Time filter bar] - -[float] -=== Select a time range from the histogram - -To select a specific time range in the histogram, choose one of the following: - -* Click the bar that represents the time range you want to zoom in on. - -* Click and drag to view a specific time range. You must start the selection with -the cursor over the background of the chart--the cursor changes to a plus sign -when you hover over a valid start point. - -* Click the dropdown, then select an interval. - -[role="screenshot"] -image::images/Histogram-Time.png[Time range selector in Histogram dropdown] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 5d0242ae319501..15b353223452a4 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -292,3 +292,8 @@ This content has moved. refer to <>. == Tutorial: Use role-based access control to customize Kibana spaces This content has moved. refer to <>. + +[role="exclude",id="search"] +== Search your data + +This content has moved. refer to <>. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 39e3a8e41ea6aa..4565f7c9616c34 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -189,7 +189,7 @@ Saving a search saves the query and the filters. . In the toolbar, click **Save**. . Give your search a title, and then click **Save**. -+ ++ [role="screenshot"] image:images/discover-save-saved-search.png[Save saved search in Discover, width=50%] @@ -215,7 +215,7 @@ image:images/visualize-from-discover.png[Visualization that opens from Discover If your documents contain geo point fields (image:images/geoip-icon.png[Geo point field icon, width=20px]), you can visualize them in **Maps**. -. Make sure the index pattern is set to **kibana_sample_data_ecommerce** and the configured time range +. Make sure the index pattern is set to **kibana_sample_data_ecommerce** and the configured time range contains data. . From the **Available fields** list, click `geoip.location`, and then click **Visualize**. @@ -243,12 +243,10 @@ the table columns that display by default, and more. -- -include::{kib-repo-dir}/management/index-patterns.asciidoc[] - -include::{kib-repo-dir}/discover/set-time-filter.asciidoc[] - -include::{kib-repo-dir}/discover/search.asciidoc[] - include::{kib-repo-dir}/discover/context.asciidoc[] include::{kib-repo-dir}/discover/search-for-relevance.asciidoc[] + +include::{kib-repo-dir}/discover/save-search.asciidoc[] + +include::{kib-repo-dir}/discover/search-sessions.asciidoc[] From fa0c74fe300a1d77382197890f212b960508aa73 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 21 Apr 2021 19:08:28 +0100 Subject: [PATCH 13/65] chore(NA): adds 7.13 branch and bumps 7.x on backportrc (#97804) --- .backportrc.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.backportrc.json b/.backportrc.json index 384e221329a4f6..59a101195bef78 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "targetBranchChoices": [ { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.13", "7.12", "7.11", "7.10", @@ -30,7 +31,7 @@ "targetPRLabels": ["backport"], "branchLabelMapping": { "^v8.0.0$": "master", - "^v7.13.0$": "7.x", + "^v7.14.0$": "7.x", "^v(\\d+).(\\d+).\\d+$": "$1.$2" }, "autoMerge": true, From 58d4334c718bdc44de0054e6086332ade0d9c434 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Wed, 21 Apr 2021 20:21:07 +0200 Subject: [PATCH 14/65] [ML] UI enhancements for Anomaly detection rule type (#97626) * [ML] update labels * [ML] update job summary endpoint to return associated alert rules * [ML] add alert rule icon to the table * [ML] edit alert rules from ML UI * [ML] register navigation * [ML] support single job selection only * [ML] remove groups options from the job selection * [ML] deps on rule id to avoid re-rendering * [ML] fix i18n * [ML] add info message to the alert context * [ML] fix typo * [ML] register usage collection * [ML] fix telemetry --- x-pack/plugins/ml/common/types/alerts.ts | 9 +- .../anomaly_detection_jobs/combined_job.ts | 3 +- .../anomaly_detection_jobs/summary_job.ts | 2 + .../plugins/ml/common/types/capabilities.ts | 3 + x-pack/plugins/ml/public/alerting/index.ts | 8 ++ .../ml/public/alerting/job_selector.tsx | 7 +- .../ml/public/alerting/ml_alerting_flyout.tsx | 93 +++++++++++++------ .../ml/public/alerting/register_ml_alerts.ts | 50 ++++++++-- .../components/job_actions/management.js | 4 +- .../job_details/extract_job_details.js | 13 +++ .../components/job_details/job_details.js | 3 +- .../components/jobs_list/jobs_list.js | 44 ++++++++- .../multi_job_actions/actions_menu.js | 2 +- .../start_datafeed_modal.js | 2 +- .../post_save_options/post_save_options.tsx | 2 +- x-pack/plugins/ml/public/plugin.ts | 13 ++- .../ml/public/register_helper/index.ts | 1 + .../ml/server/lib/alerts/alerting_service.ts | 2 + .../register_anomaly_detection_alert_type.ts | 6 ++ .../capabilities/check_capabilities.test.ts | 3 +- x-pack/plugins/ml/server/lib/route_guard.ts | 9 +- .../ml/server/models/job_service/index.ts | 9 +- .../ml/server/models/job_service/jobs.ts | 43 ++++++++- .../plugins/ml/server/routes/job_service.ts | 16 +++- .../machine_learning/empty_ml_capabilities.ts | 1 + .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../apis/ml/system/capabilities.ts | 4 +- .../apis/ml/system/space_capabilities.ts | 8 +- 29 files changed, 291 insertions(+), 71 deletions(-) create mode 100644 x-pack/plugins/ml/public/alerting/index.ts diff --git a/x-pack/plugins/ml/common/types/alerts.ts b/x-pack/plugins/ml/common/types/alerts.ts index f2c3385c1fbc79..1677a766544a19 100644 --- a/x-pack/plugins/ml/common/types/alerts.ts +++ b/x-pack/plugins/ml/common/types/alerts.ts @@ -7,7 +7,7 @@ import { AnomalyResultType } from './anomalies'; import { ANOMALY_RESULT_TYPE } from '../constants/anomalies'; -import { AlertTypeParams } from '../../../alerting/common'; +import type { AlertTypeParams, Alert } from '../../../alerting/common'; export type PreviewResultsKeys = 'record_results' | 'bucket_results' | 'influencer_results'; export type TopHitsResultsKeys = 'top_record_hits' | 'top_bucket_hits' | 'top_influencer_hits'; @@ -25,6 +25,7 @@ export interface AlertExecutionResult { bucketRange: { start: string; end: string }; topRecords: RecordAnomalyAlertDoc[]; topInfluencers?: InfluencerAnomalyAlertDoc[]; + message: string; } export interface PreviewResponse { @@ -101,3 +102,9 @@ export type MlAnomalyDetectionAlertAdvancedSettings = Pick< MlAnomalyDetectionAlertParams, 'lookbackInterval' | 'topNBuckets' >; + +export type MlAnomalyDetectionAlertRule = Omit, 'apiKey'>; + +export interface JobAlertingRuleStats { + alerting_rules?: MlAnomalyDetectionAlertRule[]; +} diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts index 783d9f7c923bb9..31f01679c4cd82 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/combined_job.ts @@ -9,8 +9,9 @@ import { Datafeed } from './datafeed'; import { DatafeedStats } from './datafeed_stats'; import { Job } from './job'; import { JobStats } from './job_stats'; +import type { JobAlertingRuleStats } from '../alerts'; -export type JobWithStats = Job & JobStats; +export type JobWithStats = Job & JobStats & JobAlertingRuleStats; export type DatafeedWithStats = Datafeed & DatafeedStats; // in older implementations of the job config, the datafeed was placed inside the job diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts index 09f5c37ac9aeaf..624056fdf3b825 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts @@ -8,6 +8,7 @@ import { Moment } from 'moment'; import { CombinedJob, CombinedJobWithStats } from './combined_job'; +import { MlAnomalyDetectionAlertRule } from '../alerts'; export { Datafeed } from './datafeed'; export { DatafeedStats } from './datafeed_stats'; @@ -34,6 +35,7 @@ export interface MlSummaryJob { latestTimestampSortValue?: number; earliestStartTimestampMs?: number; awaitingNodeAssignment: boolean; + alertingRules?: MlAnomalyDetectionAlertRule[]; } export interface AuditMessage { diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index 129b496c00149f..1e6a76caf70e9a 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -30,6 +30,8 @@ export const userMlCapabilities = { canGetAnnotations: false, canCreateAnnotation: false, canDeleteAnnotation: false, + // Alerts + canUseMlAlerts: false, }; export const adminMlCapabilities = { @@ -59,6 +61,7 @@ export const adminMlCapabilities = { canStartStopDataFrameAnalytics: false, // Alerts canCreateMlAlerts: false, + canUseMlAlerts: false, }; export type UserMlCapabilities = typeof userMlCapabilities; diff --git a/x-pack/plugins/ml/public/alerting/index.ts b/x-pack/plugins/ml/public/alerting/index.ts new file mode 100644 index 00000000000000..584110ff39c9ef --- /dev/null +++ b/x-pack/plugins/ml/public/alerting/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { registerMlAlerts } from './register_ml_alerts'; diff --git a/x-pack/plugins/ml/public/alerting/job_selector.tsx b/x-pack/plugins/ml/public/alerting/job_selector.tsx index 11dd8362fd4432..da353b52ef1c06 100644 --- a/x-pack/plugins/ml/public/alerting/job_selector.tsx +++ b/x-pack/plugins/ml/public/alerting/job_selector.tsx @@ -66,12 +66,6 @@ export const JobSelectorControl: FC = ({ }), options: jobIdOptions.map((v) => ({ label: v })), }, - { - label: i18n.translate('xpack.ml.jobSelector.groupOptionsLabel', { - defaultMessage: 'Groups', - }), - options: groupIdOptions.map((v) => ({ label: v })), - }, ]); } catch (e) { // TODO add error handling @@ -114,6 +108,7 @@ export const JobSelectorControl: FC = ({ error={errors} > + singleSelection selectedOptions={selectedOptions} options={options} onChange={onSelectionChange} diff --git a/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx b/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx index 989cecf1da19c2..dac1fad72255c1 100644 --- a/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx +++ b/x-pack/plugins/ml/public/alerting/ml_alerting_flyout.tsx @@ -6,24 +6,30 @@ */ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; import { JobId } from '../../common/types/anomaly_detection_jobs'; import { useMlKibana } from '../application/contexts/kibana'; import { ML_ALERT_TYPES } from '../../common/constants/alerts'; import { PLUGIN_ID } from '../../common/constants/app'; +import { MlAnomalyDetectionAlertRule } from '../../common/types/alerts'; interface MlAnomalyAlertFlyoutProps { - jobIds: JobId[]; + initialAlert?: MlAnomalyDetectionAlertRule; + jobIds?: JobId[]; onSave?: () => void; onCloseFlyout: () => void; } /** * Invoke alerting flyout from the ML plugin context. + * @param initialAlert * @param jobIds * @param onCloseFlyout + * @param onSave * @constructor */ export const MlAnomalyAlertFlyout: FC = ({ + initialAlert, jobIds, onCloseFlyout, onSave, @@ -32,35 +38,45 @@ export const MlAnomalyAlertFlyout: FC = ({ services: { triggersActionsUi }, } = useMlKibana(); - const AddAlertFlyout = useMemo( - () => - triggersActionsUi && - triggersActionsUi.getAddAlertFlyout({ - consumer: PLUGIN_ID, - onClose: () => { - onCloseFlyout(); - }, - // Callback for successful save - onSave: async () => { - if (onSave) { - onSave(); - } - }, - canChangeTrigger: false, - alertTypeId: ML_ALERT_TYPES.ANOMALY_DETECTION, - metadata: {}, - initialValues: { - params: { - jobSelection: { - jobIds, - }, + const AlertFlyout = useMemo(() => { + if (!triggersActionsUi) return; + + const commonProps = { + onClose: () => { + onCloseFlyout(); + }, + onSave: async () => { + if (onSave) { + onSave(); + } + }, + }; + + if (initialAlert) { + return triggersActionsUi.getEditAlertFlyout({ + ...commonProps, + initialAlert, + }); + } + + return triggersActionsUi.getAddAlertFlyout({ + ...commonProps, + consumer: PLUGIN_ID, + canChangeTrigger: false, + alertTypeId: ML_ALERT_TYPES.ANOMALY_DETECTION, + metadata: {}, + initialValues: { + params: { + jobSelection: { + jobIds, }, }, - }), - [triggersActionsUi] - ); + }, + }); + // deps on id to avoid re-rendering on auto-refresh + }, [triggersActionsUi, initialAlert?.id, jobIds]); - return <>{AddAlertFlyout}; + return <>{AlertFlyout}; }; interface JobListMlAnomalyAlertFlyoutProps { @@ -103,3 +119,26 @@ export const JobListMlAnomalyAlertFlyout: FC = /> ) : null; }; + +interface EditRuleFlyoutProps { + initialAlert: MlAnomalyDetectionAlertRule; +} + +export const EditAlertRule: FC = ({ initialAlert }) => { + const [isVisible, setIsVisible] = useState(false); + return ( + <> + + {initialAlert.name} + + + {isVisible ? ( + + ) : null} + + ); +}; diff --git a/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts b/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts index 92a5343380cddb..5454f4da319222 100644 --- a/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts +++ b/x-pack/plugins/ml/public/alerting/register_ml_alerts.ts @@ -8,13 +8,17 @@ import { i18n } from '@kbn/i18n'; import { lazy } from 'react'; import { ML_ALERT_TYPES } from '../../common/constants/alerts'; -import { MlAnomalyDetectionAlertParams } from '../../common/types/alerts'; -import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; - -export async function registerMlAlerts(triggersActionsUi: TriggersAndActionsUIPublicPluginSetup) { - // async import validators to reduce initial bundle size - const { validateLookbackInterval, validateTopNBucket } = await import('./validators'); +import type { MlAnomalyDetectionAlertParams } from '../../common/types/alerts'; +import type { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; +import type { PluginSetupContract as AlertingSetup } from '../../../alerting/public'; +import { PLUGIN_ID } from '../../common/constants/app'; +import { createExplorerUrl } from '../ml_url_generator/anomaly_detection_urls_generator'; +import { validateLookbackInterval, validateTopNBucket } from './validators'; +export function registerMlAlerts( + triggersActionsUi: TriggersAndActionsUIPublicPluginSetup, + alerting?: AlertingSetup +) { triggersActionsUi.alertTypeRegistry.register({ id: ML_ALERT_TYPES.ANOMALY_DETECTION, description: i18n.translate('xpack.ml.alertTypes.anomalyDetection.description', { @@ -47,6 +51,20 @@ export async function registerMlAlerts(triggersActionsUi: TriggersAndActionsUIPu ); } + // Since 7.13 we support single job selection only + if ( + (Array.isArray(alertParams.jobSelection?.groupIds) && + alertParams.jobSelection?.groupIds.length > 0) || + (Array.isArray(alertParams.jobSelection?.jobIds) && + alertParams.jobSelection?.jobIds.length > 1) + ) { + validationResult.errors.jobSelection.push( + i18n.translate('xpack.ml.alertTypes.anomalyDetection.singleJobSelection.errorMessage', { + defaultMessage: 'Only one job per rule is allowed', + }) + ); + } + if (alertParams.severity === undefined) { validationResult.errors.severity.push( i18n.translate('xpack.ml.alertTypes.anomalyDetection.severity.errorMessage', { @@ -96,7 +114,7 @@ export async function registerMlAlerts(triggersActionsUi: TriggersAndActionsUIPu - Time: \\{\\{context.timestampIso8601\\}\\} - Anomaly score: \\{\\{context.score\\}\\} -Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed. +\\{\\{context.message\\}\\} \\{\\{#context.topInfluencers.length\\}\\} Top influencers: @@ -118,4 +136,22 @@ Alerts are raised based on real-time scores. Remember that scores may be adjuste } ), }); + + if (alerting) { + registerNavigation(alerting); + } +} + +export function registerNavigation(alerting: AlertingSetup) { + alerting.registerNavigation(PLUGIN_ID, ML_ALERT_TYPES.ANOMALY_DETECTION, (alert) => { + const alertParams = alert.params as MlAnomalyDetectionAlertParams; + const jobIds = [ + ...new Set([ + ...(alertParams.jobSelection.jobIds ?? []), + ...(alertParams.jobSelection.groupIds ?? []), + ]), + ]; + + return createExplorerUrl('', { jobIds }); + }); } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js index 471295938acde3..82adc8df8f3447 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/management.js @@ -63,10 +63,10 @@ export function actionsMenuContent( }, { name: i18n.translate('xpack.ml.jobsList.managementActions.createAlertLabel', { - defaultMessage: 'Create alert', + defaultMessage: 'Create alert rule', }), description: i18n.translate('xpack.ml.jobsList.managementActions.createAlertLabel', { - defaultMessage: 'Create alert', + defaultMessage: 'Create alert rule', }), icon: 'bell', enabled: (item) => item.deleting !== true, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 6e6b4df6dd4525..5b7a41e572dabc 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -10,6 +10,7 @@ import { detectorToString } from '../../../../util/string_utils'; import { formatValues, filterObjects } from './format_values'; import { i18n } from '@kbn/i18n'; import { EuiLink } from '@elastic/eui'; +import { EditAlertRule } from '../../../../../alerting/ml_alerting_flyout'; export function extractJobDetails(job, basePath) { if (Object.keys(job).length === 0) { @@ -74,6 +75,17 @@ export function extractJobDetails(job, basePath) { } } + const alertRules = { + id: 'alertRules', + title: i18n.translate('xpack.ml.jobsList.jobDetails.alertRulesTitle', { + defaultMessage: 'Alert rules', + }), + position: 'right', + items: (job.alerting_rules ?? []).map((v) => { + return ['', ]; + }), + }; + const detectors = { id: 'detectors', title: i18n.translate('xpack.ml.jobsList.jobDetails.detectorsTitle', { @@ -206,5 +218,6 @@ export function extractJobDetails(job, basePath) { modelSizeStats, jobTimingStats, datafeedTimingStats, + alertRules, }; } diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 99581fb3c7e958..c8412a2a83d8a0 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -70,6 +70,7 @@ export class JobDetailsUI extends Component { modelSizeStats, jobTimingStats, datafeedTimingStats, + alertRules, } = extractJobDetails(job, basePath); const { showFullDetails, refreshJobList } = this.props; @@ -83,7 +84,7 @@ export class JobDetailsUI extends Component { content: ( ), time: job.open_time, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js index 4674342990df4a..abd0794ff2c356 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list/jobs_list.js @@ -18,7 +18,13 @@ import { JobIcon } from '../../../../components/job_message_icon'; import { JobSpacesList } from '../../../../components/job_spaces_list'; import { TIME_FORMAT } from '../../../../../../common/constants/time_format'; -import { EuiBasicTable, EuiButtonIcon, EuiScreenReaderOnly } from '@elastic/eui'; +import { + EuiBasicTable, + EuiButtonIcon, + EuiScreenReaderOnly, + EuiIcon, + EuiToolTip, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { AnomalyDetectionJobIdLink } from './job_id_link'; @@ -161,7 +167,7 @@ export class JobsList extends Component { }), sortable: true, truncateText: false, - width: '20%', + width: '15%', scope: 'row', render: isManagementTable ? (id) => this.getJobIdLink(id) : undefined, }, @@ -172,13 +178,45 @@ export class JobsList extends Component {

), render: (item) => , }, + { + field: 'alertingRules', + name: ( + +

+ +

+
+ ), + width: '30px', + render: (item) => { + return Array.isArray(item) ? ( + + } + > + + + ) : ( + + ); + }, + }, { name: i18n.translate('xpack.ml.jobsList.descriptionLabel', { defaultMessage: 'Description', diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js index e1314eb7188362..6b3d6bc8971f58 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/multi_job_actions/actions_menu.js @@ -159,7 +159,7 @@ class MultiJobActionsMenuUI extends Component { > ); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js index 361e8956c714e3..12ca42feec6daa 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/start_datafeed_modal/start_datafeed_modal.js @@ -173,7 +173,7 @@ export class StartDatafeedModal extends Component { label={ } checked={createAlert} diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx index 6cefc239905c78..472654c4b3c855 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/post_save_options/post_save_options.tsx @@ -87,7 +87,7 @@ export const PostSaveOptions: FC = ({ jobRunner }) => { > diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 1ab47256b2c2a4..c9fde252fc26dd 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -51,8 +51,8 @@ import { TriggersAndActionsUIPublicPluginSetup, TriggersAndActionsUIPublicPluginStart, } from '../../triggers_actions_ui/public'; -import { registerMlAlerts } from './alerting/register_ml_alerts'; import { FileDataVisualizerPluginStart } from '../../file_data_visualizer/public'; +import { PluginSetupContract as AlertingSetup } from '../../alerting/public'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -79,6 +79,7 @@ export interface MlSetupDependencies { share: SharePluginSetup; indexPatternManagement: IndexPatternManagementSetup; triggersActionsUi?: TriggersAndActionsUIPublicPluginSetup; + alerting?: AlertingSetup; } export type MlCoreSetup = CoreSetup; @@ -132,10 +133,6 @@ export class MlPlugin implements Plugin { this.urlGenerator = registerUrlGenerator(pluginsSetup.share, core); } - if (pluginsSetup.triggersActionsUi) { - registerMlAlerts(pluginsSetup.triggersActionsUi); - } - const licensing = pluginsSetup.licensing.license$.pipe(take(1)); licensing.subscribe(async (license) => { const [coreStart] = await core.getStartServices(); @@ -166,6 +163,7 @@ export class MlPlugin implements Plugin { registerManagementSection, registerMlUiActions, registerSearchLinks, + registerMlAlerts, } = await import('./register_helper'); const mlEnabled = isMlEnabled(license); @@ -181,6 +179,11 @@ export class MlPlugin implements Plugin { } registerEmbeddables(pluginsSetup.embeddable, core); registerMlUiActions(pluginsSetup.uiActions, core); + + const canUseMlAlerts = capabilities.ml?.canUseMlAlerts; + if (pluginsSetup.triggersActionsUi && canUseMlAlerts) { + registerMlAlerts(pluginsSetup.triggersActionsUi, pluginsSetup.alerting); + } } } }); diff --git a/x-pack/plugins/ml/public/register_helper/index.ts b/x-pack/plugins/ml/public/register_helper/index.ts index 25253400269507..278f32f683053d 100644 --- a/x-pack/plugins/ml/public/register_helper/index.ts +++ b/x-pack/plugins/ml/public/register_helper/index.ts @@ -9,3 +9,4 @@ export { registerEmbeddables } from '../embeddables'; export { registerManagementSection } from '../application/management'; export { registerMlUiActions } from '../ui_actions'; export { registerSearchLinks } from './register_search_links'; +export { registerMlAlerts } from '../alerting'; diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 04d9fcfce7d612..dc8d019125d2b7 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -289,6 +289,8 @@ export function alertingServiceProvider(mlClient: MlClient, datafeedsService: Da return { count: aggTypeResults.doc_count, key: v.key, + message: + 'Alerts are raised based on real-time scores. Remember that scores may be adjusted over time as data continues to be analyzed.', alertInstanceKey, jobIds: [...new Set(requestedAnomalies.map((h) => h._source.job_id))], isInterim: requestedAnomalies.some((h) => h._source.is_interim), diff --git a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts index 442e46d2c7335e..f39b3850b71b14 100644 --- a/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts +++ b/x-pack/plugins/ml/server/lib/alerts/register_anomaly_detection_alert_type.ts @@ -78,6 +78,12 @@ export function registerAnomalyDetectionAlertType({ defaultMessage: 'List of job IDs that triggered the alert instance', }), }, + { + name: 'message', + description: i18n.translate('xpack.ml.alertContext.messageDescription', { + defaultMessage: 'Alert info message', + }), + }, { name: 'isInterim', description: i18n.translate('xpack.ml.alertContext.isInterimDescription', { diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts index 49a63d2796969e..93c2124eae8d18 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts @@ -51,7 +51,7 @@ describe('check_capabilities', () => { ); const { capabilities } = await getCapabilities(); const count = Object.keys(capabilities).length; - expect(count).toBe(29); + expect(count).toBe(30); }); }); @@ -82,6 +82,7 @@ describe('check_capabilities', () => { expect(capabilities.canGetAnnotations).toBe(true); expect(capabilities.canCreateAnnotation).toBe(true); expect(capabilities.canDeleteAnnotation).toBe(true); + expect(capabilities.canUseMlAlerts).toBe(true); expect(capabilities.canCreateJob).toBe(false); expect(capabilities.canDeleteJob).toBe(false); diff --git a/x-pack/plugins/ml/server/lib/route_guard.ts b/x-pack/plugins/ml/server/lib/route_guard.ts index 8f2c855a7cb01b..d0a3c59e4d7e51 100644 --- a/x-pack/plugins/ml/server/lib/route_guard.ts +++ b/x-pack/plugins/ml/server/lib/route_guard.ts @@ -20,12 +20,17 @@ import { jobSavedObjectServiceFactory, JobSavedObjectService } from '../saved_ob import { MlLicense } from '../../common/license'; import { MlClient, getMlClient } from '../lib/ml_client'; +import type { AlertingApiRequestHandlerContext } from '../../../alerting/server'; + +type MLRequestHandlerContext = RequestHandlerContext & { + alerting?: AlertingApiRequestHandlerContext; +}; type Handler = (handlerParams: { client: IScopedClusterClient; request: KibanaRequest; response: KibanaResponseFactory; - context: RequestHandlerContext; + context: MLRequestHandlerContext; jobSavedObjectService: JobSavedObjectService; mlClient: MlClient; }) => ReturnType; @@ -66,7 +71,7 @@ export class RouteGuard { private _guard(check: () => boolean, handler: Handler) { return ( - context: RequestHandlerContext, + context: MLRequestHandlerContext, request: KibanaRequest, response: KibanaResponseFactory ) => { diff --git a/x-pack/plugins/ml/server/models/job_service/index.ts b/x-pack/plugins/ml/server/models/job_service/index.ts index d36ec822c13145..94dc669bfd946d 100644 --- a/x-pack/plugins/ml/server/models/job_service/index.ts +++ b/x-pack/plugins/ml/server/models/job_service/index.ts @@ -13,11 +13,16 @@ import { newJobCapsProvider } from './new_job_caps'; import { newJobChartsProvider, topCategoriesProvider } from './new_job'; import { modelSnapshotProvider } from './model_snapshots'; import type { MlClient } from '../../lib/ml_client'; +import type { AlertsClient } from '../../../../alerting/server'; -export function jobServiceProvider(client: IScopedClusterClient, mlClient: MlClient) { +export function jobServiceProvider( + client: IScopedClusterClient, + mlClient: MlClient, + alertsClient?: AlertsClient +) { return { ...datafeedsProvider(client, mlClient), - ...jobsProvider(client, mlClient), + ...jobsProvider(client, mlClient, alertsClient), ...groupsProvider(mlClient), ...newJobCapsProvider(client), ...newJobChartsProvider(client), diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 0dcef210c10ce4..a838db443bebcf 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -40,6 +40,9 @@ import { import { groupsProvider } from './groups'; import type { MlClient } from '../../lib/ml_client'; import { isPopulatedObject } from '../../../common/util/object_utils'; +import type { AlertsClient } from '../../../../alerting/server'; +import { ML_ALERT_TYPES } from '../../../common/constants/alerts'; +import { MlAnomalyDetectionAlertParams } from '../../routes/schemas/alerting_schema'; interface Results { [id: string]: { @@ -48,7 +51,11 @@ interface Results { }; } -export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { +export function jobsProvider( + client: IScopedClusterClient, + mlClient: MlClient, + alertsClient?: AlertsClient +) { const { asInternalUser } = client; const { forceDeleteDatafeed, getDatafeedIdsByJobId, getDatafeedByJobId } = datafeedsProvider( @@ -212,6 +219,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { nodeName: job.node ? job.node.name : undefined, deleting: job.deleting || undefined, awaitingNodeAssignment: isJobAwaitingNodeAssignment(job), + alertingRules: job.alerting_rules, }; if (jobIds.find((j) => j === tempJob.id)) { tempJob.fullJob = job; @@ -416,6 +424,39 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { jobs.push(tempJob); }); + + if (alertsClient) { + const mlAlertingRules = await alertsClient.find({ + options: { + filter: `alert.attributes.alertTypeId:${ML_ALERT_TYPES.ANOMALY_DETECTION}`, + perPage: 1000, + }, + }); + + mlAlertingRules.data.forEach((curr) => { + const { + params: { + jobSelection: { jobIds: ruleJobIds, groupIds: ruleGroupIds }, + }, + } = curr; + + jobs.forEach((j) => { + const isIncluded = + (Array.isArray(ruleJobIds) && ruleJobIds.includes(j.job_id)) || + (Array.isArray(ruleGroupIds) && + Array.isArray(j.groups) && + j.groups.some((g) => ruleGroupIds.includes(g))); + + if (isIncluded) { + if (Array.isArray(j.alerting_rules)) { + j.alerting_rules.push(curr); + } else { + j.alerting_rules = [curr]; + } + } + }); + }); + } } return jobs; } diff --git a/x-pack/plugins/ml/server/routes/job_service.ts b/x-pack/plugins/ml/server/routes/job_service.ts index 1f755c27db8713..39336f192a7f84 100644 --- a/x-pack/plugins/ml/server/routes/job_service.ts +++ b/x-pack/plugins/ml/server/routes/job_service.ts @@ -227,9 +227,13 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { + routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response, context }) => { try { - const { jobsSummary } = jobServiceProvider(client, mlClient); + const { jobsSummary } = jobServiceProvider( + client, + mlClient, + context.alerting?.getAlertsClient() + ); const { jobIds } = request.body; const resp = await jobsSummary(jobIds); @@ -328,9 +332,13 @@ export function jobServiceRoutes({ router, routeGuard }: RouteInitialization) { tags: ['access:ml:canGetJobs'], }, }, - routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response }) => { + routeGuard.fullLicenseAPIGuard(async ({ client, mlClient, request, response, context }) => { try { - const { createFullJobsList } = jobServiceProvider(client, mlClient); + const { createFullJobsList } = jobServiceProvider( + client, + mlClient, + context.alerting?.getAlertsClient() + ); const { jobIds } = request.body; const resp = await createFullJobsList(jobIds); diff --git a/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts b/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts index 54c2beaa06b09b..257a6f0c309816 100644 --- a/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts +++ b/x-pack/plugins/security_solution/common/machine_learning/empty_ml_capabilities.ts @@ -38,6 +38,7 @@ export const emptyMlCapabilities: MlCapabilitiesResponse = { canCreateDataFrameAnalytics: false, canStartStopDataFrameAnalytics: false, canCreateMlAlerts: false, + canUseMlAlerts: false, }, isPlatinumOrTrialLicense: false, mlFeatureEnabledInSpace: false, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3a5135ed860f2b..a4fb733d20c62f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13830,7 +13830,6 @@ "xpack.ml.jobSelector.filterBar.invalidSearchErrorMessage": "無効な検索:{errorMessage}", "xpack.ml.jobSelector.flyoutTitle": "ジョブの選択", "xpack.ml.jobSelector.formControlLabel": "ジョブまたはグループを選択", - "xpack.ml.jobSelector.groupOptionsLabel": "グループ", "xpack.ml.jobSelector.groupsTab": "グループ", "xpack.ml.jobSelector.hideBarBadges": "非表示", "xpack.ml.jobSelector.hideFlyoutBadges": "非表示", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a8338977d09753..c8f0f911084405 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14017,7 +14017,6 @@ "xpack.ml.jobSelector.filterBar.jobGroupTitle": "({jobsCount, plural, other {# 个作业}})", "xpack.ml.jobSelector.flyoutTitle": "作业选择", "xpack.ml.jobSelector.formControlLabel": "选择作业或组", - "xpack.ml.jobSelector.groupOptionsLabel": "组", "xpack.ml.jobSelector.groupsTab": "组", "xpack.ml.jobSelector.hideBarBadges": "隐藏", "xpack.ml.jobSelector.hideFlyoutBadges": "隐藏", diff --git a/x-pack/test/api_integration/apis/ml/system/capabilities.ts b/x-pack/test/api_integration/apis/ml/system/capabilities.ts index d8ab2a30ef7fba..aa1ab2016fcb50 100644 --- a/x-pack/test/api_integration/apis/ml/system/capabilities.ts +++ b/x-pack/test/api_integration/apis/ml/system/capabilities.ts @@ -45,7 +45,7 @@ export default ({ getService }: FtrProviderContext) => { it('should have the right number of capabilities', async () => { const { capabilities } = await runRequest(USER.ML_POWERUSER); - expect(Object.keys(capabilities).length).to.eql(29); + expect(Object.keys(capabilities).length).to.eql(30); }); it('should get viewer capabilities', async () => { @@ -72,6 +72,7 @@ export default ({ getService }: FtrProviderContext) => { canDeleteDataFrameAnalytics: false, canStartStopDataFrameAnalytics: false, canCreateMlAlerts: false, + canUseMlAlerts: true, canAccessML: true, canGetJobs: true, canGetDatafeeds: true, @@ -108,6 +109,7 @@ export default ({ getService }: FtrProviderContext) => { canDeleteDataFrameAnalytics: true, canStartStopDataFrameAnalytics: true, canCreateMlAlerts: true, + canUseMlAlerts: true, canAccessML: true, canGetJobs: true, canGetDatafeeds: true, diff --git a/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts b/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts index cd922bf4bae924..b9ca7794b7cd9b 100644 --- a/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts +++ b/x-pack/test/api_integration/apis/ml/system/space_capabilities.ts @@ -71,11 +71,11 @@ export default ({ getService }: FtrProviderContext) => { it('should have the right number of capabilities - space with ML', async () => { const { capabilities } = await runRequest(USER.ML_POWERUSER, idSpaceWithMl); - expect(Object.keys(capabilities).length).to.eql(29); + expect(Object.keys(capabilities).length).to.eql(30); }); it('should have the right number of capabilities - space without ML', async () => { const { capabilities } = await runRequest(USER.ML_POWERUSER, idSpaceNoMl); - expect(Object.keys(capabilities).length).to.eql(29); + expect(Object.keys(capabilities).length).to.eql(30); }); it('should get viewer capabilities - space with ML', async () => { @@ -101,6 +101,7 @@ export default ({ getService }: FtrProviderContext) => { canDeleteDataFrameAnalytics: false, canStartStopDataFrameAnalytics: false, canCreateMlAlerts: false, + canUseMlAlerts: true, canAccessML: true, canGetJobs: true, canGetDatafeeds: true, @@ -136,6 +137,7 @@ export default ({ getService }: FtrProviderContext) => { canDeleteDataFrameAnalytics: false, canStartStopDataFrameAnalytics: false, canCreateMlAlerts: false, + canUseMlAlerts: false, canAccessML: false, canGetJobs: false, canGetDatafeeds: false, @@ -171,6 +173,7 @@ export default ({ getService }: FtrProviderContext) => { canDeleteDataFrameAnalytics: true, canStartStopDataFrameAnalytics: true, canCreateMlAlerts: true, + canUseMlAlerts: true, canAccessML: true, canGetJobs: true, canGetDatafeeds: true, @@ -206,6 +209,7 @@ export default ({ getService }: FtrProviderContext) => { canDeleteDataFrameAnalytics: false, canStartStopDataFrameAnalytics: false, canCreateMlAlerts: false, + canUseMlAlerts: false, canAccessML: false, canGetJobs: false, canGetDatafeeds: false, From c60411ed4a5b7d2b7104f40840d87d2d1f9b9ff1 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 11:41:13 -0700 Subject: [PATCH 15/65] skip flaky suite (#97864) --- test/functional/apps/discover/_runtime_fields_editor.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_runtime_fields_editor.ts b/test/functional/apps/discover/_runtime_fields_editor.ts index 9add5323db814e..ea95e0adff617e 100644 --- a/test/functional/apps/discover/_runtime_fields_editor.ts +++ b/test/functional/apps/discover/_runtime_fields_editor.ts @@ -32,7 +32,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await fieldEditor.save(); }; - describe('discover integration with runtime fields editor', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/97864 + describe.skip('discover integration with runtime fields editor', function describeIndexTests() { before(async function () { await esArchiver.load('discover'); await esArchiver.loadIfNeeded('logstash_functional'); From 2744f704665646c24e949032431a571e274c8c1e Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 21 Apr 2021 20:58:39 +0200 Subject: [PATCH 16/65] Remove the no longer used release-notes script (#97806) * Remove the no longer used release-notes script * Commit missing file --- .eslintignore | 1 - package.json | 24 +- packages/kbn-docs-utils/src/index.ts | 1 - .../kbn-docs-utils/src/release_notes/cli.ts | 152 ---------- .../src/release_notes/formats/asciidoc.ts | 73 ----- .../src/release_notes/formats/csv.ts | 63 ---- .../src/release_notes/formats/format.ts | 23 -- .../src/release_notes/formats/index.ts | 14 - .../kbn-docs-utils/src/release_notes/index.ts | 9 - .../src/release_notes/lib/classify_pr.ts | 55 ---- .../lib/get_fix_references.test.ts | 57 ---- .../release_notes/lib/get_fix_references.ts | 18 -- .../lib/get_note_from_description.test.ts | 72 ----- .../lib/get_note_from_description.ts | 25 -- .../src/release_notes/lib/index.ts | 15 - .../lib/irrelevant_pr_summary.ts | 50 ---- .../src/release_notes/lib/is_pr_relevant.ts | 50 ---- .../src/release_notes/lib/pr_api.ts | 222 -------------- .../src/release_notes/lib/streams.ts | 23 -- .../src/release_notes/lib/type_helpers.ts | 9 - .../src/release_notes/lib/version.test.ts | 135 --------- .../src/release_notes/lib/version.ts | 112 ------- .../src/release_notes/release_notes_config.ts | 283 ------------------ scripts/release_notes.js | 10 - yarn.lock | 22 -- 25 files changed, 10 insertions(+), 1508 deletions(-) delete mode 100644 packages/kbn-docs-utils/src/release_notes/cli.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/csv.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/format.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/formats/index.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/index.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/index.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/streams.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/version.test.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/lib/version.ts delete mode 100644 packages/kbn-docs-utils/src/release_notes/release_notes_config.ts delete mode 100644 scripts/release_notes.js diff --git a/.eslintignore b/.eslintignore index 4058d971b76420..ce21d5bb312649 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,4 @@ **/*.js.snap -**/graphql/types.ts /.es /.chromium /build diff --git a/package.json b/package.json index 047dd38d92cd89..0ed0d3bbc3b7f0 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,6 @@ "**/deepmerge": "^4.2.2", "**/fast-deep-equal": "^3.1.1", "globby/fast-glob": "3.2.5", - "**/graphql-toolkit/lodash": "^4.17.21", "**/hoist-non-react-statics": "^3.3.2", "**/isomorphic-fetch/node-fetch": "^2.6.1", "**/istanbul-instrumenter-loader/schema-utils": "1.0.0", @@ -193,10 +192,10 @@ "compare-versions": "3.5.1", "concat-stream": "1.6.2", "constate": "^1.3.2", - "cronstrue": "^1.51.0", "content-disposition": "0.5.3", "copy-to-clipboard": "^3.0.8", "core-js": "^3.6.5", + "cronstrue": "^1.51.0", "cytoscape": "^3.10.0", "cytoscape-dagre": "^2.2.2", "d3": "3.5.17", @@ -231,8 +230,6 @@ "glob": "^7.1.2", "glob-all": "^3.2.1", "globby": "^11.0.3", - "graphql": "^0.13.2", - "graphql-tag": "^2.10.3", "handlebars": "4.7.7", "he": "^1.2.0", "history": "^4.9.0", @@ -274,9 +271,9 @@ "lodash": "^4.17.21", "lru-cache": "^4.1.5", "lz-string": "^1.4.4", - "markdown-it": "^10.0.0", "mapbox-gl": "1.13.1", "mapbox-gl-draw-rectangle-mode": "^1.0.4", + "markdown-it": "^10.0.0", "md5": "^2.1.0", "memoize-one": "^5.0.0", "mime": "^2.4.4", @@ -298,12 +295,12 @@ "object-path-immutable": "^3.1.1", "opn": "^5.5.0", "oppsy": "^2.0.0", + "p-limit": "^3.0.1", "p-map": "^4.0.0", "p-retry": "^4.2.0", "papaparse": "^5.2.0", "pdfmake": "^0.1.65", "pegjs": "0.10.0", - "p-limit": "^3.0.1", "pluralize": "3.1.0", "pngjs": "^3.4.0", "polished": "^1.9.2", @@ -335,19 +332,19 @@ "react-monaco-editor": "^0.41.2", "react-popper-tooltip": "^2.10.1", "react-query": "^3.13.10", + "react-redux": "^7.2.0", + "react-resizable": "^1.7.5", "react-resize-detector": "^4.2.0", "react-reverse-portal": "^1.0.4", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-router-redux": "^4.0.8", "react-shortcuts": "^2.0.0", "react-sizeme": "^2.3.6", "react-syntax-highlighter": "^15.3.1", - "react-redux": "^7.2.0", - "react-resizable": "^1.7.5", - "react-router": "^5.2.0", - "react-router-dom": "^5.2.0", "react-tiny-virtual-list": "^2.2.0", - "react-virtualized": "^9.21.2", "react-use": "^15.3.8", + "react-virtualized": "^9.21.2", "react-vis": "^1.8.1", "react-visibility-sensor": "^5.1.1", "reactcss": "1.2.3", @@ -376,8 +373,8 @@ "strip-ansi": "^6.0.0", "style-it": "^2.1.3", "styled-components": "^5.1.0", - "symbol-observable": "^1.2.0", "suricata-sid-db": "^1.0.2", + "symbol-observable": "^1.2.0", "tabbable": "1.1.3", "tar": "4.4.13", "tinycolor2": "1.4.1", @@ -521,7 +518,6 @@ "@types/getos": "^3.0.0", "@types/git-url-parse": "^9.0.0", "@types/glob": "^7.1.2", - "@types/graphql": "^0.13.2", "@types/gulp": "^4.0.6", "@types/gulp-zip": "^4.0.1", "@types/hapi__cookie": "^10.1.1", @@ -735,8 +731,8 @@ "jest-circus": "^26.6.3", "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", - "jest-environment-jsdom-thirteen": "^1.0.1", "jest-environment-jsdom": "^26.6.2", + "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", "jest-silent-reporter": "^0.2.1", "jest-snapshot": "^26.6.2", diff --git a/packages/kbn-docs-utils/src/index.ts b/packages/kbn-docs-utils/src/index.ts index 24aef1bf891f6f..5accd1fa2984ff 100644 --- a/packages/kbn-docs-utils/src/index.ts +++ b/packages/kbn-docs-utils/src/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export * from './release_notes'; export * from './api_docs'; diff --git a/packages/kbn-docs-utils/src/release_notes/cli.ts b/packages/kbn-docs-utils/src/release_notes/cli.ts deleted file mode 100644 index e6d1c717459b17..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/cli.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import Fs from 'fs'; -import Path from 'path'; -import { inspect } from 'util'; - -import { REPO_ROOT } from '@kbn/utils'; -import { run, createFlagError, createFailError } from '@kbn/dev-utils'; - -import { FORMATS, SomeFormat } from './formats'; -import { - PrApi, - Version, - ClassifiedPr, - streamFromIterable, - asyncPipeline, - IrrelevantPrSummary, - isPrRelevant, - classifyPr, -} from './lib'; - -const rootPackageJson = JSON.parse( - Fs.readFileSync(Path.resolve(REPO_ROOT, 'package.json'), 'utf8') -); -const extensions = FORMATS.map((f) => f.extension); - -export function runReleaseNotesCli() { - run( - async ({ flags, log }) => { - const token = flags.token; - if (!token || typeof token !== 'string') { - throw createFlagError('--token must be defined'); - } - const prApi = new PrApi(log, token); - - const version = Version.fromFlag(flags.version); - if (!version) { - throw createFlagError('unable to parse --version, use format "v{major}.{minor}.{patch}"'); - } - - const includeVersions = Version.fromFlags(flags.include || []); - if (!includeVersions) { - throw createFlagError('unable to parse --include, use format "v{major}.{minor}.{patch}"'); - } - - const Formats: SomeFormat[] = []; - for (const flag of Array.isArray(flags.format) ? flags.format : [flags.format]) { - const Format = FORMATS.find((F) => F.extension === flag); - if (!Format) { - throw createFlagError(`--format must be one of "${extensions.join('", "')}"`); - } - Formats.push(Format); - } - - const filename = flags.filename; - if (!filename || typeof filename !== 'string') { - throw createFlagError('--filename must be a string'); - } - - if (flags['debug-pr']) { - const number = parseInt(String(flags['debug-pr']), 10); - if (Number.isNaN(number)) { - throw createFlagError('--debug-pr must be a pr number when specified'); - } - - const summary = new IrrelevantPrSummary(log); - const pr = await prApi.getPr(number); - log.success( - inspect( - { - version: version.label, - includeVersions: includeVersions.map((v) => v.label), - isPrRelevant: isPrRelevant(pr, version, includeVersions, summary), - ...classifyPr(pr, log), - pr, - }, - { depth: 100 } - ) - ); - summary.logStats(); - return; - } - - log.info(`Loading all PRs with label [${version.label}] to build release notes...`); - - const summary = new IrrelevantPrSummary(log); - const prsToReport: ClassifiedPr[] = []; - const prIterable = prApi.iterRelevantPullRequests(version); - for await (const pr of prIterable) { - if (!isPrRelevant(pr, version, includeVersions, summary)) { - continue; - } - prsToReport.push(classifyPr(pr, log)); - } - summary.logStats(); - - if (!prsToReport.length) { - throw createFailError( - `All PRs with label [${version.label}] were filtered out by the config. Run again with --debug for more info.` - ); - } - - log.info(`Found ${prsToReport.length} prs to report on`); - - for (const Format of Formats) { - const format = new Format(version, prsToReport, log); - const outputPath = Path.resolve(`${filename}.${Format.extension}`); - await asyncPipeline(streamFromIterable(format.print()), Fs.createWriteStream(outputPath)); - log.success(`[${Format.extension}] report written to ${outputPath}`); - } - }, - { - usage: `node scripts/release_notes --token {token} --version {version}`, - flags: { - alias: { - version: 'v', - include: 'i', - }, - string: ['token', 'version', 'format', 'filename', 'include', 'debug-pr'], - default: { - filename: 'report', - version: rootPackageJson.version, - format: extensions, - }, - help: ` - --token (required) The Github access token to use for requests - --version, -v The version to fetch PRs by, PRs with version labels prior to - this one will be ignored (see --include-version) (default ${ - rootPackageJson.version - }) - --include, -i A version that is before --version but shouldn't be considered - "released" and cause PRs with a matching label to be excluded from - release notes. Use this when PRs are labeled with a version that - is less that --version and is expected to be released after - --version, can be specified multiple times. - --format Only produce a certain format, options: "${extensions.join('", "')}" - --filename Output filename, defaults to "report" - --debug-pr Fetch and print the details for a single PR, disabling reporting - `, - }, - description: ` - Fetch details from Github PRs for generating release notes - `, - } - ); -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts b/packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts deleted file mode 100644 index df86f6c7a40e1b..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/asciidoc.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import dedent from 'dedent'; - -import { Format } from './format'; -import { - ASCIIDOC_SECTIONS, - UNKNOWN_ASCIIDOC_SECTION, - AREAS, - UNKNOWN_AREA, -} from '../release_notes_config'; - -function* lines(body: string) { - for (const line of dedent(body).split('\n')) { - yield `${line}\n`; - } -} - -export class AsciidocFormat extends Format { - static extension = 'asciidoc'; - - *print() { - const sortedAreas = [ - ...AREAS.slice().sort((a, b) => a.title.localeCompare(b.title)), - UNKNOWN_AREA, - ]; - - yield* lines(` - [[release-notes-${this.version.label}]] - == ${this.version.label} Release Notes - - Also see <>. - `); - - for (const section of [...ASCIIDOC_SECTIONS, UNKNOWN_ASCIIDOC_SECTION]) { - const prsInSection = this.prs.filter((pr) => pr.asciidocSection === section); - if (!prsInSection.length) { - continue; - } - - yield '\n'; - yield* lines(` - [float] - [[${section.id}-${this.version.label}]] - === ${section.title} - `); - - for (const area of sortedAreas) { - const prsInArea = prsInSection.filter((pr) => pr.area === area); - - if (!prsInArea.length) { - continue; - } - - yield `${area.title}::\n`; - for (const pr of prsInArea) { - const fixes = pr.fixes.length ? `[Fixes ${pr.fixes.join(', ')}] ` : ''; - const strippedTitle = pr.title.replace(/^\s*\[[^\]]+\]\s*/, ''); - yield `* ${fixes}${strippedTitle} {kibana-pull}${pr.number}[#${pr.number}]\n`; - if (pr.note) { - yield ` - ${pr.note}\n`; - } - } - } - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/csv.ts b/packages/kbn-docs-utils/src/release_notes/formats/csv.ts deleted file mode 100644 index ad03ebaff8049a..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/csv.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Format } from './format'; - -/** - * Escape a value to conform to field and header encoding defined at https://tools.ietf.org/html/rfc4180 - */ -function esc(value: string | number) { - if (typeof value === 'number') { - return String(value); - } - - if (!value.includes(',') && !value.includes('\n') && !value.includes('"')) { - return value; - } - - return `"${value.split('"').join('""')}"`; -} - -function row(...fields: Array) { - return fields.map(esc).join(',') + '\r\n'; -} - -export class CsvFormat extends Format { - static extension = 'csv'; - - *print() { - // columns - yield row( - 'areas', - 'versions', - 'user', - 'title', - 'number', - 'url', - 'date', - 'fixes', - 'labels', - 'state' - ); - - for (const pr of this.prs) { - yield row( - pr.area.title, - pr.versions.map((v) => v.label).join(', '), - pr.user.name || pr.user.login, - pr.title, - pr.number, - pr.url, - pr.mergedAt, - pr.fixes.join(', '), - pr.labels.join(', '), - pr.state - ); - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/format.ts b/packages/kbn-docs-utils/src/release_notes/formats/format.ts deleted file mode 100644 index 937beb2f3fd677..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/format.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ToolingLog } from '@kbn/dev-utils'; - -import { Version, ClassifiedPr } from '../lib'; - -export abstract class Format { - static extension: string; - - constructor( - protected readonly version: Version, - protected readonly prs: ClassifiedPr[], - protected readonly log: ToolingLog - ) {} - - abstract print(): Iterator; -} diff --git a/packages/kbn-docs-utils/src/release_notes/formats/index.ts b/packages/kbn-docs-utils/src/release_notes/formats/index.ts deleted file mode 100644 index 2019dce53f5372..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/formats/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ArrayItem } from '../lib'; -import { AsciidocFormat } from './asciidoc'; -import { CsvFormat } from './csv'; - -export const FORMATS = [CsvFormat, AsciidocFormat] as const; -export type SomeFormat = ArrayItem; diff --git a/packages/kbn-docs-utils/src/release_notes/index.ts b/packages/kbn-docs-utils/src/release_notes/index.ts deleted file mode 100644 index 7ee97ec9aa05d8..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './cli'; diff --git a/packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts b/packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts deleted file mode 100644 index ca24367fa7288e..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/classify_pr.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ToolingLog } from '@kbn/dev-utils'; - -import { - Area, - AREAS, - UNKNOWN_AREA, - AsciidocSection, - ASCIIDOC_SECTIONS, - UNKNOWN_ASCIIDOC_SECTION, -} from '../release_notes_config'; -import { PullRequest } from './pr_api'; - -export interface ClassifiedPr extends PullRequest { - area: Area; - asciidocSection: AsciidocSection; -} - -export function classifyPr(pr: PullRequest, log: ToolingLog): ClassifiedPr { - const filter = (a: Area | AsciidocSection) => - a.labels.some((test) => - typeof test === 'string' ? pr.labels.includes(test) : pr.labels.some((l) => l.match(test)) - ); - - const areas = AREAS.filter(filter); - const asciidocSections = ASCIIDOC_SECTIONS.filter(filter); - - const pickOne = (name: string, options: T[]) => { - if (options.length > 1) { - const matches = options.map((o) => o.title).join(', '); - log.warning(`[${pr.terminalLink}] ambiguous ${name}, mulitple match [${matches}]`); - return options[0]; - } - - if (options.length === 0) { - log.error(`[${pr.terminalLink}] unable to determine ${name} because none match`); - return; - } - - return options[0]; - }; - - return { - ...pr, - area: pickOne('area', areas) || UNKNOWN_AREA, - asciidocSection: pickOne('asciidoc section', asciidocSections) || UNKNOWN_ASCIIDOC_SECTION, - }; -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts deleted file mode 100644 index 8cc8aec19f94e7..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getFixReferences } from './get_fix_references'; - -it('returns all fixed issue mentions in the PR text', () => { - expect( - getFixReferences(` - clOses #1 - closes: #2 - clOse #3 - close: #4 - clOsed #5 - closed: #6 - fiX #7 - fix: #8 - fiXes #9 - fixes: #10 - fiXed #11 - fixed: #12 - reSolve #13 - resolve: #14 - reSolves #15 - resolves: #16 - reSolved #17 - resolved: #18 - fixed - #19 - `) - ).toMatchInlineSnapshot(` - Array [ - "#1", - "#2", - "#3", - "#4", - "#5", - "#6", - "#7", - "#8", - "#9", - "#10", - "#11", - "#12", - "#13", - "#14", - "#15", - "#16", - "#17", - "#18", - ] - `); -}); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts deleted file mode 100644 index c4c8ed0f9a9ea6..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_fix_references.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const FIXES_RE = /(?:closes|close|closed|fix|fixes|fixed|resolve|resolves|resolved)[ :]*(#\d*)/gi; - -export function getFixReferences(prText: string) { - const fixes: string[] = []; - let match; - while ((match = FIXES_RE.exec(prText))) { - fixes.push(match[1]); - } - return fixes; -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts deleted file mode 100644 index 59945a835a3c94..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import MarkdownIt from 'markdown-it'; -import dedent from 'dedent'; - -import { getNoteFromDescription } from './get_note_from_description'; - -it('extracts expected components from html', () => { - const mk = new MarkdownIt(); - - expect( - getNoteFromDescription( - mk.render(dedent` - My PR description - - Fixes: #1234 - - ## Release Note: - - Checkout this feature - `), - 'release note' - ) - ).toMatchInlineSnapshot(`"Checkout this feature"`); - - expect( - getNoteFromDescription( - mk.render(dedent` - My PR description - - Fixes: #1234 - - #### Dev docs: - - We fixed an issue - `), - 'dev docs' - ) - ).toMatchInlineSnapshot(`"We fixed an issue"`); - - expect( - getNoteFromDescription( - mk.render(dedent` - My PR description - - Fixes: #1234 - - OTHER TITLE: Checkout feature foo - `), - 'other title' - ) - ).toMatchInlineSnapshot(`"Checkout feature foo"`); - - expect( - getNoteFromDescription( - mk.render(dedent` - # Summary - - My PR description - - release note : bar - `), - 'release note' - ) - ).toMatchInlineSnapshot(`"bar"`); -}); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts b/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts deleted file mode 100644 index db80c29454cf46..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/get_note_from_description.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import cheerio from 'cheerio'; - -export function getNoteFromDescription(descriptionHtml: string, header: string) { - const re = new RegExp(`^(\\s*${header.toLowerCase()}(?:s)?\\s*:?\\s*)`, 'i'); - const $ = cheerio.load(descriptionHtml); - for (const el of $('p,h1,h2,h3,h4,h5').toArray()) { - const text = $(el).text(); - const match = text.match(re); - - if (!match) { - continue; - } - - const note = text.replace(match[1], '').trim(); - return note || $(el).next().text().trim(); - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/index.ts b/packages/kbn-docs-utils/src/release_notes/lib/index.ts deleted file mode 100644 index 8578060007d736..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './pr_api'; -export * from './version'; -export * from './is_pr_relevant'; -export * from './streams'; -export * from './type_helpers'; -export * from './irrelevant_pr_summary'; -export * from './classify_pr'; diff --git a/packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts b/packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts deleted file mode 100644 index 3bc9ebfced60fa..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/irrelevant_pr_summary.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { ToolingLog } from '@kbn/dev-utils'; - -import { PullRequest } from './pr_api'; -import { Version } from './version'; - -export class IrrelevantPrSummary { - private readonly stats = { - 'skipped by label': new Map(), - 'skipped by label regexp': new Map(), - 'skipped by version': new Map(), - }; - - constructor(private readonly log: ToolingLog) {} - - skippedByLabel(pr: PullRequest, label: string) { - this.log.debug(`${pr.terminalLink} skipped, label [${label}] is ignored`); - this.increment('skipped by label', label); - } - - skippedByLabelRegExp(pr: PullRequest, regexp: RegExp, label: string) { - this.log.debug(`${pr.terminalLink} skipped, label [${label}] matches regexp [${regexp}]`); - this.increment('skipped by label regexp', `${regexp}`); - } - - skippedByVersion(pr: PullRequest, earliestVersion: Version) { - this.log.debug(`${pr.terminalLink} skipped, earliest version is [${earliestVersion.label}]`); - this.increment('skipped by version', earliestVersion.label); - } - - private increment(stat: keyof IrrelevantPrSummary['stats'], key: string) { - const n = this.stats[stat].get(key) || 0; - this.stats[stat].set(key, n + 1); - } - - logStats() { - for (const [description, stats] of Object.entries(this.stats)) { - for (const [key, count] of stats) { - this.log.warning(`${count} ${count === 1 ? 'pr was' : 'prs were'} ${description} [${key}]`); - } - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts b/packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts deleted file mode 100644 index 1de75373c0954b..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/is_pr_relevant.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Version } from './version'; -import { PullRequest } from './pr_api'; -import { IGNORE_LABELS } from '../release_notes_config'; -import { IrrelevantPrSummary } from './irrelevant_pr_summary'; - -export function isPrRelevant( - pr: PullRequest, - version: Version, - includeVersions: Version[], - summary: IrrelevantPrSummary -) { - for (const label of IGNORE_LABELS) { - if (typeof label === 'string') { - if (pr.labels.includes(label)) { - summary.skippedByLabel(pr, label); - return false; - } - } - - if (label instanceof RegExp) { - const matching = pr.labels.find((l) => label.test(l)); - if (matching) { - summary.skippedByLabelRegExp(pr, label, matching); - return false; - } - } - } - - const [earliestVersion] = Version.sort( - // filter out `includeVersions` so that they won't be considered the "earliest version", only - // versions which are actually before the current `version` or the `version` itself are eligible - pr.versions.filter((v) => !includeVersions.includes(v)), - 'asc' - ); - - if (version !== earliestVersion) { - summary.skippedByVersion(pr, earliestVersion); - return false; - } - - return true; -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts b/packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts deleted file mode 100644 index 0f4f8abc7fd9c8..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/pr_api.ts +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { inspect } from 'util'; - -import Axios from 'axios'; -import gql from 'graphql-tag'; -import * as GraphqlPrinter from 'graphql/language/printer'; -import { DocumentNode } from 'graphql/language/ast'; -import makeTerminalLink from 'terminal-link'; -import { ToolingLog, isAxiosResponseError } from '@kbn/dev-utils'; - -import { Version } from './version'; -import { getFixReferences } from './get_fix_references'; -import { getNoteFromDescription } from './get_note_from_description'; - -const PrNodeFragment = gql` - fragment PrNode on PullRequest { - number - url - title - bodyText - bodyHTML - mergedAt - baseRefName - state - author { - login - ... on User { - name - } - } - labels(first: 100) { - nodes { - name - } - } - } -`; - -export interface PullRequest { - number: number; - url: string; - title: string; - targetBranch: string; - mergedAt: string; - state: string; - labels: string[]; - fixes: string[]; - user: { - name: string; - login: string; - }; - versions: Version[]; - terminalLink: string; - note?: string; -} - -export class PrApi { - constructor(private readonly log: ToolingLog, private readonly token: string) {} - - async getPr(number: number) { - const resp = await this.gqlRequest( - gql` - query($number: Int!) { - repository(owner: "elastic", name: "kibana") { - pullRequest(number: $number) { - ...PrNode - } - } - } - ${PrNodeFragment} - `, - { - number, - } - ); - - const node = resp.data?.repository?.pullRequest; - if (!node) { - throw new Error(`unexpected github response, unable to fetch PR: ${inspect(resp)}`); - } - - return this.parsePullRequestNode(node); - } - - /** - * Iterate all of the PRs which have the `version` label - */ - async *iterRelevantPullRequests(version: Version) { - let nextCursor: string | undefined; - let hasNextPage = true; - - while (hasNextPage) { - const resp = await this.gqlRequest( - gql` - query($cursor: String, $labels: [String!]) { - repository(owner: "elastic", name: "kibana") { - pullRequests(first: 100, after: $cursor, labels: $labels, states: MERGED) { - pageInfo { - hasNextPage - endCursor - } - nodes { - ...PrNode - } - } - } - } - ${PrNodeFragment} - `, - { - cursor: nextCursor, - labels: [version.label], - } - ); - - const pullRequests = resp.data?.repository?.pullRequests; - if (!pullRequests) { - throw new Error(`unexpected github response, unable to fetch PRs: ${inspect(resp)}`); - } - - hasNextPage = pullRequests.pageInfo?.hasNextPage; - nextCursor = pullRequests.pageInfo?.endCursor; - - if (hasNextPage === undefined || (hasNextPage && !nextCursor)) { - throw new Error( - `github response does not include valid pagination information: ${inspect(resp)}` - ); - } - - for (const node of pullRequests.nodes) { - yield this.parsePullRequestNode(node); - } - } - } - - /** - * Convert the Github API response into the structure used by this tool - * - * @param node A GraphQL response from Github using the PrNode fragment - */ - private parsePullRequestNode(node: any): PullRequest { - const terminalLink = makeTerminalLink(`#${node.number}`, node.url); - - const labels: string[] = node.labels.nodes.map((l: { name: string }) => l.name); - - return { - number: node.number, - url: node.url, - terminalLink, - title: node.title, - targetBranch: node.baseRefName, - state: node.state, - mergedAt: node.mergedAt, - labels, - fixes: getFixReferences(node.bodyText), - user: { - login: node.author?.login || 'deleted user', - name: node.author?.name, - }, - versions: labels - .map((l) => Version.fromLabel(l)) - .filter((v): v is Version => v instanceof Version), - note: - getNoteFromDescription(node.bodyHTML, 'release note') || - getNoteFromDescription(node.bodyHTML, 'dev docs'), - }; - } - - /** - * Send a single request to the Github v4 GraphQL API - */ - private async gqlRequest(query: DocumentNode, variables: Record = {}) { - let attempt = 0; - - while (true) { - attempt += 1; - - try { - const resp = await Axios.request({ - url: 'https://api.github.com/graphql', - method: 'POST', - headers: { - 'user-agent': '@kbn/release-notes', - authorization: `bearer ${this.token}`, - }, - data: { - query: GraphqlPrinter.print(query), - variables, - }, - }); - - return resp.data; - } catch (error) { - if (!isAxiosResponseError(error) || error.response.status < 500) { - // rethrow error unless it is a 500+ response from github - throw error; - } - - const { status, data } = error.response; - const resp = inspect(data); - - if (attempt === 5) { - throw new Error( - `${status} response from Github, attempted request ${attempt} times: [${resp}]` - ); - } - - const delay = attempt * 2000; - this.log.debug(`Github responded with ${status}, retrying in ${delay} ms: [${resp}]`); - await new Promise((resolve) => setTimeout(resolve, delay)); - continue; - } - } - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/lib/streams.ts b/packages/kbn-docs-utils/src/release_notes/lib/streams.ts deleted file mode 100644 index 6893bfd7f4f441..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/streams.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { promisify } from 'util'; -import { Readable, pipeline } from 'stream'; - -/** - * @types/node still doesn't have this method that was added - * in 10.17.0 https://nodejs.org/api/stream.html#stream_stream_readable_from_iterable_options - */ -export function streamFromIterable( - iter: Iterable | AsyncIterable -): Readable { - // @ts-ignore - return Readable.from(iter); -} - -export const asyncPipeline = promisify(pipeline); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts b/packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts deleted file mode 100644 index 81860160094dee..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/type_helpers.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export type ArrayItem = T extends ReadonlyArray ? X : never; diff --git a/packages/kbn-docs-utils/src/release_notes/lib/version.test.ts b/packages/kbn-docs-utils/src/release_notes/lib/version.test.ts deleted file mode 100644 index b23feb0929a2d0..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/version.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { Version } from './version'; - -it('parses version labels, returns null on failure', () => { - expect(Version.fromLabel('v1.0.2')).toMatchInlineSnapshot(` - Version { - "label": "v1.0.2", - "major": 1, - "minor": 0, - "patch": 2, - "tag": undefined, - "tagNum": undefined, - "tagOrder": Infinity, - } - `); - expect(Version.fromLabel('v1.0.0')).toMatchInlineSnapshot(` - Version { - "label": "v1.0.0", - "major": 1, - "minor": 0, - "patch": 0, - "tag": undefined, - "tagNum": undefined, - "tagOrder": Infinity, - } - `); - expect(Version.fromLabel('v9.0.2')).toMatchInlineSnapshot(` - Version { - "label": "v9.0.2", - "major": 9, - "minor": 0, - "patch": 2, - "tag": undefined, - "tagNum": undefined, - "tagOrder": Infinity, - } - `); - expect(Version.fromLabel('v9.0.2-alpha0')).toMatchInlineSnapshot(` - Version { - "label": "v9.0.2-alpha0", - "major": 9, - "minor": 0, - "patch": 2, - "tag": "alpha", - "tagNum": 0, - "tagOrder": 1, - } - `); - expect(Version.fromLabel('v9.0.2-beta1')).toMatchInlineSnapshot(` - Version { - "label": "v9.0.2-beta1", - "major": 9, - "minor": 0, - "patch": 2, - "tag": "beta", - "tagNum": 1, - "tagOrder": 2, - } - `); - expect(Version.fromLabel('v9.0')).toMatchInlineSnapshot(`undefined`); - expect(Version.fromLabel('some:area')).toMatchInlineSnapshot(`undefined`); -}); - -it('sorts versions in ascending order', () => { - const versions = [ - 'v1.7.3', - 'v1.7.0', - 'v1.5.0', - 'v2.7.0', - 'v7.0.0-beta2', - 'v7.0.0-alpha1', - 'v2.0.0', - 'v0.0.0', - 'v7.0.0-beta1', - 'v7.0.0', - ].map((l) => Version.fromLabel(l)!); - - const sorted = Version.sort(versions); - - expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(` - Array [ - "v0.0.0", - "v1.5.0", - "v1.7.0", - "v1.7.3", - "v2.0.0", - "v2.7.0", - "v7.0.0-alpha1", - "v7.0.0-beta1", - "v7.0.0-beta2", - "v7.0.0", - ] - `); - - // ensure versions was not mutated - expect(sorted).not.toEqual(versions); -}); - -it('sorts versions in decending order', () => { - const versions = [ - 'v1.7.3', - 'v1.7.0', - 'v1.5.0', - 'v7.0.0-beta1', - 'v2.7.0', - 'v2.0.0', - 'v0.0.0', - 'v7.0.0', - ].map((l) => Version.fromLabel(l)!); - - const sorted = Version.sort(versions, 'desc'); - - expect(sorted.map((v) => v.label)).toMatchInlineSnapshot(` - Array [ - "v7.0.0", - "v7.0.0-beta1", - "v2.7.0", - "v2.0.0", - "v1.7.3", - "v1.7.0", - "v1.5.0", - "v0.0.0", - ] - `); - - // ensure versions was not mutated - expect(sorted).not.toEqual(versions); -}); diff --git a/packages/kbn-docs-utils/src/release_notes/lib/version.ts b/packages/kbn-docs-utils/src/release_notes/lib/version.ts deleted file mode 100644 index c59060c9902208..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/lib/version.ts +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const LABEL_RE = /^v(\d+)\.(\d+)\.(\d+)(?:-(alpha|beta)(\d+))?$/; - -const versionCache = new Map(); - -const multiCompare = (...diffs: number[]) => { - for (const diff of diffs) { - if (diff !== 0) { - return diff; - } - } - return 0; -}; - -export class Version { - static fromFlag(flag: string | string[] | boolean | undefined) { - if (typeof flag !== 'string') { - return; - } - - return Version.fromLabel(flag) || Version.fromLabel(`v${flag}`); - } - - static fromFlags(flag: string | string[] | boolean | undefined) { - const flags = Array.isArray(flag) ? flag : [flag]; - const versions: Version[] = []; - - for (const f of flags) { - const version = Version.fromFlag(f); - if (!version) { - return; - } - versions.push(version); - } - - return versions; - } - - static fromLabel(label: string) { - const match = label.match(LABEL_RE); - if (!match) { - return; - } - - const cached = versionCache.get(label); - if (cached) { - return cached; - } - - const [, major, minor, patch, tag, tagNum] = match; - const version = new Version( - parseInt(major, 10), - parseInt(minor, 10), - parseInt(patch, 10), - tag as 'alpha' | 'beta' | undefined, - tagNum ? parseInt(tagNum, 10) : undefined - ); - - versionCache.set(label, version); - return version; - } - - static sort(versions: Version[], dir: 'asc' | 'desc' = 'asc') { - const order = dir === 'asc' ? 1 : -1; - - return versions.slice().sort((a, b) => a.compare(b) * order); - } - - public readonly label = `v${this.major}.${this.minor}.${this.patch}${ - this.tag ? `-${this.tag}${this.tagNum}` : '' - }`; - private readonly tagOrder: number; - - constructor( - public readonly major: number, - public readonly minor: number, - public readonly patch: number, - public readonly tag: 'alpha' | 'beta' | undefined, - public readonly tagNum: number | undefined - ) { - switch (tag) { - case undefined: - this.tagOrder = Infinity; - break; - case 'alpha': - this.tagOrder = 1; - break; - case 'beta': - this.tagOrder = 2; - break; - default: - throw new Error('unexpected tag'); - } - } - - compare(other: Version) { - return multiCompare( - this.major - other.major, - this.minor - other.minor, - this.patch - other.patch, - this.tagOrder - other.tagOrder, - (this.tagNum ?? 0) - (other.tagNum ?? 0) - ); - } -} diff --git a/packages/kbn-docs-utils/src/release_notes/release_notes_config.ts b/packages/kbn-docs-utils/src/release_notes/release_notes_config.ts deleted file mode 100644 index a94dcd8766cbd9..00000000000000 --- a/packages/kbn-docs-utils/src/release_notes/release_notes_config.ts +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Exclude any PR from release notes that has a matching label. String - * labels must match exactly, for more complicated use a RegExp - */ -export const IGNORE_LABELS: Array = [ - 'Team:Docs', - ':KibanaApp/fix-it-week', - 'reverted', - /^test/, - 'non-issue', - 'jenkins', - 'build', - 'chore', - 'backport', - 'release_note:skip', - 'release_note:dev_docs', -]; - -/** - * Define areas that are used to categorize changes in the release notes - * based on the labels a PR has. the `labels` array can contain strings, which - * are matched exactly, or regular expressions. The first area, in definition - * order, which has a `label` which matches and label on a PR is the area - * assigned to that PR. - */ - -export interface Area { - title: string; - labels: Array; -} - -export const AREAS: Area[] = [ - { - title: 'Design', - labels: ['Team:Design', 'Project:Accessibility'], - }, - { - title: 'Logstash', - labels: ['App:Logstash', 'Feature:Logstash Pipelines'], - }, - { - title: 'Management', - labels: [ - 'Feature:license', - 'Feature:Console', - 'Feature:Search Profiler', - 'Feature:watcher', - 'Feature:Index Patterns', - 'Feature:Kibana Management', - 'Feature:Dev Tools', - 'Feature:Inspector', - 'Feature:Index Management', - 'Feature:Snapshot and Restore', - 'Team:Elasticsearch UI', - 'Feature:FieldFormatters', - 'Feature:CCR', - 'Feature:ILM', - 'Feature:Transforms', - ], - }, - { - title: 'Monitoring', - labels: ['Team:Monitoring', 'Feature:Telemetry', 'Feature:Stack Monitoring'], - }, - { - title: 'Operations', - labels: ['Team:Operations', 'Feature:License'], - }, - { - title: 'Kibana UI', - labels: ['Kibana UI', 'Team:Core UI', 'Feature:Header'], - }, - { - title: 'Platform', - labels: [ - 'Team:Platform', - 'Feature:Plugins', - 'Feature:New Platform', - 'Project:i18n', - 'Feature:ExpressionLanguage', - 'Feature:Saved Objects', - 'Team:Stack Services', - 'Feature:NP Migration', - 'Feature:Task Manager', - 'Team:Pulse', - ], - }, - { - title: 'Machine Learning', - labels: [ - ':ml', - 'Feature:Anomaly Detection', - 'Feature:Data Frames', - 'Feature:File Data Viz', - 'Feature:ml-results', - 'Feature:Data Frame Analytics', - ], - }, - { - title: 'Maps', - labels: ['Team:Geo'], - }, - { - title: 'QA', - labels: ['Team:QA'], - }, - { - title: 'Security', - labels: [ - 'Team:Security', - 'Feature:Security/Spaces', - 'Feature:users and roles', - 'Feature:Security/Authentication', - 'Feature:Security/Authorization', - 'Feature:Security/Feature Controls', - ], - }, - { - title: 'Canvas', - labels: ['Feature:Canvas'], - }, - { - title: 'Dashboard', - labels: ['Feature:Dashboard', 'Feature:Drilldowns'], - }, - { - title: 'Discover', - labels: ['Feature:Discover'], - }, - { - title: 'Kibana Home & Add Data', - labels: ['Feature:Add Data', 'Feature:Home'], - }, - { - title: 'Querying & Filtering', - labels: [ - 'Feature:Query Bar', - 'Feature:Courier', - 'Feature:Filters', - 'Feature:Timepicker', - 'Feature:Highlight', - 'Feature:KQL', - 'Feature:Rollups', - ], - }, - { - title: 'Reporting', - labels: ['Feature:Reporting', 'Team:Reporting Services'], - }, - { - title: 'Sharing', - labels: ['Feature:Embedding', 'Feature:SharingURLs'], - }, - { - title: 'Visualizations', - labels: [ - 'Feature:Timelion', - 'Feature:TSVB', - 'Feature:Coordinate Map', - 'Feature:Region Map', - 'Feature:Vega', - 'Feature:Gauge Vis', - 'Feature:Tagcloud', - 'Feature:Vis Loader', - 'Feature:Vislib', - 'Feature:Vis Editor', - 'Feature:Aggregations', - 'Feature:Input Control', - 'Feature:Visualizations', - 'Feature:Markdown', - 'Feature:Data Table', - 'Feature:Heatmap', - 'Feature:Pie Chart', - 'Feature:XYAxis', - 'Feature:Graph', - 'Feature:New Feature', - 'Feature:MetricVis', - ], - }, - { - title: 'SIEM', - labels: ['Team:SIEM'], - }, - { - title: 'Code', - labels: ['Team:Code'], - }, - { - title: 'Infrastructure', - labels: ['App:Infrastructure', 'Feature:Infra UI', 'Feature:Service Maps'], - }, - { - title: 'Logs', - labels: ['App:Logs', 'Feature:Logs UI'], - }, - { - title: 'Uptime', - labels: ['App:Uptime', 'Feature:Uptime', 'Team:uptime'], - }, - { - title: 'Beats Management', - labels: ['App:Beats', 'Feature:beats-cm', 'Team:Beats'], - }, - { - title: 'APM', - labels: ['Team:apm', /^apm[:\-]/], - }, - { - title: 'Lens', - labels: ['App:Lens', 'Feature:Lens'], - }, - { - title: 'Alerting', - labels: ['App:Alerting', 'Feature:Alerting', 'Team:Alerting Services', 'Feature:Actions'], - }, - { - title: 'Metrics', - labels: ['App:Metrics', 'Feature:Metrics UI', 'Team:logs-metrics-ui'], - }, - { - title: 'Data ingest', - labels: ['Ingest', 'Feature:Ingest Node Pipelines'], - }, -]; - -export const UNKNOWN_AREA: Area = { - title: 'Unknown', - labels: [], -}; - -/** - * Define the sections that will be assigned to PRs when generating the - * asciidoc formatted report. The order of the sections determines the - * order they will be rendered in the report - */ - -export interface AsciidocSection { - title: string; - labels: Array; - id: string; -} - -export const ASCIIDOC_SECTIONS: AsciidocSection[] = [ - { - id: 'enhancement', - title: 'Enhancements', - labels: ['release_note:enhancement'], - }, - { - id: 'bug', - title: 'Bug fixes', - labels: ['release_note:fix'], - }, - { - id: 'roadmap', - title: 'Roadmap', - labels: ['release_note:roadmap'], - }, - { - id: 'deprecation', - title: 'Deprecations', - labels: ['release_note:deprecation'], - }, - { - id: 'breaking', - title: 'Breaking Changes', - labels: ['release_note:breaking'], - }, -]; - -export const UNKNOWN_ASCIIDOC_SECTION: AsciidocSection = { - id: 'unknown', - title: 'Unknown', - labels: [], -}; diff --git a/scripts/release_notes.js b/scripts/release_notes.js deleted file mode 100644 index 7408ce322677cd..00000000000000 --- a/scripts/release_notes.js +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -require('../src/setup_node_env/no_transpilation'); -require('@kbn/docs-utils').runReleaseNotesCli(); diff --git a/yarn.lock b/yarn.lock index f8549853b47811..f40f351dfafb95 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4881,11 +4881,6 @@ dependencies: "@types/node" "*" -"@types/graphql@^0.13.2": - version "0.13.4" - resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.4.tgz#55ae9c29f0fd6b85ee536f5c72b4769d5c5e06b1" - integrity sha512-B4yel4ro2nTb3v0pYO8vO6SjgvFJSrwUY+IO6TUSLdOSB+gQFslylrhRCHxvXMIhxB71mv5PEE9dAX+24S8sew== - "@types/gulp-zip@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/gulp-zip/-/gulp-zip-4.0.1.tgz#96cd0b994219f9ae3bbbec7ec3baa043fba9d9ef" @@ -14705,18 +14700,6 @@ graphlib@^2.1.8: dependencies: lodash "^4.17.15" -graphql-tag@^2.10.3: - version "2.10.3" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03" - integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA== - -graphql@^0.13.2: - version "0.13.2" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.13.2.tgz#4c740ae3c222823e7004096f832e7b93b2108270" - integrity sha512-QZ5BL8ZO/B20VA8APauGBg3GyEgZ19eduvpLWoq5x7gMmWnHoy8rlQWPLmWgFvo1yNgjSEFMesmS4R6pPr7xog== - dependencies: - iterall "^1.2.1" - grid-index@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7" @@ -16770,11 +16753,6 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -iterall@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" - integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== - iterate-iterator@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.1.tgz#1693a768c1ddd79c969051459453f082fe82e9f6" From fad45022c16e0d33bb81b8e3e5e6d88fcb7747ab Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 21 Apr 2021 12:32:10 -0700 Subject: [PATCH 17/65] [npm] upgrade all jest related dependencies (#96367) Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 42 +- .../rendering/rendering_service.test.ts | 10 +- .../web_element_wrapper/custom_cheerio_api.ts | 62 +- .../List/__snapshots__/List.test.tsx.snap | 2611 +++++++---------- x-pack/plugins/lens/jest.config.js | 3 - .../plugins/monitoring/server/config.test.ts | 4 +- .../components/user_info/index.test.tsx | 29 +- .../page_objects/alert_details.ts | 4 +- .../page_objects/triggers_actions_ui_page.ts | 13 +- yarn.lock | 691 ++--- 10 files changed, 1485 insertions(+), 1984 deletions(-) diff --git a/package.json b/package.json index 0ed0d3bbc3b7f0..dc2eba88139bb1 100644 --- a/package.json +++ b/package.json @@ -435,7 +435,7 @@ "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", "@istanbuljs/schema": "^0.1.2", - "@jest/reporters": "^26.5.2", + "@jest/reporters": "^26.6.2", "@kbn/babel-code-parser": "link:packages/kbn-babel-code-parser", "@kbn/babel-preset": "link:bazel-bin/packages/kbn-babel-preset/npm_module", "@kbn/cli-dev-mode": "link:packages/kbn-cli-dev-mode", @@ -472,11 +472,11 @@ "@storybook/node-logger": "^6.1.20", "@storybook/react": "^6.1.20", "@storybook/theming": "^6.1.20", - "@testing-library/dom": "^7.24.2", - "@testing-library/jest-dom": "^5.11.4", - "@testing-library/react": "^11.0.4", - "@testing-library/react-hooks": "^3.4.1", - "@testing-library/user-event": "^12.1.6", + "@testing-library/dom": "^7.30.3", + "@testing-library/jest-dom": "^5.11.10", + "@testing-library/react": "^11.2.6", + "@testing-library/react-hooks": "^5.1.1", + "@testing-library/user-event": "^13.1.1", "@types/accept": "3.1.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", @@ -485,7 +485,7 @@ "@types/base64-js": "^1.2.5", "@types/bluebird": "^3.1.1", "@types/chance": "^1.0.0", - "@types/cheerio": "^0.22.10", + "@types/cheerio": "^0.22.28", "@types/chroma-js": "^1.4.2", "@types/chromedriver": "^81.0.0", "@types/classnames": "^2.2.9", @@ -505,7 +505,7 @@ "@types/delete-empty": "^2.0.0", "@types/ejs": "^3.0.6", "@types/elasticsearch": "^5.0.33", - "@types/enzyme": "^3.10.5", + "@types/enzyme": "^3.10.8", "@types/eslint": "^6.1.3", "@types/extract-zip": "^1.6.2", "@types/faker": "^5.1.5", @@ -533,9 +533,9 @@ "@types/http-proxy-agent": "^2.0.2", "@types/inquirer": "^7.3.1", "@types/intl-relativeformat": "^2.1.0", - "@types/jest": "^26.0.14", - "@types/jest-specific-snapshot": "^0.5.4", - "@types/jest-when": "^2.7.1", + "@types/jest": "^26.0.22", + "@types/jest-specific-snapshot": "^0.5.5", + "@types/jest-when": "^2.7.2", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", "@types/js-search": "^1.4.0", @@ -614,8 +614,8 @@ "@types/tar": "^4.0.3", "@types/tar-fs": "^1.16.1", "@types/tempy": "^0.2.0", - "@types/testing-library__jest-dom": "^5.9.3", - "@types/testing-library__react-hooks": "^3.4.0", + "@types/testing-library__jest-dom": "^5.9.5", + "@types/testing-library__react-hooks": "^4.0.0", "@types/tinycolor2": "^1.4.1", "@types/type-detect": "^4.0.1", "@types/use-resize-observer": "^6.0.0", @@ -678,9 +678,9 @@ "dpdm": "3.5.0", "ejs": "^3.1.6", "enzyme": "^3.11.0", - "enzyme-adapter-react-16": "^1.15.2", - "enzyme-adapter-utils": "^1.13.0", - "enzyme-to-json": "^3.4.4", + "enzyme-adapter-react-16": "^1.15.6", + "enzyme-adapter-utils": "^1.14.0", + "enzyme-to-json": "^3.6.1", "eslint": "^6.8.0", "eslint-config-prettier": "^6.15.0", "eslint-import-resolver-node": "0.3.2", @@ -691,7 +691,7 @@ "eslint-plugin-cypress": "^2.11.2", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-jest": "^24.0.2", + "eslint-plugin-jest": "^24.3.4", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-mocha": "^6.2.2", "eslint-plugin-no-unsanitized": "^3.0.2", @@ -727,18 +727,18 @@ "is-path-inside": "^3.0.2", "istanbul-instrumenter-loader": "^3.0.1", "jest": "^26.6.3", - "jest-canvas-mock": "^2.2.0", + "jest-canvas-mock": "^2.3.1", "jest-circus": "^26.6.3", "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", "jest-environment-jsdom": "^26.6.2", "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", - "jest-silent-reporter": "^0.2.1", + "jest-silent-reporter": "^0.5.0", "jest-snapshot": "^26.6.2", "jest-specific-snapshot": "2.0.0", - "jest-styled-components": "^7.0.2", - "jest-when": "^2.7.2", + "jest-styled-components": "^7.0.3", + "jest-when": "^3.2.1", "jimp": "^0.14.0", "jsdom": "13.1.0", "json5": "^1.0.1", diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 65df5cd6aa3121..bba0dc6fd8a67e 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -80,7 +80,7 @@ describe('RenderingService', () => { it('renders "core" page', async () => { const content = await render(createKibanaRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -90,7 +90,7 @@ describe('RenderingService', () => { const content = await render(createKibanaRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -99,7 +99,7 @@ describe('RenderingService', () => { uiSettings.getUserProvided.mockResolvedValue({ 'theme:darkMode': { userValue: true } }); const content = await render(createKibanaRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -109,7 +109,7 @@ describe('RenderingService', () => { includeUserSettings: false, }); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); @@ -117,7 +117,7 @@ describe('RenderingService', () => { it('renders "core" from legacy request', async () => { const content = await render(createRawRequest(), uiSettings); const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data')); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); expect(data).toMatchSnapshot(INJECTED_METADATA); }); diff --git a/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts b/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts index 301eb656ed6f6b..c01e07fd076247 100644 --- a/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts +++ b/test/functional/services/lib/web_element_wrapper/custom_cheerio_api.ts @@ -8,13 +8,13 @@ interface CheerioSelector { (selector: string): CustomCheerio; (selector: string, context: string): CustomCheerio; - (selector: string, context: CheerioElement): CustomCheerio; - (selector: string, context: CheerioElement[]): CustomCheerio; - (selector: string, context: Cheerio): CustomCheerio; + (selector: string, context: cheerio.Element): CustomCheerio; + (selector: string, context: cheerio.Element[]): CustomCheerio; + (selector: string, context: cheerio.Cheerio): CustomCheerio; (selector: string, context: string, root: string): CustomCheerio; - (selector: string, context: CheerioElement, root: string): CustomCheerio; - (selector: string, context: CheerioElement[], root: string): CustomCheerio; - (selector: string, context: Cheerio, root: string): CustomCheerio; + (selector: string, context: cheerio.Element, root: string): CustomCheerio; + (selector: string, context: cheerio.Element[], root: string): CustomCheerio; + (selector: string, context: cheerio.Cheerio, root: string): CustomCheerio; (selector: any): CustomCheerio; } @@ -24,13 +24,13 @@ export interface CustomCheerioStatic extends CheerioSelector { // JQuery http://api.jquery.com xml(): string; root(): CustomCheerio; - contains(container: CheerioElement, contained: CheerioElement): boolean; + contains(container: cheerio.Element, contained: cheerio.Element): boolean; parseHTML(data: string, context?: Document, keepScripts?: boolean): Document[]; - html(options?: CheerioOptionsInterface): string; - html(selector: string, options?: CheerioOptionsInterface): string; - html(element: CustomCheerio, options?: CheerioOptionsInterface): string; - html(element: CheerioElement, options?: CheerioOptionsInterface): string; + html(options?: cheerio.CheerioParserOptions): string; + html(selector: string, options?: cheerio.CheerioParserOptions): string; + html(element: CustomCheerio, options?: cheerio.CheerioParserOptions): string; + html(element: cheerio.Element, options?: cheerio.CheerioParserOptions): string; // // CUSTOM METHODS @@ -44,7 +44,7 @@ export interface CustomCheerio { // Cheerio https://github.com/cheeriojs/cheerio // JQuery http://api.jquery.com - [index: number]: CheerioElement; + [index: number]: cheerio.Element; length: number; // Attributes @@ -63,7 +63,7 @@ export interface CustomCheerio { removeAttr(name: string): CustomCheerio; has(selector: string): CustomCheerio; - has(element: CheerioElement): CustomCheerio; + has(element: cheerio.Element): CustomCheerio; hasClass(className: string): boolean; addClass(classNames: string): CustomCheerio; @@ -81,10 +81,10 @@ export interface CustomCheerio { ): CustomCheerio; is(selector: string): boolean; - is(element: CheerioElement): boolean; - is(element: CheerioElement[]): boolean; + is(element: cheerio.Element): boolean; + is(element: cheerio.Element[]): boolean; is(selection: CustomCheerio): boolean; - is(func: (index: number, element: CheerioElement) => boolean): boolean; + is(func: (index: number, element: cheerio.Element) => boolean): boolean; // Form serialize(): string; @@ -98,7 +98,7 @@ export interface CustomCheerio { parent(selector?: string): CustomCheerio; parents(selector?: string): CustomCheerio; parentsUntil(selector?: string, filter?: string): CustomCheerio; - parentsUntil(element: CheerioElement, filter?: string): CustomCheerio; + parentsUntil(element: cheerio.Element, filter?: string): CustomCheerio; parentsUntil(element: CustomCheerio, filter?: string): CustomCheerio; prop(name: string): any; @@ -112,7 +112,7 @@ export interface CustomCheerio { nextAll(selector: string): CustomCheerio; nextUntil(selector?: string, filter?: string): CustomCheerio; - nextUntil(element: CheerioElement, filter?: string): CustomCheerio; + nextUntil(element: cheerio.Element, filter?: string): CustomCheerio; nextUntil(element: CustomCheerio, filter?: string): CustomCheerio; prev(selector?: string): CustomCheerio; @@ -120,7 +120,7 @@ export interface CustomCheerio { prevAll(selector: string): CustomCheerio; prevUntil(selector?: string, filter?: string): CustomCheerio; - prevUntil(element: CheerioElement, filter?: string): CustomCheerio; + prevUntil(element: cheerio.Element, filter?: string): CustomCheerio; prevUntil(element: CustomCheerio, filter?: string): CustomCheerio; slice(start: number, end?: number): CustomCheerio; @@ -131,19 +131,19 @@ export interface CustomCheerio { contents(): CustomCheerio; - each(func: (index: number, element: CheerioElement) => any): CustomCheerio; - map(func: (index: number, element: CheerioElement) => any): CustomCheerio; + each(func: (index: number, element: cheerio.Element) => any): CustomCheerio; + map(func: (index: number, element: cheerio.Element) => any): CustomCheerio; filter(selector: string): CustomCheerio; filter(selection: CustomCheerio): CustomCheerio; - filter(element: CheerioElement): CustomCheerio; - filter(elements: CheerioElement[]): CustomCheerio; - filter(func: (index: number, element: CheerioElement) => boolean): CustomCheerio; + filter(element: cheerio.Element): CustomCheerio; + filter(elements: cheerio.Element[]): CustomCheerio; + filter(func: (index: number, element: cheerio.Element) => boolean): CustomCheerio; not(selector: string): CustomCheerio; not(selection: CustomCheerio): CustomCheerio; - not(element: CheerioElement): CustomCheerio; - not(func: (index: number, element: CheerioElement) => boolean): CustomCheerio; + not(element: cheerio.Element): CustomCheerio; + not(func: (index: number, element: cheerio.Element) => boolean): CustomCheerio; first(): CustomCheerio; last(): CustomCheerio; @@ -161,8 +161,8 @@ export interface CustomCheerio { add(selectorOrHtml: string): CustomCheerio; add(selector: string, context: Document): CustomCheerio; - add(element: CheerioElement): CustomCheerio; - add(elements: CheerioElement[]): CustomCheerio; + add(element: cheerio.Element): CustomCheerio; + add(elements: cheerio.Element[]): CustomCheerio; add(selection: CustomCheerio): CustomCheerio; addBack(): CustomCheerio; @@ -203,8 +203,8 @@ export interface CustomCheerio { remove(selector?: string): CustomCheerio; replaceWith(content: string): CustomCheerio; - replaceWith(content: CheerioElement): CustomCheerio; - replaceWith(content: CheerioElement[]): CustomCheerio; + replaceWith(content: cheerio.Element): CustomCheerio; + replaceWith(content: cheerio.Element[]): CustomCheerio; replaceWith(content: CustomCheerio): CustomCheerio; replaceWith(content: () => CustomCheerio): CustomCheerio; @@ -236,7 +236,7 @@ export interface CustomCheerio { // Not Documented - toArray(): CheerioElement[]; + toArray(): CustomCheerio[]; // // CUSTOM METHODS diff --git a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap index a3074bf66a0522..58d5287e084ca4 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/error_group_overview/List/__snapshots__/List.test.tsx.snap @@ -1,352 +1,278 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ErrorGroupOverview -> List should render empty state 1`] = ` - - Group ID - - - , - "render": [Function], - "sortable": false, - "width": "96px", - }, - Object { - "field": "type", - "name": "Type", - "render": [Function], - "sortable": false, - }, - Object { - "field": "message", - "name": "Error message and culprit", - "render": [Function], - "sortable": false, - "width": "50%", - }, - Object { - "align": "right", - "field": "handled", - "name": "", - "render": [Function], - "sortable": false, - }, - Object { - "dataType": "number", - "field": "occurrenceCount", - "name": "Occurrences", - "render": [Function], - "sortable": true, - }, - Object { - "align": "right", - "field": "latestOccurrenceAt", - "name": "Latest occurrence", - "render": [Function], - "sortable": true, - }, - ] - } - initialPageSize={25} - initialSortDirection="desc" - initialSortField="occurrenceCount" - items={Array []} - noItemsMessage="No errors were found" - sortItems={false} +
-
-
+
+
- +
-
-
-
- -
-
+ Sorting + + +
- +
- - - - + +
-
+ + + + - + - + - - - + - - - - - + + + + - - -
+
+
-
+ Group ID + - Group ID - - - + aria-label="Info" + className="eui-alignTop" + color="subdued" + data-euiicon-type="questionInCircle" + onBlur={[Function]} + onFocus={[Function]} + size="s" + tabIndex={0} + /> -
-
+ + +
-
- - Type - -
-
+ + +
-
- - Error message and culprit - -
-
+ + + +
-
- -
-
+ + + + - + + + Click to sort in ascending order + + + + -
+ + Click to sort in ascending order + + + +
+
-
- - No errors were found - -
-
- + No errors were found + + + +
- +
`; exports[`ErrorGroupOverview -> List should render with data 1`] = ` @@ -381,590 +307,524 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; } - - Group ID - - - , - "render": [Function], - "sortable": false, - "width": "96px", - }, - Object { - "field": "type", - "name": "Type", - "render": [Function], - "sortable": false, - }, - Object { - "field": "message", - "name": "Error message and culprit", - "render": [Function], - "sortable": false, - "width": "50%", - }, - Object { - "align": "right", - "field": "handled", - "name": "", - "render": [Function], - "sortable": false, - }, - Object { - "dataType": "number", - "field": "occurrenceCount", - "name": "Occurrences", - "render": [Function], - "sortable": true, - }, - Object { - "align": "right", - "field": "latestOccurrenceAt", - "name": "Latest occurrence", - "render": [Function], - "sortable": true, - }, - ] - } - initialPageSize={25} - initialSortDirection="desc" - initialSortField="occurrenceCount" - items={ - Array [ - Object { - "culprit": "elasticapm.contrib.django.client.capture", - "groupId": "a0ce2c8978ef92cdf2ff163ae28576ee", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:37.561Z", - "message": "About to blow up!", - "occurrenceCount": 75, - "type": "AssertionError", - }, - Object { - "culprit": "opbeans.views.oopsie", - "groupId": "f3ac95493913cc7a3cfec30a19d2120a", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:37.630Z", - "message": "AssertionError: ", - "occurrenceCount": 75, - "type": "AssertionError", - }, - Object { - "culprit": "opbeans.tasks.update_stats", - "groupId": "e90863d04b7a692435305f09bbe8c840", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:36.859Z", - "message": "AssertionError: Bad luck!", - "occurrenceCount": 24, - "type": "AssertionError", - }, - Object { - "culprit": "opbeans.views.customer", - "groupId": "8673d8bf7a032e387c101bafbab0d2bc", - "handled": true, - "latestOccurrenceAt": "2018-01-10T10:06:13.211Z", - "message": "Customer with ID 8517 not found", - "occurrenceCount": 15, - "type": "AssertionError", - }, - ] - } - noItemsMessage="No errors were found" - sortItems={false} +
-
-
+
+
- +
-
-
-
- -
-
+ Sorting + + +
- +
- - - - + +
-
+ + + + - + - + - + + - + + + + + - - - + + + + + + - + + + - + + + - + - - + - - - - + + + - + + + - + - - + - - - - + + + - + + + - + - - + - - - - + - + - - + - - - -
+
+
-
+ Group ID + - Group ID - - - + aria-label="Info" + className="eui-alignTop" + color="subdued" + data-euiicon-type="questionInCircle" + onBlur={[Function]} + onFocus={[Function]} + size="s" + tabIndex={0} + /> -
-
+ + +
-
- - Type - -
-
+ + +
-
- - Error message and culprit - -
-
+ + + +
+ +
+
+ + + Click to sort in ascending order + + + + - + + Click to sort in ascending order + + +
+
- - -
- + Type + + + +
+ Error message and culprit +
+
- Group ID - - + rel="noreferrer" + > + About to blow up! + -
-
- + - - - a0ce2 - - - + elasticapm.contrib.django.client.capture +
+
-
+ +
+
+
-
- Type -
-
+
+ 75 +
+
+
+ Latest occurrence +
+
+ - - - - AssertionError - - - -
-
+ +
+
-
- Error message and culprit -
-
+ +
+
+ - -
- - - - - About to blow up! - - - - -
- - -
- elasticapm.contrib.django.client.capture -
-
-
-
- -
-
+ + +
-
-
+
-
- Occurrences -
-
- 75 -
-
+ + +
+ Error message and culprit +
+
- Latest occurrence -
-
List should render with data 1`] = ` onMouseOut={[Function]} onMouseOver={[Function]} > - 1337 minutes ago (mocking 1515578797) + + AssertionError: + -
-
-
- Group ID - +
- - -
-
- - - - f3ac9 - - - + opbeans.views.oopsie +
+ -
+ +
+
+
-
- Type -
-
+
+ 75 +
+
+
+ Latest occurrence +
+
+ - - - - AssertionError - - - -
-
+ +
+
-
- Error message and culprit -
-
+ +
+
+ - -
- - - - - AssertionError: - - - - -
- - -
- opbeans.views.oopsie -
-
-
-
- -
-
+ + +
-
-
+
-
- Occurrences -
-
- 75 -
-
+ + +
+ Error message and culprit +
+
- Latest occurrence -
-
List should render with data 1`] = ` onMouseOut={[Function]} onMouseOver={[Function]} > - 1337 minutes ago (mocking 1515578797) + + AssertionError: Bad luck! + -
-
-
- Group ID - +
- - -
-
- - - - e9086 - - - + opbeans.tasks.update_stats +
+ -
+ +
+
+
-
- Type -
-
+
+ 24 +
+
+
+ Latest occurrence +
+
+ - - - - AssertionError - - - -
-
+ +
+
-
- Error message and culprit -
-
+ +
+
+ - -
- - - - - AssertionError: Bad luck! - - - - -
- - -
- opbeans.tasks.update_stats -
-
-
-
- -
-
+ + +
-
-
+
-
- Occurrences -
-
- 24 -
-
+ + +
+ Error message and culprit +
+
- Latest occurrence -
-
List should render with data 1`] = ` onMouseOut={[Function]} onMouseOver={[Function]} > - 1337 minutes ago (mocking 1515578796) + + Customer with ID 8517 not found + -
-
-
- Group ID - +
- - -
-
- - - - 8673d - - - + opbeans.views.customer +
+ -
+ -
- Type -
- -
+
+
+
-
- Error message and culprit -
-
- -
- - - - - Customer with ID 8517 not found - - - - -
- - -
- opbeans.views.customer -
-
-
-
-
-
-
+
-
-
+ +
-
- Occurrences -
-
- 15 -
-
+
-
- Latest occurrence -
-
- - 1337 minutes ago (mocking 1515578773) - -
-
- -
+ 1337 minutes ago (mocking 1515578773) + +
+ +
+
+
+
+
- -
-
-
+
-
- + + + + +
- +
`; diff --git a/x-pack/plugins/lens/jest.config.js b/x-pack/plugins/lens/jest.config.js index 9a3f12e1ead32b..615e540eaedce2 100644 --- a/x-pack/plugins/lens/jest.config.js +++ b/x-pack/plugins/lens/jest.config.js @@ -9,7 +9,4 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', roots: ['/x-pack/plugins/lens'], - - // TODO: migrate to "jest-environment-jsdom" https://github.com/elastic/kibana/issues/95202 - testEnvironment: 'jest-environment-jsdom-thirteen', }; diff --git a/x-pack/plugins/monitoring/server/config.test.ts b/x-pack/plugins/monitoring/server/config.test.ts index c285ff27c5a639..8ea37d04c146c6 100644 --- a/x-pack/plugins/monitoring/server/config.test.ts +++ b/x-pack/plugins/monitoring/server/config.test.ts @@ -19,7 +19,9 @@ const MOCKED_PATHS = [ beforeEach(() => { const spy = jest.spyOn(fs, 'readFileSync').mockImplementation(); - MOCKED_PATHS.forEach((file) => when(spy).calledWith(file).mockReturnValue(`contents-of-${file}`)); + MOCKED_PATHS.forEach((file) => + when(spy).calledWith(file, 'utf8').mockReturnValue(`contents-of-${file}`) + ); }); describe('config schema', () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx index 0441cee8d2cf71..08612e0b6d00d2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx @@ -33,22 +33,21 @@ describe('useUserInfo', () => { const { result, waitForNextUpdate } = renderHook(() => useUserInfo()); await waitForNextUpdate(); - expect(result).toEqual({ - current: { - canUserCRUD: null, - hasEncryptionKey: null, - hasIndexManage: null, - hasIndexMaintenance: null, - hasIndexWrite: null, - hasIndexUpdateDelete: null, - isAuthenticated: null, - isSignalIndexExists: null, - loading: true, - signalIndexName: null, - signalIndexMappingOutdated: null, - }, - error: undefined, + expect(result.all).toHaveLength(1); + expect(result.current).toEqual({ + canUserCRUD: null, + hasEncryptionKey: null, + hasIndexManage: null, + hasIndexMaintenance: null, + hasIndexWrite: null, + hasIndexUpdateDelete: null, + isAuthenticated: null, + isSignalIndexExists: null, + loading: true, + signalIndexName: null, + signalIndexMappingOutdated: null, }); + expect(result.error).toBeUndefined(); }); }); diff --git a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts index 98272294b330de..8740deaeddd6e9 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/alert_details.ts @@ -33,7 +33,7 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { const $ = await table.parseDomContent(); return $.findTestSubjects('alert-instance-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { return { instance: $(row) .findTestSubject('alertInstancesTableCell-instance') @@ -87,7 +87,7 @@ export function AlertDetailsPageProvider({ getService }: FtrProviderContext) { $.findTestSubjects('alert-instance-row') .toArray() .filter( - (row: CheerioElement) => + (row) => $(row) .findTestSubject('alertInstancesTableCell-instance') .find('.euiTableCellContent') diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index e5971ddba415f3..5fa442e289037e 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { CustomCheerioStatic } from 'test/functional/services/lib/web_element_wrapper/custom_cheerio_api'; +import { + CustomCheerio, + CustomCheerioStatic, +} from 'test/functional/services/lib/web_element_wrapper/custom_cheerio_api'; import { FtrProviderContext } from '../ftr_provider_context'; const ENTER_KEY = '\uE007'; @@ -16,7 +19,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const retry = getService('retry'); const testSubjects = getService('testSubjects'); - function getRowItemData(row: CheerioElement, $: CustomCheerioStatic) { + function getRowItemData(row: CustomCheerio, $: CustomCheerioStatic) { return { name: $(row).findTestSubject('alertsTableCell-name').find('.euiTableCellContent').text(), tagsText: $(row) @@ -79,7 +82,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const $ = await table.parseDomContent(); return $.findTestSubjects('connectors-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { return { name: $(row) .findTestSubject('connectorsTableCell-name') @@ -97,7 +100,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const $ = await table.parseDomContent(); return $.findTestSubjects('alert-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { return getRowItemData(row, $); }); }, @@ -106,7 +109,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) const $ = await table.parseDomContent(); return $.findTestSubjects('alert-row') .toArray() - .map((row: CheerioElement) => { + .map((row) => { const rowItem = getRowItemData(row, $); return { ...rowItem, diff --git a/yarn.lock b/yarn.lock index f40f351dfafb95..f6ce8d8093607f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1147,7 +1147,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== @@ -2071,7 +2071,7 @@ chalk "^2.0.1" slash "^2.0.0" -"@jest/console@^26.5.2", "@jest/console@^26.6.2": +"@jest/console@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== @@ -2127,15 +2127,6 @@ "@types/node" "*" jest-mock "^26.6.2" -"@jest/fake-timers@^24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-24.9.0.tgz#ba3e6bf0eecd09a636049896434d306636540c93" - integrity sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A== - dependencies: - "@jest/types" "^24.9.0" - jest-message-util "^24.9.0" - jest-mock "^24.9.0" - "@jest/fake-timers@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" @@ -2157,38 +2148,6 @@ "@jest/types" "^26.6.2" expect "^26.6.2" -"@jest/reporters@^26.5.2": - version "26.5.3" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.5.3.tgz#e810e9c2b670f33f1c09e9975749260ca12f1c17" - integrity sha512-X+vR0CpfMQzYcYmMFKNY9n4jklcb14Kffffp7+H/MqitWnb0440bW2L76NGWKAa+bnXhNoZr+lCVtdtPmfJVOQ== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.5.2" - "@jest/test-result" "^26.5.2" - "@jest/transform" "^26.5.2" - "@jest/types" "^26.5.2" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.5.2" - jest-resolve "^26.5.2" - jest-util "^26.5.2" - jest-worker "^26.5.0" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^6.0.1" - optionalDependencies: - node-notifier "^8.0.0" - "@jest/reporters@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" @@ -2248,7 +2207,7 @@ "@jest/types" "^24.9.0" "@types/istanbul-lib-coverage" "^2.0.0" -"@jest/test-result@^26.5.2", "@jest/test-result@^26.6.2": +"@jest/test-result@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== @@ -2269,7 +2228,7 @@ jest-runner "^26.6.3" jest-runtime "^26.6.3" -"@jest/transform@^26.0.0", "@jest/transform@^26.5.2", "@jest/transform@^26.6.2": +"@jest/transform@^26.0.0", "@jest/transform@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== @@ -2309,7 +2268,7 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.5.2", "@jest/types@^26.6.2": +"@jest/types@^26.6.2": version "26.6.2" resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== @@ -4292,23 +4251,24 @@ resolved "https://registry.yarnpkg.com/@testim/chrome-version/-/chrome-version-1.0.7.tgz#0cd915785ec4190f08a3a6acc9b61fc38fb5f1a9" integrity sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw== -"@testing-library/dom@^7.24.2": - version "7.24.2" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.24.2.tgz#6d2b7dd21efbd5358b98c2777fc47c252f3ae55e" - integrity sha512-ERxcZSoHx0EcN4HfshySEWmEf5Kkmgi+J7O79yCJ3xggzVlBJ2w/QjJUC+EBkJJ2OeSw48i3IoePN4w8JlVUIA== +"@testing-library/dom@^7.28.1", "@testing-library/dom@^7.30.3": + version "7.30.3" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.30.3.tgz#779ea9bbb92d63302461800a388a5a890ac22519" + integrity sha512-7JhIg2MW6WPwyikH2iL3o7z+FTVgSOd2jqCwTAHqK7Qal2gRRYiUQyURAxtbK9VXm/UTyG9bRihv8C5Tznr2zw== dependencies: "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.10.3" + "@babel/runtime" "^7.12.5" "@types/aria-query" "^4.2.0" aria-query "^4.2.2" chalk "^4.1.0" - dom-accessibility-api "^0.5.1" - pretty-format "^26.4.2" + dom-accessibility-api "^0.5.4" + lz-string "^1.4.4" + pretty-format "^26.6.2" -"@testing-library/jest-dom@^5.11.4": - version "5.11.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.4.tgz#f325c600db352afb92995c2576022b35621ddc99" - integrity sha512-6RRn3epuweBODDIv3dAlWjOEHQLpGJHB2i912VS3JQtsD22+ENInhdDNl4ZZQiViLlIfFinkSET/J736ytV9sw== +"@testing-library/jest-dom@^5.11.10": + version "5.11.10" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.11.10.tgz#1cd90715023e1627f5ed26ab3b38e6f22d77046c" + integrity sha512-FuKiq5xuk44Fqm0000Z9w0hjOdwZRNzgx7xGGxQYepWFZy+OYUMOT/wPI4nLYXCaVltNVpU1W/qmD88wLWDsqQ== dependencies: "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" @@ -4319,28 +4279,32 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react-hooks@^3.4.1": - version "3.4.1" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-3.4.1.tgz#1f8ccd21208086ec228d9743fe40b69d0efcd7e5" - integrity sha512-LbzvE7oKsVzuW1cxA/aOeNgeVvmHWG2p/WSzalIGyWuqZT3jVcNDT5KPEwy36sUYWde0Qsh32xqIUFXukeywXg== +"@testing-library/react-hooks@*", "@testing-library/react-hooks@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-5.1.1.tgz#1fbaae8a4e8a4a7f97b176c23e1e890c41bbbfa5" + integrity sha512-52D2XnpelFDefnWpy/V6z2qGNj8JLIvW5DjYtelMvFXdEyWiykSaI7IXHwFy4ICoqXJDmmwHAiFRiFboub/U5g== dependencies: - "@babel/runtime" "^7.5.4" - "@types/testing-library__react-hooks" "^3.3.0" + "@babel/runtime" "^7.12.5" + "@types/react" ">=16.9.0" + "@types/react-dom" ">=16.9.0" + "@types/react-test-renderer" ">=16.9.0" + filter-console "^0.1.1" + react-error-boundary "^3.1.0" -"@testing-library/react@^11.0.4": - version "11.0.4" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.0.4.tgz#c84082bfe1593d8fcd475d46baee024452f31dee" - integrity sha512-U0fZO2zxm7M0CB5h1+lh31lbAwMSmDMEMGpMT3BUPJwIjDEKYWOV4dx7lb3x2Ue0Pyt77gmz/VropuJnSz/Iew== +"@testing-library/react@^11.2.6": + version "11.2.6" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.6.tgz#586a23adc63615985d85be0c903f374dab19200b" + integrity sha512-TXMCg0jT8xmuU8BkKMtp8l7Z50Ykew5WNX8UoIKTaLFwKkP2+1YDhOLA2Ga3wY4x29jyntk7EWfum0kjlYiSjQ== dependencies: - "@babel/runtime" "^7.11.2" - "@testing-library/dom" "^7.24.2" + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" -"@testing-library/user-event@^12.1.6": - version "12.1.6" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-12.1.6.tgz#f550b138dfdc20387b89cbe3e9f3d969ab10c2bd" - integrity sha512-BdSe6cmzDEapTBH3s1NKbzu+GyX5bJKraKwVpM2vZF1+EEWxZr0EiA0z9bA5Nux8P+6nKMOZKsXQrj5q/kicfQ== +"@testing-library/user-event@^13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.1.1.tgz#1e011de944cf4d2a917cef6c3046c26389943e24" + integrity sha512-B4roX+0mpXKGj8ndd38YoIo3IV9pmTTWxr/2cOke5apTtrNabEUE0KMBccpcAcYlfPcr7uMu+dxeeC3HdXd9qQ== dependencies: - "@babel/runtime" "^7.10.2" + "@babel/runtime" "^7.12.5" "@ts-morph/common@~0.7.0": version "0.7.3" @@ -4621,10 +4585,12 @@ resolved "https://registry.yarnpkg.com/@types/chance/-/chance-1.0.1.tgz#c10703020369602c40dd9428cc6e1437027116df" integrity sha512-jtV6Bv/j+xk4gcXeLlESwNc/m/I/dIZA0xrt29g0uKcjyPob8iisj/5z0ARE+Ldfx4MxjNFNECG0z++J7zJgqg== -"@types/cheerio@*", "@types/cheerio@^0.22.10": - version "0.22.10" - resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.10.tgz#780d552467824be4a241b29510a7873a7432c4a6" - integrity sha512-fOM/Jhv51iyugY7KOBZz2ThfT1gwvsGCfWxpLpZDgkGjpEO4Le9cld07OdskikLjDUQJ43dzDaVRSFwQlpdqVg== +"@types/cheerio@*", "@types/cheerio@^0.22.22", "@types/cheerio@^0.22.28": + version "0.22.28" + resolved "https://registry.yarnpkg.com/@types/cheerio/-/cheerio-0.22.28.tgz#90808aabb44fec40fa2950f4c72351e3e4eb065b" + integrity sha512-ehUMGSW5IeDxJjbru4awKYMlKGmo1wSSGUVqXtYwlgmUM8X1a0PZttEIm6yEY7vHsY/hh6iPnklF213G0UColw== + dependencies: + "@types/node" "*" "@types/chroma-js@^1.4.2": version "1.4.2" @@ -4763,10 +4729,10 @@ resolved "https://registry.yarnpkg.com/@types/elasticsearch/-/elasticsearch-5.0.33.tgz#b0fd37dc674f498223b6d68c313bdfd71f4d812b" integrity sha512-n/g9pqJEpE4fyUE8VvHNGtl7E2Wv8TCroNwfgAeJKRV4ghDENahtrAo1KMsFNIejBD2gDAlEUa4CM4oEEd8p9Q== -"@types/enzyme@^3.10.5": - version "3.10.5" - resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.5.tgz#fe7eeba3550369eed20e7fb565bfb74eec44f1f0" - integrity sha512-R+phe509UuUYy9Tk0YlSbipRpfVtIzb/9BHn5pTEtjJTF5LXvUjrIQcZvNyANNEyFrd2YGs196PniNT1fgvOQA== +"@types/enzyme@^3.10.8": + version "3.10.8" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.8.tgz#ad7ac9d3af3de6fd0673773123fafbc63db50d42" + integrity sha512-vlOuzqsTHxog6PV79+tvOHFb6hq4QZKMq1lLD9MaWD1oec2lHTKndn76XOpSwCA0oFTaIbKVPrgM3k78Jjd16g== dependencies: "@types/cheerio" "*" "@types/react" "*" @@ -5063,27 +5029,27 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest-specific-snapshot@^0.5.3", "@types/jest-specific-snapshot@^0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@types/jest-specific-snapshot/-/jest-specific-snapshot-0.5.4.tgz#997364c39a59ddeff0ee790a19415e79dd061d1e" - integrity sha512-1qISn4fH8wkOOPFEx+uWRRjw6m/pP/It3OHLm8Ee1KQpO7Z9ZGYDtWPU5AgK05UXsNTAgOK+dPQvJKGdy9E/1g== +"@types/jest-specific-snapshot@^0.5.3", "@types/jest-specific-snapshot@^0.5.5": + version "0.5.5" + resolved "https://registry.yarnpkg.com/@types/jest-specific-snapshot/-/jest-specific-snapshot-0.5.5.tgz#47ce738870be99898ed6d7b08dbf0240c74ae553" + integrity sha512-AaPPw2tE8ewfjD6qGLkEd4DOfM6pPOK7ob/RSOe1Z8Oo70r9Jgo0SlWyfxslPAOvLfQukQtiVPm6DcnjSoZU5A== dependencies: "@types/jest" "*" -"@types/jest-when@^2.7.1": - version "2.7.1" - resolved "https://registry.yarnpkg.com/@types/jest-when/-/jest-when-2.7.1.tgz#0b04a33a48a17370c390e9830a975822b3ac5e32" - integrity sha512-PRrGzDkU859cdkFL2KwWN4fRLRDGIUkRNT0StbthhKmj+naU4wImpoJeMnhjprvSou4pKAzU0dKfdQvjceJVhg== +"@types/jest-when@^2.7.2": + version "2.7.2" + resolved "https://registry.yarnpkg.com/@types/jest-when/-/jest-when-2.7.2.tgz#619fbc5f623bcd0b29efde0e4993c7f0d50d026d" + integrity sha512-vOtj0cev6vO1VX7Jbfg/qvy+sfLI64STsHbKVkggK+1kd11rcMGzFpZKBxUvQfsm4JRULCBISu+qrfs7fYZFGg== dependencies: "@types/jest" "*" -"@types/jest@*", "@types/jest@^26.0.14": - version "26.0.14" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.14.tgz#078695f8f65cb55c5a98450d65083b2b73e5a3f3" - integrity sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg== +"@types/jest@*", "@types/jest@^26.0.22": + version "26.0.22" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.22.tgz#8308a1debdf1b807aa47be2838acdcd91e88fbe6" + integrity sha512-eeWwWjlqxvBxc4oQdkueW5OF/gtfSceKk4OnOAGlUSwS/liBRtZppbJuz1YkgbrbfGOoeBHun9fOvXnjNwrSOw== dependencies: - jest-diff "^25.2.1" - pretty-format "^25.2.1" + jest-diff "^26.0.0" + pretty-format "^26.0.0" "@types/jest@^25.1.1": version "25.2.3" @@ -5559,7 +5525,7 @@ dependencies: "@types/react" "*" -"@types/react-dom@^16.9.8": +"@types/react-dom@>=16.9.0", "@types/react-dom@^16.9.8": version "16.9.8" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== @@ -5634,7 +5600,7 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@*", "@types/react-test-renderer@^16.9.1": +"@types/react-test-renderer@>=16.9.0", "@types/react-test-renderer@^16.9.1": version "16.9.1" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4" integrity sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ== @@ -5663,7 +5629,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.9.36": +"@types/react@*", "@types/react@>=16.9.0", "@types/react@^16.9.36": version "16.9.36" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.36.tgz#ade589ff51e2a903e34ee4669e05dbfa0c1ce849" integrity sha512-mGgUb/Rk/vGx4NCvquRuSH0GHBQKb1OqpGS9cT9lFxlTLHZgkksgI60TuIxubmn7JuCb+sENHhQciqa0npm0AQ== @@ -5854,19 +5820,19 @@ resolved "https://registry.yarnpkg.com/@types/tempy/-/tempy-0.2.0.tgz#8b7a93f6912aef25cc0b8d8a80ff974151478685" integrity sha512-YaX74QljqR45Xu7dd22wMvzTS+ItUiSyDl9XJl6WTgYNE09r2TF+mV2FDjWRM5Sdzf9C9dXRTUdz9J5SoEYxXg== -"@types/testing-library__jest-dom@^5.9.1", "@types/testing-library__jest-dom@^5.9.3": - version "5.9.3" - resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.3.tgz#574039e210140a536c6ec891063289fb742a75eb" - integrity sha512-5YxiCFA2vk0cxq2LIxYgHBpFlnJvMH9bkUIVNin+1GXT+LZgVOgXBeEyyo2ZrGXMO/KWe1ZV3p7Kb6LJAvJasw== +"@types/testing-library__jest-dom@^5.9.1", "@types/testing-library__jest-dom@^5.9.5": + version "5.9.5" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.9.5.tgz#5bf25c91ad2d7b38f264b12275e5c92a66d849b0" + integrity sha512-ggn3ws+yRbOHog9GxnXiEZ/35Mow6YtPZpd7Z5mKDeZS/o7zx3yAle0ov/wjhVB5QT4N2Dt+GNoGCdqkBGCajQ== dependencies: "@types/jest" "*" -"@types/testing-library__react-hooks@^3.3.0", "@types/testing-library__react-hooks@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-3.4.0.tgz#be148b7fa7d19cd3349c4ef9d9534486bc582fcc" - integrity sha512-QYLZipqt1hpwYsBU63Ssa557v5wWbncqL36No59LI7W3nCMYKrLWTnYGn2griZ6v/3n5nKXNYkTeYpqPHY7Ukg== +"@types/testing-library__react-hooks@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/testing-library__react-hooks/-/testing-library__react-hooks-4.0.0.tgz#2612eabbbb762968985fc1aa35f979caaa78f118" + integrity sha512-UzZUXthQtVjDruR2YA+hqg9ux5AfmZ8Kaw+QDungax+T7wb/5NC4x7YOpIqRx7oY3KksGQ69bzNE/xwzb5NslQ== dependencies: - "@types/react-test-renderer" "*" + "@testing-library/react-hooks" "*" "@types/through@*": version "0.0.30" @@ -6539,21 +6505,20 @@ airbnb-js-shims@^2.2.1: string.prototype.padstart "^3.0.0" symbol.prototype.description "^1.0.0" -airbnb-prop-types@^2.15.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" - integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== +airbnb-prop-types@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.16.0.tgz#b96274cefa1abb14f623f804173ee97c13971dc2" + integrity sha512-7WHOFolP/6cS96PhKNrslCLMYAI8yB1Pp6u6XmxozQOiZbsI5ycglZr5cHhBFfuRcQQjzCMith5ZPZdYiJCxUg== dependencies: - array.prototype.find "^2.1.0" - function.prototype.name "^1.1.1" - has "^1.0.3" - is-regex "^1.0.4" - object-is "^1.0.1" + array.prototype.find "^2.1.1" + function.prototype.name "^1.1.2" + is-regex "^1.1.0" + object-is "^1.1.2" object.assign "^4.1.0" - object.entries "^1.1.0" + object.entries "^1.1.2" prop-types "^15.7.2" prop-types-exact "^1.2.0" - react-is "^16.9.0" + react-is "^16.13.1" ajv-errors@^1.0.0: version "1.0.0" @@ -7115,13 +7080,13 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.find@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.0.tgz#630f2eaf70a39e608ac3573e45cf8ccd0ede9ad7" - integrity sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg== +array.prototype.find@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.1.tgz#3baca26108ca7affb08db06bf0be6cb3115a969c" + integrity sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA== dependencies: define-properties "^1.1.3" - es-abstract "^1.13.0" + es-abstract "^1.17.4" array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: version "1.2.3" @@ -8826,6 +8791,14 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -9007,7 +8980,7 @@ chai@^4.1.2: pathval "^1.1.0" type-detect "^4.0.5" -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9564,17 +9537,12 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-convert@~0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" - integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= - color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0, color-name@~1.1.4: +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -11703,10 +11671,10 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-accessibility-api@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.2.tgz#ef3cdb5d3f0d599d8f9c8b18df2fb63c9793739d" - integrity sha512-k7hRNKAiPJXD2aBqfahSo4/01cTsKWXf+LqJgglnkN2Nz8TsxXKQBXHhKe0Ye9fEfHEZY49uSA5Sr3AqP/sWKA== +dom-accessibility-api@^0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" + integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== dom-converter@~0.2: version "0.2.0" @@ -12213,46 +12181,48 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== -enzyme-adapter-react-16@^1.15.2: - version "1.15.2" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" - integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== +enzyme-adapter-react-16@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.6.tgz#fd677a658d62661ac5afd7f7f541f141f8085901" + integrity sha512-yFlVJCXh8T+mcQo8M6my9sPgeGzj85HSHi6Apgf1Cvq/7EL/J9+1JoJmJsRxZgyTvPMAqOEpRSu/Ii/ZpyOk0g== dependencies: - enzyme-adapter-utils "^1.13.0" - enzyme-shallow-equal "^1.0.1" + enzyme-adapter-utils "^1.14.0" + enzyme-shallow-equal "^1.0.4" has "^1.0.3" - object.assign "^4.1.0" - object.values "^1.1.1" + object.assign "^4.1.2" + object.values "^1.1.2" prop-types "^15.7.2" - react-is "^16.12.0" + react-is "^16.13.1" react-test-renderer "^16.0.0-0" semver "^5.7.0" -enzyme-adapter-utils@^1.13.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" - integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== +enzyme-adapter-utils@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.14.0.tgz#afbb0485e8033aa50c744efb5f5711e64fbf1ad0" + integrity sha512-F/z/7SeLt+reKFcb7597IThpDp0bmzcH1E9Oabqv+o01cID2/YInlqHbFl7HzWBl4h3OdZYedtwNDOmSKkk0bg== dependencies: - airbnb-prop-types "^2.15.0" - function.prototype.name "^1.1.2" - object.assign "^4.1.0" - object.fromentries "^2.0.2" + airbnb-prop-types "^2.16.0" + function.prototype.name "^1.1.3" + has "^1.0.3" + object.assign "^4.1.2" + object.fromentries "^2.0.3" prop-types "^15.7.2" semver "^5.7.1" -enzyme-shallow-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" - integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== +enzyme-shallow-equal@^1.0.1, enzyme-shallow-equal@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.4.tgz#b9256cb25a5f430f9bfe073a84808c1d74fced2e" + integrity sha512-MttIwB8kKxypwHvRynuC3ahyNc+cFbR8mjVIltnmzQ0uKGqmsfO4bfBuLxb0beLNPhjblUEYvEbsg+VSygvF1Q== dependencies: has "^1.0.3" - object-is "^1.0.2" + object-is "^1.1.2" -enzyme-to-json@^3.4.4: - version "3.4.4" - resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.4.4.tgz#b30726c59091d273521b6568c859e8831e94d00e" - integrity sha512-50LELP/SCPJJGic5rAARvU7pgE3m1YaNj7JLM+Qkhl5t7PAs6fiyc8xzc50RnkKPFQCv0EeFVjEWdIFRGPWMsA== +enzyme-to-json@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.6.1.tgz#d60740950bc7ca6384dfe6fe405494ec5df996bc" + integrity sha512-15tXuONeq5ORoZjV/bUo2gbtZrN2IH+Z6DvL35QmZyKHgbY1ahn6wcnLd9Xv9OjiwbAXiiP8MRZwbZrCv1wYNg== dependencies: + "@types/cheerio" "^0.22.22" lodash "^4.17.15" react-is "^16.12.0" @@ -12324,57 +12294,27 @@ error-stack-parser@^2.0.4, error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.4, es-abstract@^1.17.5, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.9.0: - version "1.17.6" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" - integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== +es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4, es-abstract@^1.17.5, es-abstract@^1.18.0-next.2, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.9.0: + version "1.18.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" + integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== dependencies: + call-bind "^1.0.2" es-to-primitive "^1.2.1" function-bind "^1.1.1" + get-intrinsic "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.0" - is-regex "^1.1.0" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.17.2: - version "1.17.7" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.7.tgz#a4de61b2f66989fc7421676c1cb9787573ace54c" - integrity sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-regex "^1.1.1" - object-inspect "^1.8.0" - object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" - -es-abstract@^1.18.0-next.0: - version "1.18.0-next.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.1.tgz#6e3a0a4bda717e5023ab3b8e90bec36108d22c68" - integrity sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.2.2" - is-negative-zero "^2.0.0" - is-regex "^1.1.1" - object-inspect "^1.8.0" + has-symbols "^1.0.2" + is-callable "^1.2.3" + is-negative-zero "^2.0.1" + is-regex "^1.1.2" + is-string "^1.0.5" + object-inspect "^1.9.0" object-keys "^1.1.1" - object.assign "^4.1.1" - string.prototype.trimend "^1.0.1" - string.prototype.trimstart "^1.0.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.4" + string.prototype.trimstart "^1.0.4" + unbox-primitive "^1.0.0" es-array-method-boxes-properly@^1.0.0: version "1.0.0" @@ -12697,10 +12637,10 @@ eslint-plugin-import@^2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jest@^24.0.2: - version "24.0.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.0.2.tgz#4bf0fcdc86289d702a7dacb430b4363482af773b" - integrity sha512-DSBLNpkKDOpUJQkTGSs5sVJWsu0nDyQ2rYxkr0Eh7nrkc5bMUr/dlDbtTj3l8y6UaCVsem6rryF1OZrKnz1S5g== +eslint-plugin-jest@^24.3.4: + version "24.3.4" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-24.3.4.tgz#6d90c3554de0302e879603dd6405474c98849f19" + integrity sha512-3n5oY1+fictanuFkTWPwSlehugBTAgwLnYLFsCllzE3Pl1BwywHl5fL0HFxmMjoQY8xhUDk8uAWc3S4JOHGh3A== dependencies: "@typescript-eslint/experimental-utils" "^4.0.1" @@ -13547,6 +13487,11 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +filter-console@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/filter-console/-/filter-console-0.1.1.tgz#6242be28982bba7415bcc6db74a79f4a294fa67c" + integrity sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg== + finalhandler@1.1.2, finalhandler@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" @@ -14066,24 +14011,25 @@ function-bind@^1.0.2, function-bind@^1.1.1, function-bind@~1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function.prototype.name@^1.1.0, function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" - integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== +function.prototype.name@^1.1.0, function.prototype.name@^1.1.2, function.prototype.name@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.4.tgz#e4ea839b9d3672ae99d0efd9f38d9191c5eaac83" + integrity sha512-iqy1pIotY/RmhdFZygSSlW0wko2yxkSCKqsuv4pr8QESohpYyG/Z7B/XXvPRKTJS//960rgguE5mSRUsDdaJrQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - functions-have-names "^1.2.0" + es-abstract "^1.18.0-next.2" + functions-have-names "^1.2.2" functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.0.tgz#83da7583e4ea0c9ac5ff530f73394b033e0bf77d" - integrity sha512-zKXyzksTeaCSw5wIX79iCA40YAa6CJMJgNg9wdkU/ERBrIdPSimPICYiLp65lRbSBqtiHql/HZfS2DyI/AH6tQ== +functions-have-names@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" + integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== fuse.js@^3.6.1: version "3.6.1" @@ -14165,6 +14111,15 @@ get-func-name@^2.0.0: resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" + integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" @@ -14946,6 +14901,11 @@ has-ansi@^3.0.0: dependencies: ansi-regex "^3.0.0" +has-bigints@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" + integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== + has-color@~0.1.0: version "0.1.7" resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" @@ -14973,10 +14933,10 @@ has-glob@^1.0.0: dependencies: is-glob "^3.0.0" -has-symbols@^1.0.0, has-symbols@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" + integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== has-unicode@^2.0.0: version "2.0.1" @@ -16033,14 +15993,14 @@ is-arrayish@^0.2.1: integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= is-arrayish@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.1.tgz#c2dfc386abaa0c3e33c48db3fe87059e69065efd" - integrity sha1-wt/DhquqDD4zxI2z/ocFnmkGXv0= + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== -is-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4" - integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g== +is-bigint@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" + integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== is-binary-path@^1.0.0: version "1.0.1" @@ -16056,10 +16016,12 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.0.0, is-boolean-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" - integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== +is-boolean-object@^1.0.1, is-boolean-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" + integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + dependencies: + call-bind "^1.0.0" is-buffer@^1.0.2, is-buffer@^1.1.0, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1: version "1.1.6" @@ -16071,15 +16033,10 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" - integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== - -is-callable@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.2.tgz#c7c6715cd22d4ddb48d3e19970223aceabb080d9" - integrity sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA== +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" + integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-ci@^2.0.0: version "2.0.0" @@ -16297,10 +16254,10 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= -is-negative-zero@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" - integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-nil@^1.0.0: version "1.0.1" @@ -16317,7 +16274,7 @@ is-npm@^4.0.0: resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-4.0.0.tgz#c90dd8380696df87a7a6d823c20d0b12bbe3c84d" integrity sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig== -is-number-object@^1.0.3, is-number-object@^1.0.4: +is-number-object@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== @@ -16431,11 +16388,12 @@ is-redirect@^1.0.0: resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ= -is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" - integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== +is-regex@^1.0.4, is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.1, is-regex@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" + integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== dependencies: + call-bind "^1.0.2" has-symbols "^1.0.1" is-regexp@^2.0.0: @@ -16781,13 +16739,13 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" -jest-canvas-mock@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz#45fbc58589c6ce9df50dc90bd8adce747cbdada7" - integrity sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw== +jest-canvas-mock@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.3.1.tgz#9535d14bc18ccf1493be36ac37dd349928387826" + integrity sha512-5FnSZPrX3Q2ZfsbYNE3wqKR3+XorN8qFzDzB5o0golWgt6EOX1+emBnpOc9IAQ+NXFj8Nzm3h7ZdE/9H0ylBcg== dependencies: cssfontparser "^1.2.1" - parse-color "^1.0.0" + moo-color "^1.0.2" jest-changed-files@^26.6.2: version "26.6.2" @@ -16888,7 +16846,7 @@ jest-diff@^25.2.1: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-diff@^26.6.2: +jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -16916,15 +16874,6 @@ jest-each@^26.6.2: jest-util "^26.6.2" pretty-format "^26.6.2" -jest-environment-jsdom-thirteen@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom-thirteen/-/jest-environment-jsdom-thirteen-1.0.1.tgz#113e3c8aed945dadbc826636fa21139c69567bb5" - integrity sha512-Zi7OuKF7HMLlBvomitd5eKp5Ykc4Wvw0d+i+cpbCaE+7kmvL24SO4ssDmKrT++aANXR4T8+pmoJIlav5gr2peQ== - dependencies: - jest-mock "^24.0.0" - jest-util "^24.0.0" - jsdom "^13.0.0" - jest-environment-jsdom@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" @@ -16965,7 +16914,7 @@ jest-get-type@^26.3.0: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^26.5.2, jest-haste-map@^26.6.2: +jest-haste-map@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== @@ -17067,13 +17016,6 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" -jest-mock@^24.0.0, jest-mock@^24.9.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-24.9.0.tgz#c22835541ee379b908673ad51087a2185c13f1c6" - integrity sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w== - dependencies: - "@jest/types" "^24.9.0" - jest-mock@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" @@ -17122,7 +17064,7 @@ jest-resolve@^24.9.0: jest-pnp-resolver "^1.2.1" realpath-native "^1.1.0" -jest-resolve@^26.5.2, jest-resolve@^26.6.2: +jest-resolve@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== @@ -17203,13 +17145,13 @@ jest-serializer@^26.6.2: "@types/node" "*" graceful-fs "^4.2.4" -jest-silent-reporter@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.2.1.tgz#554dd62b800989cdbcfba22bf30a1c0db6ad289c" - integrity sha512-nEO3oOFHtEXFjlRCbJOlvEWA7ZHyyyvMsU4WHuAhinYBOI4PiX1EIbsZfQZ/cxHcYliHBU9zY8bPxMPdBGksYw== +jest-silent-reporter@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jest-silent-reporter/-/jest-silent-reporter-0.5.0.tgz#5fd8ccd61665227e3bf19d908b7350719d06ff38" + integrity sha512-epdLt8Oj0a1AyRiR6F8zx/1SVT1Mi7VU3y4wB2uOBHs/ohIquC7v2eeja7UN54uRPyHInIKWdL+RdG228n5pJQ== dependencies: - chalk "^2.3.1" - jest-util "^24.0.0" + chalk "^4.0.0" + jest-util "^26.0.0" jest-snapshot@^24.1.0: version "24.9.0" @@ -17266,32 +17208,14 @@ jest-specific-snapshot@^4.0.0: dependencies: jest-snapshot "^26.3.0" -jest-styled-components@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.2.tgz#b7711871ea74a04491b12bad123fa35cc65a2a80" - integrity sha512-i1Qke8Jfgx0Why31q74ohVj9S2FmMLUE8bNRSoK4DgiurKkXG6HC4NPhcOLAz6VpVd9wXkPn81hOt4aAQedqsA== +jest-styled-components@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/jest-styled-components/-/jest-styled-components-7.0.3.tgz#cc0b031f910484e68f175568682f3969ff774b2c" + integrity sha512-jj9sWyshehUnB0P9WFUaq9Bkh6RKYO8aD8lf3gUrXRwg/MRddTFk7U9D9pC4IAI3v9fbz4vmrMxwaecTpG8NKA== dependencies: css "^2.2.4" -jest-util@^24.0.0: - version "24.9.0" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-24.9.0.tgz#7396814e48536d2e85a37de3e4c431d7cb140162" - integrity sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg== - dependencies: - "@jest/console" "^24.9.0" - "@jest/fake-timers" "^24.9.0" - "@jest/source-map" "^24.9.0" - "@jest/test-result" "^24.9.0" - "@jest/types" "^24.9.0" - callsites "^3.0.0" - chalk "^2.0.1" - graceful-fs "^4.1.15" - is-ci "^2.0.0" - mkdirp "^0.5.1" - slash "^2.0.0" - source-map "^0.6.0" - -jest-util@^26.5.2, jest-util@^26.6.2: +jest-util@^26.0.0, jest-util@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== @@ -17328,10 +17252,10 @@ jest-watcher@^26.6.2: jest-util "^26.6.2" string-length "^4.0.1" -jest-when@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/jest-when/-/jest-when-2.7.2.tgz#b7b4225e8882bd84a1cfd09216b2c63d22f892bd" - integrity sha512-GuVzimG0wW18A5JlYwhHrvuwmWRAQpsnilRVdJktvrZX5V0++al1f/iwITE7+Cud8Rbw/U2eka4tyy7kvxIWnw== +jest-when@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jest-when/-/jest-when-3.2.1.tgz#69b58ff641a399a0f2db5bfee6d8dd40cd065eb8" + integrity sha512-7OuFR5f2AdDPoRs/uk99dEWI+Isc2SFThugPjVUZgLLhWqeGr64rCFuuYcxVXQKwBmF3GG/MCS6zcKR9H86qiw== dependencies: bunyan "^1.8.12" expect "^24.8.0" @@ -17344,7 +17268,7 @@ jest-worker@^25.4.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^26.2.1, jest-worker@^26.3.0, jest-worker@^26.5.0, jest-worker@^26.6.2: +jest-worker@^26.2.1, jest-worker@^26.3.0, jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== @@ -17495,7 +17419,7 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsdom@13.1.0, jsdom@^13.0.0: +jsdom@13.1.0: version "13.1.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-13.1.0.tgz#fa7356f0cc8111d0f1077cb7800d06f22f1d66c7" integrity sha512-C2Kp0qNuopw0smXFaHeayvharqF3kkcNqlcIlSX71+3XrsOFwkEPLt/9f5JksMmaul2JZYIQuY+WTpqHpQQcLg== @@ -19591,6 +19515,13 @@ monocle-ts@^1.0.0: resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-1.7.1.tgz#03a615938aa90983a4fa29749969d30f72d80ba1" integrity sha512-X9OzpOyd/R83sYex8NYpJjUzi/MLQMvGNVfxDYiIvs+QMXMEUDwR61MQoARFN10Cqz5h/mbFSPnIQNUIGhYd2Q== +moo-color@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/moo-color/-/moo-color-1.0.2.tgz#837c40758d2d58763825d1359a84e330531eca64" + integrity sha512-5iXz5n9LWQzx/C2WesGFfpE6RLamzdHwsn3KpfzShwbfIqs7stnoEpaNErf/7+3mbxwZ4s8Foq7I0tPxw7BWHg== + dependencies: + color-name "^1.1.4" + moo@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" @@ -20397,15 +20328,10 @@ object-identity-map@^1.0.2: dependencies: object.entries "^1.1.0" -object-inspect@^1.6.0, object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-inspect@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" - integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.6.0, object-inspect@^1.7.0, object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== object-inspect@~1.6.0: version "1.6.0" @@ -20420,7 +20346,7 @@ object-is@^1.0.1, object-is@^1.0.2, object-is@^1.1.2: define-properties "^1.1.3" es-abstract "^1.17.5" -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== @@ -20439,23 +20365,13 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@^4.0.4, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.assign@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.1.tgz#303867a666cdd41936ecdedfb1f8f3e32a478cdd" - integrity sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA== +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== dependencies: + call-bind "^1.0.0" define-properties "^1.1.3" - es-abstract "^1.18.0-next.0" has-symbols "^1.0.1" object-keys "^1.1.1" @@ -20478,14 +20394,14 @@ object.entries@^1.0.4, object.entries@^1.1.0, object.entries@^1.1.1, object.entr es-abstract "^1.17.5" has "^1.0.3" -"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" - integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== +"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.2, object.fromentries@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.4.tgz#26e1ba5c4571c5c6f0890cef4473066456a120b8" + integrity sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" has "^1.0.3" object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0: @@ -20519,14 +20435,14 @@ object.reduce@^1.0.0: for-own "^1.0.0" make-iterator "^1.0.0" -object.values@^1.1.0, object.values@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== +object.values@^1.1.0, object.values@^1.1.1, object.values@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" + integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" + es-abstract "^1.18.0-next.2" has "^1.0.3" objectorarray@^1.0.4: @@ -21022,13 +20938,6 @@ parse-bmfont-xml@^1.1.4: xml-parse-from-string "^1.0.0" xml2js "^0.4.5" -parse-color@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" - integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= - dependencies: - color-convert "~0.5.0" - parse-data-uri@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/parse-data-uri/-/parse-data-uri-0.2.0.tgz#bf04d851dd5c87b0ab238e5d01ace494b604b4c9" @@ -22054,7 +21963,7 @@ pretty-format@^25.2.1, pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.4.0, pretty-format@^26.4.2, pretty-format@^26.6.2: +pretty-format@^26.0.0, pretty-format@^26.4.0, pretty-format@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== @@ -22802,6 +22711,13 @@ react-element-to-jsx-string@^14.3.1: "@base2/pretty-print-object" "1.0.0" is-plain-object "3.0.0" +react-error-boundary@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.1.tgz#932c5ca5cbab8ec4fe37fd7b415aa5c3a47597e7" + integrity sha512-W3xCd9zXnanqrTUeViceufD3mIW8Ut29BUD+S2f0eO2XCOU8b6UrJfY46RDGe5lxCJzfe4j0yvIfh0RbTZhKJw== + dependencies: + "@babel/runtime" "^7.12.5" + react-error-overlay@^6.0.9: version "6.0.9" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" @@ -22905,7 +22821,7 @@ react-intl@^2.8.0: intl-relativeformat "^2.1.0" invariant "^2.1.1" -react-is@^16.12.0, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: +react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -25852,21 +25768,21 @@ string.prototype.trim@~1.1.2: es-abstract "^1.5.0" function-bind "^1.0.2" -string.prototype.trimend@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== +string.prototype.trimend@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" + integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" -string.prototype.trimstart@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== +string.prototype.trimstart@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" + integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw== dependencies: + call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.17.5" string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1: version "1.1.1" @@ -27346,6 +27262,16 @@ umd@^3.0.0: resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== +unbox-primitive@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" + integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== + dependencies: + function-bind "^1.1.1" + has-bigints "^1.0.1" + has-symbols "^1.0.2" + which-boxed-primitive "^1.0.2" + unbzip2-stream@^1.3.3: version "1.4.3" resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" @@ -28069,15 +27995,6 @@ v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.1, v8-compile-cache@^2.2.0: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz#9471efa3ef9128d2f7c6a7ca39c4dd6b5055b132" integrity sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q== -v8-to-istanbul@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-6.0.1.tgz#7ef0e32faa10f841fe4c1b0f8de96ed067c0be1e" - integrity sha512-PzM1WlqquhBvsV+Gco6WSFeg1AGdD53ccMRkFeyHRE/KRZaVacPOmQYP3EeVgDBtKD2BJ8kgynBQ5OtKiHCH+w== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - v8-to-istanbul@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz#b4fe00e35649ef7785a9b7fcebcea05f37c332fc" @@ -28993,16 +28910,16 @@ whatwg-url@^8.0.0: tr46 "^2.0.2" webidl-conversions "^6.1.0" -which-boxed-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1" - integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ== +which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: - is-bigint "^1.0.0" - is-boolean-object "^1.0.0" - is-number-object "^1.0.3" - is-string "^1.0.4" - is-symbol "^1.0.2" + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" which-collection@^1.0.1: version "1.0.1" From 4fbb48daea8e29fe22bf57510f65443036489679 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Wed, 21 Apr 2021 20:50:05 +0100 Subject: [PATCH 18/65] chore(NA): chore(NA): moving @kbn/std into bazel (#97771) --- .../monorepo-packages.asciidoc | 1 + package.json | 2 +- packages/BUILD.bazel | 1 + packages/kbn-cli-dev-mode/package.json | 1 - packages/kbn-config/package.json | 3 +- packages/kbn-logging/package.json | 3 - packages/kbn-optimizer/package.json | 1 - packages/kbn-server-http-tools/package.json | 3 +- packages/kbn-std/BUILD.bazel | 85 +++++++++++++++++++ packages/kbn-std/package.json | 9 +- packages/kbn-std/tsconfig.json | 7 +- packages/kbn-utility-types/BUILD.bazel | 5 +- yarn.lock | 2 +- 13 files changed, 96 insertions(+), 27 deletions(-) create mode 100644 packages/kbn-std/BUILD.bazel diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 9564087dabefee..610d78bacccd4b 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -65,6 +65,7 @@ yarn kbn watch-bazel - @kbn/apm-utils - @kbn/babel-preset - @kbn/config-schema +- @kbn/std - @kbn/tinymath - @kbn/utility-types diff --git a/package.json b/package.json index dc2eba88139bb1..9981dc04cb3e47 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "@kbn/monaco": "link:packages/kbn-monaco", "@kbn/server-http-tools": "link:packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:packages/kbn-server-route-repository", - "@kbn/std": "link:packages/kbn-std", + "@kbn/std": "link:bazel-bin/packages/kbn-std/npm_module", "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath/npm_module", "@kbn/ui-framework": "link:packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index e1a85e926f049d..552eed64d418cd 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -7,6 +7,7 @@ filegroup( "//packages/kbn-apm-utils:build", "//packages/kbn-babel-preset:build", "//packages/kbn-config-schema:build", + "//packages/kbn-std:build", "//packages/kbn-tinymath:build", "//packages/kbn-utility-types:build", ], diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json index 1ea319ef3601c9..2ffa09d7e1604a 100644 --- a/packages/kbn-cli-dev-mode/package.json +++ b/packages/kbn-cli-dev-mode/package.json @@ -18,7 +18,6 @@ "@kbn/logging": "link:../kbn-logging", "@kbn/server-http-tools": "link:../kbn-server-http-tools", "@kbn/optimizer": "link:../kbn-optimizer", - "@kbn/std": "link:../kbn-std", "@kbn/dev-utils": "link:../kbn-dev-utils", "@kbn/utils": "link:../kbn-utils" } diff --git a/packages/kbn-config/package.json b/packages/kbn-config/package.json index 8093b6ac0d211d..9bf491e300871c 100644 --- a/packages/kbn-config/package.json +++ b/packages/kbn-config/package.json @@ -11,8 +11,7 @@ }, "dependencies": { "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set", - "@kbn/logging": "link:../kbn-logging", - "@kbn/std": "link:../kbn-std" + "@kbn/logging": "link:../kbn-logging" }, "devDependencies": { "@kbn/dev-utils": "link:../kbn-dev-utils", diff --git a/packages/kbn-logging/package.json b/packages/kbn-logging/package.json index c7db148c75a2a1..596eda1fe625ac 100644 --- a/packages/kbn-logging/package.json +++ b/packages/kbn-logging/package.json @@ -9,8 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/std": "link:../kbn-std" } } \ No newline at end of file diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 3c14d98755a32a..423bba0fd8c7a5 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -13,7 +13,6 @@ "dependencies": { "@kbn/config": "link:../kbn-config", "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/std": "link:../kbn-std", "@kbn/ui-shared-deps": "link:../kbn-ui-shared-deps" } } \ No newline at end of file diff --git a/packages/kbn-server-http-tools/package.json b/packages/kbn-server-http-tools/package.json index 24f8f8d67dfd70..5a1bb0d5b536a0 100644 --- a/packages/kbn-server-http-tools/package.json +++ b/packages/kbn-server-http-tools/package.json @@ -11,8 +11,7 @@ "kbn:watch": "yarn build --watch" }, "dependencies": { - "@kbn/crypto": "link:../kbn-crypto", - "@kbn/std": "link:../kbn-std" + "@kbn/crypto": "link:../kbn-crypto" }, "devDependencies": { "@kbn/utility-types": "link:../kbn-utility-types" diff --git a/packages/kbn-std/BUILD.bazel b/packages/kbn-std/BUILD.bazel new file mode 100644 index 00000000000000..82520be97df1f7 --- /dev/null +++ b/packages/kbn-std/BUILD.bazel @@ -0,0 +1,85 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-std" +PKG_REQUIRE_NAME = "@kbn/std" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = ["**/*.test.*"], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/kbn-utility-types", + "@npm//lodash", + "@npm//query-string", + "@npm//rxjs", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/lodash", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-std/package.json b/packages/kbn-std/package.json index b914356d992498..d88422ec1aa81b 100644 --- a/packages/kbn-std/package.json +++ b/packages/kbn-std/package.json @@ -4,12 +4,5 @@ "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build" - }, - "devDependencies": { - "@kbn/utility-types": "link:../kbn-utility-types" - } + "private": true } \ No newline at end of file diff --git a/packages/kbn-std/tsconfig.json b/packages/kbn-std/tsconfig.json index d2ed46dcad6f83..dec2d2df640861 100644 --- a/packages/kbn-std/tsconfig.json +++ b/packages/kbn-std/tsconfig.json @@ -1,12 +1,12 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, - "declarationDir": "./target", + "incremental": true, "outDir": "./target", "stripInternal": true, "declaration": true, "declarationMap": true, + "rootDir": "src", "sourceMap": true, "sourceRoot": "../../../../packages/kbn-std/src", "types": [ @@ -16,8 +16,5 @@ }, "include": [ "./src/**/*.ts" - ], - "exclude": [ - "**/__fixture__/**/*" ] } diff --git a/packages/kbn-utility-types/BUILD.bazel b/packages/kbn-utility-types/BUILD.bazel index e22ba38b24a489..1a02f94a88f4a4 100644 --- a/packages/kbn-utility-types/BUILD.bazel +++ b/packages/kbn-utility-types/BUILD.bazel @@ -57,15 +57,14 @@ ts_project( js_library( name = PKG_BASE_NAME, - srcs = [], + srcs = NPM_MODULE_EXTRA_FILES, deps = [":tsc"] + DEPS, - package_name = PKG_REQUIRE_NAME, + package_name = "@kbn/utility-types", visibility = ["//visibility:public"], ) pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/yarn.lock b/yarn.lock index f6ce8d8093607f..f4d76841749673 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2685,7 +2685,7 @@ version "0.0.0" uid "" -"@kbn/std@link:packages/kbn-std": +"@kbn/std@link:bazel-bin/packages/kbn-std/npm_module": version "0.0.0" uid "" From 45255425f492321ec178a85d65173ca5257b5e8a Mon Sep 17 00:00:00 2001 From: John Schulz Date: Wed, 21 Apr 2021 15:52:02 -0400 Subject: [PATCH 19/65] Add the more explicit & actionable text listed in ticket (#97857) ## Summary closes #92591 Create a new `HostedAgentPolicyRestrictionRelatedError` which ensures the additional text from #92591 is included in all the appropriate places, but only specified once. #### Some current examples * `Cannot update integrations of hosted agent policy ${id}` * `Cannot remove integrations of hosted agent policy ${id}` * `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id}` #### In this PR are now * `Cannot update integrations of hosted agent policy ${id} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` * `Cannot remove integrations of hosted agent policy ${id} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` * `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` ### 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 --- x-pack/plugins/fleet/server/errors/index.ts | 9 +++++++-- .../fleet/server/services/agent_policy.ts | 16 ++++++++-------- .../server/services/agents/reassign.test.ts | 8 ++++---- .../fleet/server/services/agents/reassign.ts | 6 +++--- .../server/services/agents/unenroll.test.ts | 6 +++--- .../fleet/server/services/agents/unenroll.ts | 4 ++-- .../fleet/server/services/agents/upgrade.ts | 10 +++++++--- .../fleet/server/services/package_policy.ts | 8 ++++++-- .../apis/agents/reassign.ts | 6 ++++-- .../apis/agents/unenroll.ts | 6 ++++-- .../fleet_api_integration/apis/agents/upgrade.ts | 6 +++++- 11 files changed, 53 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index 793a349f730f34..8d75726fbe2ded 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -43,8 +43,13 @@ export class PackageOperationNotSupportedError extends IngestManagerError {} export class FleetAdminUserInvalidError extends IngestManagerError {} export class ConcurrentInstallOperationError extends IngestManagerError {} export class AgentReassignmentError extends IngestManagerError {} -export class AgentUnenrollmentError extends IngestManagerError {} -export class AgentPolicyDeletionError extends IngestManagerError {} +export class HostedAgentPolicyRestrictionRelatedError extends IngestManagerError { + constructor(message = 'Cannot perform that action') { + super( + `${message} in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.` + ); + } +} export class FleetSetupError extends IngestManagerError {} export class GenerateServiceTokenError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index deb2da8dee5532..0d1c5c4dd3143b 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -46,11 +46,7 @@ import type { Installation, Output, } from '../../common'; -import { - AgentPolicyNameExistsError, - AgentPolicyDeletionError, - IngestManagerError, -} from '../errors'; +import { AgentPolicyNameExistsError, HostedAgentPolicyRestrictionRelatedError } from '../errors'; import { getPackageInfo } from './epm/packages'; import { getAgentsByKuery } from './agents'; @@ -476,7 +472,9 @@ class AgentPolicyService { } if (oldAgentPolicy.is_managed && !options?.force) { - throw new IngestManagerError(`Cannot update integrations of hosted agent policy ${id}`); + throw new HostedAgentPolicyRestrictionRelatedError( + `Cannot update integrations of hosted agent policy ${id}` + ); } return await this._update( @@ -507,7 +505,9 @@ class AgentPolicyService { } if (oldAgentPolicy.is_managed && !options?.force) { - throw new IngestManagerError(`Cannot remove integrations of hosted agent policy ${id}`); + throw new HostedAgentPolicyRestrictionRelatedError( + `Cannot remove integrations of hosted agent policy ${id}` + ); } return await this._update( @@ -550,7 +550,7 @@ class AgentPolicyService { } if (agentPolicy.is_managed) { - throw new AgentPolicyDeletionError(`Cannot delete hosted agent policy ${id}`); + throw new HostedAgentPolicyRestrictionRelatedError(`Cannot delete hosted agent policy ${id}`); } const { diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts index 4dfc29df8c3987..63085b7729c4bc 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import type { SavedObject } from 'kibana/server'; import type { AgentPolicy } from '../../types'; -import { AgentReassignmentError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { reassignAgent, reassignAgents } from './reassign'; @@ -54,7 +54,7 @@ describe('reassignAgent (singular)', () => { const { soClient, esClient } = createClientsMock(); await expect( reassignAgent(soClient, esClient, agentInRegularDoc._id, hostedAgentPolicySO.id) - ).rejects.toThrowError(AgentReassignmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); @@ -64,13 +64,13 @@ describe('reassignAgent (singular)', () => { const { soClient, esClient } = createClientsMock(); await expect( reassignAgent(soClient, esClient, agentInHostedDoc._id, regularAgentPolicySO.id) - ).rejects.toThrowError(AgentReassignmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); await expect( reassignAgent(soClient, esClient, agentInHostedDoc._id, hostedAgentPolicySO.id) - ).rejects.toThrowError(AgentReassignmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); }); diff --git a/x-pack/plugins/fleet/server/services/agents/reassign.ts b/x-pack/plugins/fleet/server/services/agents/reassign.ts index 4c95d19e2f13aa..e72f441afd0314 100644 --- a/x-pack/plugins/fleet/server/services/agents/reassign.ts +++ b/x-pack/plugins/fleet/server/services/agents/reassign.ts @@ -10,7 +10,7 @@ import Boom from '@hapi/boom'; import type { Agent, BulkActionResult } from '../../types'; import { agentPolicyService } from '../agent_policy'; -import { AgentReassignmentError } from '../../errors'; +import { AgentReassignmentError, HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { getAgentDocuments, @@ -56,14 +56,14 @@ export async function reassignAgentIsAllowed( ) { const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { - throw new AgentReassignmentError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot reassign an agent from hosted agent policy ${agentPolicy.id}` ); } const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId); if (newAgentPolicy?.is_managed) { - throw new AgentReassignmentError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot reassign an agent to hosted agent policy ${newAgentPolicy.id}` ); } diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index 24a3dea3bcb917..33f12dc52dc009 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -9,7 +9,7 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve import type { SavedObject } from 'kibana/server'; import type { AgentPolicy } from '../../types'; -import { AgentUnenrollmentError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { unenrollAgent, unenrollAgents } from './unenroll'; @@ -49,7 +49,7 @@ describe('unenrollAgent (singular)', () => { it('cannot unenroll from hosted agent policy by default', async () => { const { soClient, esClient } = createClientMock(); await expect(unenrollAgent(soClient, esClient, agentInHostedDoc._id)).rejects.toThrowError( - AgentUnenrollmentError + HostedAgentPolicyRestrictionRelatedError ); // does not call ES update expect(esClient.update).toBeCalledTimes(0); @@ -59,7 +59,7 @@ describe('unenrollAgent (singular)', () => { const { soClient, esClient } = createClientMock(); await expect( unenrollAgent(soClient, esClient, agentInHostedDoc._id, { revoke: true }) - ).rejects.toThrowError(AgentUnenrollmentError); + ).rejects.toThrowError(HostedAgentPolicyRestrictionRelatedError); // does not call ES update expect(esClient.update).toBeCalledTimes(0); }); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.ts index fc1f80fe7521bc..4d062e8bd5368c 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.ts @@ -9,7 +9,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s import type { Agent, BulkActionResult } from '../../types'; import * as APIKeyService from '../api_keys'; -import { AgentUnenrollmentError } from '../../errors'; +import { HostedAgentPolicyRestrictionRelatedError } from '../../errors'; import { createAgentAction, bulkCreateAgentActions } from './actions'; import type { GetAgentsOptions } from './crud'; @@ -28,7 +28,7 @@ async function unenrollAgentIsAllowed( ) { const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { - throw new AgentUnenrollmentError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot unenroll ${agentId} from a hosted agent policy ${agentPolicy.id}` ); } diff --git a/x-pack/plugins/fleet/server/services/agents/upgrade.ts b/x-pack/plugins/fleet/server/services/agents/upgrade.ts index 61e785828bf231..988d3c63223f49 100644 --- a/x-pack/plugins/fleet/server/services/agents/upgrade.ts +++ b/x-pack/plugins/fleet/server/services/agents/upgrade.ts @@ -10,7 +10,11 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/s import type { Agent, AgentAction, AgentActionSOAttributes, BulkActionResult } from '../../types'; import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../constants'; import { agentPolicyService } from '../../services'; -import { AgentReassignmentError, IngestManagerError } from '../../errors'; +import { + AgentReassignmentError, + HostedAgentPolicyRestrictionRelatedError, + IngestManagerError, +} from '../../errors'; import { isAgentUpgradeable } from '../../../common/services'; import { appContextService } from '../app_context'; @@ -46,7 +50,7 @@ export async function sendUpgradeAgentAction({ const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId); if (agentPolicy?.is_managed) { - throw new IngestManagerError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot upgrade agent ${agentId} in hosted agent policy ${agentPolicy.id}` ); } @@ -142,7 +146,7 @@ export async function sendUpgradeAgentsActions( } if (!options.force && isHostedAgent(agent)) { - throw new IngestManagerError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot upgrade agent in hosted agent policy ${agent.policy_id}` ); } diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 7c009299a3de31..234fa4df516884 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -30,7 +30,11 @@ import type { ListResult, } from '../../common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants'; -import { IngestManagerError, ingestErrorToResponseOptions } from '../errors'; +import { + HostedAgentPolicyRestrictionRelatedError, + IngestManagerError, + ingestErrorToResponseOptions, +} from '../errors'; import { NewPackagePolicySchema, UpdatePackagePolicySchema } from '../types'; import type { NewPackagePolicy, @@ -75,7 +79,7 @@ class PackagePolicyService { throw new Error('Agent policy not found'); } if (parentAgentPolicy.is_managed && !options?.force) { - throw new IngestManagerError( + throw new HostedAgentPolicyRestrictionRelatedError( `Cannot add integrations to hosted agent policy ${parentAgentPolicy.id}` ); } diff --git a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts index ad3c224bb92369..ac5aabc5c50846 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/reassign.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/reassign.ts @@ -157,7 +157,8 @@ export default function (providerContext: FtrProviderContext) { expect(body).to.eql({ agent2: { success: false, - error: 'Cannot reassign an agent from hosted agent policy policy1', + error: + 'Cannot reassign an agent from hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, INVALID_ID: { success: false, @@ -165,7 +166,8 @@ export default function (providerContext: FtrProviderContext) { }, agent3: { success: false, - error: 'Cannot reassign an agent from hosted agent policy policy1', + error: + 'Cannot reassign an agent from hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, }); diff --git a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts index f0e41d75136c32..df213e82bac7c4 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/unenroll.ts @@ -138,11 +138,13 @@ export default function (providerContext: FtrProviderContext) { expect(unenrolledBody).to.eql({ agent2: { success: false, - error: 'Cannot unenroll agent2 from a hosted agent policy policy1', + error: + 'Cannot unenroll agent2 from a hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, agent3: { success: false, - error: 'Cannot unenroll agent3 from a hosted agent policy policy1', + error: + 'Cannot unenroll agent3 from a hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', }, }); // but agents are still enrolled diff --git a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts index 142c360e9232a7..b692699182cac0 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/upgrade.ts @@ -593,7 +593,11 @@ export default function (providerContext: FtrProviderContext) { .expect(200); expect(body).to.eql({ - agent1: { success: false, error: 'Cannot upgrade agent in hosted agent policy policy1' }, + agent1: { + success: false, + error: + 'Cannot upgrade agent in hosted agent policy policy1 in Fleet because the agent policy is managed by an external orchestration solution, such as Elastic Cloud, Kubernetes, etc. Please make changes using your orchestration solution.', + }, agent2: { success: true }, }); From 688bb6d3e229952e8af30862557bc95cb9243a1b Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 13:03:59 -0700 Subject: [PATCH 20/65] remove jest-environment-jsdom-thirteen from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 9981dc04cb3e47..992433e17e6c15 100644 --- a/package.json +++ b/package.json @@ -732,7 +732,6 @@ "jest-cli": "^26.6.3", "jest-diff": "^26.6.2", "jest-environment-jsdom": "^26.6.2", - "jest-environment-jsdom-thirteen": "^1.0.1", "jest-raw-loader": "^1.0.1", "jest-silent-reporter": "^0.5.0", "jest-snapshot": "^26.6.2", From 1fb00900e1c2c229675d407ac810c1fffc797510 Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:13:15 -0400 Subject: [PATCH 21/65] [Security Solution][Detections] Fix flaky threshold API tests (#97768) * Explicitly refreshes signals index for threshold api tests to prevent flakiness * Unskip test suite --- .../tests/generating_signals.ts | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index 7d69e006666cde..4ae949d0cba863 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -20,6 +20,7 @@ import { createSignalsIndex, deleteAllAlerts, deleteSignalsIndex, + getOpenSignals, getRuleForSignalTesting, getSignalsByIds, getSignalsByRuleIds, @@ -39,9 +40,9 @@ export const ID = 'BhbXBmkBR346wHgn4PeZ'; export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); + const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/97584 - describe.skip('Generating signals from source indexes', () => { + describe('Generating signals from source indexes', () => { beforeEach(async () => { await createSignalsIndex(supertest); }); @@ -728,9 +729,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -753,9 +753,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -778,9 +777,8 @@ export default ({ getService }: FtrProviderContext) => { ], }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const signal = signalsOpen.hits.hits[0]; expect(signal._source.signal.threshold_result).eql({ @@ -814,9 +812,8 @@ export default ({ getService }: FtrProviderContext) => { value: 22, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(0); }); @@ -833,9 +830,8 @@ export default ({ getService }: FtrProviderContext) => { value: 21, }, }; - const { id } = await createRule(supertest, rule); - await waitForRuleSuccessOrStatus(supertest, id); - const signalsOpen = await getSignalsByRuleIds(supertest, [ruleId]); + const createdRule = await createRule(supertest, rule); + const signalsOpen = await getOpenSignals(supertest, es, createdRule); expect(signalsOpen.hits.hits.length).eql(1); const signal = signalsOpen.hits.hits[0]; expect(signal._source.signal.threshold_result).eql({ From e76987e13c8972086b5ad198374b1cb5278353b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:47:03 -0400 Subject: [PATCH 22/65] [APM] add comparison to Instances latency distribution (#97710) * adding comparion data to chart * fixing api test * addressing comments * refactoring --- ...ice_overview_instances_chart_and_table.tsx | 68 ++++++----- .../index.tsx | 2 +- .../index.tsx | 56 +++++++-- x-pack/plugins/apm/server/routes/services.ts | 44 +++++-- .../instances_main_statistics.ts | 109 ++++++++++++++++-- 5 files changed, 215 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index 8305b5a0dde3ba..8513e0835d373b 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -33,15 +33,16 @@ export interface MainStatsServiceInstanceItem { cpuUsage: number; memoryUsage: number; } +type ApiResponseMainStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; +type ApiResponseDetailedStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; const INITIAL_STATE_MAIN_STATS = { - mainStatsItems: [] as MainStatsServiceInstanceItem[], - mainStatsRequestId: undefined, - mainStatsItemCount: 0, + currentPeriodItems: [] as ApiResponseMainStats['currentPeriod'], + previousPeriodItems: [] as ApiResponseMainStats['previousPeriod'], + requestId: undefined, + currentPeriodItemsCount: 0, }; -type ApiResponseDetailedStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/detailed_statistics'>; - const INITIAL_STATE_DETAILED_STATISTICS: ApiResponseDetailedStats = { currentPeriod: {}, previousPeriod: {}, @@ -117,28 +118,17 @@ export function ServiceOverviewInstancesChartAndTable({ start, end, transactionType, + comparisonStart, + comparisonEnd, }, }, }).then((response) => { - const mainStatsItems = orderBy( - // need top-level sortable fields for the managed table - response.serviceInstances.map((item) => ({ - ...item, - latency: item.latency ?? 0, - throughput: item.throughput ?? 0, - errorRate: item.errorRate ?? 0, - cpuUsage: item.cpuUsage ?? 0, - memoryUsage: item.memoryUsage ?? 0, - })), - field, - direction - ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); - return { // Everytime the main statistics is refetched, updates the requestId making the detailed API to be refetched. - mainStatsRequestId: uuid(), - mainStatsItems, - mainStatsItemCount: response.serviceInstances.length, + requestId: uuid(), + currentPeriodItems: response.currentPeriod, + currentPeriodItemsCount: response.currentPeriod.length, + previousPeriodItems: response.previousPeriod, }; }); }, @@ -162,11 +152,26 @@ export function ServiceOverviewInstancesChartAndTable({ ); const { - mainStatsItems, - mainStatsRequestId, - mainStatsItemCount, + currentPeriodItems, + previousPeriodItems, + requestId, + currentPeriodItemsCount, } = mainStatsData; + const currentPeriodOrderedItems = orderBy( + // need top-level sortable fields for the managed table + currentPeriodItems.map((item) => ({ + ...item, + latency: item.latency ?? 0, + throughput: item.throughput ?? 0, + errorRate: item.errorRate ?? 0, + cpuUsage: item.cpuUsage ?? 0, + memoryUsage: item.memoryUsage ?? 0, + })), + field, + direction + ).slice(pageIndex * PAGE_SIZE, (pageIndex + 1) * PAGE_SIZE); + const { data: detailedStatsData = INITIAL_STATE_DETAILED_STATISTICS, status: detailedStatsStatus, @@ -177,7 +182,7 @@ export function ServiceOverviewInstancesChartAndTable({ !end || !transactionType || !latencyAggregationType || - !mainStatsItemCount + !currentPeriodItemsCount ) { return; } @@ -198,7 +203,7 @@ export function ServiceOverviewInstancesChartAndTable({ numBuckets: 20, transactionType, serviceNodeIds: JSON.stringify( - mainStatsItems.map((item) => item.serviceNodeName) + currentPeriodOrderedItems.map((item) => item.serviceNodeName) ), comparisonStart, comparisonEnd, @@ -208,7 +213,7 @@ export function ServiceOverviewInstancesChartAndTable({ }, // only fetches detailed statistics when requestId is invalidated by main statistics api call // eslint-disable-next-line react-hooks/exhaustive-deps - [mainStatsRequestId], + [requestId], { preservePreviousData: false } ); @@ -217,16 +222,17 @@ export function ServiceOverviewInstancesChartAndTable({

{i18n.translate('xpack.apm.serviceOverview.instancesTableTitle', { - defaultMessage: 'All instances', + defaultMessage: 'Instances', })}

diff --git a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx index 394d5b5410d414..ce4f36ced79038 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/instances_latency_distribution_chart/index.tsx @@ -30,33 +30,32 @@ import { } from '../../../../../common/utils/formatters'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; -import { MainStatsServiceInstanceItem } from '../../../app/service_overview/service_overview_instances_chart_and_table'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import * as urlHelpers from '../../Links/url_helpers'; import { ChartContainer } from '../chart_container'; import { getResponseTimeTickFormatter } from '../transaction_charts/helper'; import { CustomTooltip } from './custom_tooltip'; +type ApiResponseMainStats = APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics'>; + export interface InstancesLatencyDistributionChartProps { height: number; - items?: MainStatsServiceInstanceItem[]; + items?: ApiResponseMainStats['currentPeriod']; status: FETCH_STATUS; + comparisonItems?: ApiResponseMainStats['previousPeriod']; } export function InstancesLatencyDistributionChart({ height, items = [], status, + comparisonItems = [], }: InstancesLatencyDistributionChartProps) { const history = useHistory(); const hasData = items.length > 0; const theme = useTheme(); - const chartTheme = { - ...useChartTheme(), - bubbleSeriesStyle: { - point: { strokeWidth: 0, fill: theme.eui.euiColorVis1, radius: 4 }, - }, - }; + const chartTheme = useChartTheme(); const maxLatency = Math.max(...items.map((item) => item.latency ?? 0)); const latencyFormatter = getDurationFormatter(maxLatency); @@ -96,7 +95,13 @@ export function InstancesLatencyDistributionChart({ // there's just a single instance) they'll show along the origin. Make sure // the x-axis domain is [0, maxThroughput]. const maxThroughput = Math.max(...items.map((item) => item.throughput ?? 0)); - const xDomain = { min: 0, max: maxThroughput }; + const maxComparisonThroughput = Math.max( + ...comparisonItems.map((item) => item.throughput ?? 0) + ); + const xDomain = { + min: 0, + max: Math.max(maxThroughput, maxComparisonThroughput), + }; return ( @@ -118,7 +123,7 @@ export function InstancesLatencyDistributionChart({ xDomain={xDomain} /> item.latency]} yScaleType={ScaleType.Linear} + bubbleSeriesStyle={{ + point: { + strokeWidth: 0, + radius: 4, + fill: theme.eui.euiColorVis0, + }, + }} /> + + {!!comparisonItems.length && ( + item.throughput} + xScaleType={ScaleType.Linear} + yAccessors={[(item) => item.latency]} + yScaleType={ScaleType.Linear} + color={theme.eui.euiColorMediumShade} + bubbleSeriesStyle={{ + point: { + shape: 'square', + radius: 4, + fill: theme.eui.euiColorLightestShade, + stroke: theme.eui.euiColorMediumShade, + strokeWidth: 2, + }, + }} + /> + )} { describe('fetching java data', () => { @@ -72,11 +75,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns a service node item', () => { - expect(response.body.serviceInstances.length).to.be.greaterThan(0); + expect(response.body.currentPeriod.length).to.be.greaterThan(0); }); it('returns statistics for each service node', () => { - const item = response.body.serviceInstances[0]; + const item = response.body.currentPeriod[0]; expect(isFiniteNumber(item.cpuUsage)).to.be(true); expect(isFiniteNumber(item.memoryUsage)).to.be(true); @@ -86,7 +89,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right data', () => { - const items = sortBy(response.body.serviceInstances, 'serviceNodeName'); + const items = sortBy(response.body.currentPeriod, 'serviceNodeName'); const serviceNodeNames = items.map((item) => item.serviceNodeName); @@ -141,7 +144,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns statistics for each service node', () => { - const item = response.body.serviceInstances[0]; + const item = response.body.currentPeriod[0]; expect(isFiniteNumber(item.cpuUsage)).to.be(true); expect(isFiniteNumber(item.memoryUsage)).to.be(true); @@ -151,7 +154,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); it('returns the right data', () => { - const items = sortBy(response.body.serviceInstances, 'serviceNodeName'); + const items = sortBy(response.body.currentPeriod, 'serviceNodeName'); const serviceNodeNames = items.map((item) => item.serviceNodeName); @@ -181,4 +184,90 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); } ); + + registry.when( + 'Service overview instances main statistics when data is loaded with comparison', + { config: 'basic', archives: [archiveName] }, + () => { + describe('fetching java data', () => { + let response: { + body: APIReturnType<`GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`>; + }; + + beforeEach(async () => { + response = await apmApiSupertest({ + endpoint: `GET /api/apm/services/{serviceName}/service_overview_instances/main_statistics`, + params: { + path: { serviceName: 'opbeans-java' }, + query: { + latencyAggregationType: LatencyAggregationType.avg, + transactionType: 'request', + start: moment(end).subtract(15, 'minutes').toISOString(), + end, + comparisonStart: start, + comparisonEnd: moment(start).add(15, 'minutes').toISOString(), + }, + }, + }); + }); + + it('returns a service node item', () => { + expect(response.body.currentPeriod.length).to.be.greaterThan(0); + expect(response.body.previousPeriod.length).to.be.greaterThan(0); + }); + + it('returns statistics for each service node', () => { + const currentItem = response.body.currentPeriod[0]; + + expect(isFiniteNumber(currentItem.cpuUsage)).to.be(true); + expect(isFiniteNumber(currentItem.memoryUsage)).to.be(true); + expect(isFiniteNumber(currentItem.errorRate)).to.be(true); + expect(isFiniteNumber(currentItem.throughput)).to.be(true); + expect(isFiniteNumber(currentItem.latency)).to.be(true); + + const previousItem = response.body.previousPeriod[0]; + + expect(isFiniteNumber(previousItem.cpuUsage)).to.be(true); + expect(isFiniteNumber(previousItem.memoryUsage)).to.be(true); + expect(isFiniteNumber(previousItem.errorRate)).to.be(true); + expect(isFiniteNumber(previousItem.throughput)).to.be(true); + expect(isFiniteNumber(previousItem.latency)).to.be(true); + }); + + it('returns the right data', () => { + const items = sortBy(response.body.previousPeriod, 'serviceNodeName'); + + const serviceNodeNames = items.map((item) => item.serviceNodeName); + + expectSnapshot(items.length).toMatchInline(`1`); + + expectSnapshot(serviceNodeNames).toMatchInline(` + Array [ + "02950c4c5fbb0fda1cc98c47bf4024b473a8a17629db6530d95dcee68bd54c6c", + ] + `); + + const item = items[0]; + + const values = pick(item, [ + 'cpuUsage', + 'memoryUsage', + 'errorRate', + 'throughput', + 'latency', + ]); + + expectSnapshot(values).toMatchInline(` + Object { + "cpuUsage": 0.0120666666666667, + "errorRate": 0.111111111111111, + "latency": 379742.555555556, + "memoryUsage": 0.939879608154297, + "throughput": 3, + } + `); + }); + }); + } + ); } From 78c0c6eb8344d30fbf4f8d41e0cdc7149c765e75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Wed, 21 Apr 2021 16:51:04 -0400 Subject: [PATCH 23/65] [APM] Adding comparison data to the dependencies table (#97843) * adding comparison to dependencies api * fixing client * fixing client * changing client --- .../offset_previous_period_coordinate.test.ts | 0 .../offset_previous_period_coordinate.ts | 0 .../index.tsx | 134 ++++++++++++++++-- .../get_columns.tsx | 2 +- ...service_error_group_detailed_statistics.ts | 2 +- .../detailed_statistics.ts | 2 +- ...e_transaction_group_detailed_statistics.ts | 2 +- .../lib/transaction_groups/get_error_rate.ts | 2 +- .../transactions/get_latency_charts/index.ts | 2 +- x-pack/plugins/apm/server/routes/services.ts | 2 +- 10 files changed, 128 insertions(+), 20 deletions(-) rename x-pack/plugins/apm/{server => common}/utils/offset_previous_period_coordinate.test.ts (100%) rename x-pack/plugins/apm/{server => common}/utils/offset_previous_period_coordinate.ts (100%) diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts b/x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.test.ts similarity index 100% rename from x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.test.ts rename to x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.test.ts diff --git a/x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts b/x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.ts similarity index 100% rename from x-pack/plugins/apm/server/utils/offset_previous_period_coordinate.ts rename to x-pack/plugins/apm/common/utils/offset_previous_period_coordinate.ts diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 4ff42b151dc8e9..55da021e046877 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -13,7 +13,10 @@ import { EuiTitle, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { keyBy } from 'lodash'; import React from 'react'; +import { offsetPreviousPeriodCoordinates } from '../../../../../common/utils/offset_previous_period_coordinate'; +import { Coordinate } from '../../../../../typings/timeseries'; import { getNextEnvironmentUrlParam } from '../../../../../common/environment_filter_values'; import { asMillisecondDuration, @@ -32,6 +35,7 @@ import { ServiceMapLink } from '../../../shared/Links/apm/ServiceMapLink'; import { ServiceOverviewLink } from '../../../shared/Links/apm/service_overview_link'; import { SpanIcon } from '../../../shared/span_icon'; import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; +import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; @@ -39,12 +43,68 @@ interface Props { serviceName: string; } +type ServiceDependencyPeriods = ServiceDependencyItem & { + latency: { previousPeriodTimeseries?: Coordinate[] }; + throughput: { previousPeriodTimeseries?: Coordinate[] }; + errorRate: { previousPeriodTimeseries?: Coordinate[] }; + previousPeriodImpact?: number; +}; + +function mergeCurrentAndPreviousPeriods({ + currentPeriod = [], + previousPeriod = [], +}: { + currentPeriod?: ServiceDependencyItem[]; + previousPeriod?: ServiceDependencyItem[]; +}): ServiceDependencyPeriods[] { + const previousPeriodMap = keyBy(previousPeriod, 'name'); + + return currentPeriod.map((currentDependency) => { + const previousDependency = previousPeriodMap[currentDependency.name]; + if (!previousDependency) { + return currentDependency; + } + return { + ...currentDependency, + latency: { + ...currentDependency.latency, + previousPeriodTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentDependency.latency.timeseries, + previousPeriodTimeseries: previousDependency.latency?.timeseries, + }), + }, + throughput: { + ...currentDependency.throughput, + previousPeriodTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentDependency.throughput.timeseries, + previousPeriodTimeseries: previousDependency.throughput?.timeseries, + }), + }, + errorRate: { + ...currentDependency.errorRate, + previousPeriodTimeseries: offsetPreviousPeriodCoordinates({ + currentPeriodTimeseries: currentDependency.errorRate.timeseries, + previousPeriodTimeseries: previousDependency.errorRate?.timeseries, + }), + }, + previousPeriodImpact: previousDependency.impact, + }; + }); +} + export function ServiceOverviewDependenciesTable({ serviceName }: Props) { const { - urlParams: { start, end, environment }, + urlParams: { start, end, environment, comparisonEnabled, comparisonType }, } = useUrlParams(); - const columns: Array> = [ + const { comparisonStart, comparisonEnd } = getTimeRangeComparison({ + start, + end, + comparisonEnabled, + comparisonType, + }); + + const columns: Array> = [ { field: 'name', name: i18n.translate( @@ -102,6 +162,9 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { ); @@ -121,6 +184,11 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { compact color="euiColorVis0" series={throughput.timeseries} + comparisonSeries={ + comparisonEnabled + ? throughput.previousPeriodTimeseries + : undefined + } valueLabel={asTransactionRate(throughput.value)} /> ); @@ -142,6 +210,9 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { compact color="euiColorVis7" series={errorRate.timeseries} + comparisonSeries={ + comparisonEnabled ? errorRate.previousPeriodTimeseries : undefined + } valueLabel={asPercent(errorRate.value, 1)} /> ); @@ -157,13 +228,28 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { } ), width: px(unit * 5), - render: (_, { impact }) => { - return ; + render: (_, { impact, previousPeriodImpact }) => { + return ( + + + + + {comparisonEnabled && previousPeriodImpact !== undefined && ( + + + + )} + + ); }, sortable: true, }, ]; - + // Fetches current period dependencies const { data, status } = useFetcher( (callApmApi) => { if (!start || !end) { @@ -173,22 +259,41 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { return callApmApi({ endpoint: 'GET /api/apm/services/{serviceName}/dependencies', params: { - path: { - serviceName, - }, + path: { serviceName }, + query: { start, end, environment, numBuckets: 20 }, + }, + }); + }, + [start, end, serviceName, environment] + ); + + // Fetches previous period dependencies + const { data: previousPeriodData, status: previousPeriodStatus } = useFetcher( + (callApmApi) => { + if (!comparisonStart || !comparisonEnd) { + return; + } + + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/dependencies', + params: { + path: { serviceName }, query: { - start, - end, + start: comparisonStart, + end: comparisonEnd, environment, numBuckets: 20, }, }, }); }, - [start, end, serviceName, environment] + [comparisonStart, comparisonEnd, serviceName, environment] ); - const serviceDependencies = data?.serviceDependencies ?? []; + const serviceDependencies = mergeCurrentAndPreviousPeriods({ + currentPeriod: data?.serviceDependencies, + previousPeriod: previousPeriodData?.serviceDependencies, + }); // need top-level sortable fields for the managed table const items = serviceDependencies.map((item) => ({ @@ -238,7 +343,10 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { columns={columns} items={items} allowNeutralSort={false} - loading={status === FETCH_STATUS.LOADING} + loading={ + status === FETCH_STATUS.LOADING || + previousPeriodStatus === FETCH_STATUS.LOADING + } pagination={{ initialPageSize: 5, pageSizeOptions: [5], diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx index 02aad49ddfc9c4..9ac1c7d64d8b23 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/get_columns.tsx @@ -162,7 +162,7 @@ export function getColumns({ - {comparisonEnabled && previousImpact && ( + {comparisonEnabled && previousImpact !== undefined && ( diff --git a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts index dd41269f0bad6d..e45864de2fc1e5 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_error_groups/get_service_error_group_detailed_statistics.ts @@ -5,6 +5,7 @@ * 2.0. */ import { keyBy } from 'lodash'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { Coordinate } from '../../../../typings/timeseries'; import { ERROR_GROUP_ID, @@ -17,7 +18,6 @@ import { rangeQuery, kqlQuery, } from '../../../../server/utils/queries'; -import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts index 85414100a1563a..804ed91c54a51b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_instances/detailed_statistics.ts @@ -6,10 +6,10 @@ */ import { keyBy } from 'lodash'; +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { Coordinate } from '../../../../typings/timeseries'; import { LatencyAggregationType } from '../../../../common/latency_aggregation_types'; import { joinByKey } from '../../../../common/utils/join_by_key'; -import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; import { getServiceInstancesSystemMetricStatistics } from './get_service_instances_system_metric_statistics'; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts index 314d6c7bd14589..f14dba69bf404b 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_transaction_group_detailed_statistics.ts @@ -6,6 +6,7 @@ */ import { keyBy } from 'lodash'; +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { EVENT_OUTCOME, SERVICE_NAME, @@ -20,7 +21,6 @@ import { kqlQuery, } from '../../../server/utils/queries'; import { Coordinate } from '../../../typings/timeseries'; -import { offsetPreviousPeriodCoordinates } from '../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../utils/with_apm_span'; import { getDocumentTypeFilterForAggregatedTransactions, diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts index ec5dd1308cb7ec..71f803a03bf85f 100644 --- a/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts +++ b/x-pack/plugins/apm/server/lib/transaction_groups/get_error_rate.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { offsetPreviousPeriodCoordinates } from '../../../common/utils/offset_previous_period_coordinate'; import { Coordinate } from '../../../typings/timeseries'; import { @@ -31,7 +32,6 @@ import { getTransactionErrorRateTimeSeries, } from '../helpers/transaction_error_rate'; import { withApmSpan } from '../../utils/with_apm_span'; -import { offsetPreviousPeriodCoordinates } from '../../utils/offset_previous_period_coordinate'; export async function getErrorRate({ environment, diff --git a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts index 468585ddd23cb6..5a583605978282 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_latency_charts/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { offsetPreviousPeriodCoordinates } from '../../../../common/utils/offset_previous_period_coordinate'; import { ESFilter } from '../../../../../../../typings/elasticsearch'; import { PromiseReturnType } from '../../../../../observability/typings/common'; import { @@ -25,7 +26,6 @@ import { } from '../../../lib/helpers/aggregated_transactions'; import { getBucketSize } from '../../../lib/helpers/get_bucket_size'; import { Setup, SetupTimeRange } from '../../../lib/helpers/setup_request'; -import { offsetPreviousPeriodCoordinates } from '../../../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../../../utils/with_apm_span'; import { getLatencyAggregation, diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 06382c00177d40..54e59f2be7ae35 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -30,7 +30,6 @@ import { getServiceTransactionTypes } from '../lib/services/get_service_transact import { getThroughput } from '../lib/services/get_throughput'; import { getServiceProfilingStatistics } from '../lib/services/profiling/get_service_profiling_statistics'; import { getServiceProfilingTimeline } from '../lib/services/profiling/get_service_profiling_timeline'; -import { offsetPreviousPeriodCoordinates } from '../utils/offset_previous_period_coordinate'; import { withApmSpan } from '../utils/with_apm_span'; import { createApmServerRoute } from './create_apm_server_route'; import { createApmServerRouteRepository } from './create_apm_server_route_repository'; @@ -40,6 +39,7 @@ import { kueryRt, rangeRt, } from './default_api_types'; +import { offsetPreviousPeriodCoordinates } from '../../common/utils/offset_previous_period_coordinate'; const servicesRoute = createApmServerRoute({ endpoint: 'GET /api/apm/services', From ee7ba71b43cfd11e7f671f5d318d881de9767464 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Wed, 21 Apr 2021 17:13:48 -0400 Subject: [PATCH 24/65] [Fleet] Always create agent policy as active (#97874) --- x-pack/plugins/fleet/server/services/agent_policy.ts | 1 + .../fleet_api_integration/apis/agent_policy/agent_policy.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 0d1c5c4dd3143b..c0a2e80af4bf12 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -212,6 +212,7 @@ class AgentPolicyService { SAVED_OBJECT_TYPE, { ...agentPolicy, + status: 'active', is_managed: agentPolicy.is_managed ?? false, revision: 1, updated_at: new Date().toISOString(), diff --git a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts index 779c4d767c0008..33a4e404827812 100644 --- a/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts +++ b/x-pack/test/fleet_api_integration/apis/agent_policy/agent_policy.ts @@ -34,6 +34,7 @@ export default function ({ getService }: FtrProviderContext) { const { body } = await supertest.get(`/api/fleet/agent_policies/${createdPolicy.id}`); expect(body.item.is_managed).to.equal(false); + expect(body.item.status).to.be('active'); }); it('sets given is_managed value', async () => { @@ -140,6 +141,7 @@ export default function ({ getService }: FtrProviderContext) { expect(newPolicy).to.eql({ name: 'Copied policy', + status: 'active', description: 'Test', is_managed: false, namespace: 'default', @@ -242,6 +244,7 @@ export default function ({ getService }: FtrProviderContext) { const { id, updated_at, ...newPolicy } = updatedPolicy; expect(newPolicy).to.eql({ + status: 'active', name: 'Updated name', description: 'Updated description', namespace: 'default', From 0a0fd695d34328cbfaf1a3cd7a835420895ce3dd Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Wed, 21 Apr 2021 17:52:07 -0400 Subject: [PATCH 25/65] [ML][Transform] display script_exception compile error message in wizards (#97697) * ensure error rootCause script gets added to message * ensure script part of error is shown in DFA wizard * use isPopulatedObject to check for script field --- x-pack/plugins/ml/common/util/errors/process_errors.ts | 9 +++++++++ x-pack/plugins/ml/common/util/errors/types.ts | 1 + .../plugins/transform/server/routes/api/error_utils.ts | 7 ++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/common/util/errors/process_errors.ts b/x-pack/plugins/ml/common/util/errors/process_errors.ts index 821ba670e2ddbf..e5c6ed38161abe 100644 --- a/x-pack/plugins/ml/common/util/errors/process_errors.ts +++ b/x-pack/plugins/ml/common/util/errors/process_errors.ts @@ -14,6 +14,7 @@ import { isEsErrorBody, isMLResponseError, } from './types'; +import { isPopulatedObject } from '../object_utils'; export const extractErrorProperties = (error: ErrorType): MLErrorObject => { // extract properties of the error object from within the response error @@ -73,6 +74,14 @@ export const extractErrorProperties = (error: ErrorType): MLErrorObject => { error.body.attributes.body.error.caused_by?.caused_by?.reason || error.body.attributes.body.error.caused_by?.reason; } + if ( + Array.isArray(error.body.attributes.body.error.root_cause) && + typeof error.body.attributes.body.error.root_cause[0] === 'object' && + isPopulatedObject(error.body.attributes.body.error.root_cause[0], ['script']) + ) { + errObj.causedBy = error.body.attributes.body.error.root_cause[0].script; + errObj.message += `: '${error.body.attributes.body.error.root_cause[0].script}'`; + } return errObj; } else { return { diff --git a/x-pack/plugins/ml/common/util/errors/types.ts b/x-pack/plugins/ml/common/util/errors/types.ts index 39e9ed4e2575f8..3110f09e441cd2 100644 --- a/x-pack/plugins/ml/common/util/errors/types.ts +++ b/x-pack/plugins/ml/common/util/errors/types.ts @@ -12,6 +12,7 @@ export interface EsErrorRootCause { type: string; reason: string; caused_by?: EsErrorRootCause; + script?: string; } export interface EsErrorBody { diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index 6a10f170bc6e3a..7c4ac885766786 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -152,17 +152,22 @@ interface EsError { reason: string; line?: number; col?: number; + script?: string; } /** * Returns an error message based on the root cause */ -function extractErrorMessage({ type, reason, line, col }: EsError): string { +function extractErrorMessage({ type, reason, script, line, col }: EsError): string { let message = `[${type}] ${reason}`; if (line !== undefined && col !== undefined) { message += `, with line=${line} & col=${col}`; } + if (script !== undefined) { + message += ` '${script}'`; + } + return message; } From c59d0ddeeff179c8ae6632c9f2cc66b5c1c60f38 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Wed, 21 Apr 2021 15:37:44 -0700 Subject: [PATCH 26/65] [kbn-es] Increase ES heap (#97905) Signed-off-by: Tyler Smalley --- packages/kbn-es/src/cluster.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 2ecfa5c907a23d..236d5cf2521360 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -272,7 +272,7 @@ exports.Cluster = class Cluster { // especially because we currently run many instances of ES on the same machine during CI options.esEnvVars.ES_JAVA_OPTS = (options.esEnvVars.ES_JAVA_OPTS ? `${options.esEnvVars.ES_JAVA_OPTS} ` : '') + - '-Xms1g -Xmx1g'; + '-Xms2g -Xmx2g'; this._process = execa(ES_BIN, args, { cwd: installPath, From fc15032242d9de570acfcaaa0ab3968383756ad1 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 15:38:52 -0700 Subject: [PATCH 27/65] bump 7.x version in renovate config --- renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index 4dec49ceddf67d..86c8f29f2170c6 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -54,7 +54,7 @@ packageNames: ['@elastic/elasticsearch'], reviewers: ['team:kibana-operations'], matchBaseBranches: ['7.x'], - labels: ['release_note:skip', 'v7.13.0', 'Team:Operations', 'backport:skip'], + labels: ['release_note:skip', 'v7.14.0', 'Team:Operations', 'backport:skip'], enabled: true, }, { From cbd6bf5b4af8cd3483e58990e6bae46bde44d193 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 21 Apr 2021 17:39:04 -0500 Subject: [PATCH 28/65] [cli/keystore] Transpile sources in dev (#97501) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/cli_keystore/dev.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli_keystore/dev.js b/src/cli_keystore/dev.js index f7497add69ba0a..8cc08fa8e3232e 100644 --- a/src/cli_keystore/dev.js +++ b/src/cli_keystore/dev.js @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -require('../setup_node_env/no_transpilation'); +require('../setup_node_env'); require('./cli_keystore'); From 6077816cf7daeffb6bf6dccc2ff0f076e3ad7b73 Mon Sep 17 00:00:00 2001 From: spalger Date: Wed, 21 Apr 2021 15:39:23 -0700 Subject: [PATCH 29/65] bump version label in elastic/charts section of renovate config --- renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/renovate.json5 b/renovate.json5 index 86c8f29f2170c6..ea41175e1aaab5 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -38,7 +38,7 @@ packageNames: ['@elastic/charts'], reviewers: ['markov00', 'nickofthyme'], matchBaseBranches: ['master'], - labels: ['release_note:skip', 'v8.0.0', 'v7.13.0'], + labels: ['release_note:skip', 'v8.0.0', 'v7.14.0'], enabled: true, }, { From 9938f5c9a3fe49b782b1c9e43bb4e637ca2fd151 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Wed, 21 Apr 2021 18:05:18 -0500 Subject: [PATCH 30/65] [deb/rpm] Cleanup log and pid folders on package uninstall (#97884) This is a consistency check with elasticsearch, which removes the log and pid folders on package uninstall. Part of #86042 --- .../os_packages/package_scripts/post_remove.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh b/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh index 4e36ae11a29bb4..7e587e26ffa9e2 100644 --- a/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh +++ b/src/dev/build/tasks/os_packages/package_scripts/post_remove.sh @@ -34,12 +34,25 @@ case $1 in esac if [ "$REMOVE_DIRS" = "true" ]; then + + if [ -d "<%= logDir %>" ]; then + echo -n "Deleting log directory..." + rm -rf "<%= logDir %>" + echo " OK" + fi + if [ -d "<%= pluginsDir %>" ]; then echo -n "Deleting plugins directory..." rm -rf "<%= pluginsDir %>" echo " OK" fi + if [ -d "<%= pidDir %>" ]; then + echo -n "Deleting PID directory..." + rm -rf "<%= pidDir %>" + echo " OK" + fi + if [ -d "<%= configDir %>" ]; then rmdir --ignore-fail-on-non-empty "<%= configDir %>" fi From 6e64e15f95fb7099ad4db10e1611f3178f55acd6 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Wed, 21 Apr 2021 19:53:08 -0400 Subject: [PATCH 31/65] [App Search] Remove unknowns: 'allow' from enterprise_search routes (#97510) * Remove unknown allows from enterprisesearch routes It is a best practice to avoid `unknowns: allow` in request body validations. Usage of `unknowns: allow` in the App Search plugin has primarily been to avoid creating full validation schemas, as they can often be complex. Rather than have a half-baked validation that uses `unknowns: allow`, we have the option to skip parsing and validating a JSON body entirely, and simply pass it through "as-is" to the enterprise_search server for validation. * Move to helper * Swap comment block style * PR feedback * Update x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts Co-authored-by: Constance * Update x-pack/plugins/enterprise_search/server/lib/enterprise_search_request_handler.ts Co-authored-by: Constance * Better TS * Change to TS validations Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Constance --- .../lib/enterprise_search_request_handler.ts | 16 +++- .../server/lib/route_config_helpers.test.ts | 61 ++++++++++++++ .../server/lib/route_config_helpers.ts | 82 +++++++++++++++++++ .../routes/app_search/documents.test.ts | 22 ----- .../server/routes/app_search/documents.ts | 9 +- .../routes/app_search/result_settings.test.ts | 32 -------- .../routes/app_search/result_settings.ts | 19 ++--- .../routes/app_search/search_settings.test.ts | 29 ------- .../routes/app_search/search_settings.ts | 31 ++----- 9 files changed, 172 insertions(+), 129 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts 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 e5394fd580efc2..2fc0a13f2ff721 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 @@ -79,9 +79,7 @@ export class EnterpriseSearchRequestHandler { // Set up API options const { method } = request.route; const headers = { Authorization: request.headers.authorization as string, ...JSON_HEADER }; - const body = !this.isEmptyObj(request.body as object) - ? JSON.stringify(request.body) - : undefined; + const body = this.getBodyAsString(request.body as object | Buffer); // Call the Enterprise Search API const apiResponse = await fetch(url, { method, headers, body }); @@ -147,6 +145,18 @@ export class EnterpriseSearchRequestHandler { }; } + /** + * There are a number of different expected incoming bodies that we handle & pass on to Enterprise Search for ingestion: + * - Standard object data (should be JSON stringified) + * - Empty (should be passed as undefined and not as an empty obj) + * - Raw buffers (passed on as a string, occurs when using the `skipBodyValidation` lib helper) + */ + getBodyAsString(body: object | Buffer): string | undefined { + if (Buffer.isBuffer(body)) return body.toString(); + if (this.isEmptyObj(body)) return undefined; + return JSON.stringify(body); + } + /** * This path helper is similar to React Router's generatePath, but much simpler & * does not use regexes. It enables us to pass a static '/foo/:bar/baz' string to diff --git a/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts new file mode 100644 index 00000000000000..1843d5f942e377 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.test.ts @@ -0,0 +1,61 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +const mockBuffer = {}; +jest.mock('@kbn/config-schema', () => ({ + schema: { + buffer: () => mockBuffer, + object: () => ({}), + }, +})); + +const mockSchema = schema.object({}); + +import { skipBodyValidation } from './route_config_helpers'; + +describe('skipBodyValidation', () => { + it('adds "options.body.parse" and "validate.body" properties to a route config', () => { + expect( + skipBodyValidation({ + path: '/example/path', + validate: {}, + }) + ).toEqual({ + path: '/example/path', + validate: { + body: mockBuffer, + }, + options: { body: { parse: false } }, + }); + }); + + it('persists all other properties, e.g. "path" & other non-"body" properties on "options" & "validate"', () => { + expect( + skipBodyValidation({ + path: '/example/path', + validate: { + params: mockSchema, + }, + options: { + authRequired: true, + }, + }) + ).toEqual({ + path: '/example/path', + validate: { + params: mockSchema, + body: mockBuffer, + }, + options: { + authRequired: true, + body: { parse: false }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts new file mode 100644 index 00000000000000..ab5bbc994a1037 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/route_config_helpers.ts @@ -0,0 +1,82 @@ +/* + * 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 { + DestructiveRouteMethod, + RouteConfig, + RouteConfigOptions, + RouteMethod, + RouteValidatorFullConfig, +} from 'kibana/server'; + +import { schema } from '@kbn/config-schema'; + +type Config = RouteConfig; + +// We disallow options to set 'body' because we override them. +interface ConfigWithoutBodyOptions + extends RouteConfig { + validate: Omit, 'body'>; + options?: Omit, 'body'>; +} + +/** + * Kibana Enterprise Search Plugin API endpoints often times pass through the request + * body to the Enterprise Search API endpoints for validation. In those cases, we do not + * need to validate them in Kibana. + * + * The safe way to do that is to turn off body parsing entirely using `options.body.parse: false`. + * The will pass a String Buffer to the route handler. The proper way to validate this when validation + * is enabled to to use `body: schema.buffer()`. + * + * @see https://github.com/elastic/kibana/blob/master/docs/development/core/server/kibana-plugin-core-server.routeconfigoptionsbody.md + * @see https://github.com/elastic/kibana/blob/master/packages/kbn-config-schema/README.md#schemabuffer + * + * Example: + * router.put({ + * path: '/api/app_search/engines/{engineName}/example', + * validate: { + * params: schema.object({ + * engineName: schema.string(), + * }), + * body: schema.buffer(), + * }, + * options: { body: { parse: false } }, + * }, + * ... + * + * This helper applies that pattern, while maintaining existing options: + * + * router.put(skipBodyValidation({ + * path: '/api/app_search/engines/{engineName}/example', + * validate: { + * params: schema.object({ + * engineName: schema.string(), + * }), + * }, + * }, + * ... + */ +export const skipBodyValidation = ( + // DestructiveRouteMethod is the Kibana type for everything except 'get' and 'options'. + // Body configuration doesn't apply to those types so we disallow it with this helper. + config: ConfigWithoutBodyOptions +): Config => { + return { + ...config, + validate: { + ...config.validate, + body: schema.buffer(), + }, + options: { + ...(config.options || {}), + body: { + parse: false, + } as RouteConfigOptions['body'], + }, + }; +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts index af54d340ad150b..b584412a7a8f37 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.test.ts @@ -31,28 +31,6 @@ describe('documents routes', () => { path: '/as/engines/:engineName/documents/new', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { body: { documents: [{ foo: 'bar' }] } }; - mockRouter.shouldValidate(request); - }); - - it('missing documents', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - - it('wrong document type', () => { - const request = { body: { documents: ['test'] } }; - mockRouter.shouldThrow(request); - }); - - it('non-array documents type', () => { - const request = { body: { documents: { foo: 'bar' } } }; - mockRouter.shouldThrow(request); - }); - }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts index 78463fc8724ac7..929e9f7aef6903 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/documents.ts @@ -7,6 +7,8 @@ import { schema } from '@kbn/config-schema'; +import { skipBodyValidation } from '../../lib/route_config_helpers'; + import { RouteDependencies } from '../../plugin'; export function registerDocumentsRoutes({ @@ -14,17 +16,14 @@ export function registerDocumentsRoutes({ enterpriseSearchRequestHandler, }: RouteDependencies) { router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/documents', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - documents: schema.arrayOf(schema.object({}, { unknowns: 'allow' })), - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/documents/new', }) diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts index e38380d60c6e9c..eab4acd1ea4e15 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.test.ts @@ -71,22 +71,6 @@ describe('result settings routes', () => { path: '/as/engines/:engineName/result_settings', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { - body: { - result_fields: resultFields, - }, - }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); describe('POST /api/app_search/engines/{name}/sample_response_search', () => { @@ -115,21 +99,5 @@ describe('result settings routes', () => { path: '/as/engines/:engineName/sample_response_search', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { - body: { - query: 'test', - result_fields: resultFields, - }, - }; - mockRouter.shouldValidate(request); - }); - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts index b091ae7a539c29..9a81b8a9f5907c 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/result_settings.ts @@ -7,9 +7,9 @@ import { schema } from '@kbn/config-schema'; -import { RouteDependencies } from '../../plugin'; +import { skipBodyValidation } from '../../lib/route_config_helpers'; -const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); +import { RouteDependencies } from '../../plugin'; export function registerResultSettingsRoutes({ router, @@ -30,35 +30,28 @@ export function registerResultSettingsRoutes({ ); router.put( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/result_settings', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - result_fields: resultFields, - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/result_settings', }) ); router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/sample_response_search', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - query: schema.string(), - result_fields: schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })), - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/sample_response_search', }) diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts index 26204916deeca8..0c11a10ea10f96 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts @@ -107,18 +107,6 @@ describe('search settings routes', () => { path: '/as/engines/:engineName/search_settings', }); }); - - describe('validates', () => { - it('correctly', () => { - const request = { body: searchSettings }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); describe('POST /api/app_search/engines/{name}/search_settings/reset', () => { @@ -169,23 +157,6 @@ describe('search settings routes', () => { }); }); - describe('validates body', () => { - it('correctly', () => { - const request = { - body: { - boosts, - search_fields: searchFields, - }, - }; - mockRouter.shouldValidate(request); - }); - - it('missing required fields', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); - describe('validates query', () => { it('correctly', () => { const request = { diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts index 7291f7cfe64f7b..c8801d60897308 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts @@ -7,23 +7,9 @@ import { schema } from '@kbn/config-schema'; -import { RouteDependencies } from '../../plugin'; - -// We only do a very light type check here, and allow unknowns, because the request is validated -// on the ent-search server, so it would be redundant to check it here as well. -const boosts = schema.recordOf( - schema.string(), - schema.arrayOf(schema.object({}, { unknowns: 'allow' })) -); -const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); -const searchFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); +import { skipBodyValidation } from '../../lib/route_config_helpers'; -const searchSettingsSchema = schema.object({ - boosts, - result_fields: resultFields, - search_fields: searchFields, - precision: schema.number(), -}); +import { RouteDependencies } from '../../plugin'; export function registerSearchSettingsRoutes({ router, @@ -58,36 +44,31 @@ export function registerSearchSettingsRoutes({ ); router.put( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/search_settings', validate: { params: schema.object({ engineName: schema.string(), }), - body: searchSettingsSchema, }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/search_settings', }) ); router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/search_settings_search', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - boosts: schema.maybe(boosts), - search_fields: searchFields, - }), query: schema.object({ query: schema.string(), }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/search_settings_search', }) From a158ee17aa918d6afc5ac2824781b532435155c5 Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Wed, 21 Apr 2021 18:53:26 -0500 Subject: [PATCH 32/65] set initial STRING for confirm box (#97907) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/delete_field_modal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx index 69092b2bc0922a..a9d6d596baf220 100644 --- a/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx +++ b/src/plugins/index_pattern_field_editor/public/components/delete_field_modal.tsx @@ -87,7 +87,7 @@ export function DeleteFieldModal({ fieldsToDelete, closeModal, confirmDelete }: const i18nTexts = geti18nTexts(fieldsToDelete); const { modalTitle, confirmButtonText, cancelButtonText, warningMultipleFields } = i18nTexts; const isMultiple = Boolean(fieldsToDelete.length > 1); - const [confirmContent, setConfirmContent] = useState(); + const [confirmContent, setConfirmContent] = useState(''); return ( Date: Thu, 22 Apr 2021 07:20:28 +0100 Subject: [PATCH 33/65] fix tooltip content (#97847) --- .../public/timelines/components/formatted_ip/index.tsx | 2 +- .../timelines/components/timeline/body/renderers/host_name.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx index 7e3db95e7ddc13..8cdb263fe42bbc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/formatted_ip/index.tsx @@ -196,7 +196,7 @@ const AddressLinksItemComponent: React.FC = ({ ) : ( - + = ({ fieldName, contextId, eventId, val Date: Thu, 22 Apr 2021 10:08:33 +0200 Subject: [PATCH 34/65] [Security Solution] User can select event from event list and create a filter (#96940) * Initial version of event filtering form/dialog. Pending to add all redux services * Uses redux store instead of props to get the form values * Manage errors on redux * Creates even filter list on service constructor * Add os type selector depending on form parent by props. Also added create action * Allows add exception to an event. This commit has to be reviewed and maybe it will change depending on next changes * Fix imports because changes on ExceptionBuilder component and add needed type export * Adds constants. Rename eventFilters to eventFilter. Add http wrapper as a hook to check if the list has been created or not * Adds missing files on last commit. * Relocate async resource state to be shared between different pages * Use async resource state to manage async operations on components. Relocate initial entry status to an utils module instead of hook. * Adds comments into redux store from component * Fixes typechecks and wrong imports * Fixes translations and adds subheader and description modal * Relocates form description * Removes unused import * Sanitize entries before submit to remove entry.id * Missed file on last commit * Use specific fields for endpoint_event type builder * Split error field for each kind of errors to prevent unexpected renders. Adds unit test for event filter form component * Set event.kind == event by default * Changes folder names. Add notifications when success. Remove default event.king * Adds notifications when api error and fixed multiple notifications showed for same error * Adds new test for event filter modal and changes component name to be consistent * Adds unit tests for event filter notification * Adds middleware unit tests. Also isolate common event for all tests * Adds unit tests for event filter reducer * Adds unit tests for event filter selector * Fixes same key on different multilanguages. Fixes naming incoherence * Adds feature flag for event filtering * Fixes unit tests and weird behavior when changing items after name or comments on event filter form * Removes unused import * Fixes unit tests. Add imports from lists plugin. Add expects on tests. Change some names * Renames everything from eventFilter to eventFilters (plural) * Rename state variable * Create hook for notifications instead of a component. Removes className from modal body. * Updates available fields for enpoint events builder Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/lists/common/shared_exports.ts | 10 +- .../exceptions/components/builder/index.tsx | 2 +- x-pack/plugins/lists/public/shared_exports.ts | 1 + .../common/shared_imports.ts | 5 + .../events_viewer/events_viewer.test.tsx | 5 + .../exceptionable_endpoint_event_fields.json | 332 ++++++++++++++++++ ...son => exceptionable_endpoint_fields.json} | 0 .../common/components/exceptions/helpers.tsx | 23 +- .../public/common/store/actions.ts | 4 +- .../timeline_actions/alert_context_menu.tsx | 48 ++- .../components/alerts_table/translations.ts | 7 + .../public/management/common/constants.ts | 2 + .../pages/event_filters/constants.ts | 27 ++ .../pages/event_filters/service/index.ts | 47 +++ .../pages/event_filters/state/index.ts | 18 + .../pages/event_filters/store/action.ts | 45 +++ .../pages/event_filters/store/builders.ts | 18 + .../event_filters/store/middleware.test.ts | 129 +++++++ .../pages/event_filters/store/middleware.ts | 73 ++++ .../pages/event_filters/store/reducer.test.ts | 82 +++++ .../pages/event_filters/store/reducer.ts | 83 +++++ .../pages/event_filters/store/selector.ts | 39 ++ .../event_filters/store/selectors.test.ts | 80 +++++ .../pages/event_filters/store/utils.ts | 44 +++ .../pages/event_filters/test_utils/index.ts | 91 +++++ .../view/components/form/index.test.tsx | 118 +++++++ .../view/components/form/index.tsx | 207 +++++++++++ .../view/components/form/translations.ts | 38 ++ .../view/components/modal/index.test.tsx | 170 +++++++++ .../view/components/modal/index.tsx | 133 +++++++ .../view/components/modal/translations.ts | 30 ++ .../pages/event_filters/view/hooks.ts | 44 +++ .../pages/event_filters/view/translations.ts | 27 ++ .../use_event_filters_notification.test.tsx | 117 ++++++ .../pages/trusted_apps/state/index.ts | 2 +- .../state/trusted_apps_list_page_state.ts | 2 +- .../state/async_resource_state.test.ts | 0 .../state/async_resource_state.ts | 4 +- .../public/management/store/middleware.ts | 6 + .../public/management/store/reducer.ts | 5 + .../public/management/types.ts | 2 + .../public/shared_imports.ts | 1 + .../body/events/event_column_view.test.tsx | 5 + .../body/events/event_column_view.tsx | 6 +- .../components/timeline/body/index.test.tsx | 6 + 45 files changed, 2122 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json rename x-pack/plugins/security_solution/public/common/components/exceptions/{exceptionable_fields.json => exceptionable_endpoint_fields.json} (100%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx rename x-pack/plugins/security_solution/public/management/{pages/trusted_apps => }/state/async_resource_state.test.ts (100%) rename x-pack/plugins/security_solution/public/management/{pages/trusted_apps => }/state/async_resource_state.ts (97%) diff --git a/x-pack/plugins/lists/common/shared_exports.ts b/x-pack/plugins/lists/common/shared_exports.ts index 23da48b35a9d4f..286fee6de5425b 100644 --- a/x-pack/plugins/lists/common/shared_exports.ts +++ b/x-pack/plugins/lists/common/shared_exports.ts @@ -51,4 +51,12 @@ export { export { buildExceptionFilter } from './exceptions'; -export { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from './constants'; +export { + ENDPOINT_LIST_ID, + ENDPOINT_TRUSTED_APPS_LIST_ID, + EXCEPTION_LIST_URL, + EXCEPTION_LIST_ITEM_URL, + ENDPOINT_EVENT_FILTERS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_NAME, + ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, +} from './constants'; diff --git a/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx b/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx index 5b3e754f7e4238..833034aa0a542c 100644 --- a/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx +++ b/x-pack/plugins/lists/public/exceptions/components/builder/index.tsx @@ -7,4 +7,4 @@ export { BuilderEntryItem } from './entry_renderer'; export { BuilderExceptionListItemComponent } from './exception_item_renderer'; -export { ExceptionBuilderComponent } from './exception_items_renderer'; +export { ExceptionBuilderComponent, OnChangeProps } from './exception_items_renderer'; diff --git a/x-pack/plugins/lists/public/shared_exports.ts b/x-pack/plugins/lists/public/shared_exports.ts index 39825e5feb6ba7..0aecf8a8f6c85d 100644 --- a/x-pack/plugins/lists/public/shared_exports.ts +++ b/x-pack/plugins/lists/public/shared_exports.ts @@ -39,3 +39,4 @@ export { UseExceptionListsSuccess, } from './exceptions/types'; export * as ExceptionBuilder from './exceptions/components/builder/index'; +export { transformNewItemOutput } from './exceptions/transforms'; diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts index aaae0d4dc25ef2..033df0df6c4586 100644 --- a/x-pack/plugins/security_solution/common/shared_imports.ts +++ b/x-pack/plugins/security_solution/common/shared_imports.ts @@ -45,6 +45,11 @@ export { Type, ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, + EXCEPTION_LIST_URL, + EXCEPTION_LIST_ITEM_URL, + ENDPOINT_EVENT_FILTERS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_NAME, + ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, osType, osTypeArray, OsTypeArray, diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx index 8962f5e6c51466..36986f5f8d3534 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx @@ -29,6 +29,10 @@ import { SourcererScopeName } from '../../store/sourcerer/model'; import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer'; import { useTimelineEvents } from '../../../timelines/containers'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; + +jest.mock('../../hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; jest.mock('../../../timelines/components/graph_overlay', () => ({ GraphOverlay: jest.fn(() =>
), @@ -135,6 +139,7 @@ describe('EventsViewer', () => { }); describe('event details', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); beforeEach(() => { mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponseWithEvents]); }); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json new file mode 100644 index 00000000000000..2677006d1afac8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_event_fields.json @@ -0,0 +1,332 @@ +[ + "@timestamp", + "agent.id", + "agent.name", + "agent.type", + "agent.version", + "data_stream.dataset", + "data_stream.namespace", + "data_stream.type", + "destination.address", + "destination.bytes", + "destination.domain", + "destination.geo.city_name", + "destination.geo.continent_name", + "destination.geo.country_iso_code", + "destination.geo.country_name", + "destination.geo.location", + "destination.geo.name", + "destination.geo.region_iso_code", + "destination.geo.region_name", + "destination.ip", + "destination.packets", + "destination.port", + "destination.registered_domain", + "destination.top_level_domain", + "dll.code_signature.exists", + "dll.code_signature.status", + "dll.code_signature.subject_name", + "dll.code_signature.trusted", + "dll.code_signature.valid", + "dll.Ext", + "dll.Ext.code_signature", + "dll.Ext.code_signature.exists", + "dll.Ext.code_signature.status", + "dll.Ext.code_signature.subject_name", + "dll.Ext.code_signature.trusted", + "dll.Ext.code_signature.valid", + "dll.Ext.load_index", + "dll.hash.md5", + "dll.hash.sha1", + "dll.hash.sha256", + "dll.hash.sha512", + "dll.name", + "dll.path", + "dll.pe.company", + "dll.pe.description", + "dll.pe.file_version", + "dll.pe.imphash", + "dll.pe.original_file_name", + "dll.pe.product", + "dns.Ext", + "dns.Ext.options", + "dns.Ext.status", + "dns.question.name", + "dns.question.registered_domain", + "dns.question.subdomain", + "dns.question.top_level_domain", + "dns.question.type", + "dns.resolved_ip", + "ecs.version", + "elastic.agent", + "elastic.agent.id", + "Endpoint.policy", + "Endpoint.policy.applied", + "Endpoint.policy.applied.id", + "Endpoint.policy.applied.name", + "Endpoint.policy.applied.status", + "Endpoint.status", + "event.action", + "event.category", + "event.code", + "event.created", + "event.dataset", + "event.Ext", + "event.Ext.correlation", + "event.Ext.correlation.id", + "event.hash", + "event.id", + "event.ingested", + "event.module", + "event.outcome", + "event.provider", + "event.sequence", + "event.severity", + "event.type", + "file.accessed", + "file.attributes", + "file.created", + "file.ctime", + "file.device", + "file.directory", + "file.drive_letter", + "file.Ext", + "file.Ext.code_signature", + "file.Ext.code_signature.exists", + "file.Ext.code_signature.status", + "file.Ext.code_signature.subject_name", + "file.Ext.code_signature.trusted", + "file.Ext.code_signature.valid", + "file.Ext.entropy", + "file.Ext.header_data", + "file.Ext.monotonic_id", + "file.Ext.original", + "file.Ext.original.gid", + "file.Ext.original.group", + "file.Ext.original.mode", + "file.Ext.original.name", + "file.Ext.original.owner", + "file.Ext.original.path", + "file.Ext.original.uid", + "file.Ext.windows", + "file.Ext.windows.zone_identifier", + "file.extension", + "file.gid", + "file.group", + "file.hash.md5", + "file.hash.sha1", + "file.hash.sha256", + "file.hash.sha512", + "file.inode", + "file.mime_type", + "file.mode", + "file.mtime", + "file.name", + "file.owner", + "file.path", + "file.path.caseless", + "file.path.text", + "file.pe.company", + "file.pe.description", + "file.pe.file_version", + "file.pe.imphash", + "file.pe.original_file_name", + "file.pe.product", + "file.size", + "file.target_path", + "file.target_path.caseless", + "file.target_path.text", + "file.type", + "file.uid", + "group.domain", + "group.Ext", + "group.Ext.real", + "group.Ext.real.id", + "group.Ext.real.name", + "group.id", + "group.name", + "host.architecture", + "host.domain", + "host.hostname", + "host.id", + "host.ip", + "host.mac", + "host.name", + "host.os.Ext", + "host.os.Ext.variant", + "host.os.family", + "host.os.full", + "host.os.full.caseless", + "host.os.full.text", + "host.os.kernel", + "host.os.name", + "host.os.name.caseless", + "host.os.name.text", + "host.os.platform", + "host.os.version", + "host.type", + "host.uptime", + "http.request.body.bytes", + "http.request.body.content", + "http.request.body.content.text", + "http.request.bytes", + "http.response.body.bytes", + "http.response.body.content", + "http.response.body.content.text", + "http.response.bytes", + "http.response.Ext", + "http.response.Ext.version", + "http.response.status_code", + "message", + "network.bytes", + "network.community_id", + "network.direction", + "network.iana_number", + "network.packets", + "network.protocol", + "network.transport", + "network.type", + "package.name", + "process.args", + "process.args_count", + "process.code_signature.exists", + "process.code_signature.status", + "process.code_signature.subject_name", + "process.code_signature.trusted", + "process.code_signature.valid", + "process.command_line", + "process.command_line.caseless", + "process.command_line.text", + "process.entity_id", + "process.executable", + "process.executable.caseless", + "process.executable.text", + "process.exit_code", + "process.Ext", + "process.Ext.ancestry", + "process.Ext.authentication_id", + "process.Ext.code_signature", + "process.Ext.code_signature.exists", + "process.Ext.code_signature.status", + "process.Ext.code_signature.subject_name", + "process.Ext.code_signature.trusted", + "process.Ext.code_signature.valid", + "process.Ext.defense_evasions", + "process.Ext.session", + "process.Ext.token.elevation", + "process.Ext.token.elevation_type", + "process.Ext.token.integrity_level_name", + "process.hash.md5", + "process.hash.sha1", + "process.hash.sha256", + "process.hash.sha512", + "process.name", + "process.name.caseless", + "process.name.text", + "process.parent.args", + "process.parent.args_count", + "process.parent.code_signature.exists", + "process.parent.code_signature.status", + "process.parent.code_signature.subject_name", + "process.parent.code_signature.trusted", + "process.parent.code_signature.valid", + "process.parent.command_line", + "process.parent.command_line.caseless", + "process.parent.command_line.text", + "process.parent.entity_id", + "process.parent.executable", + "process.parent.executable.caseless", + "process.parent.executable.text", + "process.parent.exit_code", + "process.parent.Ext", + "process.parent.Ext.code_signature", + "process.parent.Ext.code_signature.exists", + "process.parent.Ext.code_signature.status", + "process.parent.Ext.code_signature.subject_name", + "process.parent.Ext.code_signature.trusted", + "process.parent.Ext.code_signature.valid", + "process.parent.Ext.real", + "process.parent.Ext.real.pid", + "process.parent.hash.md5", + "process.parent.hash.sha1", + "process.parent.hash.sha256", + "process.parent.hash.sha512", + "process.parent.name", + "process.parent.name.caseless", + "process.parent.name.text", + "process.parent.pe.company", + "process.parent.pe.description", + "process.parent.pe.file_version", + "process.parent.pe.imphash", + "process.parent.pe.original_file_name", + "process.parent.pe.product", + "process.parent.pgid", + "process.parent.pid", + "process.parent.ppid", + "process.parent.thread.id", + "process.parent.thread.name", + "process.parent.title", + "process.parent.title.text", + "process.parent.uptime", + "process.parent.working_directory", + "process.parent.working_directory.caseless", + "process.parent.working_directory.text", + "process.pe.company", + "process.pe.description", + "process.pe.file_version", + "process.pe.imphash", + "process.pe.original_file_name", + "process.pe.product", + "process.pgid", + "process.pid", + "process.ppid", + "process.thread.id", + "process.thread.name", + "process.title", + "process.title.text", + "process.uptime", + "process.working_directory", + "process.working_directory.caseless", + "process.working_directory.text", + "registry.data.bytes", + "registry.data.strings", + "registry.hive", + "registry.key", + "registry.path", + "registry.value", + "source.address", + "source.bytes", + "source.domain", + "source.geo.city_name", + "source.geo.continent_name", + "source.geo.country_iso_code", + "source.geo.country_name", + "source.geo.location", + "source.geo.name", + "source.geo.region_iso_code", + "source.geo.region_name", + "source.ip", + "source.packets", + "source.port", + "source.registered_domain", + "source.top_level_domain", + "user.domain", + "user.email", + "user.Ext", + "user.Ext.real", + "user.Ext.real.id", + "user.Ext.real.name", + "user.full_name", + "user.full_name.text", + "user.group.domain", + "user.group.Ext", + "user.group.Ext.real", + "user.group.Ext.real.id", + "user.group.Ext.real.name", + "user.group.id", + "user.group.name", + "user.hash", + "user.id", + "user.name", + "user.name.text" +] \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_fields.json b/x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json similarity index 100% rename from x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_fields.json rename to x-pack/plugins/security_solution/public/common/components/exceptions/exceptionable_endpoint_fields.json diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx index 69ec3120a064ba..81383bb9c0fa6a 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx @@ -49,18 +49,29 @@ import { Ecs } from '../../../../common/ecs'; import { CodeSignature } from '../../../../common/ecs/file'; import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard'; import { addIdToItem, removeIdFromItem } from '../../../../common'; -import exceptionableFields from './exceptionable_fields.json'; +import exceptionableEndpointFields from './exceptionable_endpoint_fields.json'; +import exceptionableEndpointEventFields from './exceptionable_endpoint_event_fields.json'; export const filterIndexPatterns = ( patterns: IIndexPattern, type: ExceptionListType ): IIndexPattern => { - return type === 'endpoint' - ? { + switch (type) { + case 'endpoint': + return { ...patterns, - fields: patterns.fields.filter(({ name }) => exceptionableFields.includes(name)), - } - : patterns; + fields: patterns.fields.filter(({ name }) => exceptionableEndpointFields.includes(name)), + }; + case 'endpoint_events': + return { + ...patterns, + fields: patterns.fields.filter(({ name }) => + exceptionableEndpointEventFields.includes(name) + ), + }; + default: + return patterns; + } }; export const addIdToEntries = (entries: EntriesArray): EntriesArray => { diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts index 7e42afc4400203..1987edc0e73072 100644 --- a/x-pack/plugins/security_solution/public/common/store/actions.ts +++ b/x-pack/plugins/security_solution/public/common/store/actions.ts @@ -8,6 +8,7 @@ import { EndpointAction } from '../../management/pages/endpoint_hosts/store/action'; import { PolicyDetailsAction } from '../../management/pages/policy/store/policy_details'; import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store/action'; +import { EventFiltersPageAction } from '../../management/pages/event_filters/store/action'; export { appActions } from './app'; export { dragAndDropActions } from './drag_and_drop'; @@ -19,4 +20,5 @@ export type AppAction = | EndpointAction | RoutingAction | PolicyDetailsAction - | TrustedAppsPageAction; + | TrustedAppsPageAction + | EventFiltersPageAction; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 26b9662a8f19b4..a2f568efd3a7d2 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -17,6 +17,7 @@ import { } from '@elastic/eui'; import styled from 'styled-components'; import { getOr } from 'lodash/fp'; +import { indexOf } from 'lodash'; import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers'; import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; @@ -47,6 +48,7 @@ import { ExceptionListType } from '../../../../../common/shared_imports'; import { AlertData, EcsHit } from '../../../../common/components/exceptions/types'; import { useQueryAlerts } from '../../../containers/detection_engine/alerts/use_query'; import { useSignalIndex } from '../../../containers/detection_engine/alerts/use_signal_index'; +import { EventFiltersModal } from '../../../../management/pages/event_filters/view/components/modal'; interface AlertContextMenuProps { ariaLabel?: string; @@ -81,6 +83,8 @@ const AlertContextMenuComponent: React.FC = ({ '', [ecsRowData] ); + + const isEvent = useMemo(() => indexOf(ecsRowData.event?.kind, 'event') !== -1, [ecsRowData]); const ruleIndices = useMemo((): string[] => { if ( ecsRowData.signal?.rule && @@ -107,6 +111,7 @@ const AlertContextMenuComponent: React.FC = ({ setPopover(false); }, []); const [exceptionModalType, setOpenAddExceptionModal] = useState(null); + const [isAddEventExceptionModalOpen, setIsAddEventExceptionModalOpen] = useState(false); const [{ canUserCRUD, hasIndexWrite, hasIndexMaintenance, hasIndexUpdateDelete }] = useUserData(); const isEndpointAlert = useMemo((): boolean => { @@ -124,6 +129,10 @@ const AlertContextMenuComponent: React.FC = ({ setOpenAddExceptionModal(null); }, []); + const closeAddEventExceptionModal = useCallback((): void => { + setIsAddEventExceptionModalOpen(false); + }, []); + const onAddExceptionCancel = useCallback(() => { closeAddExceptionModal(); }, [closeAddExceptionModal]); @@ -355,6 +364,28 @@ const AlertContextMenuComponent: React.FC = ({ ); }, [handleAddExceptionClick, canUserCRUD, hasIndexWrite]); + const handleAddEventExceptionClick = useCallback((): void => { + closePopover(); + setIsAddEventExceptionModalOpen(true); + }, [closePopover]); + + const addEventExceptionComponent = useMemo( + () => ( + + + {i18n.ACTION_ADD_EVENT_EXCEPTION} + + + ), + [handleAddEventExceptionClick] + ); + const statusFilters = useMemo(() => { if (!alertStatus) { return []; @@ -378,8 +409,18 @@ const AlertContextMenuComponent: React.FC = ({ ]); const items = useMemo( - () => [...statusFilters, addEndpointExceptionComponent, addExceptionComponent], - [addEndpointExceptionComponent, addExceptionComponent, statusFilters] + () => + !isEvent && ruleId + ? [...statusFilters, addEndpointExceptionComponent, addExceptionComponent] + : [addEventExceptionComponent], + [ + addEndpointExceptionComponent, + addExceptionComponent, + addEventExceptionComponent, + statusFilters, + ruleId, + isEvent, + ] ); return ( @@ -412,6 +453,9 @@ const AlertContextMenuComponent: React.FC = ({ onRuleChange={onRuleChange} /> )} + {isAddEventExceptionModalOpen && ecsRowData != null && ( + + )} ); }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts index 1829b3822e6a48..56f6337d5a55ce 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/translations.ts @@ -151,6 +151,13 @@ export const ACTION_ADD_EXCEPTION = i18n.translate( } ); +export const ACTION_ADD_EVENT_EXCEPTION = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.actions.addEventException', + { + defaultMessage: 'Add Endpoint event exception', + } +); + export const ACTION_ADD_ENDPOINT_EXCEPTION = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.actions.addEndpointException', { diff --git a/x-pack/plugins/security_solution/public/management/common/constants.ts b/x-pack/plugins/security_solution/public/management/common/constants.ts index 208fe04448709c..1b5ddd28d328f1 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -26,6 +26,8 @@ export const MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE = 'policyDetails'; export const MANAGEMENT_STORE_ENDPOINTS_NAMESPACE = 'endpoints'; /** Namespace within the Management state where trusted apps page state is maintained */ export const MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE = 'trustedApps'; +/** Namespace within the Management state where event filters page state is maintained */ +export const MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE = 'eventFilters'; export const MANAGEMENT_PAGE_SIZE_OPTIONS: readonly number[] = [10, 20, 50]; export const MANAGEMENT_DEFAULT_PAGE = 0; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts new file mode 100644 index 00000000000000..882b964d54bb53 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/constants.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + ExceptionListType, + ExceptionListTypeEnum, + EXCEPTION_LIST_URL, + EXCEPTION_LIST_ITEM_URL, + ENDPOINT_EVENT_FILTERS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_NAME, + ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, +} from '../../../../common/shared_imports'; + +export const EVENT_FILTER_LIST_TYPE: ExceptionListType = ExceptionListTypeEnum.ENDPOINT_EVENTS; +export const EVENT_FILTER_LIST = { + name: ENDPOINT_EVENT_FILTERS_LIST_NAME, + namespace_type: 'agnostic', + description: ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION, + list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, + type: EVENT_FILTER_LIST_TYPE, +}; + +export { ENDPOINT_EVENT_FILTERS_LIST_ID, EXCEPTION_LIST_URL, EXCEPTION_LIST_ITEM_URL }; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts new file mode 100644 index 00000000000000..b3521d7499362a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/service/index.ts @@ -0,0 +1,47 @@ +/* + * 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 { HttpStart } from 'kibana/public'; +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { EVENT_FILTER_LIST, EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '../constants'; + +export interface EventFiltersService { + addEventFilters( + exception: Immutable + ): Promise; +} +export class EventFiltersHttpService implements EventFiltersService { + private listHasBeenCreated: boolean; + + constructor(private http: HttpStart) { + this.listHasBeenCreated = false; + } + + private async createEndpointEventList() { + try { + await this.http.post(EXCEPTION_LIST_URL, { + body: JSON.stringify(EVENT_FILTER_LIST), + }); + } catch (err) { + // Ignore 409 errors. List already created + if (err.response.status === 409) this.listHasBeenCreated = true; + else throw err; + } + } + + private async httpWrapper() { + if (!this.listHasBeenCreated) await this.createEndpointEventList(); + return this.http; + } + + async addEventFilters(exception: ExceptionListItemSchema | CreateExceptionListItemSchema) { + return (await this.httpWrapper()).post(EXCEPTION_LIST_ITEM_URL, { + body: JSON.stringify(exception), + }); + } +} diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts new file mode 100644 index 00000000000000..b5e867e64888de --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/state/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { AsyncResourceState } from '../../../state/async_resource_state'; +export interface EventFiltersListPageState { + entries: ExceptionListItemSchema[]; + form: { + entry: CreateExceptionListItemSchema | ExceptionListItemSchema | undefined; + hasNameError: boolean; + hasItemsError: boolean; + submissionResourceState: AsyncResourceState; + }; +} diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts new file mode 100644 index 00000000000000..de2a74ed6f72d0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/action.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Action } from 'redux'; +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { AsyncResourceState } from '../../../state/async_resource_state'; + +export type EventFiltersInitForm = Action<'eventFiltersInitForm'> & { + payload: { + entry: ExceptionListItemSchema | CreateExceptionListItemSchema; + }; +}; + +export type EventFiltersChangeForm = Action<'eventFiltersChangeForm'> & { + payload: { + entry: ExceptionListItemSchema | CreateExceptionListItemSchema; + hasNameError?: boolean; + hasItemsError?: boolean; + }; +}; + +export type EventFiltersCreateStart = Action<'eventFiltersCreateStart'>; +export type EventFiltersCreateSuccess = Action<'eventFiltersCreateSuccess'> & { + payload: { + exception: ExceptionListItemSchema; + }; +}; +export type EventFiltersCreateError = Action<'eventFiltersCreateError'>; + +export type EventFiltersFormStateChanged = Action<'eventFiltersFormStateChanged'> & { + payload: AsyncResourceState; +}; + +export type EventFiltersPageAction = + | EventFiltersCreateStart + | EventFiltersInitForm + | EventFiltersChangeForm + | EventFiltersCreateStart + | EventFiltersCreateSuccess + | EventFiltersCreateError + | EventFiltersFormStateChanged; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts new file mode 100644 index 00000000000000..86ba9b1e49c38c --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/builders.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EventFiltersListPageState } from '../state'; + +export const initialEventFiltersPageState = (): EventFiltersListPageState => ({ + entries: [], + form: { + entry: undefined, + hasNameError: false, + hasItemsError: false, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts new file mode 100644 index 00000000000000..afb82ebe011ff1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.test.ts @@ -0,0 +1,129 @@ +/* + * 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 { applyMiddleware, createStore, Store } from 'redux'; + +import { + createSpyMiddleware, + MiddlewareActionSpyHelper, +} from '../../../../common/store/test_utils'; +import { AppAction } from '../../../../common/store/actions'; +import { createEventFiltersPageMiddleware } from './middleware'; +import { eventFiltersPageReducer } from './reducer'; +import { EventFiltersService } from '../service'; +import { EventFiltersListPageState } from '../state'; +import { initialEventFiltersPageState } from './builders'; +import { getInitialExceptionFromEvent } from './utils'; +import { createdEventFilterEntryMock, ecsEventMock } from '../test_utils'; + +const initialState: EventFiltersListPageState = initialEventFiltersPageState(); + +const createEventFiltersServiceMock = (): jest.Mocked => ({ + addEventFilters: jest.fn(), +}); + +const createStoreSetup = (eventFiltersService: EventFiltersService) => { + const spyMiddleware = createSpyMiddleware(); + + return { + spyMiddleware, + store: createStore( + eventFiltersPageReducer, + applyMiddleware( + createEventFiltersPageMiddleware(eventFiltersService), + spyMiddleware.actionSpyMiddleware + ) + ), + }; +}; + +describe('middleware', () => { + describe('initial state', () => { + it('sets initial state properly', async () => { + expect(createStoreSetup(createEventFiltersServiceMock()).store.getState()).toStrictEqual( + initialState + ); + }); + }); + + describe('submit creation event filter', () => { + let service: jest.Mocked; + let store: Store; + let spyMiddleware: MiddlewareActionSpyHelper; + + beforeEach(() => { + service = createEventFiltersServiceMock(); + const storeSetup = createStoreSetup(service); + store = storeSetup.store as Store; + spyMiddleware = storeSetup.spyMiddleware; + }); + + it('does not submit when entry is undefined', async () => { + store.dispatch({ type: 'eventFiltersCreateStart' }); + expect(store.getState()).toStrictEqual({ + ...initialState, + form: { + ...store.getState().form, + submissionResourceState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + + it('does submit when entry is not undefined', async () => { + service.addEventFilters.mockResolvedValue(createdEventFilterEntryMock()); + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + + store.dispatch({ type: 'eventFiltersCreateStart' }); + + await spyMiddleware.waitForAction('eventFiltersFormStateChanged'); + expect(store.getState()).toStrictEqual({ + ...initialState, + form: { + ...store.getState().form, + submissionResourceState: { + type: 'LoadedResourceState', + data: createdEventFilterEntryMock(), + }, + }, + }); + }); + + it('does throw error when creating', async () => { + service.addEventFilters.mockRejectedValue({ + body: { message: 'error message', statusCode: 500, error: 'Internal Server Error' }, + }); + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + + store.dispatch({ type: 'eventFiltersCreateStart' }); + + await spyMiddleware.waitForAction('eventFiltersFormStateChanged'); + expect(store.getState()).toStrictEqual({ + ...initialState, + form: { + ...store.getState().form, + submissionResourceState: { + type: 'FailedResourceState', + lastLoadedState: undefined, + error: { + error: 'Internal Server Error', + message: 'error message', + statusCode: 500, + }, + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts new file mode 100644 index 00000000000000..2f2c7a9e22661f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/middleware.ts @@ -0,0 +1,73 @@ +/* + * 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 { AppAction } from '../../../../common/store/actions'; +import { + ImmutableMiddleware, + ImmutableMiddlewareAPI, + ImmutableMiddlewareFactory, +} from '../../../../common/store'; + +import { EventFiltersHttpService, EventFiltersService } from '../service'; + +import { EventFiltersListPageState } from '../state'; +import { getLastLoadedResourceState } from '../../../state/async_resource_state'; +import { CreateExceptionListItemSchema, transformNewItemOutput } from '../../../../shared_imports'; + +const eventFiltersCreate = async ( + store: ImmutableMiddlewareAPI, + eventFiltersService: EventFiltersService +) => { + const submissionResourceState = store.getState().form.submissionResourceState; + try { + const formEntry = store.getState().form.entry; + if (!formEntry) return; + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + + const sanitizedEntry = transformNewItemOutput(formEntry as CreateExceptionListItemSchema); + + const exception = await eventFiltersService.addEventFilters(sanitizedEntry); + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: exception, + }, + }); + } catch (error) { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'FailedResourceState', + error: error.body || error, + lastLoadedState: getLastLoadedResourceState(submissionResourceState), + }, + }); + } +}; + +export const createEventFiltersPageMiddleware = ( + eventFiltersService: EventFiltersService +): ImmutableMiddleware => { + return (store) => (next) => async (action) => { + next(action); + + if (action.type === 'eventFiltersCreateStart') { + await eventFiltersCreate(store, eventFiltersService); + } + }; +}; + +export const eventFiltersPageMiddlewareFactory: ImmutableMiddlewareFactory = ( + coreStart +) => createEventFiltersPageMiddleware(new EventFiltersHttpService(coreStart.http)); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts new file mode 100644 index 00000000000000..5ee956ddac3e9f --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { initialEventFiltersPageState } from './builders'; +import { eventFiltersPageReducer } from './reducer'; +import { getInitialExceptionFromEvent } from './utils'; +import { createdEventFilterEntryMock, ecsEventMock } from '../test_utils'; + +const initialState = initialEventFiltersPageState(); + +describe('reducer', () => { + describe('EventFiltersForm', () => { + it('sets the initial form values', () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + const result = eventFiltersPageReducer(initialState, { + type: 'eventFiltersInitForm', + payload: { entry }, + }); + + expect(result).toStrictEqual({ + ...initialState, + form: { + ...initialState.form, + entry, + hasNameError: !entry.name, + submissionResourceState: { + type: 'UninitialisedResourceState', + }, + }, + }); + }); + + it('change form values', () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + const nameChanged = 'name changed'; + const result = eventFiltersPageReducer(initialState, { + type: 'eventFiltersChangeForm', + payload: { entry: { ...entry, name: nameChanged } }, + }); + + expect(result).toStrictEqual({ + ...initialState, + form: { + ...initialState.form, + entry: { + ...entry, + name: nameChanged, + }, + hasNameError: false, + submissionResourceState: { + type: 'UninitialisedResourceState', + }, + }, + }); + }); + + it('change form status', () => { + const result = eventFiltersPageReducer(initialState, { + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: createdEventFilterEntryMock(), + }, + }); + + expect(result).toStrictEqual({ + ...initialState, + form: { + ...initialState.form, + submissionResourceState: { + type: 'LoadedResourceState', + data: createdEventFilterEntryMock(), + }, + }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts new file mode 100644 index 00000000000000..c8f80eaee18524 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/reducer.ts @@ -0,0 +1,83 @@ +/* + * 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 { ImmutableReducer } from '../../../../common/store'; +import { Immutable } from '../../../../../common/endpoint/types'; +import { AppAction } from '../../../../common/store/actions'; + +import { + EventFiltersInitForm, + EventFiltersChangeForm, + EventFiltersFormStateChanged, +} from './action'; + +import { EventFiltersListPageState } from '../state'; +import { initialEventFiltersPageState } from './builders'; + +type StateReducer = ImmutableReducer; +type CaseReducer = ( + state: Immutable, + action: Immutable +) => Immutable; + +const eventFiltersInitForm: CaseReducer = (state, action) => { + return { + ...state, + form: { + ...state.form, + entry: action.payload.entry, + hasNameError: !action.payload.entry.name, + submissionResourceState: { + type: 'UninitialisedResourceState', + }, + }, + }; +}; + +const eventFiltersChangeForm: CaseReducer = (state, action) => { + return { + ...state, + form: { + ...state.form, + entry: action.payload.entry, + hasItemsError: + action.payload.hasItemsError !== undefined + ? action.payload.hasItemsError + : state.form.hasItemsError, + hasNameError: + action.payload.hasNameError !== undefined + ? action.payload.hasNameError + : state.form.hasNameError, + }, + }; +}; + +const eventFiltersFormStateChanged: CaseReducer = (state, action) => { + return { + ...state, + form: { + ...state.form, + submissionResourceState: action.payload, + }, + }; +}; + +export const eventFiltersPageReducer: StateReducer = ( + state = initialEventFiltersPageState(), + action +) => { + switch (action.type) { + case 'eventFiltersInitForm': + return eventFiltersInitForm(state, action); + case 'eventFiltersChangeForm': + return eventFiltersChangeForm(state, action); + case 'eventFiltersFormStateChanged': + return eventFiltersFormStateChanged(state, action); + } + + return state; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts new file mode 100644 index 00000000000000..ece754f71b3183 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selector.ts @@ -0,0 +1,39 @@ +/* + * 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 { EventFiltersListPageState } from '../state'; +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { ServerApiError } from '../../../../common/types'; +import { + isLoadingResourceState, + isLoadedResourceState, + isFailedResourceState, +} from '../../../state/async_resource_state'; + +export const getFormEntry = ( + state: EventFiltersListPageState +): CreateExceptionListItemSchema | ExceptionListItemSchema | undefined => { + return state.form.entry; +}; + +export const getFormHasError = (state: EventFiltersListPageState): boolean => { + return state.form.hasItemsError || state.form.hasNameError; +}; + +export const isCreationInProgress = (state: EventFiltersListPageState): boolean => { + return isLoadingResourceState(state.form.submissionResourceState); +}; + +export const isCreationSuccessful = (state: EventFiltersListPageState): boolean => { + return isLoadedResourceState(state.form.submissionResourceState); +}; + +export const getCreationError = (state: EventFiltersListPageState): ServerApiError | undefined => { + const submissionResourceState = state.form.submissionResourceState; + + return isFailedResourceState(submissionResourceState) ? submissionResourceState.error : undefined; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts new file mode 100644 index 00000000000000..94f15907fb58ed --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/selectors.test.ts @@ -0,0 +1,80 @@ +/* + * 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 { initialEventFiltersPageState } from './builders'; +import { getFormEntry, getFormHasError } from './selector'; +import { ecsEventMock } from '../test_utils'; +import { getInitialExceptionFromEvent } from './utils'; + +const initialState = initialEventFiltersPageState(); + +describe('selectors', () => { + describe('getFormEntry()', () => { + it('returns undefined when there is no entry', () => { + expect(getFormEntry(initialState)).toBe(undefined); + }); + it('returns entry when there is an entry on form', () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + const state = { + ...initialState, + form: { + ...initialState.form, + entry, + }, + }; + expect(getFormEntry(state)).toBe(entry); + }); + }); + describe('getFormHasError()', () => { + it('returns false when there is no entry', () => { + expect(getFormHasError(initialState)).toBeFalsy(); + }); + it('returns true when entry with name error', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasNameError: true, + }, + }; + expect(getFormHasError(state)).toBeTruthy(); + }); + it('returns true when entry with item error', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasItemsError: true, + }, + }; + expect(getFormHasError(state)).toBeTruthy(); + }); + it('returns true when entry with item error and name error', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasItemsError: true, + hasNameError: true, + }, + }; + expect(getFormHasError(state)).toBeTruthy(); + }); + + it('returns false when entry without errors', () => { + const state = { + ...initialState, + form: { + ...initialState.form, + hasItemsError: false, + hasNameError: false, + }, + }; + expect(getFormHasError(state)).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts new file mode 100644 index 00000000000000..251aaef0897e4e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/store/utils.ts @@ -0,0 +1,44 @@ +/* + * 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 uuid from 'uuid'; +import { CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { Ecs } from '../../../../../common/ecs'; +import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '../constants'; + +export const getInitialExceptionFromEvent = (data: Ecs): CreateExceptionListItemSchema => ({ + comments: [], + description: '', + entries: + data.event && data.process + ? [ + { + field: 'event.category', + operator: 'included', + type: 'match', + value: (data.event.category ?? [])[0], + }, + { + field: 'process.executable', + operator: 'included', + type: 'match', + value: (data.process.executable ?? [])[0], + }, + ] + : [], + item_id: undefined, + list_id: ENDPOINT_EVENT_FILTERS_LIST_ID, + meta: { + temporaryUuid: uuid.v4(), + }, + name: '', + namespace_type: 'agnostic', + tags: [], + type: 'simple', + // TODO: Try to fix this type casting + os_types: [(data.host ? data.host.os?.family ?? [] : [])[0] as 'windows' | 'linux' | 'macos'], +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts new file mode 100644 index 00000000000000..c38de842521f55 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/test_utils/index.ts @@ -0,0 +1,91 @@ +/* + * 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 { combineReducers, createStore } from 'redux'; +import { Ecs } from '../../../../../common/ecs'; + +import { + MANAGEMENT_STORE_GLOBAL_NAMESPACE, + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, +} from '../../../common/constants'; +import { ExceptionListItemSchema } from '../../../../shared_imports'; + +import { eventFiltersPageReducer } from '../store/reducer'; + +export const createGlobalNoMiddlewareStore = () => { + return createStore( + combineReducers({ + [MANAGEMENT_STORE_GLOBAL_NAMESPACE]: combineReducers({ + [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: eventFiltersPageReducer, + }), + }) + ); +}; + +export const ecsEventMock = (): Ecs => ({ + _id: 'unLfz3gB2mJZsMY3ytx3', + timestamp: '2021-04-14T15:34:15.330Z', + _index: '.ds-logs-endpoint.events.process-default-2021.04.12-000001', + event: { + category: ['network'], + id: ['2c4f51be-7736-4ab8-a255-54e7023c4653'], + kind: ['event'], + type: ['start'], + }, + host: { + name: ['Host-tvs68wo3qc'], + os: { + family: ['windows'], + }, + id: ['a563b365-2bee-40df-adcd-ae84d889f523'], + ip: ['10.242.233.187'], + }, + user: { + name: ['uegem17ws4'], + domain: ['hr8jofpkxp'], + }, + agent: { + type: ['endpoint'], + }, + process: { + hash: { + md5: ['c4653870-99b8-4f36-abde-24812d08a289'], + }, + parent: { + pid: [4852], + }, + pid: [3652], + name: ['lsass.exe'], + args: ['"C:\\lsass.exe" \\6z9'], + entity_id: ['9qotd1i8rf'], + executable: ['C:\\lsass.exe'], + }, +}); + +export const createdEventFilterEntryMock = (): ExceptionListItemSchema => ({ + _version: 'WzM4MDgsMV0=', + meta: undefined, + comments: [], + created_at: '2021-04-19T10:30:36.425Z', + created_by: 'elastic', + description: '', + entries: [ + { field: 'event.category', operator: 'included', type: 'match', value: 'process' }, + { field: 'process.executable', operator: 'included', type: 'match', value: 'C:\\iexlorer.exe' }, + ], + id: '47598790-a0fa-11eb-8458-69ac85f1fa18', + item_id: '93f65a04-6f5c-4f9e-9be5-e674b3c2392f', + list_id: '.endpointEventFilterList', + name: 'Test', + namespace_type: 'agnostic', + os_types: ['windows'], + tags: [], + tie_breaker_id: 'c42f3dbd-292f-49e8-83ab-158d024a4d8b', + type: 'simple', + updated_at: '2021-04-19T10:30:36.428Z', + updated_by: 'elastic', +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx new file mode 100644 index 00000000000000..d0d8ea12cf1605 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.test.tsx @@ -0,0 +1,118 @@ +/* + * 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 { EventFiltersForm } from '.'; +import { RenderResult, act, render } from '@testing-library/react'; +import { fireEvent } from '@testing-library/dom'; +import { stubIndexPatternWithFields } from 'src/plugins/data/common/index_patterns/index_pattern.stub'; +import { getInitialExceptionFromEvent } from '../../../store/utils'; +import { Provider } from 'react-redux'; +import { useFetchIndex } from '../../../../../../common/containers/source'; +import { ThemeProvider } from 'styled-components'; +import { createGlobalNoMiddlewareStore, ecsEventMock } from '../../../test_utils'; +import { getMockTheme } from '../../../../../../common/lib/kibana/kibana_react.mock'; +import { NAME_ERROR, NAME_PLACEHOLDER } from './translations'; +import { useCurrentUser, useKibana } from '../../../../../../common/lib/kibana'; + +jest.mock('../../../../../../common/lib/kibana'); +jest.mock('../../../../../../common/containers/source'); + +const mockTheme = getMockTheme({ + eui: { + paddingSizes: { m: '2' }, + }, +}); + +describe('Event filter form', () => { + let component: RenderResult; + let store: ReturnType; + + const renderForm = () => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + return render(, { wrapper: Wrapper }); + }; + + const renderComponentWithdata = () => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + act(() => { + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + }); + return renderForm(); + }; + + beforeEach(() => { + (useFetchIndex as jest.Mock).mockImplementation(() => [ + false, + { + indexPatterns: stubIndexPatternWithFields, + }, + ]); + (useCurrentUser as jest.Mock).mockReturnValue({ username: 'test-username' }); + (useKibana as jest.Mock).mockReturnValue({ + services: { + http: {}, + data: {}, + notifications: {}, + }, + }); + store = createGlobalNoMiddlewareStore(); + }); + it('should renders correctly without data', () => { + component = renderForm(); + expect(component.getByTestId('loading-spinner')).not.toBeNull(); + }); + + it('should renders correctly with data', () => { + component = renderComponentWithdata(); + + expect(component.getByText(ecsEventMock().process!.executable![0])).not.toBeNull(); + expect(component.getByText(NAME_ERROR)).not.toBeNull(); + }); + + it('should change name', async () => { + component = renderComponentWithdata(); + + const nameInput = component.getByPlaceholderText(NAME_PLACEHOLDER); + + act(() => { + fireEvent.change(nameInput, { + target: { + value: 'Exception name', + }, + }); + }); + + expect(store.getState()!.management!.eventFilters!.form!.entry!.name).toBe('Exception name'); + expect(store.getState()!.management!.eventFilters!.form!.hasNameError).toBeFalsy(); + }); + + it('should change comments', async () => { + component = renderComponentWithdata(); + + const commentInput = component.getByPlaceholderText('Add a new comment...'); + + act(() => { + fireEvent.change(commentInput, { + target: { + value: 'Exception comment', + }, + }); + }); + + expect(store.getState()!.management!.eventFilters!.form!.entry!.comments![0].comment).toBe( + 'Exception comment' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx new file mode 100644 index 00000000000000..2aeb53ed5bcb2b --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/index.tsx @@ -0,0 +1,207 @@ +/* + * 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, { memo, useMemo, useCallback, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + EuiFieldText, + EuiSpacer, + EuiForm, + EuiFormRow, + EuiSuperSelect, + EuiSuperSelectOption, + EuiText, +} from '@elastic/eui'; + +import { isEmpty } from 'lodash'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { AddExceptionComments } from '../../../../../../common/components/exceptions/add_exception_comments'; +import { filterIndexPatterns } from '../../../../../../common/components/exceptions/helpers'; +import { Loader } from '../../../../../../common/components/loader'; +import { useKibana } from '../../../../../../common/lib/kibana'; +import { useFetchIndex } from '../../../../../../common/containers/source'; +import { AppAction } from '../../../../../../common/store/actions'; +import { ExceptionListItemSchema, ExceptionBuilder } from '../../../../../../shared_imports'; + +import { useEventFiltersSelector } from '../../hooks'; +import { getFormEntry } from '../../../store/selector'; +import { + FORM_DESCRIPTION, + NAME_LABEL, + NAME_ERROR, + NAME_PLACEHOLDER, + OS_LABEL, + RULE_NAME, +} from './translations'; +import { OS_TITLES } from '../../../../../common/translations'; +import { ENDPOINT_EVENT_FILTERS_LIST_ID, EVENT_FILTER_LIST_TYPE } from '../../../constants'; + +const OPERATING_SYSTEMS: readonly OperatingSystem[] = [ + OperatingSystem.MAC, + OperatingSystem.WINDOWS, + OperatingSystem.LINUX, +]; + +interface EventFiltersFormProps { + allowSelectOs?: boolean; +} +export const EventFiltersForm: React.FC = memo( + ({ allowSelectOs = false }) => { + const { http, data } = useKibana().services; + const dispatch = useDispatch>(); + const exception = useEventFiltersSelector(getFormEntry); + + const [isIndexPatternLoading, { indexPatterns }] = useFetchIndex(['logs-endpoint.events.*']); + + const osOptions: Array> = useMemo( + () => OPERATING_SYSTEMS.map((os) => ({ value: os, inputDisplay: OS_TITLES[os] })), + [] + ); + + const [hasNameError, setHasNameError] = useState(!exception || !exception.name); + const [comment, setComment] = useState(''); + + const handleOnBuilderChange = useCallback( + (arg: ExceptionBuilder.OnChangeProps) => { + if (isEmpty(arg.exceptionItems)) return; + dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { + ...arg.exceptionItems[0], + name: exception?.name ?? '', + comments: exception?.comments ?? [], + }, + hasItemsError: arg.errorExists, + }, + }); + }, + [dispatch, exception?.name, exception?.comments] + ); + + const handleOnChangeName = useCallback( + (e: React.ChangeEvent) => { + if (!exception) return; + setHasNameError(!e.target.value); + dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { ...exception, name: e.target.value.toString() }, + hasNameError: !e.target.value, + }, + }); + }, + [dispatch, exception] + ); + + const handleOnChangeComment = useCallback( + (value: string) => { + setComment(value); + if (!exception) return; + dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { ...exception, comments: [{ comment: value }] }, + }, + }); + }, + [dispatch, exception, setComment] + ); + + const exceptionBuilderComponentMemo = useMemo( + () => ( + + ), + [data, handleOnBuilderChange, http, indexPatterns, exception] + ); + + const nameInputMemo = useMemo( + () => ( + + + + ), + [hasNameError, exception?.name, handleOnChangeName] + ); + + const osInputMemo = useMemo( + () => ( + + + + ), + [exception?.os_types, osOptions] + ); + + const commentsInputMemo = useMemo( + () => ( + + ), + [comment, handleOnChangeComment] + ); + + return !isIndexPatternLoading && exception ? ( + + {FORM_DESCRIPTION} + + {nameInputMemo} + + {allowSelectOs ? ( + <> + {osInputMemo} + + + ) : null} + {exceptionBuilderComponentMemo} + + {commentsInputMemo} + + ) : ( + + ); + } +); + +EventFiltersForm.displayName = 'EventFiltersForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts new file mode 100644 index 00000000000000..79cf296928e842 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/form/translations.ts @@ -0,0 +1,38 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const FORM_DESCRIPTION = i18n.translate( + 'xpack.securitySolution.eventFilter.modal.description', + { + defaultMessage: "Events are filtered when the rule's conditions are met:", + } +); + +export const NAME_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.eventFilter.form.name.placeholder', + { + defaultMessage: 'Event exception name', + } +); + +export const NAME_LABEL = i18n.translate('xpack.securitySolution.eventFilter.form.name.label', { + defaultMessage: 'Name your event exception', +}); + +export const NAME_ERROR = i18n.translate('xpack.securitySolution.eventFilter.form.name.error', { + defaultMessage: "The name can't be empty", +}); + +export const OS_LABEL = i18n.translate('xpack.securitySolution.eventFilter.form.os.label', { + defaultMessage: 'Seelct OS', +}); + +export const RULE_NAME = i18n.translate('xpack.securitySolution.eventFilter.form.rule.name', { + defaultMessage: 'Endpoint Event Filtering', +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx new file mode 100644 index 00000000000000..0c976b35715157 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.test.tsx @@ -0,0 +1,170 @@ +/* + * 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 { EventFiltersModal } from '.'; +import { RenderResult, act, render } from '@testing-library/react'; +import { fireEvent } from '@testing-library/dom'; +import { Provider } from 'react-redux'; +import { ThemeProvider } from 'styled-components'; +import { createGlobalNoMiddlewareStore, ecsEventMock } from '../../../test_utils'; +import { getMockTheme } from '../../../../../../common/lib/kibana/kibana_react.mock'; +import { MODAL_TITLE, MODAL_SUBTITLE, ACTIONS_CONFIRM, ACTIONS_CANCEL } from './translations'; +import { + CreateExceptionListItemSchema, + ExceptionListItemSchema, +} from '../../../../../../shared_imports'; + +jest.mock('../form'); +jest.mock('../../hooks', () => { + const originalModule = jest.requireActual('../../hooks'); + const useEventFiltersNotification = jest.fn().mockImplementation(() => {}); + + return { + ...originalModule, + useEventFiltersNotification, + }; +}); + +const mockTheme = getMockTheme({ + eui: { + paddingSizes: { m: '2' }, + euiBreakpoints: { l: '2' }, + }, +}); + +describe('Event filter modal', () => { + let component: RenderResult; + let store: ReturnType; + let onCancelMock: jest.Mock; + + const renderForm = () => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + + return render(, { + wrapper: Wrapper, + }); + }; + + beforeEach(() => { + store = createGlobalNoMiddlewareStore(); + onCancelMock = jest.fn(); + }); + + it('should renders correctly', () => { + component = renderForm(); + expect(component.getAllByText(MODAL_TITLE)).not.toBeNull(); + expect(component.getByText(MODAL_SUBTITLE)).not.toBeNull(); + expect(component.getAllByText(ACTIONS_CONFIRM)).not.toBeNull(); + expect(component.getByText(ACTIONS_CANCEL)).not.toBeNull(); + }); + + it('should dispatch action to init form store on mount', () => { + component = renderForm(); + expect(store.getState()!.management!.eventFilters!.form!.entry).not.toBeNull(); + }); + + it('should confirm form when button is disabled', () => { + component = renderForm(); + const confirmButton = component.getByTestId('add-exception-confirm-button'); + act(() => { + fireEvent.click(confirmButton); + }); + expect(store.getState()!.management!.eventFilters!.form!.submissionResourceState.type).toBe( + 'UninitialisedResourceState' + ); + }); + + it('should confirm form when button is enabled', () => { + component = renderForm(); + store.dispatch({ + type: 'eventFiltersChangeForm', + payload: { + entry: { + ...(store.getState()!.management!.eventFilters!.form! + .entry as CreateExceptionListItemSchema), + name: 'test', + }, + hasNameError: false, + }, + }); + const confirmButton = component.getByTestId('add-exception-confirm-button'); + act(() => { + fireEvent.click(confirmButton); + }); + expect(store.getState()!.management!.eventFilters!.form!.submissionResourceState.type).toBe( + 'UninitialisedResourceState' + ); + expect(confirmButton.hasAttribute('disabled')).toBeFalsy(); + }); + + it('should close when exception has been submitted correctly', () => { + component = renderForm(); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: store.getState()!.management!.eventFilters!.form!.entry as ExceptionListItemSchema, + }, + }); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + }); + + it('should close when click on cancel button', () => { + component = renderForm(); + const cancelButton = component.getByText(ACTIONS_CANCEL); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + fireEvent.click(cancelButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + }); + + it('should close when close modal', () => { + component = renderForm(); + const modalCloseButton = component.getByLabelText('Closes this modal window'); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + fireEvent.click(modalCloseButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(1); + }); + + it('should prevent close when is loading action', () => { + component = renderForm(); + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadingResourceState', + previousState: { type: 'UninitialisedResourceState' }, + }, + }); + }); + + const cancelButton = component.getByText(ACTIONS_CANCEL); + expect(onCancelMock).toHaveBeenCalledTimes(0); + + act(() => { + fireEvent.click(cancelButton); + }); + + expect(onCancelMock).toHaveBeenCalledTimes(0); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx new file mode 100644 index 00000000000000..50102d09248b12 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/index.tsx @@ -0,0 +1,133 @@ +/* + * 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, { memo, useMemo, useEffect, useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import styled, { css } from 'styled-components'; +import { + EuiModal, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalFooter, + EuiButton, + EuiButtonEmpty, +} from '@elastic/eui'; +import { AppAction } from '../../../../../../common/store/actions'; +import { Ecs } from '../../../../../../../common/ecs'; +import { EventFiltersForm } from '../form'; +import { useEventFiltersSelector, useEventFiltersNotification } from '../../hooks'; +import { + getFormHasError, + isCreationInProgress, + isCreationSuccessful, +} from '../../../store/selector'; +import { getInitialExceptionFromEvent } from '../../../store/utils'; +import { MODAL_TITLE, MODAL_SUBTITLE, ACTIONS_CONFIRM, ACTIONS_CANCEL } from './translations'; + +export interface EventFiltersModalProps { + data: Ecs; + onCancel(): void; +} + +const Modal = styled(EuiModal)` + ${({ theme }) => css` + width: ${theme.eui.euiBreakpoints.l}; + max-width: ${theme.eui.euiBreakpoints.l}; + `} +`; + +const ModalHeader = styled(EuiModalHeader)` + flex-direction: column; + align-items: flex-start; +`; + +const ModalHeaderSubtitle = styled.div` + ${({ theme }) => css` + color: ${theme.eui.euiColorMediumShade}; + `} +`; + +const ModalBodySection = styled.section` + ${({ theme }) => css` + padding: ${theme.eui.euiSizeS} ${theme.eui.euiSizeL}; + overflow-y: scroll; + `} +`; + +export const EventFiltersModal: React.FC = memo(({ data, onCancel }) => { + useEventFiltersNotification(); + const dispatch = useDispatch>(); + const formHasError = useEventFiltersSelector(getFormHasError); + const creationInProgress = useEventFiltersSelector(isCreationInProgress); + const creationSuccessful = useEventFiltersSelector(isCreationSuccessful); + + useEffect(() => { + if (creationSuccessful) { + onCancel(); + dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'UninitialisedResourceState', + }, + }); + } + }, [creationSuccessful, onCancel, dispatch]); + + // Initialize the store with the event passed as prop to allow render the form. It acts as componentDidMount + useEffect(() => { + dispatch({ + type: 'eventFiltersInitForm', + payload: { entry: getInitialExceptionFromEvent(data) }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleOnCancel = useCallback(() => { + if (creationInProgress) return; + onCancel(); + }, [creationInProgress, onCancel]); + + const confirmButtonMemo = useMemo( + () => ( + { + dispatch({ type: 'eventFiltersCreateStart' }); + }} + isLoading={creationInProgress} + > + {ACTIONS_CONFIRM} + + ), + [dispatch, formHasError, creationInProgress] + ); + + return ( + + + {MODAL_TITLE} + {MODAL_SUBTITLE} + + + + + + + + + {ACTIONS_CANCEL} + + {confirmButtonMemo} + + + ); +}); + +EventFiltersModal.displayName = 'EventFiltersModal'; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.ts new file mode 100644 index 00000000000000..982d9b3bb12b32 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/components/modal/translations.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 { i18n } from '@kbn/i18n'; + +export const MODAL_TITLE = i18n.translate('xpack.securitySolution.eventFilter.modal.title', { + defaultMessage: 'Add Endpoint Event Filter', +}); + +export const MODAL_SUBTITLE = i18n.translate('xpack.securitySolution.eventFilter.modal.subtitle', { + defaultMessage: 'Endpoint Security', +}); + +export const ACTIONS_CONFIRM = i18n.translate( + 'xpack.securitySolution.eventFilter.modal.actions.confirm', + { + defaultMessage: 'Add Endpoint Event Filter', + } +); + +export const ACTIONS_CANCEL = i18n.translate( + 'xpack.securitySolution.eventFilter.modal.actions.cancel', + { + defaultMessage: 'cancel', + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts new file mode 100644 index 00000000000000..407dee896f5ac4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/hooks.ts @@ -0,0 +1,44 @@ +/* + * 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 { useState } from 'react'; +import { useSelector } from 'react-redux'; + +import { isCreationSuccessful, getFormEntry, getCreationError } from '../store/selector'; + +import { useToasts } from '../../../../common/lib/kibana'; +import { getCreationSuccessMessage, getCreationErrorMessage } from './translations'; + +import { State } from '../../../../common/store'; +import { EventFiltersListPageState } from '../state'; + +import { + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE as EVENT_FILTER_NS, + MANAGEMENT_STORE_GLOBAL_NAMESPACE as GLOBAL_NS, +} from '../../../common/constants'; + +export function useEventFiltersSelector(selector: (state: EventFiltersListPageState) => R): R { + return useSelector((state: State) => + selector(state[GLOBAL_NS][EVENT_FILTER_NS] as EventFiltersListPageState) + ); +} + +export const useEventFiltersNotification = () => { + const creationSuccessful = useEventFiltersSelector(isCreationSuccessful); + const creationError = useEventFiltersSelector(getCreationError); + const formEntry = useEventFiltersSelector(getFormEntry); + const toasts = useToasts(); + const [wasAlreadyHandled] = useState(new WeakSet()); + + if (creationSuccessful && formEntry && !wasAlreadyHandled.has(formEntry)) { + wasAlreadyHandled.add(formEntry); + toasts.addSuccess(getCreationSuccessMessage(formEntry)); + } else if (creationError && !wasAlreadyHandled.has(creationError)) { + wasAlreadyHandled.add(creationError); + toasts.addDanger(getCreationErrorMessage(creationError)); + } +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts new file mode 100644 index 00000000000000..711ab8224ea604 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/translations.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; + +import { ExceptionListItemSchema, CreateExceptionListItemSchema } from '../../../../shared_imports'; +import { ServerApiError } from '../../../../common/types'; + +export const getCreationSuccessMessage = ( + entry: CreateExceptionListItemSchema | ExceptionListItemSchema | undefined +) => { + return i18n.translate('xpack.securitySolution.eventFilter.form.successToastTitle', { + defaultMessage: '"{name}" has been added to the event exceptions list.', + values: { name: entry?.name }, + }); +}; + +export const getCreationErrorMessage = (creationError: ServerApiError) => { + return i18n.translate('xpack.securitySolution.eventFilter.form.failedToastTitle', { + defaultMessage: 'There was an error creating the new exception: "{error}"', + values: { error: creationError.message }, + }); +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx new file mode 100644 index 00000000000000..37f7dff8408a7e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/use_event_filters_notification.test.tsx @@ -0,0 +1,117 @@ +/* + * 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 { Provider } from 'react-redux'; +import { renderHook, act } from '@testing-library/react-hooks'; + +import { NotificationsStart } from 'kibana/public'; +import { coreMock } from '../../../../../../../../src/core/public/mocks'; +import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public/context'; +import { CreateExceptionListItemSchema, ExceptionListItemSchema } from '../../../../shared_imports'; + +import { createGlobalNoMiddlewareStore, ecsEventMock } from '../test_utils'; +import { useEventFiltersNotification } from './hooks'; +import { getCreationErrorMessage, getCreationSuccessMessage } from './translations'; +import { getInitialExceptionFromEvent } from '../store/utils'; +import { + getLastLoadedResourceState, + FailedResourceState, +} from '../../../state/async_resource_state'; + +const mockNotifications = () => coreMock.createStart({ basePath: '/mock' }).notifications; + +const renderNotifications = ( + store: ReturnType, + notifications: NotificationsStart +) => { + const Wrapper: React.FC = ({ children }) => ( + + {children} + + ); + return renderHook(useEventFiltersNotification, { wrapper: Wrapper }); +}; + +describe('EventFiltersNotification', () => { + it('renders correctly initially', () => { + const notifications = mockNotifications(); + + renderNotifications(createGlobalNoMiddlewareStore(), notifications); + + expect(notifications.toasts.addSuccess).not.toBeCalled(); + expect(notifications.toasts.addDanger).not.toBeCalled(); + }); + + it('shows success notification when creation successful', () => { + const store = createGlobalNoMiddlewareStore(); + const notifications = mockNotifications(); + + renderNotifications(store, notifications); + + act(() => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + }); + + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'LoadedResourceState', + data: store.getState()!.management!.eventFilters!.form!.entry as ExceptionListItemSchema, + }, + }); + }); + + expect(notifications.toasts.addSuccess).toBeCalledWith( + getCreationSuccessMessage( + store.getState()!.management!.eventFilters!.form!.entry as CreateExceptionListItemSchema + ) + ); + expect(notifications.toasts.addDanger).not.toBeCalled(); + }); + + it('shows error notification when creation fails', () => { + const store = createGlobalNoMiddlewareStore(); + const notifications = mockNotifications(); + + renderNotifications(store, notifications); + + act(() => { + const entry = getInitialExceptionFromEvent(ecsEventMock()); + store.dispatch({ + type: 'eventFiltersInitForm', + payload: { entry }, + }); + }); + + act(() => { + store.dispatch({ + type: 'eventFiltersFormStateChanged', + payload: { + type: 'FailedResourceState', + error: { message: 'error message', statusCode: 500, error: 'error' }, + lastLoadedState: getLastLoadedResourceState( + store.getState()!.management!.eventFilters!.form!.submissionResourceState + ), + }, + }); + }); + + expect(notifications.toasts.addSuccess).not.toBeCalled(); + expect(notifications.toasts.addDanger).toBeCalledWith( + getCreationErrorMessage( + (store.getState()!.management!.eventFilters!.form! + .submissionResourceState as FailedResourceState).error + ) + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts index 308dbf3df5f73d..a06fceab29d4c2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export * from './async_resource_state'; +export * from '../../../state/async_resource_state'; export * from './trusted_apps_list_page_state'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts index e37b0f262603d8..5041f0a6118dc5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/trusted_apps_list_page_state.ts @@ -6,7 +6,7 @@ */ import { NewTrustedApp, TrustedApp } from '../../../../../common/endpoint/types/trusted_apps'; -import { AsyncResourceState } from '.'; +import { AsyncResourceState } from '../../../state/async_resource_state'; import { GetPolicyListResponse } from '../../policy/types'; export interface Pagination { diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts b/x-pack/plugins/security_solution/public/management/state/async_resource_state.test.ts similarity index 100% rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.test.ts rename to x-pack/plugins/security_solution/public/management/state/async_resource_state.test.ts diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts b/x-pack/plugins/security_solution/public/management/state/async_resource_state.ts similarity index 97% rename from x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts rename to x-pack/plugins/security_solution/public/management/state/async_resource_state.ts index 5208d534221a8f..1b6dec54ec0b35 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/state/async_resource_state.ts +++ b/x-pack/plugins/security_solution/public/management/state/async_resource_state.ts @@ -15,8 +15,8 @@ * - update can fail due to multiple reasons and also needs to be communicated to the user */ -import { Immutable } from '../../../../../common/endpoint/types'; -import { ServerApiError } from '../../../../common/types'; +import { Immutable } from '../../../common/endpoint/types'; +import { ServerApiError } from '../../common/types'; /** * Data type to represent uninitialised state of asynchronous resource. diff --git a/x-pack/plugins/security_solution/public/management/store/middleware.ts b/x-pack/plugins/security_solution/public/management/store/middleware.ts index aff1f7db7f0662..dae2357d5bb107 100644 --- a/x-pack/plugins/security_solution/public/management/store/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/store/middleware.ts @@ -15,10 +15,12 @@ import { MANAGEMENT_STORE_GLOBAL_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, } from '../common/constants'; import { policyDetailsMiddlewareFactory } from '../pages/policy/store/policy_details'; import { endpointMiddlewareFactory } from '../pages/endpoint_hosts/store/middleware'; import { trustedAppsPageMiddlewareFactory } from '../pages/trusted_apps/store/middleware'; +import { eventFiltersPageMiddlewareFactory } from '../pages/event_filters/store/middleware'; type ManagementSubStateKey = keyof State[typeof MANAGEMENT_STORE_GLOBAL_NAMESPACE]; @@ -42,5 +44,9 @@ export const managementMiddlewareFactory: SecuritySubPluginMiddlewareFactory = ( createSubStateSelector(MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE), trustedAppsPageMiddlewareFactory(coreStart, depsStart) ), + substateMiddlewareFactory( + createSubStateSelector(MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE), + eventFiltersPageMiddlewareFactory(coreStart, depsStart) + ), ]; }; diff --git a/x-pack/plugins/security_solution/public/management/store/reducer.ts b/x-pack/plugins/security_solution/public/management/store/reducer.ts index 683840d852271c..bf8cd416a3e395 100644 --- a/x-pack/plugins/security_solution/public/management/store/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/store/reducer.ts @@ -14,6 +14,7 @@ import { MANAGEMENT_STORE_ENDPOINTS_NAMESPACE, MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE, MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE, + MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE, } from '../common/constants'; import { ImmutableCombineReducers } from '../../common/store'; import { Immutable } from '../../../common/endpoint/types'; @@ -24,6 +25,8 @@ import { } from '../pages/endpoint_hosts/store/reducer'; import { initialTrustedAppsPageState } from '../pages/trusted_apps/store/builders'; import { trustedAppsPageReducer } from '../pages/trusted_apps/store/reducer'; +import { initialEventFiltersPageState } from '../pages/event_filters/store/builders'; +import { eventFiltersPageReducer } from '../pages/event_filters/store/reducer'; const immutableCombineReducers: ImmutableCombineReducers = combineReducers; @@ -34,6 +37,7 @@ export const mockManagementState: Immutable = { [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: initialPolicyDetailsState(), [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: initialEndpointListState, [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: initialTrustedAppsPageState(), + [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: initialEventFiltersPageState(), }; /** @@ -43,4 +47,5 @@ export const managementReducer = immutableCombineReducers({ [MANAGEMENT_STORE_POLICY_DETAILS_NAMESPACE]: policyDetailsReducer, [MANAGEMENT_STORE_ENDPOINTS_NAMESPACE]: endpointListReducer, [MANAGEMENT_STORE_TRUSTED_APPS_NAMESPACE]: trustedAppsPageReducer, + [MANAGEMENT_STORE_EVENT_FILTERS_NAMESPACE]: eventFiltersPageReducer, }); diff --git a/x-pack/plugins/security_solution/public/management/types.ts b/x-pack/plugins/security_solution/public/management/types.ts index a21f182fb9d675..902010a97603c9 100644 --- a/x-pack/plugins/security_solution/public/management/types.ts +++ b/x-pack/plugins/security_solution/public/management/types.ts @@ -10,6 +10,7 @@ import { SecurityPageName } from '../app/types'; import { PolicyDetailsState } from './pages/policy/types'; import { EndpointState } from './pages/endpoint_hosts/types'; import { TrustedAppsListPageState } from './pages/trusted_apps/state'; +import { EventFiltersListPageState } from './pages/event_filters/state'; /** * The type for the management store global namespace. Used mostly internally to reference @@ -21,6 +22,7 @@ export type ManagementState = CombinedState<{ policyDetails: PolicyDetailsState; endpoints: EndpointState; trustedApps: TrustedAppsListPageState; + eventFilters: EventFiltersListPageState; }>; /** diff --git a/x-pack/plugins/security_solution/public/shared_imports.ts b/x-pack/plugins/security_solution/public/shared_imports.ts index 757191fdb54ec6..4a1fdbf0564d18 100644 --- a/x-pack/plugins/security_solution/public/shared_imports.ts +++ b/x-pack/plugins/security_solution/public/shared_imports.ts @@ -59,4 +59,5 @@ export { addEndpointExceptionList, withOptionalSignal, ExceptionBuilder, + transformNewItemOutput, } from '../../lists/public'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 74724dedf4d11d..deb94521cde0ba 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -17,6 +17,10 @@ import { EventColumnView } from './event_column_view'; import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer'; import { TimelineTabs, TimelineType, TimelineId } from '../../../../../../common/types/timeline'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; + +jest.mock('../../../../../common/hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; jest.mock('../../../../../common/hooks/use_selector'); @@ -29,6 +33,7 @@ jest.mock('../../../../../cases/components/timeline_actions/add_to_case_action', }); describe('EventColumnView', () => { + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); (useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default); const props = { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index a0a0aeb23e8f74..cb1fff1cf9562a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react'; import { CellValueElementProps } from '../../cell_rendering'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; +import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { Ecs } from '../../../../../../common/ecs'; import { TimelineNonEcsData } from '../../../../../../common/search_strategy/timeline'; import { ColumnHeaderOptions } from '../../../../../timelines/store/timeline/model'; @@ -96,6 +97,8 @@ export const EventColumnView = React.memo( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).timelineType ); + const isEventFilteringEnabled = useIsExperimentalFeatureEnabled('eventFilteringEnabled'); + // Each action button shall announce itself to screen readers via an `aria-label` // in the following format: // "button description, for the event in row {ariaRowindex}, with columns {columnValues}", @@ -183,7 +186,7 @@ export const EventColumnView = React.memo( key="alert-context-menu" ecsRowData={ecsData} timelineId={timelineId} - disabled={eventType !== 'signal'} + disabled={eventType !== 'signal' && (!isEventFilteringEnabled || eventType !== 'raw')} refetch={refetch} onRuleChange={onRuleChange} />, @@ -205,6 +208,7 @@ export const EventColumnView = React.memo( timelineId, timelineType, toggleShowNotes, + isEventFilteringEnabled, ] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index 76dbfc553d228d..b526ce7e0d996c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -21,6 +21,10 @@ import { useMountAppended } from '../../../../common/utils/use_mount_appended'; import { timelineActions } from '../../../store/timeline'; import { TimelineTabs } from '../../../../../common/types/timeline'; import { defaultRowRenderers } from './renderers'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; + +jest.mock('../../../../common/hooks/use_experimental_features'); +const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock; const mockSort: Sort[] = [ { @@ -88,6 +92,8 @@ describe('Body', () => { totalPages: 1, }; + useIsExperimentalFeatureEnabledMock.mockReturnValue(false); + describe('rendering', () => { test('it renders the column headers', () => { const wrapper = mount( From 705feeb87ba2b1af692ac1e8ca428e4c8ecc55f3 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Thu, 22 Apr 2021 09:18:01 +0100 Subject: [PATCH 35/65] [ML] Indicate Apache and Nginx modules as legacy versions (#97801) * [ML] Indicate Apache and Nginx modules as legacy versions * [ML] Edits to module titles to capitalize Filebeat --- .../models/data_recognizer/modules/apache_ecs/manifest.json | 4 ++-- .../models/data_recognizer/modules/nginx_ecs/manifest.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json index c4b6b18f56bc84..e9cd932ce264c0 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/apache_ecs/manifest.json @@ -1,7 +1,7 @@ { "id": "apache_ecs", - "title": "Apache access logs", - "description": "Find unusual activity in HTTP access logs from filebeat (ECS).", + "title": "Apache access logs (Filebeat)", + "description": "Legacy jobs for finding unusual activity in HTTP access logs. The latest versions are installed with the Apache integration in Fleet.", "type": "Web Access Logs", "logoFile": "logo.json", "defaultIndexPattern": "filebeat-*", diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json index c39e4e5e4faf61..e2190351789419 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/manifest.json @@ -1,7 +1,7 @@ { "id": "nginx_ecs", - "title": "Nginx access logs", - "description": "Find unusual activity in HTTP access logs from filebeat (ECS).", + "title": "Nginx access logs (Filebeat)", + "description": "Legacy jobs for finding unusual activity in HTTP access logs. The latest versions are installed with the Nginx integration in Fleet.", "type": "Web Access Logs", "logoFile": "logo.json", "defaultIndexPattern": "filebeat-*", From 87fa92bf3a69618943275dbc1a8e828dccc13723 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Thu, 22 Apr 2021 10:30:53 +0200 Subject: [PATCH 36/65] Fix broken advanced setting i18n (#97782) --- src/plugins/discover/server/plugin.ts | 4 ++-- src/plugins/discover/server/ui_settings.ts | 15 +++++++++++---- src/plugins/vis_type_timeseries/server/plugin.ts | 4 ++-- .../vis_type_timeseries/server/ui_settings.ts | 4 ++-- src/plugins/vis_type_vislib/server/plugin.ts | 4 ++-- src/plugins/vis_type_vislib/server/ui_settings.ts | 4 ++-- src/plugins/vis_type_xy/server/plugin.ts | 6 +++--- 7 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/plugins/discover/server/plugin.ts b/src/plugins/discover/server/plugin.ts index 0078aacd6dfd19..27cb3cec8be417 100644 --- a/src/plugins/discover/server/plugin.ts +++ b/src/plugins/discover/server/plugin.ts @@ -7,14 +7,14 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/server'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; import { capabilitiesProvider } from './capabilities_provider'; import { searchSavedObjectType } from './saved_objects'; export class DiscoverServerPlugin implements Plugin { public setup(core: CoreSetup) { core.capabilities.registerProvider(capabilitiesProvider); - core.uiSettings.register(uiSettings); + core.uiSettings.register(getUiSettings()); core.savedObjects.registerType(searchSavedObjectType); return {}; diff --git a/src/plugins/discover/server/ui_settings.ts b/src/plugins/discover/server/ui_settings.ts index 103a06965835ea..5f361ba2711cbf 100644 --- a/src/plugins/discover/server/ui_settings.ts +++ b/src/plugins/discover/server/ui_settings.ts @@ -27,7 +27,7 @@ import { MAX_DOC_FIELDS_DISPLAYED, } from '../common'; -export const uiSettings: Record = { +export const getUiSettings: () => Record = () => ({ [DEFAULT_COLUMNS_SETTING]: { name: i18n.translate('discover.advancedSettings.defaultColumnsTitle', { defaultMessage: 'Default columns', @@ -186,10 +186,17 @@ export const uiSettings: Record = { }, }, [SEARCH_FIELDS_FROM_SOURCE]: { - name: 'Read fields from _source', - description: `When enabled will load documents directly from \`_source\`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.`, + name: i18n.translate('discover.advancedSettings.discover.readFieldsFromSource', { + defaultMessage: 'Read fields from _source', + }), + description: i18n.translate( + 'discover.advancedSettings.discover.readFieldsFromSourceDescription', + { + defaultMessage: `When enabled will load documents directly from \`_source\`. This is soon going to be deprecated. When disabled, will retrieve fields via the new Fields API in the high-level search service.`, + } + ), value: false, category: ['discover'], schema: schema.boolean(), }, -}; +}); diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index 8bc752e944709f..93fd39528e70e6 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -24,7 +24,7 @@ import { PluginStart } from '../../data/server'; import { IndexPatternsService } from '../../data/common'; import { visDataRoutes } from './routes/vis'; import { fieldsRoutes } from './routes/fields'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesVisDataRequest, @@ -83,7 +83,7 @@ export class VisTypeTimeseriesPlugin implements Plugin { plugins: VisTypeTimeseriesPluginSetupDependencies ) { const logger = this.initializerContext.logger.get('visTypeTimeseries'); - core.uiSettings.register(uiSettings); + core.uiSettings.register(getUiSettings()); const config$ = this.initializerContext.config.create(); // Global config contains things like the ES shard timeout const globalConfig$ = this.initializerContext.config.legacy.globalConfig$; diff --git a/src/plugins/vis_type_timeseries/server/ui_settings.ts b/src/plugins/vis_type_timeseries/server/ui_settings.ts index 07d2355b222539..e61635058cee06 100644 --- a/src/plugins/vis_type_timeseries/server/ui_settings.ts +++ b/src/plugins/vis_type_timeseries/server/ui_settings.ts @@ -13,7 +13,7 @@ import { UiSettingsParams } from 'kibana/server'; import { MAX_BUCKETS_SETTING } from '../common/constants'; -export const uiSettings: Record = { +export const getUiSettings: () => Record = () => ({ [MAX_BUCKETS_SETTING]: { name: i18n.translate('visTypeTimeseries.advancedSettings.maxBucketsTitle', { defaultMessage: 'TSVB buckets limit', @@ -25,4 +25,4 @@ export const uiSettings: Record = { }), schema: schema.number(), }, -}; +}); diff --git a/src/plugins/vis_type_vislib/server/plugin.ts b/src/plugins/vis_type_vislib/server/plugin.ts index f670c81a733867..dfbb30a13dbb14 100644 --- a/src/plugins/vis_type_vislib/server/plugin.ts +++ b/src/plugins/vis_type_vislib/server/plugin.ts @@ -7,11 +7,11 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/server'; -import { uiSettings } from './ui_settings'; +import { getUiSettings } from './ui_settings'; export class VisTypeVislibServerPlugin implements Plugin { public setup(core: CoreSetup) { - core.uiSettings.register(uiSettings); + core.uiSettings.register(getUiSettings()); return {}; } diff --git a/src/plugins/vis_type_vislib/server/ui_settings.ts b/src/plugins/vis_type_vislib/server/ui_settings.ts index c0ef3f3b40e1da..bd4615e47fb6e7 100644 --- a/src/plugins/vis_type_vislib/server/ui_settings.ts +++ b/src/plugins/vis_type_vislib/server/ui_settings.ts @@ -12,7 +12,7 @@ import { schema } from '@kbn/config-schema'; import { UiSettingsParams } from 'kibana/server'; import { DIMMING_OPACITY_SETTING, HEATMAP_MAX_BUCKETS_SETTING } from '../common'; -export const uiSettings: Record = { +export const getUiSettings: () => Record = () => ({ // TODO: move this to vis_type_xy when vislib is removed // https://github.com/elastic/kibana/issues/56143 [DIMMING_OPACITY_SETTING]: { @@ -47,4 +47,4 @@ export const uiSettings: Record = { category: ['visualization'], schema: schema.number(), }, -}; +}); diff --git a/src/plugins/vis_type_xy/server/plugin.ts b/src/plugins/vis_type_xy/server/plugin.ts index a9e6020cf3ee8d..08aefdeb836b0e 100644 --- a/src/plugins/vis_type_xy/server/plugin.ts +++ b/src/plugins/vis_type_xy/server/plugin.ts @@ -13,7 +13,7 @@ import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server'; import { LEGACY_CHARTS_LIBRARY } from '../common'; -export const uiSettingsConfig: Record> = { +export const getUiSettingsConfig: () => Record> = () => ({ // TODO: Remove this when vis_type_vislib is removed // https://github.com/elastic/kibana/issues/56143 [LEGACY_CHARTS_LIBRARY]: { @@ -31,11 +31,11 @@ export const uiSettingsConfig: Record> = { category: ['visualization'], schema: schema.boolean(), }, -}; +}); export class VisTypeXyServerPlugin implements Plugin { public setup(core: CoreSetup) { - core.uiSettings.register(uiSettingsConfig); + core.uiSettings.register(getUiSettingsConfig()); return {}; } From b2a9dd12a5db3c6ce5545290f5ff9748085dd7bd Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Thu, 22 Apr 2021 13:23:51 +0300 Subject: [PATCH 37/65] [Search] Remove msearch from searchsource (#96936) * Move inspector adapter integration into search source * docs and ts * Move other bucket to search source * test ts + delete unused tabilfy function * hierarchical param in aggconfig. ts improvements more inspector tests * fix jest * separate inspect more tests * jest * inspector * Error handling and more tests * put the fun in functional tests * delete client side legacy msearch code * ts * override to sync search in search source * delete more legacy code * ts * delete moarrrr --- .../create_search_source.test.ts | 5 - .../search/search_source/fetch/types.ts | 6 - .../search_source/legacy/call_client.test.ts | 102 -------------- .../search_source/legacy/call_client.ts | 38 ----- .../legacy/default_search_strategy.test.ts | 61 -------- .../legacy/default_search_strategy.ts | 63 --------- .../search_source/legacy/fetch_soon.test.ts | 132 ------------------ .../search/search_source/legacy/fetch_soon.ts | 83 ----------- .../search/search_source/legacy/index.ts | 1 - .../search/search_source/legacy/types.ts | 26 ---- .../data/common/search/search_source/mocks.ts | 6 +- .../search_source/search_source.test.ts | 30 ++-- .../search/search_source/search_source.ts | 35 ++--- .../search_source_service.test.ts | 5 - src/plugins/data/public/public.api.md | 3 +- .../public/search/legacy/call_msearch.test.ts | 43 ------ .../data/public/search/legacy/call_msearch.ts | 26 ---- .../data/public/search/legacy/index.ts | 9 -- .../data/public/search/search_service.ts | 9 +- .../data/server/search/search_service.ts | 12 +- src/plugins/data/server/server.api.md | 2 - src/plugins/data/server/ui_settings.ts | 4 +- 22 files changed, 33 insertions(+), 668 deletions(-) delete mode 100644 src/plugins/data/common/search/search_source/legacy/call_client.test.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/call_client.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts delete mode 100644 src/plugins/data/common/search/search_source/legacy/fetch_soon.ts delete mode 100644 src/plugins/data/public/search/legacy/call_msearch.test.ts delete mode 100644 src/plugins/data/public/search/legacy/call_msearch.ts delete mode 100644 src/plugins/data/public/search/legacy/index.ts diff --git a/src/plugins/data/common/search/search_source/create_search_source.test.ts b/src/plugins/data/common/search/search_source/create_search_source.test.ts index df31719b0aec43..6a6ac1dfa93e7e 100644 --- a/src/plugins/data/common/search/search_source/create_search_source.test.ts +++ b/src/plugins/data/common/search/search_source/create_search_source.test.ts @@ -11,7 +11,6 @@ import { SearchSourceDependencies } from './search_source'; import { IIndexPattern } from '../../index_patterns'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { Filter } from '../../es_query/filters'; -import { BehaviorSubject } from 'rxjs'; describe('createSearchSource', () => { const indexPatternMock: IIndexPattern = {} as IIndexPattern; @@ -24,10 +23,6 @@ describe('createSearchSource', () => { getConfig: jest.fn(), search: jest.fn(), onResponse: (req, res) => res, - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }; indexPatternContractMock = ({ diff --git a/src/plugins/data/common/search/search_source/fetch/types.ts b/src/plugins/data/common/search/search_source/fetch/types.ts index 8e8a9f1025b80e..79aa45163b913e 100644 --- a/src/plugins/data/common/search/search_source/fetch/types.ts +++ b/src/plugins/data/common/search/search_source/fetch/types.ts @@ -7,7 +7,6 @@ */ import type { estypes } from '@elastic/elasticsearch'; -import { LegacyFetchHandlers } from '../legacy/types'; import { GetConfigFn } from '../../../types'; /** @@ -29,11 +28,6 @@ export interface FetchHandlers { request: SearchRequest, response: estypes.SearchResponse ) => estypes.SearchResponse; - /** - * These handlers are only used by the legacy defaultSearchStrategy and can be removed - * once that strategy has been deprecated. - */ - legacy: LegacyFetchHandlers; } export interface SearchError { diff --git a/src/plugins/data/common/search/search_source/legacy/call_client.test.ts b/src/plugins/data/common/search/search_source/legacy/call_client.test.ts deleted file mode 100644 index 93849b63939e42..00000000000000 --- a/src/plugins/data/common/search/search_source/legacy/call_client.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { callClient } from './call_client'; -import { SearchStrategySearchParams } from './types'; -import { defaultSearchStrategy } from './default_search_strategy'; -import { FetchHandlers } from '../fetch'; -import { BehaviorSubject } from 'rxjs'; - -const mockAbortFn = jest.fn(); - -jest.mock('./default_search_strategy', () => { - return { - defaultSearchStrategy: { - search: jest.fn(({ searchRequests }: SearchStrategySearchParams) => { - return { - searching: Promise.resolve( - searchRequests.map((req) => { - return { - id: req._searchStrategyId, - }; - }) - ), - abort: mockAbortFn, - }; - }), - }, - }; -}); - -describe('callClient', () => { - const handleResponse = jest.fn().mockImplementation((req, res) => res); - const handlers = { - getConfig: jest.fn(), - onResponse: handleResponse, - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, - } as FetchHandlers; - - beforeEach(() => { - handleResponse.mockClear(); - }); - - test('Passes the additional arguments it is given to the search strategy', () => { - const searchRequests = [{ _searchStrategyId: 0 }]; - - callClient(searchRequests, [], handlers); - - expect(defaultSearchStrategy.search).toBeCalled(); - expect((defaultSearchStrategy.search as any).mock.calls[0][0]).toEqual({ - searchRequests, - ...handlers, - }); - }); - - test('Returns the responses in the original order', async () => { - const searchRequests = [{ _searchStrategyId: 1 }, { _searchStrategyId: 0 }]; - - const responses = await Promise.all(callClient(searchRequests, [], handlers)); - - expect(responses[0]).toEqual({ id: searchRequests[0]._searchStrategyId }); - expect(responses[1]).toEqual({ id: searchRequests[1]._searchStrategyId }); - }); - - test('Calls handleResponse with each request and response', async () => { - const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }]; - - const responses = callClient(searchRequests, [], handlers); - await Promise.all(responses); - - expect(handleResponse).toBeCalledTimes(2); - expect(handleResponse).toBeCalledWith(searchRequests[0], { - id: searchRequests[0]._searchStrategyId, - }); - expect(handleResponse).toBeCalledWith(searchRequests[1], { - id: searchRequests[1]._searchStrategyId, - }); - }); - - test('If passed an abortSignal, calls abort on the strategy if the signal is aborted', () => { - const searchRequests = [{ _searchStrategyId: 0 }, { _searchStrategyId: 1 }]; - const abortController = new AbortController(); - const requestOptions = [ - { - abortSignal: abortController.signal, - }, - ]; - - callClient(searchRequests, requestOptions, handlers); - abortController.abort(); - - expect(mockAbortFn).toBeCalled(); - // expect(mockAbortFns[1]).not.toBeCalled(); - }); -}); diff --git a/src/plugins/data/common/search/search_source/legacy/call_client.ts b/src/plugins/data/common/search/search_source/legacy/call_client.ts deleted file mode 100644 index 4c1156aac70153..00000000000000 --- a/src/plugins/data/common/search/search_source/legacy/call_client.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { estypes } from '@elastic/elasticsearch'; -import { FetchHandlers, SearchRequest } from '../fetch'; -import { defaultSearchStrategy } from './default_search_strategy'; -import { ISearchOptions } from '../../index'; - -export function callClient( - searchRequests: SearchRequest[], - requestsOptions: ISearchOptions[] = [], - fetchHandlers: FetchHandlers -) { - // Correlate the options with the request that they're associated with - const requestOptionEntries: Array< - [SearchRequest, ISearchOptions] - > = searchRequests.map((request, i) => [request, requestsOptions[i]]); - const requestOptionsMap = new Map(requestOptionEntries); - const requestResponseMap = new Map>>(); - - const { searching, abort } = defaultSearchStrategy.search({ - searchRequests, - ...fetchHandlers, - }); - - searchRequests.forEach((request, i) => { - const response = searching.then((results) => fetchHandlers.onResponse(request, results[i])); - const { abortSignal = null } = requestOptionsMap.get(request) || {}; - if (abortSignal) abortSignal.addEventListener('abort', abort); - requestResponseMap.set(request, response); - }); - return searchRequests.map((request) => requestResponseMap.get(request)!); -} diff --git a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts b/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts deleted file mode 100644 index 9b03ebdbd116f7..00000000000000 --- a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { MockedKeys } from '@kbn/utility-types/jest'; -import { defaultSearchStrategy } from './default_search_strategy'; -import { LegacyFetchHandlers, SearchStrategySearchParams } from './types'; -import { BehaviorSubject } from 'rxjs'; - -const { search } = defaultSearchStrategy; - -describe('defaultSearchStrategy', () => { - describe('search', () => { - let searchArgs: MockedKeys; - - beforeEach(() => { - searchArgs = { - searchRequests: [ - { - index: { title: 'foo' }, - body: {}, - }, - ], - getConfig: jest.fn(), - onResponse: (req, res) => res, - legacy: { - callMsearch: jest.fn().mockResolvedValue(undefined), - loadingCount$: new BehaviorSubject(0) as any, - } as jest.Mocked, - }; - }); - - test('calls callMsearch with the correct arguments', async () => { - await search({ ...searchArgs }); - expect(searchArgs.legacy.callMsearch.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "body": Object { - "searches": Array [ - Object { - "body": Object {}, - "header": Object { - "index": "foo", - "preference": undefined, - }, - }, - ], - }, - "signal": AbortSignal {}, - }, - ], - ] - `); - }); - }); -}); diff --git a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts b/src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts deleted file mode 100644 index 16e109d65a5be9..00000000000000 --- a/src/plugins/data/common/search/search_source/legacy/default_search_strategy.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { getPreference } from '../fetch'; -import { SearchStrategyProvider, SearchStrategySearchParams } from './types'; - -// @deprecated -export const defaultSearchStrategy: SearchStrategyProvider = { - id: 'default', - - search: (params) => { - return msearch(params); - }, -}; - -function msearch({ searchRequests, getConfig, legacy }: SearchStrategySearchParams) { - const { callMsearch, loadingCount$ } = legacy; - - const requests = searchRequests.map(({ index, body }) => { - return { - header: { - index: index.title || index, - preference: getPreference(getConfig), - }, - body, - }; - }); - - const abortController = new AbortController(); - let resolved = false; - - // Start LoadingIndicator - loadingCount$.next(loadingCount$.getValue() + 1); - - const cleanup = () => { - if (!resolved) { - resolved = true; - // Decrement loading counter & cleanup BehaviorSubject - loadingCount$.next(loadingCount$.getValue() - 1); - loadingCount$.complete(); - } - }; - - const searching = callMsearch({ - body: { searches: requests }, - signal: abortController.signal, - }) - .then((res: any) => res?.body?.responses) - .finally(() => cleanup()); - - return { - abort: () => { - abortController.abort(); - cleanup(); - }, - searching, - }; -} diff --git a/src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts b/src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts deleted file mode 100644 index eca6e75fc69dee..00000000000000 --- a/src/plugins/data/common/search/search_source/legacy/fetch_soon.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { SearchResponse } from 'elasticsearch'; -import { UI_SETTINGS } from '../../../constants'; -import { GetConfigFn } from '../../../types'; -import { FetchHandlers, SearchRequest } from '../fetch'; -import { ISearchOptions } from '../../index'; -import { callClient } from './call_client'; -import { fetchSoon } from './fetch_soon'; - -function getConfigStub(config: any = {}): GetConfigFn { - return (key) => config[key]; -} - -const mockResponses: Record> = { - foo: { - took: 1, - timed_out: false, - } as SearchResponse, - bar: { - took: 2, - timed_out: false, - } as SearchResponse, - baz: { - took: 3, - timed_out: false, - } as SearchResponse, -}; - -jest.useFakeTimers(); - -jest.mock('./call_client', () => ({ - callClient: jest.fn((requests: SearchRequest[]) => { - // Allow a request object to specify which mockResponse it wants to receive (_mockResponseId) - // in addition to how long to simulate waiting before returning a response (_waitMs) - const responses = requests.map((request) => { - const waitMs = requests.reduce((total, { _waitMs }) => total + _waitMs || 0, 0); - return new Promise((resolve) => { - setTimeout(() => { - resolve(mockResponses[request._mockResponseId]); - }, waitMs); - }); - }); - return Promise.resolve(responses); - }), -})); - -describe('fetchSoon', () => { - beforeEach(() => { - (callClient as jest.Mock).mockClear(); - }); - - test('should execute asap if config is set to not batch searches', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: false }); - const request = {}; - const options = {}; - - fetchSoon(request, options, { getConfig } as FetchHandlers); - - expect(callClient).toBeCalled(); - }); - - test('should delay by 50ms if config is set to batch searches', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const request = {}; - const options = {}; - - fetchSoon(request, options, { getConfig } as FetchHandlers); - - expect(callClient).not.toBeCalled(); - jest.advanceTimersByTime(0); - expect(callClient).not.toBeCalled(); - jest.advanceTimersByTime(50); - expect(callClient).toBeCalled(); - }); - - test('should send a batch of requests to callClient', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const requests = [{ foo: 1 }, { foo: 2 }]; - const options = [{ bar: 1 }, { bar: 2 }]; - - requests.forEach((request, i) => { - fetchSoon(request, options[i] as ISearchOptions, { getConfig } as FetchHandlers); - }); - - jest.advanceTimersByTime(50); - expect(callClient).toBeCalledTimes(1); - expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(requests); - expect((callClient as jest.Mock).mock.calls[0][1]).toEqual(options); - }); - - test('should return the response to the corresponding call for multiple batched requests', async () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const requests = [{ _mockResponseId: 'foo' }, { _mockResponseId: 'bar' }]; - - const promises = requests.map((request) => { - return fetchSoon(request, {}, { getConfig } as FetchHandlers); - }); - jest.advanceTimersByTime(50); - const results = await Promise.all(promises); - - expect(results).toEqual([mockResponses.foo, mockResponses.bar]); - }); - - test('should wait for the previous batch to start before starting a new batch', () => { - const getConfig = getConfigStub({ [UI_SETTINGS.COURIER_BATCH_SEARCHES]: true }); - const firstBatch = [{ foo: 1 }, { foo: 2 }]; - const secondBatch = [{ bar: 1 }, { bar: 2 }]; - - firstBatch.forEach((request) => { - fetchSoon(request, {}, { getConfig } as FetchHandlers); - }); - jest.advanceTimersByTime(50); - secondBatch.forEach((request) => { - fetchSoon(request, {}, { getConfig } as FetchHandlers); - }); - - expect(callClient).toBeCalledTimes(1); - expect((callClient as jest.Mock).mock.calls[0][0]).toEqual(firstBatch); - - jest.advanceTimersByTime(50); - - expect(callClient).toBeCalledTimes(2); - expect((callClient as jest.Mock).mock.calls[1][0]).toEqual(secondBatch); - }); -}); diff --git a/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts b/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts deleted file mode 100644 index ff8ae2d19bd56b..00000000000000 --- a/src/plugins/data/common/search/search_source/legacy/fetch_soon.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { estypes } from '@elastic/elasticsearch'; -import { UI_SETTINGS } from '../../../constants'; -import { FetchHandlers, SearchRequest } from '../fetch'; -import { ISearchOptions } from '../../index'; -import { callClient } from './call_client'; - -/** - * This function introduces a slight delay in the request process to allow multiple requests to queue - * up (e.g. when a dashboard is loading). - */ -export async function fetchSoon( - request: SearchRequest, - options: ISearchOptions, - fetchHandlers: FetchHandlers -) { - const msToDelay = fetchHandlers.getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES) ? 50 : 0; - return delayedFetch(request, options, fetchHandlers, msToDelay); -} - -/** - * Delays executing a function for a given amount of time, and returns a promise that resolves - * with the result. - * @param fn The function to invoke - * @param ms The number of milliseconds to wait - * @return Promise A promise that resolves with the result of executing the function - */ -function delay(fn: (...args: any) => T, ms: number): Promise { - return new Promise((resolve) => { - setTimeout(() => resolve(fn()), ms); - }); -} - -// The current batch/queue of requests to fetch -let requestsToFetch: SearchRequest[] = []; -let requestOptions: ISearchOptions[] = []; - -// The in-progress fetch (if there is one) -let fetchInProgress: any = null; - -/** - * Delay fetching for a given amount of time, while batching up the requests to be fetched. - * Returns a promise that resolves with the response for the given request. - * @param request The request to fetch - * @param ms The number of milliseconds to wait (and batch requests) - * @return Promise The response for the given request - */ -async function delayedFetch( - request: SearchRequest, - options: ISearchOptions, - fetchHandlers: FetchHandlers, - ms: number -): Promise> { - if (ms === 0) { - return callClient([request], [options], fetchHandlers)[0] as Promise< - estypes.SearchResponse - >; - } - - const i = requestsToFetch.length; - requestsToFetch = [...requestsToFetch, request]; - requestOptions = [...requestOptions, options]; - - // Note: the typescript here only worked because `SearchResponse` was `any` - // Since this code is legacy, I'm leaving the any here. - const responses: any[] = await (fetchInProgress = - fetchInProgress || - delay(() => { - const response = callClient(requestsToFetch, requestOptions, fetchHandlers); - requestsToFetch = []; - requestOptions = []; - fetchInProgress = null; - return response; - }, ms)); - return responses[i]; -} diff --git a/src/plugins/data/common/search/search_source/legacy/index.ts b/src/plugins/data/common/search/search_source/legacy/index.ts index 2c90dc67954237..12594660136d8f 100644 --- a/src/plugins/data/common/search/search_source/legacy/index.ts +++ b/src/plugins/data/common/search/search_source/legacy/index.ts @@ -6,5 +6,4 @@ * Side Public License, v 1. */ -export { fetchSoon } from './fetch_soon'; export * from './types'; diff --git a/src/plugins/data/common/search/search_source/legacy/types.ts b/src/plugins/data/common/search/search_source/legacy/types.ts index a4328528fd6625..6778be77c21c5e 100644 --- a/src/plugins/data/common/search/search_source/legacy/types.ts +++ b/src/plugins/data/common/search/search_source/legacy/types.ts @@ -6,9 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject } from 'rxjs'; import type { estypes, ApiResponse } from '@elastic/elasticsearch'; -import { FetchHandlers, SearchRequest } from '../fetch'; interface MsearchHeaders { index: string; @@ -29,27 +27,3 @@ export interface MsearchRequestBody { export interface MsearchResponse { body: ApiResponse<{ responses: Array> }>; } - -// @internal -export interface LegacyFetchHandlers { - callMsearch: (params: { - body: MsearchRequestBody; - signal: AbortSignal; - }) => Promise; - loadingCount$: BehaviorSubject; -} - -export interface SearchStrategySearchParams extends FetchHandlers { - searchRequests: SearchRequest[]; -} - -// @deprecated -export interface SearchStrategyProvider { - id: string; - search: (params: SearchStrategySearchParams) => SearchStrategyResponse; -} - -export interface SearchStrategyResponse { - searching: Promise>>; - abort: () => void; -} diff --git a/src/plugins/data/common/search/search_source/mocks.ts b/src/plugins/data/common/search/search_source/mocks.ts index ade22c20596d90..64ed82f36e81bc 100644 --- a/src/plugins/data/common/search/search_source/mocks.ts +++ b/src/plugins/data/common/search/search_source/mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject, of } from 'rxjs'; +import { of } from 'rxjs'; import type { MockedKeys } from '@kbn/utility-types/jest'; import { uiSettingsServiceMock } from '../../../../../core/public/mocks'; @@ -47,8 +47,4 @@ export const createSearchSourceMock = (fields?: SearchSourceFields) => getConfig: uiSettingsServiceMock.createStartContract().get, search: jest.fn(), onResponse: jest.fn().mockImplementation((req, res) => res), - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }); diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index 012fc5257397b5..68e386acfd48c4 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -6,20 +6,15 @@ * Side Public License, v 1. */ -import { BehaviorSubject, of } from 'rxjs'; +import { of } from 'rxjs'; import { IndexPattern } from '../../index_patterns'; import { GetConfigFn } from '../../types'; -import { fetchSoon } from './legacy'; import { SearchSource, SearchSourceDependencies, SortDirection } from './'; -import { AggConfigs, AggTypesRegistryStart } from '../../'; +import { AggConfigs, AggTypesRegistryStart, ES_SEARCH_STRATEGY } from '../../'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { RequestResponder } from 'src/plugins/inspector/common'; import { switchMap } from 'rxjs/operators'; -jest.mock('./legacy', () => ({ - fetchSoon: jest.fn().mockResolvedValue({}), -})); - const getComputedFields = () => ({ storedFields: [], scriptFields: {}, @@ -89,10 +84,6 @@ describe('SearchSource', () => { getConfig: getConfigMock, search: mockSearchMethod, onResponse: (req, res) => res, - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }; searchSource = new SearchSource({}, searchSourceDependencies); @@ -869,7 +860,7 @@ describe('SearchSource', () => { }); describe('fetch$', () => { - describe('#legacy fetch()', () => { + describe('#legacy COURIER_BATCH_SEARCHES', () => { beforeEach(() => { searchSourceDependencies = { ...searchSourceDependencies, @@ -879,11 +870,22 @@ describe('SearchSource', () => { }; }); - test('should call msearch', async () => { + test('should override to use sync search if not set', async () => { searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); const options = {}; await searchSource.fetch$(options).toPromise(); - expect(fetchSoon).toBeCalledTimes(1); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe(ES_SEARCH_STRATEGY); + }); + + test('should not override strategy if set ', async () => { + searchSource = new SearchSource({ index: indexPattern }, searchSourceDependencies); + const options = { strategy: 'banana' }; + await searchSource.fetch$(options).toPromise(); + + const [, callOptions] = mockSearchMethod.mock.calls[0]; + expect(callOptions.strategy).toBe('banana'); }); }); diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index 6f34d5ce1f29cc..585126e1184d22 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -75,7 +75,7 @@ import { estypes } from '@elastic/elasticsearch'; import { normalizeSortRequest } from './normalize_sort_request'; import { fieldWildcardFilter } from '../../../../kibana_utils/common'; import { IIndexPattern, IndexPattern, IndexPatternField } from '../../index_patterns'; -import { AggConfigs, ISearchGeneric, ISearchOptions } from '../..'; +import { AggConfigs, ES_SEARCH_STRATEGY, ISearchGeneric, ISearchOptions } from '../..'; import type { ISearchSource, SearchFieldValue, @@ -95,7 +95,6 @@ import { IKibanaSearchResponse, } from '../../../common'; import { getHighlightRequest } from '../../../common/field_formats'; -import { fetchSoon } from './legacy'; import { extractReferences } from './extract_references'; /** @internal */ @@ -274,6 +273,13 @@ export class SearchSource { */ fetch$(options: ISearchOptions = {}) { const { getConfig } = this.dependencies; + const syncSearchByDefault = getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES); + + // Use the sync search strategy if legacy search is enabled. + // This still uses bfetch for batching. + if (!options?.strategy && syncSearchByDefault) { + options.strategy = ES_SEARCH_STRATEGY; + } const s$ = defer(() => this.requestIsStarting(options)).pipe( switchMap(() => { @@ -283,9 +289,7 @@ export class SearchSource { options.indexPattern = searchRequest.index; } - return getConfig(UI_SETTINGS.COURIER_BATCH_SEARCHES) - ? from(this.legacyFetch(searchRequest, options)) - : this.fetchSearch$(searchRequest, options); + return this.fetchSearch$(searchRequest, options); }), tap((response) => { // TODO: Remove casting when https://github.com/elastic/elasticsearch-js/issues/1287 is resolved @@ -477,27 +481,6 @@ export class SearchSource { ); } - /** - * Run a search using the search service - * @return {Promise>} - */ - private async legacyFetch(searchRequest: SearchRequest, options: ISearchOptions) { - const { getConfig, legacy, onResponse } = this.dependencies; - - return await fetchSoon( - searchRequest, - { - ...(this.searchStrategyId && { searchStrategyId: this.searchStrategyId }), - ...options, - }, - { - getConfig, - onResponse, - legacy, - } - ); - } - /** * Called by requests of this search source when they are started * @param options diff --git a/src/plugins/data/common/search/search_source/search_source_service.test.ts b/src/plugins/data/common/search/search_source/search_source_service.test.ts index 9e36d3c6002da4..23bb809092bde1 100644 --- a/src/plugins/data/common/search/search_source/search_source_service.test.ts +++ b/src/plugins/data/common/search/search_source/search_source_service.test.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { BehaviorSubject } from 'rxjs'; import { IndexPatternsContract } from '../../index_patterns/index_patterns'; import { SearchSourceService, SearchSourceDependencies } from './'; @@ -19,10 +18,6 @@ describe('SearchSource service', () => { getConfig: jest.fn(), search: jest.fn(), onResponse: jest.fn(), - legacy: { - callMsearch: jest.fn(), - loadingCount$: new BehaviorSubject(0), - }, }; }); diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 820619aa05ed8a..cc7228268cb7a0 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -7,8 +7,7 @@ import { $Values } from '@kbn/utility-types'; import { Action } from 'history'; import { Adapters as Adapters_2 } from 'src/plugins/inspector/common'; -import { ApiResponse } from '@elastic/elasticsearch'; -import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch/lib/Transport'; +import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; import { ApplicationStart } from 'kibana/public'; import { Assign } from '@kbn/utility-types'; import { BehaviorSubject } from 'rxjs'; diff --git a/src/plugins/data/public/search/legacy/call_msearch.test.ts b/src/plugins/data/public/search/legacy/call_msearch.test.ts deleted file mode 100644 index 0627a09e12e678..00000000000000 --- a/src/plugins/data/public/search/legacy/call_msearch.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { HttpStart } from 'src/core/public'; -import { coreMock } from '../../../../../core/public/mocks'; -import { getCallMsearch } from './call_msearch'; - -describe('callMsearch', () => { - const msearchMock = jest.fn().mockResolvedValue({ body: { responses: [] } }); - let http: jest.Mocked; - - beforeEach(() => { - msearchMock.mockClear(); - http = coreMock.createStart().http; - http.post.mockResolvedValue(msearchMock); - }); - - test('calls http.post with the correct arguments', async () => { - const searches = [{ header: { index: 'foo' }, body: {} }]; - const callMsearch = getCallMsearch({ http }); - await callMsearch({ - body: { searches }, - signal: new AbortController().signal, - }); - - expect(http.post.mock.calls).toMatchInlineSnapshot(` - Array [ - Array [ - "/internal/_msearch", - Object { - "body": "{\\"searches\\":[{\\"header\\":{\\"index\\":\\"foo\\"},\\"body\\":{}}]}", - "signal": AbortSignal {}, - }, - ], - ] - `); - }); -}); diff --git a/src/plugins/data/public/search/legacy/call_msearch.ts b/src/plugins/data/public/search/legacy/call_msearch.ts deleted file mode 100644 index f20ae322fee573..00000000000000 --- a/src/plugins/data/public/search/legacy/call_msearch.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { HttpStart } from 'src/core/public'; -import { LegacyFetchHandlers } from '../../../common/search/search_source'; - -/** - * Wrapper for calling the internal msearch endpoint from the client. - * This is needed to abstract away differences in the http service - * between client & server. - * - * @internal - */ -export function getCallMsearch({ http }: { http: HttpStart }): LegacyFetchHandlers['callMsearch'] { - return async ({ body, signal }) => { - return http.post('/internal/_msearch', { - body: JSON.stringify(body), - signal, - }); - }; -} diff --git a/src/plugins/data/public/search/legacy/index.ts b/src/plugins/data/public/search/legacy/index.ts deleted file mode 100644 index 52f576d1b2e34a..00000000000000 --- a/src/plugins/data/public/search/legacy/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export * from './call_msearch'; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index 83a44b6f68af64..ec7a486445b71e 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -35,7 +35,6 @@ import { phraseFilterFunction, esRawResponse, } from '../../common/search'; -import { getCallMsearch } from './legacy'; import { AggsService, AggsStartDependencies } from './aggs'; import { IndexPatternsContract } from '../index_patterns/index_patterns'; import { ISearchInterceptor, SearchInterceptor } from './search_interceptor'; @@ -157,10 +156,10 @@ export class SearchService implements Plugin { } public start( - { application, http, notifications, uiSettings }: CoreStart, + { http, uiSettings }: CoreStart, { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { - const search = ((request, options) => { + const search = ((request, options = {}) => { return this.searchInterceptor.search(request, options); }) as ISearchGeneric; @@ -171,10 +170,6 @@ export class SearchService implements Plugin { getConfig: uiSettings.get.bind(uiSettings), search, onResponse: handleResponse, - legacy: { - callMsearch: getCallMsearch({ http }), - loadingCount$, - }, }; return { diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 0201f3226fd38d..383e09b4a6ebe8 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { BehaviorSubject, from, Observable, throwError } from 'rxjs'; +import { from, Observable, throwError } from 'rxjs'; import { pick } from 'lodash'; import moment from 'moment'; import { @@ -36,7 +36,7 @@ import { AggsService } from './aggs'; import { FieldFormatsStart } from '../field_formats'; import { IndexPatternsServiceStart } from '../index_patterns'; -import { getCallMsearch, registerMsearchRoute, registerSearchRoute } from './routes'; +import { registerMsearchRoute, registerSearchRoute } from './routes'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './strategies/es_search'; import { DataPluginStart, DataPluginStartDependencies } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; @@ -237,14 +237,6 @@ export class SearchService implements Plugin { getConfig: (key: string): T => uiSettingsCache[key], search: this.asScoped(request).search, onResponse: (req, res) => res, - legacy: { - callMsearch: getCallMsearch({ - esClient, - globalConfig$: this.initializerContext.config.legacy.globalConfig$, - uiSettings: uiSettingsClient, - }), - loadingCount$: new BehaviorSubject(0), - }, }; return this.searchSourceService.start(scopedIndexPatterns, searchSourceDependencies); diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index be502950a84e3c..15d3f5c403b1f6 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -6,9 +6,7 @@ import { $Values } from '@kbn/utility-types'; import { Adapters } from 'src/plugins/inspector/common'; -import { ApiResponse } from '@elastic/elasticsearch'; import { Assign } from '@kbn/utility-types'; -import { BehaviorSubject } from 'rxjs'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ConfigDeprecationProvider } from '@kbn/config'; import { CoreSetup } from 'src/core/server'; diff --git a/src/plugins/data/server/ui_settings.ts b/src/plugins/data/server/ui_settings.ts index 971ae3bb7507b1..78d7c15cac5d6b 100644 --- a/src/plugins/data/server/ui_settings.ts +++ b/src/plugins/data/server/ui_settings.ts @@ -276,12 +276,12 @@ export function getUiSettings(): Record> { }, [UI_SETTINGS.COURIER_BATCH_SEARCHES]: { name: i18n.translate('data.advancedSettings.courier.batchSearchesTitle', { - defaultMessage: 'Use legacy search', + defaultMessage: 'Use sync search', }), value: false, type: 'boolean', description: i18n.translate('data.advancedSettings.courier.batchSearchesText', { - defaultMessage: `Kibana uses a new search and batching infrastructure. + defaultMessage: `Kibana uses a new asynchronous search and infrastructure. Enable this option if you prefer to fallback to the legacy synchronous behavior`, }), deprecation: { From fbc6eb733d49bf0a57aefde64370a511ef2d21ac Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 22 Apr 2021 13:12:21 +0200 Subject: [PATCH 38/65] [Ingest Pipelines] Fix descriptions not showing and minor appearance tweaks (#97799) * fix descriptions not showing and minor appearance tweaks * remove unnecessary prop * added jest test for presence of descriptions * refactored variable names Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../pipeline_processors_editor.helpers.tsx | 2 + .../pipeline_processors_editor.test.tsx | 62 +++++++++++++++++++ .../inline_text_input.tsx | 5 +- .../pipeline_processors_editor_item.tsx | 17 +++-- .../processor_information.tsx | 16 +++-- 5 files changed, 90 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx index fabb6a46c49435..4677ea4b747b5f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx @@ -92,9 +92,11 @@ const createActions = (testBed: TestBed) => { jsonContent: JSON.stringify(options), }); }); + component.update(); await act(async () => { find('addProcessorForm.submitButton').simulate('click'); }); + component.update(); }, removeProcessor(processorSelector: string) { diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx index e89e91c1cbaa91..fbc46159c4e139 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx @@ -13,6 +13,7 @@ const testProcessors: Pick = { { script: { source: 'ctx._type = null', + description: 'my script', }, }, { @@ -252,5 +253,66 @@ describe('Pipeline Editor', () => { expect(data.processors).toEqual([testProcessors.processors[1], testProcessors.processors[2]]); expect(data.on_failure).toEqual([testProcessors.processors[0]]); }); + + it('shows user provided descriptions rather than default descriptions and default descriptions rather than no description', async () => { + const { actions, find } = testBed; + + await actions.addProcessor('processors', 'test', { if: '1 == 1' }); + + const processorDescriptions = { + userProvided: 'my script', + default: 'Sets value of "test" to "test"', + none: 'No description', + }; + + const createAssertForProcessor = (processorIndex: string) => ({ + description, + descriptionVisible, + }: { + description: string; + descriptionVisible: boolean; + }) => { + expect(find(`processors>${processorIndex}.inlineTextInputNonEditableText`).text()).toBe( + description + ); + expect( + (find(`processors>${processorIndex}.pipelineProcessorItemDescriptionContainer`).props() + .className as string).includes('--displayNone') + ).toBe(!descriptionVisible); + }; + + const assertScriptProcessor = createAssertForProcessor('0'); + const assertSetProcessor = createAssertForProcessor('2'); + const assertTestProcessor = createAssertForProcessor('3'); + + assertScriptProcessor({ + description: processorDescriptions.userProvided, + descriptionVisible: true, + }); + + assertSetProcessor({ + description: processorDescriptions.default, + descriptionVisible: true, + }); + + assertTestProcessor({ description: processorDescriptions.none, descriptionVisible: true }); + + // Enter "move" mode + find('processors>0.moveItemButton').simulate('click'); + + // We expect that descriptions remain exactly the same, but the processor with "No description" has + // its description hidden + assertScriptProcessor({ + description: processorDescriptions.userProvided, + descriptionVisible: true, + }); + + assertSetProcessor({ + description: processorDescriptions.default, + descriptionVisible: true, + }); + + assertTestProcessor({ description: processorDescriptions.none, descriptionVisible: false }); + }); }); }); diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx index cd7c91685467a6..f23442426d713e 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/inline_text_input.tsx @@ -92,7 +92,10 @@ function _InlineTextInput({ > -
+
{text || {placeholder}}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx index 1ba883990ce184..4d3238bdb7e44f 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item/pipeline_processors_editor_item.tsx @@ -87,12 +87,16 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( 'pipelineProcessorsEditor__item--dimmed': isDimmed, }); + const defaultDescription = processorDescriptor?.getDefaultDescription(processor.options); + + const hasNoDescription = !defaultDescription && !processor.options.description; + const inlineTextInputContainerClasses = classNames( 'pipelineProcessorsEditor__item__descriptionContainer', { // eslint-disable-next-line @typescript-eslint/naming-convention 'pipelineProcessorsEditor__item__descriptionContainer--displayNone': - isInMoveMode && !processor.options.description, + isInMoveMode && hasNoDescription, } ); @@ -208,16 +212,17 @@ export const PipelineProcessorsEditorItem: FunctionComponent = memo( - + diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx index 5dc50625546a9e..7950313bedfa40 100644 --- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx +++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/pipeline_processors_editor_item_tooltip/processor_information.tsx @@ -15,17 +15,23 @@ interface Props { } export const ProcessorInformation: FunctionComponent = memo(({ processor }) => { - const label = getProcessorDescriptor(processor.type)?.label ?? processor.type; + const processorDescriptor = getProcessorDescriptor(processor.type); + const label = processorDescriptor?.label ?? processor.type; + const description = + processor.options.description ?? processorDescriptor?.getDefaultDescription(processor.options); + return ( - {label} + + {label} + - {processor.options.description ? ( + {description ? ( - - {processor.options.description} + + {description} ) : undefined} From c8bd4b0bf8a38b2a398535ce45e516d5b66c105c Mon Sep 17 00:00:00 2001 From: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Date: Thu, 22 Apr 2021 12:24:35 +0100 Subject: [PATCH 39/65] Use create over update (#97818) --- x-pack/plugins/infra/server/lib/sources/sources.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts index 0016e4716725da..24b204665c0143 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.ts @@ -160,12 +160,15 @@ export class InfraSources { ); const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration( - await savedObjectsClient.update( + // update() will perform a deep merge. We use create() with overwrite: true instead. mergeSourceConfiguration() + // ensures the correct and intended merging of properties. + await savedObjectsClient.create( infraSourceConfigurationSavedObjectName, - sourceId, pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any, { + id: sourceId, version, + overwrite: true, } ) ); From f094d9fdc23d0bc393c072b96e437cdefc12bc4f Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Thu, 22 Apr 2021 12:50:35 +0100 Subject: [PATCH 40/65] [Security Solution] Events integration test (#97519) * events integration test * add constants Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apis/security_solution/events.ts | 499 ++++++++++++++++++ .../apis/security_solution/index.js | 1 + 2 files changed, 500 insertions(+) create mode 100644 x-pack/test/api_integration/apis/security_solution/events.ts diff --git a/x-pack/test/api_integration/apis/security_solution/events.ts b/x-pack/test/api_integration/apis/security_solution/events.ts new file mode 100644 index 00000000000000..e5363ec57de062 --- /dev/null +++ b/x-pack/test/api_integration/apis/security_solution/events.ts @@ -0,0 +1,499 @@ +/* + * 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 { + Direction, + TimelineEventsQueries, +} from '../../../../plugins/security_solution/common/search_strategy'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +const TO = '3000-01-01T00:00:00.000Z'; +const FROM = '2000-01-01T00:00:00.000Z'; + +// typical values that have to change after an update from "scripts/es_archiver" +const DATA_COUNT = 7; +const HOST_NAME = 'suricata-sensor-amsterdam'; +const TOTAL_COUNT = 96; +const EDGE_LENGTH = 25; +const ACTIVE_PAGE = 0; +const PAGE_SIZE = 25; +const LIMITED_PAGE_SIZE = 2; + +const FILTER_VALUE = { + bool: { + filter: [ + { + bool: { + should: [{ match_phrase: { 'host.name': HOST_NAME } }], + minimum_should_match: 1, + }, + }, + { + bool: { + filter: [ + { + bool: { + should: [{ range: { '@timestamp': { gte: FROM } } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ range: { '@timestamp': { lte: TO } } }], + minimum_should_match: 1, + }, + }, + ], + }, + }, + ], + }, +}; + +/** + * https://www.elastic.co/guide/en/elasticsearch/reference/7.12/search-fields.html#docvalue-fields + * Use the docvalue_fields parameter to get values for selected fields. + * This can be a good choice when returning a fairly small number of fields that support doc values, + * such as keywords and dates. + */ +const DOC_VALUE_FIELDS = [ + { + field: '@timestamp', + }, + { + field: 'agent.ephemeral_id', + }, + { + field: 'agent.id', + }, + { + field: 'agent.name', + }, + { + field: 'agent.type', + }, + { + field: 'agent.version', + }, + { + field: 'as.number', + }, + { + field: 'as.organization.name', + }, + { + field: 'client.address', + }, + { + field: 'client.as.number', + }, + { + field: 'client.as.organization.name', + }, + { + field: 'client.bytes', + format: 'bytes', + }, + { + field: 'client.domain', + }, + { + field: 'client.geo.city_name', + }, + { + field: 'client.geo.continent_name', + }, + { + field: 'client.geo.country_iso_code', + }, + { + field: 'client.geo.country_name', + }, + { + field: 'client.geo.location', + }, + { + field: 'client.geo.name', + }, + { + field: 'client.geo.region_iso_code', + }, + { + field: 'client.geo.region_name', + }, + { + field: 'client.ip', + }, + { + field: 'client.mac', + }, + { + field: 'client.nat.ip', + }, + { + field: 'client.nat.port', + format: 'string', + }, + { + field: 'client.packets', + }, + { + field: 'client.port', + format: 'string', + }, + { + field: 'client.registered_domain', + }, + { + field: 'client.top_level_domain', + }, + { + field: 'client.user.domain', + }, + { + field: 'client.user.email', + }, + { + field: 'client.user.full_name', + }, + { + field: 'client.user.group.domain', + }, + { + field: 'client.user.group.id', + }, + { + field: 'client.user.group.name', + }, + { + field: 'client.user.hash', + }, + { + field: 'client.user.id', + }, + { + field: 'client.user.name', + }, + { + field: 'cloud.account.id', + }, + { + field: 'cloud.availability_zone', + }, + { + field: 'cloud.instance.id', + }, + { + field: 'cloud.instance.name', + }, + { + field: 'cloud.machine.type', + }, + { + field: 'cloud.provider', + }, + { + field: 'cloud.region', + }, + { + field: 'code_signature.exists', + }, + { + field: 'code_signature.status', + }, + { + field: 'code_signature.subject_name', + }, + { + field: 'code_signature.trusted', + }, + { + field: 'code_signature.valid', + }, + { + field: 'container.id', + }, + { + field: 'container.image.name', + }, + { + field: 'container.image.tag', + }, + { + field: 'container.name', + }, + { + field: 'container.runtime', + }, + { + field: 'destination.address', + }, + { + field: 'destination.as.number', + }, + { + field: 'destination.as.organization.name', + }, + { + field: 'destination.bytes', + format: 'bytes', + }, + { + field: 'destination.domain', + }, + { + field: 'destination.geo.city_name', + }, + { + field: 'destination.geo.continent_name', + }, + { + field: 'destination.geo.country_iso_code', + }, + { + field: 'destination.geo.country_name', + }, + { + field: 'destination.geo.location', + }, + { + field: 'destination.geo.name', + }, + { + field: 'destination.geo.region_iso_code', + }, + { + field: 'destination.geo.region_name', + }, + { + field: 'destination.ip', + }, + { + field: 'destination.mac', + }, + { + field: 'destination.nat.ip', + }, + { + field: 'destination.nat.port', + format: 'string', + }, + { + field: 'destination.packets', + }, + { + field: 'destination.port', + format: 'string', + }, + { + field: 'destination.registered_domain', + }, + { + field: 'destination.top_level_domain', + }, + { + field: 'destination.user.domain', + }, + { + field: 'destination.user.email', + }, + { + field: 'destination.user.full_name', + }, + { + field: 'destination.user.group.domain', + }, + { + field: 'destination.user.group.id', + }, + { + field: 'destination.user.group.name', + }, + { + field: 'destination.user.hash', + }, + { + field: 'destination.user.id', + }, + { + field: 'destination.user.name', + }, + { + field: 'dll.code_signature.exists', + }, + { + field: 'dll.code_signature.status', + }, + { + field: 'dll.code_signature.subject_name', + }, + { + field: 'dll.code_signature.trusted', + }, + { + field: 'dll.code_signature.valid', + }, + { + field: 'dll.hash.md5', + }, + { + field: 'dll.hash.sha1', + }, + { + field: 'dll.hash.sha256', + }, + { + field: 'dll.hash.sha512', + }, + { + field: 'dll.name', + }, + { + field: 'dll.path', + }, + { + field: 'dll.pe.company', + }, + { + field: 'dll.pe.description', + }, + { + field: 'dll.pe.file_version', + }, + { + field: 'dll.pe.original_file_name', + }, +]; +const FIELD_REQUESTED = [ + '@timestamp', + 'message', + 'event.category', + 'event.action', + 'host.name', + 'source.ip', + 'destination.ip', + 'user.name', + '@timestamp', + 'signal.status', + 'signal.group.id', + 'signal.original_time', + 'signal.rule.building_block_type', + 'signal.rule.filters', + 'signal.rule.from', + 'signal.rule.language', + 'signal.rule.query', + 'signal.rule.name', + 'signal.rule.to', + 'signal.rule.id', + 'signal.rule.index', + 'signal.rule.type', + 'signal.original_event.kind', + 'signal.original_event.module', + 'file.path', + 'file.Ext.code_signature.subject_name', + 'file.Ext.code_signature.trusted', + 'file.hash.sha256', + 'host.os.family', + 'event.code', +]; + +export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + + describe('Timeline', () => { + before(() => esArchiver.load('auditbeat/hosts')); + after(() => esArchiver.unload('auditbeat/hosts')); + + it('Make sure that we get Timeline data', async () => { + await retry.try(async () => { + const resp = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .set('Content-Type', 'application/json') + .send({ + defaultIndex: ['auditbeat-*'], + docValueFields: DOC_VALUE_FIELDS, + factoryQueryType: TimelineEventsQueries.all, + fieldRequested: FIELD_REQUESTED, + fields: [], + filterQuery: FILTER_VALUE, + pagination: { + activePage: 0, + querySize: 25, + }, + language: 'kuery', + sort: [ + { + field: '@timestamp', + direction: Direction.desc, + type: 'number', + }, + ], + timerange: { + from: FROM, + to: TO, + interval: '12h', + }, + }) + .expect(200); + + const timeline = resp.body; + expect(timeline.edges.length).to.be(EDGE_LENGTH); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.totalCount).to.be(TOTAL_COUNT); + expect(timeline.pageInfo.activePage).to.equal(ACTIVE_PAGE); + expect(timeline.pageInfo.querySize).to.equal(PAGE_SIZE); + }); + }); + + it('Make sure that pagination is working in Timeline query', async () => { + await retry.try(async () => { + const resp = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .set('Content-Type', 'application/json') + .send({ + defaultIndex: ['auditbeat-*'], + docValueFields: DOC_VALUE_FIELDS, + factoryQueryType: TimelineEventsQueries.all, + fieldRequested: FIELD_REQUESTED, + fields: [], + filterQuery: FILTER_VALUE, + pagination: { + activePage: 0, + querySize: LIMITED_PAGE_SIZE, + }, + language: 'kuery', + sort: [ + { + field: '@timestamp', + direction: Direction.desc, + type: 'number', + }, + ], + timerange: { + from: FROM, + to: TO, + interval: '12h', + }, + }) + .expect(200); + + const timeline = resp.body; + expect(timeline.edges.length).to.be(LIMITED_PAGE_SIZE); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.totalCount).to.be(TOTAL_COUNT); + expect(timeline.edges[0].node.data.length).to.be(DATA_COUNT); + expect(timeline.edges[0]!.node.ecs.host!.name).to.eql([HOST_NAME]); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/security_solution/index.js b/x-pack/test/api_integration/apis/security_solution/index.js index 18c315a3b8c3de..3f9afba18b9ef9 100644 --- a/x-pack/test/api_integration/apis/security_solution/index.js +++ b/x-pack/test/api_integration/apis/security_solution/index.js @@ -8,6 +8,7 @@ export default function ({ loadTestFile }) { describe('SecuritySolution Endpoints', () => { loadTestFile(require.resolve('./authentications')); + loadTestFile(require.resolve('./events')); loadTestFile(require.resolve('./hosts')); loadTestFile(require.resolve('./host_details')); loadTestFile(require.resolve('./kpi_network')); From 4295f8512461816aa6535e36496f40f6364aa572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Thu, 22 Apr 2021 14:22:42 +0200 Subject: [PATCH 41/65] [flaky-test] Unskip SOM edit_saved_object tests (#97846) --- .../apps/saved_objects_management/edit_saved_object.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/functional/apps/saved_objects_management/edit_saved_object.ts b/test/functional/apps/saved_objects_management/edit_saved_object.ts index 81569c5bfc498a..89889088bd73ba 100644 --- a/test/functional/apps/saved_objects_management/edit_saved_object.ts +++ b/test/functional/apps/saved_objects_management/edit_saved_object.ts @@ -55,8 +55,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await button.click(); }; - // Flaky: https://github.com/elastic/kibana/issues/68400 - describe.skip('saved objects edition page', () => { + describe('saved objects edition page', () => { beforeEach(async () => { await esArchiver.load('saved_objects_management/edit_saved_object'); }); From 649a2e01fc723f549b869607a0993af39a91174d Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 22 Apr 2021 08:01:45 -0500 Subject: [PATCH 42/65] [Workplace Search] PR#3358 to Kibana (#97921) * Render flash message when server sends back an error * Fetch data on all route changes This produced a bug where the nav and loading states were triggered between route changes. Added conditional to prevent resetting between in-source changes --- .../content_sources/source_logic.test.ts | 14 +++++++++++ .../views/content_sources/source_logic.ts | 6 +++++ .../content_sources/source_router.test.tsx | 24 ++++++++++++++++++- .../views/content_sources/source_router.tsx | 11 ++++++--- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts index a9712cc4e1dc09..2cf867446b7fb2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.test.ts @@ -33,6 +33,7 @@ describe('SourceLogic', () => { flashAPIErrors, setSuccessMessage, setQueuedSuccessMessage, + setErrorMessage, } = mockFlashMessageHelpers; const { navigateToUrl } = mockKibanaValues; const { mount, getListeners } = new LogicMounter(SourceLogic); @@ -204,6 +205,19 @@ describe('SourceLogic', () => { expect(navigateToUrl).toHaveBeenCalledWith(NOT_FOUND_PATH); }); + + it('renders error messages passed in success response from server', async () => { + const errors = ['ERROR']; + const promise = Promise.resolve({ + ...contentSource, + errors, + }); + http.get.mockReturnValue(promise); + SourceLogic.actions.initializeSource(contentSource.id); + await promise; + + expect(setErrorMessage).toHaveBeenCalledWith(errors); + }); }); describe('initializeFederatedSummary', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts index ff3e1e83925d03..2e6a3c65597eac 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_logic.ts @@ -13,6 +13,7 @@ import { DEFAULT_META } from '../../../shared/constants'; import { flashAPIErrors, setSuccessMessage, + setErrorMessage, setQueuedSuccessMessage, clearFlashMessages, } from '../../../shared/flash_messages'; @@ -148,6 +149,11 @@ export const SourceLogic = kea>({ if (response.isFederatedSource) { actions.initializeFederatedSummary(sourceId); } + if (response.errors) { + setErrorMessage(response.errors); + } else { + clearFlashMessages(); + } } catch (e) { if (e.response.status === 404) { KibanaLogic.values.navigateToUrl(NOT_FOUND_PATH); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx index 463468d1304b6e..528065da23af60 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx @@ -8,6 +8,8 @@ import '../../../__mocks__/shallow_useeffect.mock'; import { setMockValues, setMockActions } from '../../../__mocks__'; +import { mockLocation } from '../../../__mocks__/react_router_history.mock'; +import { unmountHandler } from '../../../__mocks__/shallow_useeffect.mock'; import { contentSources } from '../../__mocks__/content_sources.mock'; import React from 'react'; @@ -30,6 +32,7 @@ import { SourceRouter } from './source_router'; describe('SourceRouter', () => { const initializeSource = jest.fn(); + const resetSourceState = jest.fn(); const contentSource = contentSources[1]; const customSource = contentSources[0]; const mockValues = { @@ -40,10 +43,11 @@ describe('SourceRouter', () => { beforeEach(() => { setMockActions({ initializeSource, + resetSourceState, }); setMockValues({ ...mockValues }); (useParams as jest.Mock).mockImplementationOnce(() => ({ - sourceId: '1', + sourceId: contentSource.id, })); }); @@ -114,4 +118,22 @@ describe('SourceRouter', () => { NAV.DISPLAY_SETTINGS, ]); }); + + describe('reset state', () => { + it('does not reset state when switching between source tree views', () => { + mockLocation.pathname = `/sources/${contentSource.id}`; + shallow(); + unmountHandler(); + + expect(resetSourceState).not.toHaveBeenCalled(); + }); + + it('resets state when leaving source tree', () => { + mockLocation.pathname = '/home'; + shallow(); + unmountHandler(); + + expect(resetSourceState).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index b14ea4ebd7a736..cd20e32def16df 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -6,7 +6,8 @@ */ import React, { useEffect } from 'react'; -import { Route, Switch, useParams } from 'react-router-dom'; + +import { Route, Switch, useLocation, useParams } from 'react-router-dom'; import { useActions, useValues } from 'kea'; import moment from 'moment'; @@ -47,14 +48,18 @@ import { SourceLogic } from './source_logic'; export const SourceRouter: React.FC = () => { const { sourceId } = useParams() as { sourceId: string }; + const { pathname } = useLocation(); const { initializeSource, resetSourceState } = useActions(SourceLogic); const { contentSource, dataLoading } = useValues(SourceLogic); const { isOrganization } = useValues(AppLogic); useEffect(() => { initializeSource(sourceId); - return resetSourceState; - }, []); + return () => { + // We only want to reset the state when leaving the source section. Otherwise there is an unwanted flash of UI. + if (!pathname.includes(sourceId)) resetSourceState(); + }; + }, [pathname]); if (dataLoading) return ; From ff0276b6a21eee7fcb714f3a593eabd57786ac56 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Thu, 22 Apr 2021 15:14:17 +0200 Subject: [PATCH 43/65] Block value `0` for terms.min_doc_count aggregation (#97966) * Block value `0` for terms.min_doc_count aggregation * do the same for histogram --- .../aggregations/aggs_types/bucket_aggs.ts | 4 +- .../aggregations/aggs_types/schemas.test.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts index 1508cab69a0486..599c32137c553b 100644 --- a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/bucket_aggs.ts @@ -49,7 +49,7 @@ export const bucketAggsSchemas: Record = { histogram: s.object({ field: s.maybe(s.string()), interval: s.maybe(s.number()), - min_doc_count: s.maybe(s.number()), + min_doc_count: s.maybe(s.number({ min: 1 })), extended_bounds: s.maybe( s.object({ min: s.number(), @@ -78,7 +78,7 @@ export const bucketAggsSchemas: Record = { include: s.maybe(s.oneOf([s.string(), s.arrayOf(s.string())])), execution_hint: s.maybe(s.string()), missing: s.maybe(s.number()), - min_doc_count: s.maybe(s.number()), + min_doc_count: s.maybe(s.number({ min: 1 })), size: s.maybe(s.number()), show_term_doc_count_error: s.maybe(s.boolean()), order: s.maybe(s.oneOf([s.literal('asc'), s.literal('desc')])), diff --git a/src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts new file mode 100644 index 00000000000000..33f7ca12abc53b --- /dev/null +++ b/src/core/server/saved_objects/service/lib/aggregations/aggs_types/schemas.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { bucketAggsSchemas } from './bucket_aggs'; + +describe('bucket aggregation schemas', () => { + describe('terms aggregation schema', () => { + const schema = bucketAggsSchemas.terms; + + it('passes validation when using `1` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 1 })).not.toThrow(); + }); + + // see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#_minimum_document_count_4 + // Setting min_doc_count=0 will also return buckets for terms that didn’t match any hit, + // bypassing any filtering perform via `filter` or `query` + // causing a potential security issue as we can return values from other spaces. + it('throws an error when using `0` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 0 })).toThrowErrorMatchingInlineSnapshot( + `"[min_doc_count]: Value must be equal to or greater than [1]."` + ); + }); + }); + + describe('histogram aggregation schema', () => { + const schema = bucketAggsSchemas.histogram; + + it('passes validation when using `1` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 1 })).not.toThrow(); + }); + + it('throws an error when using `0` for `min_doc_count`', () => { + expect(() => schema.validate({ min_doc_count: 0 })).toThrowErrorMatchingInlineSnapshot( + `"[min_doc_count]: Value must be equal to or greater than [1]."` + ); + }); + }); +}); From 158fff3297aafda63f193a2d50ee7d6dc2940b26 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 22 Apr 2021 07:51:30 -0600 Subject: [PATCH 44/65] [Maps] fix cannot read propery 'getImage' of undefined (#97829) --- .../connected_components/mb_map/mb_map.tsx | 29 +++++++++++++------ .../connected_components/mb_map/utils.js | 5 ---- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 66c9a2462736af..9ec6cbcb5d4ac0 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -26,7 +26,7 @@ import { getInitialView } from './get_initial_view'; import { getPreserveDrawingBuffer } from '../../kibana_services'; import { ILayer } from '../../classes/layers/layer'; import { MapSettings } from '../../reducers/map'; -import { Goto } from '../../../common/descriptor_types'; +import { Goto, MapCenterAndZoom } from '../../../common/descriptor_types'; import { DECIMAL_DEGREES_PRECISION, KBN_TOO_MANY_FEATURES_IMAGE_ID, @@ -35,8 +35,12 @@ import { } from '../../../common/constants'; import { getGlyphUrl, isRetina } from '../../util'; import { syncLayerOrder } from './sort_layers'; -// @ts-expect-error -import { removeOrphanedSourcesAndLayers, addSpritesheetToMap } from './utils'; +import { + addSpriteSheetToMapFromImageData, + loadSpriteSheetImageData, + removeOrphanedSourcesAndLayers, + // @ts-expect-error +} from './utils'; import { ResizeChecker } from '../../../../../../src/plugins/kibana_utils/public'; import { GeoFieldWithIndex } from '../../components/geo_field_with_index'; import { RenderToolTipContent } from '../../classes/tooltips/tooltip_property'; @@ -172,8 +176,7 @@ export class MBMap extends Component { }; } - async _createMbMapInstance(): Promise { - const initialView = await getInitialView(this.props.goto, this.props.settings); + async _createMbMapInstance(initialView: MapCenterAndZoom | null): Promise { return new Promise((resolve) => { const mbStyle = { version: 8, @@ -237,9 +240,14 @@ export class MBMap extends Component { } async _initializeMap() { + const initialView = await getInitialView(this.props.goto, this.props.settings); + if (!this._isMounted) { + return; + } + let mbMap: MapboxMap; try { - mbMap = await this._createMbMapInstance(); + mbMap = await this._createMbMapInstance(initialView); } catch (error) { this.props.setMapInitError(error.message); return; @@ -293,10 +301,13 @@ export class MBMap extends Component { }); } - _loadMakiSprites(mbMap: MapboxMap) { - const sprites = isRetina() ? sprites2 : sprites1; + async _loadMakiSprites(mbMap: MapboxMap) { + const spritesUrl = isRetina() ? sprites2 : sprites1; const json = isRetina() ? spritesheet[2] : spritesheet[1]; - addSpritesheetToMap(json, sprites, mbMap); + const spritesData = await loadSpriteSheetImageData(spritesUrl); + if (this._isMounted) { + addSpriteSheetToMapFromImageData(json, spritesData, mbMap); + } } _syncMbMapWithMapState = () => { diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/utils.js b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js index f79f9bdffe366d..5a2a98a24fca15 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/utils.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/utils.js @@ -51,11 +51,6 @@ export function removeOrphanedSourcesAndLayers(mbMap, layerList, spatialFilterLa mbSourcesToRemove.forEach((mbSourceId) => mbMap.removeSource(mbSourceId)); } -export async function addSpritesheetToMap(json, imgUrl, mbMap) { - const imgData = await loadSpriteSheetImageData(imgUrl); - addSpriteSheetToMapFromImageData(json, imgData, mbMap); -} - function getImageData(img) { const canvas = window.document.createElement('canvas'); const context = canvas.getContext('2d'); From fecdd45d93dc105264017bbe04dbf1f96102bcfb Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Thu, 22 Apr 2021 08:59:24 -0500 Subject: [PATCH 45/65] [ML] Fix Data Visualizer event rate chart empty for some indices when using long time range (#97655) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../content_types/document_count_content.tsx | 1 + .../document_count_chart.tsx | 22 ++++++++++++++++--- .../stats_table/types/field_vis_config.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx index 1fcc301fbdba72..588d85f24a0237 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/content_types/document_count_content.tsx @@ -41,6 +41,7 @@ export const DocumentCountContent: FC = ({ config, totalCount }) => { chartPoints={chartPoints} timeRangeEarliest={timeRangeEarliest} timeRangeLatest={timeRangeLatest} + interval={documentCounts.interval} /> ); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx index 4d0f4323753303..4c8740cc76b6fc 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/document_count_chart/document_count_chart.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC } from 'react'; +import React, { FC, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; @@ -29,6 +29,7 @@ interface Props { chartPoints: DocumentCountChartPoint[]; timeRangeEarliest: number; timeRangeLatest: number; + interval?: number; } const SPEC_ID = 'document_count'; @@ -38,6 +39,7 @@ export const DocumentCountChart: FC = ({ chartPoints, timeRangeEarliest, timeRangeLatest, + interval, }) => { const seriesName = i18n.translate('xpack.ml.fieldDataCard.documentCountChart.seriesLabel', { defaultMessage: 'document count', @@ -50,6 +52,21 @@ export const DocumentCountChart: FC = ({ const dateFormatter = niceTimeFormatter([timeRangeEarliest, timeRangeLatest]); + const adjustedChartPoints = useMemo(() => { + // Display empty chart when no data in range + if (chartPoints.length < 1) return [{ time: timeRangeEarliest, value: 0 }]; + + // If chart has only one bucket + // it won't show up correctly unless we add an extra data point + if (chartPoints.length === 1) { + return [ + ...chartPoints, + { time: interval ? Number(chartPoints[0].time) + interval : timeRangeEarliest, value: 0 }, + ]; + } + return chartPoints; + }, [chartPoints, timeRangeEarliest, timeRangeLatest, interval]); + return (
= ({ yScaleType={ScaleType.Linear} xAccessor="time" yAccessors={['value']} - // Display empty chart when no data in range - data={chartPoints.length > 0 ? chartPoints : [{ time: timeRangeEarliest, value: 0 }]} + data={adjustedChartPoints} />
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts index 0bf3b951f42461..aa7bd2f5ecf6db 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/types/field_vis_config.ts @@ -39,6 +39,7 @@ export interface FieldVisStats { latest?: number; documentCounts?: { buckets?: DocumentCountBuckets; + interval?: number; }; avg?: number; distribution?: { From 98c7d7da0a4806b47a13c156ec788caa4823cd32 Mon Sep 17 00:00:00 2001 From: Eric Wei Date: Thu, 22 Apr 2021 22:32:02 +0800 Subject: [PATCH 46/65] #95263 Set kbn-href when location changed (#95377) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/monitoring/public/angular/app_modules.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/monitoring/public/angular/app_modules.ts b/x-pack/plugins/monitoring/public/angular/app_modules.ts index 8fa0629d013cf7..71dc4919237e5f 100644 --- a/x-pack/plugins/monitoring/public/angular/app_modules.ts +++ b/x-pack/plugins/monitoring/public/angular/app_modules.ts @@ -232,6 +232,11 @@ function createHrefModule(core: CoreStart) { $attr.$set('href', core.http.basePath.prepend(url)); } }); + + _$scope.$on('$locationChangeSuccess', () => { + const url = getSafeForExternalLink($attr.href as string); + $attr.$set('href', core.http.basePath.prepend(url)); + }); }, }, }; From 967b17275b4671de80aa79681284dd52df8162c2 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 22 Apr 2021 10:32:22 -0400 Subject: [PATCH 47/65] ensure progress is updated correctly (#97889) --- .../components/create_step_footer/create_step_footer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx index edbfc11343e38d..3123a43594c937 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step_footer/create_step_footer.tsx @@ -83,10 +83,12 @@ export const CreateStepFooter: FC = ({ jobId, jobType, showProgress }) => } setCurrentProgress(progressStats); + // Clear if job is completed or stopped (after having started) if ( (progressStats.currentPhase === progressStats.totalPhases && progressStats.progress === 100) || - jobStats.state === DATA_FRAME_TASK_STATE.STOPPED + (jobStats.state === DATA_FRAME_TASK_STATE.STOPPED && + !(progressStats.currentPhase === 1 && progressStats.progress === 0)) ) { clearInterval(interval); // Check job has started. Jobs that fail to start will also have STOPPED state From 65287dffaddf6e6b481ad10c01bf2d3deeac6ee1 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 22 Apr 2021 09:43:53 -0500 Subject: [PATCH 48/65] [Enterprise Search] Fix unstyled UI for Schema Errors (#97776) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor main component * Refactor faux view button The original design called for a button-looking component on the accordion header so it looks clickable to reveal the error underneath. Using a button element caused the console to error because the entire header is a button and this is a child. Adding an href fixes the error and allows for styling as a button * Use empty button rather than styles components for view button * Remove extra classNames and fix layout to stretch cells * Add stylesheet Without the width: 100% on .euiIEFlexWrapFix, the header table does not stretch across the screen. Couldn’t find another way around this and happy to take suggestions of a better idea. * Add prop to prevent line break on IDs Existing implementation was causing multi-character IDs to break to a new line. So for 12, its was: 1 2 --- .../schema/schema_errors_accordion.scss | 20 +++++++ .../schema/schema_errors_accordion.test.tsx | 6 +- .../shared/schema/schema_errors_accordion.tsx | 55 +++++++++---------- .../schema/schema_change_errors.tsx | 19 +++---- 4 files changed, 55 insertions(+), 45 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss new file mode 100644 index 00000000000000..e8e55ad2827c5b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.scss @@ -0,0 +1,20 @@ +/* + * 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. + */ + +.schemaFieldError { + border-top: 1px solid $euiColorLightShade; + + &:last-child { + border-bottom: 1px solid $euiColorLightShade; + } + + // Something about the EuiFlexGroup being inside a button collapses the row of items. + // This wrapper div was injected by EUI and had 'with: auto' on it. + .euiIEFlexWrapFix { + width: 100%; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx index a82f9e9b6113b5..a15d39c4471269 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.test.tsx @@ -11,7 +11,7 @@ import { shallow } from 'enzyme'; import { EuiAccordion, EuiTableRow } from '@elastic/eui'; -import { EuiLinkTo } from '../react_router_helpers'; +import { EuiButtonEmptyTo } from '../react_router_helpers'; import { SchemaErrorsAccordion } from './schema_errors_accordion'; @@ -40,12 +40,12 @@ describe('SchemaErrorsAccordion', () => { expect(wrapper.find(EuiAccordion)).toHaveLength(1); expect(wrapper.find(EuiTableRow)).toHaveLength(2); - expect(wrapper.find(EuiLinkTo)).toHaveLength(0); + expect(wrapper.find(EuiButtonEmptyTo)).toHaveLength(0); }); it('renders document buttons', () => { const wrapper = shallow(); - expect(wrapper.find(EuiLinkTo)).toHaveLength(2); + expect(wrapper.find(EuiButtonEmptyTo)).toHaveLength(2); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx index c41781deafb959..09f499e540e932 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/schema/schema_errors_accordion.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { EuiAccordion, + EuiButton, EuiFlexGroup, EuiFlexItem, EuiTable, @@ -19,10 +20,12 @@ import { EuiTableRowCell, } from '@elastic/eui'; -import { EuiLinkTo } from '../react_router_helpers'; +import { EuiButtonEmptyTo } from '../react_router_helpers'; import { TruncatedContent } from '../truncate'; +import './schema_errors_accordion.scss'; + import { ERROR_TABLE_ID_HEADER, ERROR_TABLE_ERROR_HEADER, @@ -60,14 +63,19 @@ export const SchemaErrorsAccordion: React.FC = ({ - - + + + + - {schema[fieldName]} + {schema[fieldName]} - {ERROR_TABLE_REVIEW_CONTROL} + {/* href is needed here because a button cannot be nested in a button or console will error and EuiAccordion uses a button to wrap this. */} + + {ERROR_TABLE_REVIEW_CONTROL} + ); @@ -76,12 +84,12 @@ export const SchemaErrorsAccordion: React.FC = ({ - + {ERROR_TABLE_ID_HEADER} {ERROR_TABLE_ERROR_HEADER} @@ -93,34 +101,21 @@ export const SchemaErrorsAccordion: React.FC = ({ const documentPath = getRoute && itemId ? getRoute(itemId, error.external_id) : ''; const viewButton = showViewButton && ( - - - - {ERROR_TABLE_VIEW_LINK} - - + + {ERROR_TABLE_VIEW_LINK} ); return ( - - -
- -
-
- - {error.error} + + + + {error.error} {showViewButton ? viewButton : } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx index 29cb2b7589220b..7f7b26e380c55d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/schema/schema_change_errors.tsx @@ -10,8 +10,6 @@ import { useParams } from 'react-router-dom'; import { useActions, useValues } from 'kea'; -import { EuiSpacer } from '@elastic/eui'; - import { SchemaErrorsAccordion } from '../../../../../shared/schema/schema_errors_accordion'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; @@ -32,16 +30,13 @@ export const SchemaChangeErrors: React.FC = () => { }, []); return ( -
+ <> - -
- -
-
+ + ); }; From 2f679e6df3c349012c34e76708afb13c99ea3419 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Thu, 22 Apr 2021 08:57:18 -0600 Subject: [PATCH 49/65] [Security Solutions] Critical bug to add network responses to error toasters (#97945) ## Summary When we updated our codebase to use the newer bsearch/async search, we ended up using error messages which were not the same as we had before which would show the network errors in the full message when the "see full error button" was clicked. This has been bad lately as users and quality assurance people have been posting screen shots that have very little information, blank error messages, and/or stack traces instead of network errors. This makes them think the code has issues when it is a configuration issue or a networking issue that is happening. This PR does the following: * Changes all the bsearch queries to use the use useAppToasts * Modifies the useAppToasts to be able to transform bsearch with the kibana global notification * Cleans up the useAppToasts some * Deprecates the GlobalErrorToaster in favor of the useAppToasts * Fixes and adds a few i18n missing strings found * Removes most of the deprecated error dispatch toasters from detection_engine except for 1 place where it is not a hook. Before screen shot of errors with no buttons and messages that were not pointing to network errors: Screen Shot 2021-04-21 at 4 24 45 PM After screen shot where you have a button and that button will show you the network error: Screen Shot 2021-04-21 at 3 26 12 PM Screen Shot 2021-04-21 at 3 26 21 PM You can manually test this easily by making non ECS indexes to cause errors and then add them as a kibana index and use them in the data sourcer. ### 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 --- .../common/components/toasters/index.tsx | 42 +++++++++++ .../common/components/toasters/utils.ts | 16 +++-- .../events/last_event_time/index.test.ts | 5 ++ .../events/last_event_time/index.ts | 12 ++-- .../containers/matrix_histogram/index.ts | 11 +-- .../public/common/containers/source/index.tsx | 21 +++--- .../containers/sourcerer/index.test.tsx | 5 ++ .../common/hooks/eql/use_eql_preview.test.ts | 29 ++++---- .../common/hooks/eql/use_eql_preview.ts | 12 ++-- .../public/common/hooks/translations.ts | 7 ++ .../common/hooks/use_app_toasts.mock.ts | 1 + .../common/hooks/use_app_toasts.test.ts | 38 +++++----- .../public/common/hooks/use_app_toasts.ts | 70 ++++++++++++++----- .../public/common/utils/api/index.ts | 4 +- .../load_empty_prompt.test.tsx | 11 +++ .../alerts/use_privilege_user.test.tsx | 11 +++ .../alerts/use_privilege_user.tsx | 8 +-- .../alerts/use_signal_index.test.tsx | 8 +++ .../alerts/use_signal_index.tsx | 10 +-- .../rules/rules_table/use_rules.test.tsx | 7 ++ .../rules/rules_table/use_rules.tsx | 6 +- .../rules/rules_table/use_rules_table.ts | 6 +- .../rules/use_create_rule.test.tsx | 11 +++ .../rules/use_create_rule.tsx | 8 +-- .../rules/use_pre_packaged_rules.test.tsx | 9 +++ .../rules/use_pre_packaged_rules.tsx | 20 +++--- .../detection_engine/rules/use_rule.test.tsx | 11 +++ .../detection_engine/rules/use_rule.tsx | 8 +-- .../rules/use_rule_status.test.tsx | 6 ++ .../rules/use_rule_status.tsx | 14 ++-- .../detection_engine/rules/use_tags.test.tsx | 11 +++ .../detection_engine/rules/use_tags.tsx | 8 +-- .../rules/use_update_rule.test.tsx | 11 +++ .../rules/use_update_rule.tsx | 8 +-- .../rules/all/exceptions/exceptions_table.tsx | 11 +-- .../rules_table_filters.test.tsx | 11 +++ .../rules/create/index.test.tsx | 10 +++ .../rules/edit/index.test.tsx | 10 +++ .../detection_engine/rules/index.test.tsx | 7 ++ .../containers/authentications/index.tsx | 11 +-- .../hosts/containers/hosts/details/index.tsx | 12 ++-- .../hosts/first_last_seen/index.tsx | 12 ++-- .../public/hosts/containers/hosts/index.tsx | 11 +-- .../kpi_hosts/authentications/index.tsx | 12 ++-- .../containers/kpi_hosts/hosts/index.tsx | 12 ++-- .../containers/kpi_hosts/unique_ips/index.tsx | 12 ++-- .../containers/uncommon_processes/index.tsx | 11 +-- .../network/containers/details/index.tsx | 12 ++-- .../containers/kpi_network/dns/index.tsx | 12 ++-- .../kpi_network/network_events/index.tsx | 12 ++-- .../kpi_network/tls_handshakes/index.tsx | 12 ++-- .../kpi_network/unique_flows/index.tsx | 12 ++-- .../kpi_network/unique_private_ips/index.tsx | 12 ++-- .../network/containers/network_dns/index.tsx | 12 ++-- .../network/containers/network_http/index.tsx | 12 ++-- .../network_top_countries/index.tsx | 12 ++-- .../containers/network_top_n_flow/index.tsx | 12 ++-- .../public/network/containers/tls/index.tsx | 12 ++-- .../public/network/containers/users/index.tsx | 12 ++-- .../containers/overview_host/index.tsx | 12 ++-- .../containers/overview_network/index.tsx | 12 ++-- .../timelines/containers/details/index.tsx | 13 ++-- .../containers/details/translations.ts | 22 ++++++ .../timelines/containers/index.test.tsx | 5 ++ .../public/timelines/containers/index.tsx | 11 +-- .../timelines/containers/kpis/index.tsx | 12 ++-- .../timelines/containers/kpis/translations.ts | 22 ++++++ 67 files changed, 583 insertions(+), 277 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts create mode 100644 x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx index ea17b03082751a..b9dd782d8a6532 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/toasters/index.tsx @@ -16,34 +16,58 @@ import * as i18n from './translations'; export * from './utils'; export * from './errors'; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export interface AppToast extends Toast { errors?: string[]; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ interface ToastState { toasts: AppToast[]; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const initialToasterState: ToastState = { toasts: [], }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export type ActionToaster = | { type: 'addToaster'; toast: AppToast } | { type: 'deleteToaster'; id: string } | { type: 'toggleWaitToShowNextToast' }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const StateToasterContext = createContext<[ToastState, Dispatch]>([ initialToasterState, () => noop, ]); +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const useStateToaster = () => useContext(StateToasterContext); +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ interface ManageGlobalToasterProps { children: React.ReactNode; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const ManageGlobalToaster = ({ children }: ManageGlobalToasterProps) => { const reducerToaster = (state: ToastState, action: ActionToaster) => { switch (action.type) { @@ -63,16 +87,25 @@ export const ManageGlobalToaster = ({ children }: ManageGlobalToasterProps) => { ); }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const GlobalToasterListContainer = styled.div` position: absolute; right: 0; bottom: 0; `; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ interface GlobalToasterProps { toastLifeTimeMs?: number; } +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ export const GlobalToaster = ({ toastLifeTimeMs = 5000 }: GlobalToasterProps) => { const [{ toasts }, dispatch] = useStateToaster(); const [isShowing, setIsShowing] = useState(false); @@ -108,6 +141,9 @@ export const GlobalToaster = ({ toastLifeTimeMs = 5000 }: GlobalToasterProps) => ); }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const formatToErrorToastIfNeeded = ( toast: AppToast, toggle: (toast: AppToast) => void @@ -129,8 +165,14 @@ const formatToErrorToastIfNeeded = ( return toast; }; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ const ErrorToastContainer = styled.div` text-align: right; `; +/** + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead + */ ErrorToastContainer.displayName = 'ErrorToastContainer'; diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts index 9ce8ec0cb6fd3c..70e095c88576f9 100644 --- a/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts +++ b/x-pack/plugins/security_solution/public/common/components/toasters/utils.ts @@ -15,7 +15,7 @@ import { isAppError } from '../../utils/api'; /** * Displays an error toast for the provided title and message - * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param errorTitle Title of error to display in toaster and modal * @param errorMessages Message to display in error modal when clicked * @param dispatchToaster provided by useStateToaster() @@ -41,7 +41,7 @@ export const displayErrorToast = ( /** * Displays a warning toast for the provided title and message - * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param title warning message to display in toaster and modal * @param dispatchToaster provided by useStateToaster() * @param id unique ID if necessary @@ -65,7 +65,7 @@ export const displayWarningToast = ( /** * Displays a success toast for the provided title and message - * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param title success message to display in toaster and modal * @param dispatchToaster provided by useStateToaster() */ @@ -92,8 +92,16 @@ export type ErrorToToasterArgs = Partial & { }; /** - * Displays an error toast with messages parsed from the error + * Displays an error toast with messages parsed from the error. + * + * This has shortcomings and bugs compared to using the use_app_toasts because it takes naive guesses at the + * underlying data structure and does not display much about the error. This is not compatible with bsearch (async search) + * and sometimes can display to the user blank messages. + * + * The use_app_toasts has more feature rich logic and uses the Kibana toaster system to figure out which type of + * error you have in a more robust way then this function does and supersedes this function. * + * @deprecated Use x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts instead * @param title error message to display in toaster and modal * @param error the error from which messages will be parsed * @param dispatchToaster provided by useStateToaster() diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts index 4f12ec2e5de2d2..21791952fec067 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.test.ts @@ -43,6 +43,11 @@ const mockUseKibana = { jest.mock('../../../../common/lib/kibana', () => ({ useKibana: jest.fn(), + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), })); describe('useTimelineLastEventTime', () => { diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts index 0a7df66f6c1d5c..3e690e50b04b14 100644 --- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts @@ -25,6 +25,7 @@ import { } from '../../../../../../../../src/plugins/data/common'; import * as i18n from './translations'; import { DocValueFields } from '../../../../../common/search_strategy'; +import { useAppToasts } from '../../../hooks/use_app_toasts'; export interface UseTimelineLastEventTimeArgs { lastSeen: string | null; @@ -45,7 +46,7 @@ export const useTimelineLastEventTime = ({ indexNames, details, }: UseTimelineLastEventTimeProps): [boolean, UseTimelineLastEventTimeArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -69,6 +70,7 @@ export const useTimelineLastEventTime = ({ refetch: refetch.current, errorMessage: undefined, }); + const { addError, addWarning } = useAppToasts(); const timelineLastEventTimeSearch = useCallback( (request: TimelineEventsLastEventTimeRequestOptions) => { @@ -96,15 +98,13 @@ export const useTimelineLastEventTime = ({ })); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_LAST_EVENT_TIME); + addWarning(i18n.ERROR_LAST_EVENT_TIME); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_LAST_EVENT_TIME, - text: msg.message, }); setTimelineLastEventTimeResponse((prevResponse) => ({ ...prevResponse, @@ -118,7 +118,7 @@ export const useTimelineLastEventTime = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts index 7884c7bd002636..19c706b86577dd 100644 --- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts +++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts @@ -24,6 +24,7 @@ import { isErrorResponse, isCompleteResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../hooks/use_app_toasts'; export type Buckets = Array<{ key: string; @@ -60,7 +61,7 @@ export const useMatrixHistogram = ({ UseMatrixHistogramArgs, (to: string, from: string) => void ] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -83,6 +84,7 @@ export const useMatrixHistogram = ({ ...(isPtrIncluded != null ? { isPtrIncluded } : {}), ...(!isEmpty(docValueFields) ? { docValueFields } : {}), }); + const { addError, addWarning } = useAppToasts(); const [matrixHistogramResponse, setMatrixHistogramResponse] = useState({ data: [], @@ -126,14 +128,13 @@ export const useMatrixHistogram = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_MATRIX_HISTOGRAM); + addWarning(i18n.ERROR_MATRIX_HISTOGRAM); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addError(msg, { + addError(msg, { title: errorMessage ?? i18n.FAIL_MATRIX_HISTOGRAM, }); searchSubscription$.current.unsubscribe(); @@ -145,7 +146,7 @@ export const useMatrixHistogram = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, errorMessage, notifications.toasts] + [data.search, errorMessage, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx index f66b060b166bf8..1c17f95bb6ba04 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx @@ -26,6 +26,7 @@ import * as i18n from './translations'; import { SourcererScopeName } from '../../store/sourcerer/model'; import { sourcererActions, sourcererSelectors } from '../../store/sourcerer'; import { DocValueFields } from '../../../../common/search_strategy/common'; +import { useAppToasts } from '../../hooks/use_app_toasts'; export { BrowserField, BrowserFields, DocValueFields }; @@ -125,7 +126,7 @@ export const useFetchIndex = ( indexNames: string[], onlyCheckIfIndicesExist: boolean = false ): [boolean, FetchIndexReturn] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const previousIndexesName = useRef([]); @@ -138,6 +139,7 @@ export const useFetchIndex = ( indexExists: true, indexPatterns: DEFAULT_INDEX_PATTERNS, }); + const { addError, addWarning } = useAppToasts(); const indexFieldsSearch = useCallback( (iNames) => { @@ -168,14 +170,13 @@ export const useFetchIndex = ( searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + addWarning(i18n.ERROR_BEAT_FIELDS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ - text: msg.message, + addError(msg, { title: i18n.FAIL_BEAT_FIELDS, }); searchSubscription$.current.unsubscribe(); @@ -186,7 +187,7 @@ export const useFetchIndex = ( abortCtrl.current.abort(); asyncSearch(); }, - [data.search, notifications.toasts, onlyCheckIfIndicesExist] + [data.search, addError, addWarning, onlyCheckIfIndicesExist] ); useEffect(() => { @@ -203,7 +204,7 @@ export const useFetchIndex = ( }; export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const dispatch = useDispatch(); @@ -215,6 +216,7 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { indexNames: string[]; previousIndexNames: string; }>((state) => indexNamesSelectedSelector(state, sourcererScopeName)); + const { addError, addWarning } = useAppToasts(); const setLoading = useCallback( (loading: boolean) => { @@ -257,14 +259,13 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS); + addWarning(i18n.ERROR_BEAT_FIELDS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ - text: msg.message, + addError(msg, { title: i18n.FAIL_BEAT_FIELDS, }); searchSubscription$.current.unsubscribe(); @@ -275,7 +276,7 @@ export const useIndexFields = (sourcererScopeName: SourcererScopeName) => { abortCtrl.current.abort(); asyncSearch(); }, - [data.search, dispatch, notifications.toasts, setLoading, sourcererScopeName] + [data.search, dispatch, addError, addWarning, setLoading, sourcererScopeName] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx index 542369fdf5aa36..702a532949428d 100644 --- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx @@ -51,6 +51,11 @@ jest.mock('../../utils/route/use_route_spy', () => ({ useRouteSpy: () => [mockRouteSpy], })); jest.mock('../../lib/kibana', () => ({ + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), useKibana: jest.fn().mockReturnValue({ services: { application: { diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts index 2afe14644f5e94..b1cd14fa039b56 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.test.ts @@ -17,8 +17,10 @@ import { EqlSearchResponse } from '../../../../common/detection_engine/types'; import { useKibana } from '../../../common/lib/kibana'; import { useEqlPreview } from '.'; import { getMockEqlResponse } from './eql_search_response.mock'; +import { useAppToasts } from '../use_app_toasts'; jest.mock('../../../common/lib/kibana'); +jest.mock('../use_app_toasts'); describe('useEqlPreview', () => { const params = { @@ -29,10 +31,19 @@ describe('useEqlPreview', () => { from: '2020-10-04T15:00:54.368707900Z', }; - beforeEach(() => { - useKibana().services.notifications.toasts.addError = jest.fn(); + let addErrorMock: jest.Mock; + let addSuccessMock: jest.Mock; + let addWarningMock: jest.Mock; - useKibana().services.notifications.toasts.addWarning = jest.fn(); + beforeEach(() => { + addErrorMock = jest.fn(); + addSuccessMock = jest.fn(); + addWarningMock = jest.fn(); + (useAppToasts as jest.Mock).mockImplementation(() => ({ + addError: addErrorMock, + addWarning: addWarningMock, + addSuccess: addSuccessMock, + })); (useKibana().services.data.search.search as jest.Mock).mockReturnValue( of(getMockEqlResponse()) @@ -134,11 +145,8 @@ describe('useEqlPreview', () => { result.current[1](params); - const mockCalls = (useKibana().services.notifications.toasts.addWarning as jest.Mock).mock - .calls; - expect(result.current[0]).toBeFalsy(); - expect(mockCalls[0][0]).toEqual(i18n.EQL_PREVIEW_FETCH_FAILURE); + expect(addWarningMock.mock.calls[0][0]).toEqual(i18n.EQL_PREVIEW_FETCH_FAILURE); }); }); @@ -166,7 +174,7 @@ describe('useEqlPreview', () => { }); }); - it('should add danger toast if search throws', async () => { + it('should add error toast if search throws', async () => { await act(async () => { (useKibana().services.data.search.search as jest.Mock).mockReturnValue( throwError('This is an error!') @@ -178,11 +186,8 @@ describe('useEqlPreview', () => { result.current[1](params); - const mockCalls = (useKibana().services.notifications.toasts.addError as jest.Mock).mock - .calls; - expect(result.current[0]).toBeFalsy(); - expect(mockCalls[0][0]).toEqual('This is an error!'); + expect(addErrorMock.mock.calls[0][0]).toEqual('This is an error!'); }); }); diff --git a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts index 5632dd0ed03bed..788ce00ba1b1d3 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/eql/use_eql_preview.ts @@ -27,18 +27,20 @@ import { EqlSearchResponse } from '../../../../common/detection_engine/types'; import { parseScheduleDates } from '../../../../common/detection_engine/parse_schedule_dates'; import { inputsModel } from '../../../common/store'; import { EQL_SEARCH_STRATEGY } from '../../../../../data_enhanced/public'; +import { useAppToasts } from '../use_app_toasts'; export const useEqlPreview = (): [ boolean, (arg: EqlPreviewRequest) => void, EqlPreviewResponse ] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const unsubscribeStream = useRef(new Subject()); const [loading, setLoading] = useState(false); const didCancel = useRef(false); + const { addError, addWarning } = useAppToasts(); const [response, setResponse] = useState({ data: [], @@ -53,7 +55,7 @@ export const useEqlPreview = (): [ const searchEql = useCallback( ({ from, to, query, index, interval }: EqlPreviewRequest) => { if (parseScheduleDates(to) == null || parseScheduleDates(from) == null) { - notifications.toasts.addWarning('Time intervals are not defined.'); + addWarning(i18n.EQL_TIME_INTERVAL_NOT_DEFINED); return; } @@ -138,7 +140,7 @@ export const useEqlPreview = (): [ setResponse((prev) => ({ ...prev, inspect: formatInspect(res, index) })); } else if (isErrorResponse(res)) { setLoading(false); - notifications.toasts.addWarning(i18n.EQL_PREVIEW_FETCH_FAILURE); + addWarning(i18n.EQL_PREVIEW_FETCH_FAILURE); unsubscribeStream.current.next(); } }, @@ -154,7 +156,7 @@ export const useEqlPreview = (): [ refetch: refetch.current, totalCount: 0, }); - notifications.toasts.addError(err, { + addError(err, { title: i18n.EQL_PREVIEW_FETCH_FAILURE, }); } @@ -166,7 +168,7 @@ export const useEqlPreview = (): [ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect((): (() => void) => { diff --git a/x-pack/plugins/security_solution/public/common/hooks/translations.ts b/x-pack/plugins/security_solution/public/common/hooks/translations.ts index 90a848329c0135..520cfef74ce414 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/translations.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/translations.ts @@ -46,3 +46,10 @@ export const EQL_PREVIEW_FETCH_FAILURE = i18n.translate( defaultMessage: 'EQL Preview Error', } ); + +export const EQL_TIME_INTERVAL_NOT_DEFINED = i18n.translate( + 'xpack.securitySolution.components.hooks.errors.timeIntervalsNotDefined', + { + defaultMessage: 'Time intervals are not defined.', + } +); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts index 2b224f1bb61254..25c0f5411f25cf 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.mock.ts @@ -8,6 +8,7 @@ const createAppToastsMock = () => ({ addError: jest.fn(), addSuccess: jest.fn(), + addWarning: jest.fn(), }); export const useAppToastsMock = { diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts index e8a13a1cc183e8..27f584bb172481 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.test.ts @@ -6,13 +6,14 @@ */ import { renderHook } from '@testing-library/react-hooks'; +import { IEsError } from 'src/plugins/data/public'; import { useToasts } from '../lib/kibana'; import { useAppToasts } from './use_app_toasts'; jest.mock('../lib/kibana'); -describe('useDeleteList', () => { +describe('useAppToasts', () => { let addErrorMock: jest.Mock; let addSuccessMock: jest.Mock; let addWarningMock: jest.Mock; @@ -37,31 +38,36 @@ describe('useDeleteList', () => { expect(addErrorMock).toHaveBeenCalledWith(error, { title: 'title' }); }); - it("uses a AppError's body.message as the toastMessage", async () => { - const kibanaApiError = { - message: 'Not Found', - body: { status_code: 404, message: 'Detailed Message' }, - }; + it('converts an unknown error to an Error', () => { + const unknownError = undefined; const { result } = renderHook(() => useAppToasts()); - result.current.addError(kibanaApiError, { title: 'title' }); + result.current.addError(unknownError, { title: 'title' }); - expect(addErrorMock).toHaveBeenCalledWith(kibanaApiError, { + expect(addErrorMock).toHaveBeenCalledWith(Error(`${undefined}`), { title: 'title', - toastMessage: 'Detailed Message', }); }); - it('converts an unknown error to an Error', () => { - const unknownError = undefined; - + it('works normally with a bsearch type error', async () => { + const error = ({ + message: 'some message', + attributes: {}, + err: { + statusCode: 400, + innerMessages: { somethingElse: 'message' }, + }, + } as unknown) as IEsError; const { result } = renderHook(() => useAppToasts()); - result.current.addError(unknownError, { title: 'title' }); - - expect(addErrorMock).toHaveBeenCalledWith(Error(`${undefined}`), { - title: 'title', + result.current.addError(error, { title: 'title' }); + const errorObj = addErrorMock.mock.calls[0][0]; + expect(errorObj).toEqual({ + message: 'some message (400)', + name: 'some message', + stack: + '{\n "statusCode": 400,\n "innerMessages": {\n "somethingElse": "message"\n }\n}', }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts index a797d56835ae79..f5a3c75747e52f 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_app_toasts.ts @@ -6,45 +6,79 @@ */ import { useCallback, useRef } from 'react'; +import { IEsError, isEsError } from '../../../../../../src/plugins/data/public'; import { ErrorToastOptions, ToastsStart, Toast } from '../../../../../../src/core/public'; import { useToasts } from '../lib/kibana'; -import { isAppError, AppError } from '../utils/api'; +import { isAppError } from '../utils/api'; export type UseAppToasts = Pick & { api: ToastsStart; addError: (error: unknown, options: ErrorToastOptions) => Toast; }; +/** + * This gives a better presentation of error data sent from the API (both general platform errors and app-specific errors). + * This uses platform's new Toasts service to prevent modal/toast z-index collision issues. + * This fixes some issues you can see with re-rendering since using a class such as notifications.toasts. + * This also has an adapter and transform for detecting if a bsearch's EsError is present and then adapts that to the + * Kibana error toaster model so that the network error message will be shown rather than a stack trace. + */ export const useAppToasts = (): UseAppToasts => { const toasts = useToasts(); const addError = useRef(toasts.addError.bind(toasts)).current; const addSuccess = useRef(toasts.addSuccess.bind(toasts)).current; const addWarning = useRef(toasts.addWarning.bind(toasts)).current; - const addAppError = useCallback( - (error: AppError, options: ErrorToastOptions) => - addError(error, { - ...options, - toastMessage: error.body.message, - }), - [addError] - ); - const _addError = useCallback( (error: unknown, options: ErrorToastOptions) => { - if (isAppError(error)) { - return addAppError(error, options); + if (error != null && isEsError(error)) { + const err = esErrorToRequestError(error); + return addError(err, options); + } else if (isAppError(error)) { + return addError(error, options); + } else if (error instanceof Error) { + return addError(error, options); } else { - if (error instanceof Error) { - return addError(error, options); - } else { - return addError(new Error(String(error)), options); - } + // Best guess that this is a stringable error. + const err = new Error(String(error)); + return addError(err, options); } }, - [addAppError, addError] + [addError] ); return { api: toasts, addError: _addError, addSuccess, addWarning }; }; + +/** + * See this file, we are not allowed to import files such as es_error. + * So instead we say maybe err is on there so that we can unwrap it and get + * our status code from it if possible within the error in our function. + * src/plugins/data/public/search/errors/es_error.tsx + */ +type MaybeESError = IEsError & { err?: Record }; + +/** + * This attempts its best to map between an IEsError which comes from bsearch to a error_toaster + * See the file: src/core/public/notifications/toasts/error_toast.tsx + * + * NOTE: This is brittle at the moment from bsearch and the hope is that better support between + * the error message and formatting of bsearch and the error_toast.tsx from Kibana core will be + * supported in the future. However, for now, this is _hopefully_ temporary. + * + * Also see the file: + * x-pack/plugins/security_solution/public/app/home/setup.tsx + * + * Where this same technique of overriding and changing the stack is occurring. + */ +export const esErrorToRequestError = (error: IEsError & MaybeESError): Error => { + const maybeUnWrapped = error.err != null ? error.err : error; + const statusCode = error.err?.statusCode != null ? `(${error.err.statusCode})` : ''; + const stringifiedError = JSON.stringify(maybeUnWrapped, null, 2); + return { + message: `${error.attributes?.reason ?? error.message} ${statusCode}`, + name: error.attributes?.reason ?? error.message, + stack: stringifiedError, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/utils/api/index.ts b/x-pack/plugins/security_solution/public/common/utils/api/index.ts index 198757e9ceade5..513fed36f678ca 100644 --- a/x-pack/plugins/security_solution/public/common/utils/api/index.ts +++ b/x-pack/plugins/security_solution/public/common/utils/api/index.ts @@ -7,9 +7,7 @@ import { has } from 'lodash/fp'; -export interface AppError { - name: string; - message: string; +export interface AppError extends Error { body: { message: string; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx index 679aac71c6fdfa..004a904828ecf9 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx @@ -12,6 +12,8 @@ import { shallow, mount, ReactWrapper } from 'enzyme'; import '../../../../common/mock/match_media'; import { PrePackagedRulesPrompt } from './load_empty_prompt'; import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -37,6 +39,7 @@ jest.mock('../../../containers/detection_engine/rules/api', () => ({ }), createPrepackagedRules: jest.fn(), })); +jest.mock('../../../../common/hooks/use_app_toasts'); const props = { createPrePackagedRules: jest.fn(), @@ -46,6 +49,14 @@ const props = { }; describe('PrePackagedRulesPrompt', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders correctly', () => { const wrapper = shallow(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx index 91b53c11ddda1a..c17d227428391b 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx @@ -9,10 +9,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePrivilegeUser, ReturnPrivilegeUser } from './use_privilege_user'; import * as api from './api'; import { Privilege } from './types'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('usePrivilegeUser', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx index a527123fffb4ac..dd4da78db4e8eb 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx @@ -6,8 +6,8 @@ */ import { useEffect, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { getUserPrivilege } from './api'; import * as i18n from './translations'; @@ -44,7 +44,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { hasIndexUpdateDelete: null, hasIndexMaintenance: null, }); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -84,7 +84,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { hasIndexUpdateDelete: false, hasIndexMaintenance: false, }); - errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.PRIVILEGE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -97,7 +97,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { isSubscribed = false; abortCtrl.abort(); }; - }, [dispatchToaster]); + }, [addError]); return { loading, ...privilegeUser }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx index dc6747510a3ea7..e8cd501816afe9 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.test.tsx @@ -8,14 +8,22 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useSignalIndex, ReturnSignalIndex } from './use_signal_index'; import * as api from './api'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useSignalIndex', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx index 00ecbca338b708..74adc8d36b0aac 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_signal_index.tsx @@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { createSignalIndex, getSignalIndex } from './api'; import * as i18n from './translations'; import { isSecurityAppError } from '../../../../common/utils/api'; @@ -35,7 +35,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { signalIndexMappingOutdated: null, createDeSignalIndex: null, }); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -63,7 +63,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { createDeSignalIndex: createIndex, }); if (isSecurityAppError(error) && error.body.status_code !== 404) { - errorToToaster({ title: i18n.SIGNAL_GET_NAME_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.SIGNAL_GET_NAME_FAILURE }); } } } @@ -93,7 +93,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { signalIndexMappingOutdated: null, createDeSignalIndex: createIndex, }); - errorToToaster({ title: i18n.SIGNAL_POST_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.SIGNAL_POST_FAILURE }); } } } @@ -107,7 +107,7 @@ export const useSignalIndex = (): ReturnSignalIndex => { isSubscribed = false; abortCtrl.abort(); }; - }, [dispatchToaster]); + }, [addError]); return { loading, ...signalIndex }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx index 4532d3427375b1..6a527ca00f5251 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.test.tsx @@ -8,12 +8,19 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useRules, UseRules, ReturnRules } from './use_rules'; import * as api from '../api'; +import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; jest.mock('../api'); +jest.mock('../../../../../common/hooks/use_app_toasts'); describe('useRules', () => { + let appToastsMock: jest.Mocked>; + beforeEach(() => { jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); test('init', async () => { await act(async () => { diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx index f3c90ae12ae33c..b7ef04c79d3da4 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules.tsx @@ -7,8 +7,8 @@ import { useEffect, useState, useRef } from 'react'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import { FetchRulesResponse, FilterOptions, PaginationOptions, Rule } from '../types'; -import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; import { fetchRules } from '../api'; import * as i18n from '../translations'; @@ -34,7 +34,7 @@ export const useRules = ({ const [rules, setRules] = useState(null); const reFetchRules = useRef<() => Promise>(() => Promise.resolve()); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); const filterTags = filterOptions.tags.sort().join(); useEffect(() => { @@ -62,7 +62,7 @@ export const useRules = ({ } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); if (dispatchRulesInReducer != null) { dispatchRulesInReducer([], {}); } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts index 7fcefe02cfe33b..8969843f61a1cd 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/rules_table/use_rules_table.ts @@ -8,7 +8,7 @@ import { Dispatch, useMemo, useReducer, useEffect, useRef } from 'react'; import { EuiBasicTable } from '@elastic/eui'; -import { errorToToaster, useStateToaster } from '../../../../../common/components/toasters'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; import * as i18n from '../translations'; import { fetchRules } from '../api'; @@ -65,9 +65,9 @@ export const useRulesTable = (params: UseRulesTableParams): UseRulesTableReturn const reducer = useMemo(() => createRulesTableReducer(tableRef), [tableRef]); const [state, dispatch] = useReducer(reducer, initialState); const facade = useRef(createRulesTableFacade(dispatch)); + const { addError } = useAppToasts(); const reFetchRules = useRef<() => Promise>(() => Promise.resolve()); - const [, dispatchToaster] = useStateToaster(); const { pagination, filterOptions } = state; const filterTags = filterOptions.tags.sort().join(); @@ -95,7 +95,7 @@ export const useRulesTable = (params: UseRulesTableParams): UseRulesTableReturn } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); facade.current.setRules([], {}); } } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx index 0074808057ca72..d6d6dec6edc6aa 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.test.tsx @@ -10,10 +10,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useCreateRule, ReturnCreateRule } from './use_create_rule'; import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; import { getRulesSchemaMock } from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useCreateRule', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { const { result } = renderHook(() => useCreateRule()); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx index e9a807d772d8b4..d50ef49593f409 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_create_rule.tsx @@ -7,7 +7,7 @@ import { useEffect, useState, Dispatch } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { CreateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { createRule } from './api'; @@ -25,7 +25,7 @@ export const useCreateRule = (): ReturnCreateRule => { const [rule, setRule] = useState(null); const [ruleId, setRuleId] = useState(null); const [isLoading, setIsLoading] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -44,7 +44,7 @@ export const useCreateRule = (): ReturnCreateRule => { } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_ADD_FAILURE }); } } if (isSubscribed) { @@ -58,7 +58,7 @@ export const useCreateRule = (): ReturnCreateRule => { isSubscribed = false; abortCtrl.abort(); }; - }, [rule, dispatchToaster]); + }, [rule, addError]); return [{ isLoading, ruleId }, setRule]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx index 8107d1ca84fda7..9ea8cee1060522 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx @@ -12,6 +12,15 @@ import * as api from './api'; import { shallow } from 'enzyme'; import * as i18n from './translations'; +jest.mock('../../../../common/lib/kibana', () => ({ + useKibana: jest.fn(), + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), +})); + jest.mock('./api', () => ({ getPrePackagedRulesStatus: jest.fn(), createPrepackagedRules: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx index 3fbda3e4533ea6..be474bbdc4fd88 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx @@ -8,11 +8,7 @@ import React, { useCallback, useMemo, useState, useEffect } from 'react'; import { EuiButton } from '@elastic/eui'; -import { - errorToToaster, - useStateToaster, - displaySuccessToast, -} from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { getPrePackagedRulesStatus, createPrepackagedRules } from './api'; import * as i18n from './translations'; @@ -114,7 +110,8 @@ export const usePrePackagedRules = ({ const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError, addSuccess } = useAppToasts(); + const getSuccessToastMessage = (result: { rules_installed: number; rules_updated: number; @@ -173,7 +170,7 @@ export const usePrePackagedRules = ({ timelinesNotUpdated: null, }); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -231,7 +228,7 @@ export const usePrePackagedRules = ({ timelinesNotInstalled: prePackagedRuleStatusResponse.timelines_not_installed, timelinesNotUpdated: prePackagedRuleStatusResponse.timelines_not_updated, }); - displaySuccessToast(getSuccessToastMessage(result), dispatchToaster); + addSuccess(getSuccessToastMessage(result)); stopTimeOut(); resolve(true); } else { @@ -246,10 +243,8 @@ export const usePrePackagedRules = ({ } catch (error) { if (isSubscribed) { setLoadingCreatePrePackagedRules(false); - errorToToaster({ + addError(error, { title: i18n.RULE_AND_TIMELINE_PREPACKAGED_FAILURE, - error, - dispatchToaster, }); resolve(false); } @@ -269,7 +264,8 @@ export const usePrePackagedRules = ({ isAuthenticated, hasEncryptionKey, isSignalIndexExists, - dispatchToaster, + addError, + addSuccess, ]); const prePackagedRuleStatus = useMemo( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx index 4b062bee6176b0..3c87a20dea6bb9 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.test.tsx @@ -8,10 +8,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useRule, ReturnRule } from './use_rule'; import * as api from './api'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useRule', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx index 5ecc7904871a46..4e5480a9214934 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule.tsx @@ -6,8 +6,8 @@ */ import { useEffect, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { fetchRuleById } from './api'; import { transformInput } from './transforms'; import * as i18n from './translations'; @@ -24,7 +24,7 @@ export type ReturnRule = [boolean, Rule | null]; export const useRule = (id: string | undefined): ReturnRule => { const [rule, setRule] = useState(null); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -45,7 +45,7 @@ export const useRule = (id: string | undefined): ReturnRule => { } catch (error) { if (isSubscribed) { setRule(null); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -59,7 +59,7 @@ export const useRule = (id: string | undefined): ReturnRule => { isSubscribed = false; abortCtrl.abort(); }; - }, [id, dispatchToaster]); + }, [id, addError]); return [loading, rule]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx index c773cac5dcfef6..96a8b00bf4966e 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.test.tsx @@ -14,8 +14,11 @@ import { } from './use_rule_status'; import * as api from './api'; import { Rule } from './types'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); const testRule: Rule = { actions: [ @@ -67,10 +70,13 @@ const testRule: Rule = { }; describe('useRuleStatus', () => { + let appToastsMock: jest.Mocked>; beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); jest.clearAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); afterEach(async () => { cleanup(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx index 1f221c9abc798d..e3e2351b40a32f 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_rule_status.tsx @@ -6,8 +6,8 @@ */ import { useEffect, useRef, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; import { RuleStatusRowItemType } from '../../../pages/detection_engine/rules/all/columns'; import { getRuleStatusById, getRulesStatusByIds } from './api'; import * as i18n from './translations'; @@ -30,7 +30,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = const [ruleStatus, setRuleStatus] = useState(null); const fetchRuleStatus = useRef(null); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -50,7 +50,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = } catch (error) { if (isSubscribed) { setRuleStatus(null); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -65,7 +65,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = isSubscribed = false; abortCtrl.abort(); }; - }, [id, dispatchToaster]); + }, [id, addError]); return [loading, ruleStatus, fetchRuleStatus.current]; }; @@ -79,7 +79,7 @@ export const useRuleStatus = (id: string | undefined | null): ReturnRuleStatus = export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { const [rulesStatuses, setRuleStatuses] = useState([]); const [loading, setLoading] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -106,7 +106,7 @@ export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { } catch (error) { if (isSubscribed) { setRuleStatuses([]); - errorToToaster({ title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_AND_TIMELINE_FETCH_FAILURE }); } } if (isSubscribed) { @@ -122,7 +122,7 @@ export const useRulesStatuses = (rules: Rules): ReturnRulesStatuses => { isSubscribed = false; abortCtrl.abort(); }; - }, [rules, dispatchToaster]); + }, [rules, addError]); return { loading, rulesStatuses }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx index f9488caaa91329..e177d36057b1d2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.test.tsx @@ -6,11 +6,22 @@ */ import { renderHook, act } from '@testing-library/react-hooks'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; import { useTags, ReturnTags } from './use_tags'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useTags', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { await act(async () => { const { result, waitForNextUpdate } = renderHook(() => useTags()); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx index 5681b076aa6bbd..5f16cb593a516d 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_tags.tsx @@ -7,7 +7,7 @@ import { noop } from 'lodash/fp'; import { useEffect, useState, useRef } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { fetchTags } from './api'; import * as i18n from './translations'; @@ -20,8 +20,8 @@ export type ReturnTags = [boolean, string[], () => void]; export const useTags = (): ReturnTags => { const [tags, setTags] = useState([]); const [loading, setLoading] = useState(true); - const [, dispatchToaster] = useStateToaster(); const reFetchTags = useRef<() => void>(noop); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -39,7 +39,7 @@ export const useTags = (): ReturnTags => { } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.TAG_FETCH_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.TAG_FETCH_FAILURE }); } } if (isSubscribed) { @@ -54,7 +54,7 @@ export const useTags = (): ReturnTags => { isSubscribed = false; abortCtrl.abort(); }; - }, [dispatchToaster]); + }, [addError]); return [loading, tags, reFetchTags.current]; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx index c000870e8e51f2..3b16d0266e5662 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.test.tsx @@ -9,10 +9,21 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { useUpdateRule, ReturnUpdateRule } from './use_update_rule'; import { getUpdateRulesSchemaMock } from '../../../../../common/detection_engine/schemas/request/rule_schemas.mock'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; jest.mock('./api'); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('useUpdateRule', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + test('init', async () => { const { result } = renderHook(() => useUpdateRule()); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx index 046702323db380..a5953b6ec3e658 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_update_rule.tsx @@ -7,7 +7,7 @@ import { useEffect, useState, Dispatch } from 'react'; -import { errorToToaster, useStateToaster } from '../../../../common/components/toasters'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { UpdateRulesSchema } from '../../../../../common/detection_engine/schemas/request'; import { transformOutput } from './transforms'; @@ -26,7 +26,7 @@ export const useUpdateRule = (): ReturnUpdateRule => { const [rule, setRule] = useState(null); const [isSaved, setIsSaved] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [, dispatchToaster] = useStateToaster(); + const { addError } = useAppToasts(); useEffect(() => { let isSubscribed = true; @@ -42,7 +42,7 @@ export const useUpdateRule = (): ReturnUpdateRule => { } } catch (error) { if (isSubscribed) { - errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster }); + addError(error, { title: i18n.RULE_ADD_FAILURE }); } } if (isSubscribed) { @@ -56,7 +56,7 @@ export const useUpdateRule = (): ReturnUpdateRule => { isSubscribed = false; abortCtrl.abort(); }; - }, [rule, dispatchToaster]); + }, [rule, addError]); return [{ isLoading, isSaved }, setRule]; }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index 5cfa5ecd225ecc..146b7e84707180 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -15,6 +15,7 @@ import { } from '@elastic/eui'; import { History } from 'history'; +import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; import { AutoDownload } from '../../../../../../common/components/auto_download/auto_download'; import { NamespaceType } from '../../../../../../../../lists/common'; import { useKibana } from '../../../../../../common/lib/kibana'; @@ -88,6 +89,7 @@ export const ExceptionListsTable = React.memo( const [deletingListIds, setDeletingListIds] = useState([]); const [exportingListIds, setExportingListIds] = useState([]); const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({}); + const { addError } = useAppToasts(); const handleDeleteSuccess = useCallback( (listId?: string) => () => { @@ -100,12 +102,11 @@ export const ExceptionListsTable = React.memo( const handleDeleteError = useCallback( (err: Error & { body?: { message: string } }): void => { - notifications.toasts.addError(err, { + addError(err, { title: i18n.EXCEPTION_DELETE_ERROR, - toastMessage: err.body != null ? err.body.message : err.message, }); }, - [notifications.toasts] + [addError] ); const handleDelete = useCallback( @@ -170,9 +171,9 @@ export const ExceptionListsTable = React.memo( const handleExportError = useCallback( (err: Error) => { - notifications.toasts.addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); + addError(err, { title: i18n.EXCEPTION_EXPORT_ERROR }); }, - [notifications.toasts] + [addError] ); const handleExport = useCallback( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx index 3ea60004018724..a84a60af51b391 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_table_filters/rules_table_filters.test.tsx @@ -10,8 +10,19 @@ import { mount } from 'enzyme'; import { act } from '@testing-library/react'; import { RulesTableFilters } from './rules_table_filters'; +import { useAppToastsMock } from '../../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../../common/hooks/use_app_toasts'; +jest.mock('../../../../../../common/hooks/use_app_toasts'); describe('RulesTableFilters', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + jest.resetAllMocks(); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders no numbers next to rule type button filter if none exist', async () => { await act(async () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx index 5b443b73f11e2a..9622610f3c637f 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.test.tsx @@ -12,6 +12,8 @@ import '../../../../../common/mock/match_media'; import { TestProviders } from '../../../../../common/mock'; import { CreateRulePage } from './index'; import { useUserData } from '../../../../components/user_info'; +import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -27,8 +29,16 @@ jest.mock('react-router-dom', () => { jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); jest.mock('../../../../components/user_info'); +jest.mock('../../../../../common/hooks/use_app_toasts'); describe('CreateRulePage', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders correctly', () => { (useUserData as jest.Mock).mockReturnValue([{}]); const wrapper = shallow(, { wrappingComponent: TestProviders }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx index 5f485dcaa01952..e7cdfbe268fe68 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.test.tsx @@ -13,6 +13,8 @@ import { TestProviders } from '../../../../../common/mock'; import { EditRulePage } from './index'; import { useUserData } from '../../../../components/user_info'; import { useParams } from 'react-router-dom'; +import { useAppToastsMock } from '../../../../../common/hooks/use_app_toasts.mock'; +import { useAppToasts } from '../../../../../common/hooks/use_app_toasts'; jest.mock('../../../../containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../../common/components/link_to'); @@ -26,8 +28,16 @@ jest.mock('react-router-dom', () => { useParams: jest.fn(), }; }); +jest.mock('../../../../../common/hooks/use_app_toasts'); describe('EditRulePage', () => { + let appToastsMock: jest.Mocked>; + + beforeEach(() => { + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); + }); + it('renders correctly', () => { (useUserData as jest.Mock).mockReturnValue([{}]); (useParams as jest.Mock).mockReturnValue({}); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx index 0ffeaa42245448..bcd5ccdc0b5ac5 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx @@ -14,6 +14,8 @@ import { useUserData } from '../../../components/user_info'; import { waitFor } from '@testing-library/react'; import { TestProviders } from '../../../../common/mock'; import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; +import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock'; jest.mock('react-router-dom', () => { const original = jest.requireActual('react-router-dom'); @@ -73,10 +75,15 @@ jest.mock('../../../components/rules/pre_packaged_rules/update_callout', () => { UpdatePrePackagedRulesCallOut: jest.fn().mockReturnValue(
), }; }); +jest.mock('../../../../common/hooks/use_app_toasts'); describe('RulesPage', () => { + let appToastsMock: jest.Mocked>; + beforeAll(() => { (useUserData as jest.Mock).mockReturnValue([{}]); + appToastsMock = useAppToastsMock.create(); + (useAppToasts as jest.Mock).mockReturnValue(appToastsMock); }); it('renders AllRules', () => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx index f1efdd2e3c4329..c31094b5778d5b 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx @@ -33,6 +33,7 @@ import { InspectResponse } from '../../../types'; import { hostsModel, hostsSelectors } from '../../store'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'hostsAuthenticationsQuery'; @@ -71,7 +72,7 @@ export const useAuthentications = ({ const { activePage, limit } = useDeepEqualSelector((state) => pick(['activePage', 'limit'], getAuthenticationsSelector(state, type)) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -80,6 +81,7 @@ export const useAuthentications = ({ authenticationsRequest, setAuthenticationsRequest, ] = useState(null); + const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -145,15 +147,14 @@ export const useAuthentications = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_AUTHENTICATIONS); + addWarning(i18n.ERROR_AUTHENTICATIONS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_AUTHENTICATIONS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -164,7 +165,7 @@ export const useAuthentications = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx index 1eaa89575de260..dd55bdb4c69489 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { useKibana } from '../../../../common/lib/kibana'; import { @@ -55,7 +56,7 @@ export const useHostDetails = ({ skip = false, startDate, }: UseHostDetails): [boolean, HostDetailsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -63,6 +64,7 @@ export const useHostDetails = ({ const [hostDetailsRequest, setHostDetailsRequest] = useState( null ); + const { addError, addWarning } = useAppToasts(); const [hostDetailsResponse, setHostDetailsResponse] = useState({ endDate, @@ -104,16 +106,14 @@ export const useHostDetails = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOST_OVERVIEW); + addWarning(i18n.ERROR_HOST_OVERVIEW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOST_OVERVIEW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -124,7 +124,7 @@ export const useHostDetails = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx index 380e6b05471a89..a3703ab64beda4 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx @@ -9,6 +9,7 @@ import deepEqual from 'fast-deep-equal'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { useKibana } from '../../../../common/lib/kibana'; import { HostsQueries, @@ -45,7 +46,7 @@ export const useFirstLastSeenHost = ({ indexNames, order, }: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); const [loading, setLoading] = useState(false); @@ -69,6 +70,7 @@ export const useFirstLastSeenHost = ({ id: ID, } ); + const { addError, addWarning } = useAppToasts(); const firstLastSeenHostSearch = useCallback( (request: HostFirstLastSeenRequestOptions) => { @@ -93,8 +95,7 @@ export const useFirstLastSeenHost = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_FIRST_LAST_SEEN_HOST); + addWarning(i18n.ERROR_FIRST_LAST_SEEN_HOST); searchSubscription$.current.unsubscribe(); } }, @@ -104,9 +105,8 @@ export const useFirstLastSeenHost = ({ ...prevResponse, errorMessage: msg, })); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_FIRST_LAST_SEEN_HOST, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -116,7 +116,7 @@ export const useFirstLastSeenHost = ({ abortCtrl.current.abort(); asyncSearch(); }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx index 383c4c233914f8..7bf681092c075a 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx @@ -30,6 +30,7 @@ import * as i18n from './translations'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/common'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'hostsAllQuery'; @@ -70,12 +71,13 @@ export const useAllHost = ({ const { activePage, direction, limit, sortField } = useDeepEqualSelector((state: State) => getHostsSelector(state, type) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription = useRef(new Subscription()); const [loading, setLoading] = useState(false); const [hostsRequest, setHostRequest] = useState(null); + const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -143,14 +145,13 @@ export const useAllHost = ({ searchSubscription.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_ALL_HOST); + addWarning(i18n.ERROR_ALL_HOST); searchSubscription.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ title: i18n.FAIL_ALL_HOST, text: msg.message }); + addError(msg, { title: i18n.FAIL_ALL_HOST }); searchSubscription.current.unsubscribe(); }, }); @@ -160,7 +161,7 @@ export const useAllHost = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx index ad3c7e0e829fba..6a3323da4fb440 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -49,7 +50,7 @@ export const useHostsKpiAuthentications = ({ skip = false, startDate, }: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -75,6 +76,7 @@ export const useHostsKpiAuthentications = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const hostsKpiAuthenticationsSearch = useCallback( (request: HostsKpiAuthenticationsRequestOptions | null) => { @@ -110,16 +112,14 @@ export const useHostsKpiAuthentications = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); + addWarning(i18n.ERROR_HOSTS_KPI_AUTHENTICATIONS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOSTS_KPI_AUTHENTICATIONS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -130,7 +130,7 @@ export const useHostsKpiAuthentications = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx index 8ed1aaecb6f0ec..5af91539e8be36 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -48,7 +49,7 @@ export const useHostsKpiHosts = ({ skip = false, startDate, }: UseHostsKpiHosts): [boolean, HostsKpiHostsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -69,6 +70,7 @@ export const useHostsKpiHosts = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const hostsKpiHostsSearch = useCallback( (request: HostsKpiHostsRequestOptions | null) => { @@ -98,16 +100,14 @@ export const useHostsKpiHosts = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); + addWarning(i18n.ERROR_HOSTS_KPI_HOSTS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOSTS_KPI_HOSTS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -118,7 +118,7 @@ export const useHostsKpiHosts = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx index b34de267f45192..9a72fa1d6cfca7 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -49,7 +50,7 @@ export const useHostsKpiUniqueIps = ({ skip = false, startDate, }: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -74,6 +75,7 @@ export const useHostsKpiUniqueIps = ({ refetch: refetch.current, } ); + const { addError, addWarning } = useAppToasts(); const hostsKpiUniqueIpsSearch = useCallback( (request: HostsKpiUniqueIpsRequestOptions | null) => { @@ -105,16 +107,14 @@ export const useHostsKpiUniqueIps = ({ searchSubscription$.current.unsubscribe(); } else if (response.isPartial && !response.isRunning) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); + addWarning(i18n.ERROR_HOSTS_KPI_UNIQUE_IPS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOSTS_KPI_UNIQUE_IPS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -125,7 +125,7 @@ export const useHostsKpiUniqueIps = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx index 1e07b94b55b740..e94873dee5632e 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx @@ -32,6 +32,7 @@ import { ESTermQuery } from '../../../../common/typed_json'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import { useDeepEqualSelector } from '../../../common/hooks/use_selector'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'hostsUncommonProcessesQuery'; @@ -72,7 +73,7 @@ export const useUncommonProcesses = ({ const { activePage, limit } = useDeepEqualSelector((state: State) => getUncommonProcessesSelector(state, type) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -81,6 +82,7 @@ export const useUncommonProcesses = ({ uncommonProcessesRequest, setUncommonProcessesRequest, ] = useState(null); + const { addError, addWarning } = useAppToasts(); const wrappedLoadMore = useCallback( (newActivePage: number) => { @@ -150,15 +152,14 @@ export const useUncommonProcesses = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_UNCOMMON_PROCESSES); + addWarning(i18n.ERROR_UNCOMMON_PROCESSES); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_UNCOMMON_PROCESSES, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -169,7 +170,7 @@ export const useUncommonProcesses = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx index 6bbe7f8f437739..cf7d8e05858d5f 100644 --- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx @@ -24,6 +24,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkDetailsQuery'; @@ -52,7 +53,7 @@ export const useNetworkDetails = ({ skip, ip, }: UseNetworkDetails): [boolean, NetworkDetailsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -73,6 +74,7 @@ export const useNetworkDetails = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkDetailsSearch = useCallback( (request: NetworkDetailsRequestOptions | null) => { @@ -100,16 +102,14 @@ export const useNetworkDetails = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_DETAILS); + addWarning(i18n.ERROR_NETWORK_DETAILS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_DETAILS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -120,7 +120,7 @@ export const useNetworkDetails = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx index 345aee4de2df20..c835aa6c6a3e36 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiDns = ({ skip = false, startDate, }: UseNetworkKpiDns): [boolean, NetworkKpiDnsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -73,6 +74,7 @@ export const useNetworkKpiDns = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiDnsSearch = useCallback( (request: NetworkKpiDnsRequestOptions | null) => { @@ -102,16 +104,14 @@ export const useNetworkKpiDns = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_DNS); + addWarning(i18n.ERROR_NETWORK_KPI_DNS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_DNS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -122,7 +122,7 @@ export const useNetworkKpiDns = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx index 6dd3df1055f9ce..2e4f3b83e67083 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiNetworkEvents = ({ skip = false, startDate, }: UseNetworkKpiNetworkEvents): [boolean, NetworkKpiNetworkEventsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -76,6 +77,7 @@ export const useNetworkKpiNetworkEvents = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiNetworkEventsSearch = useCallback( (request: NetworkKpiNetworkEventsRequestOptions | null) => { @@ -108,16 +110,14 @@ export const useNetworkKpiNetworkEvents = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_NETWORK_EVENTS); + addWarning(i18n.ERROR_NETWORK_KPI_NETWORK_EVENTS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_NETWORK_EVENTS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -128,7 +128,7 @@ export const useNetworkKpiNetworkEvents = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx index dfc7d0a28db79c..b9d3e8639c5603 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/tls_handshakes/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiTlsHandshakes = ({ skip = false, startDate, }: UseNetworkKpiTlsHandshakes): [boolean, NetworkKpiTlsHandshakesArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -76,6 +77,7 @@ export const useNetworkKpiTlsHandshakes = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiTlsHandshakesSearch = useCallback( (request: NetworkKpiTlsHandshakesRequestOptions | null) => { @@ -107,16 +109,14 @@ export const useNetworkKpiTlsHandshakes = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_TLS_HANDSHAKES); + addWarning(i18n.ERROR_NETWORK_KPI_TLS_HANDSHAKES); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_TLS_HANDSHAKES, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -127,7 +127,7 @@ export const useNetworkKpiTlsHandshakes = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx index 08c4d917f5da3f..2699d63144be14 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_flows/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -53,7 +54,7 @@ export const useNetworkKpiUniqueFlows = ({ skip = false, startDate, }: UseNetworkKpiUniqueFlows): [boolean, NetworkKpiUniqueFlowsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -76,6 +77,7 @@ export const useNetworkKpiUniqueFlows = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiUniqueFlowsSearch = useCallback( (request: NetworkKpiUniqueFlowsRequestOptions | null) => { @@ -108,16 +110,14 @@ export const useNetworkKpiUniqueFlows = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_FLOWS); + addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_FLOWS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_UNIQUE_FLOWS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -128,7 +128,7 @@ export const useNetworkKpiUniqueFlows = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx index a532f4f11a3015..488c526134525b 100644 --- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/unique_private_ips/index.tsx @@ -10,6 +10,7 @@ import { noop } from 'lodash/fp'; import { useCallback, useEffect, useRef, useState } from 'react'; import { Subscription } from 'rxjs'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import { inputsModel } from '../../../../common/store'; import { createFilter } from '../../../../common/containers/helpers'; import { useKibana } from '../../../../common/lib/kibana'; @@ -57,7 +58,7 @@ export const useNetworkKpiUniquePrivateIps = ({ skip = false, startDate, }: UseNetworkKpiUniquePrivateIps): [boolean, NetworkKpiUniquePrivateIpsArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -83,6 +84,7 @@ export const useNetworkKpiUniquePrivateIps = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const networkKpiUniquePrivateIpsSearch = useCallback( (request: NetworkKpiUniquePrivateIpsRequestOptions | null) => { @@ -119,16 +121,14 @@ export const useNetworkKpiUniquePrivateIps = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_PRIVATE_IPS); + addWarning(i18n.ERROR_NETWORK_KPI_UNIQUE_PRIVATE_IPS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_KPI_UNIQUE_PRIVATE_IPS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -139,7 +139,7 @@ export const useNetworkKpiUniquePrivateIps = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx index 5ce31bada520b1..47e60f27a7dbdf 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_dns/index.tsx @@ -30,6 +30,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkDnsQuery'; @@ -68,7 +69,7 @@ export const useNetworkDns = ({ }: UseNetworkDns): [boolean, NetworkDnsArgs] => { const getNetworkDnsSelector = useMemo(() => networkSelectors.dnsSelector(), []); const { activePage, sort, isPtrIncluded, limit } = useDeepEqualSelector(getNetworkDnsSelector); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -110,6 +111,7 @@ export const useNetworkDns = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkDnsSearch = useCallback( (request: NetworkDnsRequestOptions | null) => { @@ -142,16 +144,14 @@ export const useNetworkDns = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_DNS); + addWarning(i18n.ERROR_NETWORK_DNS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_DNS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -162,7 +162,7 @@ export const useNetworkDns = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx index d1ff9da1fa6c22..98105f5cac25a5 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_http/index.tsx @@ -29,6 +29,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { InspectResponse } from '../../../types'; import { getInspectResponse } from '../../../helpers'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkHttpQuery'; @@ -67,7 +68,7 @@ export const useNetworkHttp = ({ }: UseNetworkHttp): [boolean, NetworkHttpArgs] => { const getHttpSelector = useMemo(() => networkSelectors.httpSelector(), []); const { activePage, limit, sort } = useDeepEqualSelector((state) => getHttpSelector(state, type)); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -108,6 +109,7 @@ export const useNetworkHttp = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkHttpSearch = useCallback( (request: NetworkHttpRequestOptions | null) => { @@ -139,16 +141,14 @@ export const useNetworkHttp = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_HTTP); + addWarning(i18n.ERROR_NETWORK_HTTP); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_HTTP, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -159,7 +159,7 @@ export const useNetworkHttp = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx index 405957d98055e0..e7f3cf3f2675a0 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_countries/index.tsx @@ -29,6 +29,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkTopCountriesQuery'; @@ -68,7 +69,7 @@ export const useNetworkTopCountries = ({ const { activePage, limit, sort } = useDeepEqualSelector((state) => getTopCountriesSelector(state, type, flowTarget) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -95,6 +96,7 @@ export const useNetworkTopCountries = ({ }, [limit] ); + const { addError, addWarning } = useAppToasts(); const [ networkTopCountriesResponse, @@ -147,16 +149,14 @@ export const useNetworkTopCountries = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_TOP_COUNTRIES); + addWarning(i18n.ERROR_NETWORK_TOP_COUNTRIES); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_TOP_COUNTRIES, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -167,7 +167,7 @@ export const useNetworkTopCountries = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addWarning, addError, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx index 9c6a4b3d1147f6..3cbaf0fbc976ce 100644 --- a/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/network_top_n_flow/index.tsx @@ -29,6 +29,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkTopNFlowQuery'; @@ -68,7 +69,7 @@ export const useNetworkTopNFlow = ({ const { activePage, limit, sort } = useDeepEqualSelector((state) => getTopNFlowSelector(state, type, flowTarget) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -112,6 +113,7 @@ export const useNetworkTopNFlow = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkTopNFlowSearch = useCallback( (request: NetworkTopNFlowRequestOptions | null) => { @@ -143,16 +145,14 @@ export const useNetworkTopNFlow = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_TOP_N_FLOW); + addWarning(i18n.ERROR_NETWORK_TOP_N_FLOW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_TOP_N_FLOW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -163,7 +163,7 @@ export const useNetworkTopNFlow = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx index 49a7064113c307..754f0cac8868c1 100644 --- a/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/tls/index.tsx @@ -27,6 +27,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { FlowTargetSourceDest, PageInfoPaginated } from '../../../../common/search_strategy'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkTlsQuery'; @@ -68,7 +69,7 @@ export const useNetworkTls = ({ const { activePage, limit, sort } = useDeepEqualSelector((state) => getTlsSelector(state, type, flowTarget) ); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -109,6 +110,7 @@ export const useNetworkTls = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkTlsSearch = useCallback( (request: NetworkTlsRequestOptions | null) => { @@ -141,16 +143,14 @@ export const useNetworkTls = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_TLS); + addWarning(i18n.ERROR_NETWORK_TLS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_TLS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -161,7 +161,7 @@ export const useNetworkTls = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx index e000981733eedb..d4be09f97591d6 100644 --- a/x-pack/plugins/security_solution/public/network/containers/users/index.tsx +++ b/x-pack/plugins/security_solution/public/network/containers/users/index.tsx @@ -29,6 +29,7 @@ import * as i18n from './translations'; import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import { PageInfoPaginated } from '../../../../common/search_strategy'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; const ID = 'networkUsersQuery'; @@ -65,7 +66,7 @@ export const useNetworkUsers = ({ }: UseNetworkUsers): [boolean, NetworkUsersArgs] => { const getNetworkUsersSelector = useMemo(() => networkSelectors.usersSelector(), []); const { activePage, sort, limit } = useDeepEqualSelector(getNetworkUsersSelector); - const { data, notifications, uiSettings } = useKibana().services; + const { data, uiSettings } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -109,6 +110,7 @@ export const useNetworkUsers = ({ refetch: refetch.current, totalCount: -1, }); + const { addError, addWarning } = useAppToasts(); const networkUsersSearch = useCallback( (request: NetworkUsersRequestOptions | null) => { @@ -140,16 +142,14 @@ export const useNetworkUsers = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_USERS); + addWarning(i18n.ERROR_NETWORK_USERS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_USERS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -160,7 +160,7 @@ export const useNetworkUsers = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx index 8b17a7288eae3d..52b58439af0ab8 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_host/index.tsx @@ -23,6 +23,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; export const ID = 'overviewHostQuery'; @@ -49,7 +50,7 @@ export const useHostOverview = ({ skip = false, startDate, }: UseHostOverview): [boolean, HostOverviewArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -66,6 +67,7 @@ export const useHostOverview = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const overviewHostSearch = useCallback( (request: HostOverviewRequestOptions | null) => { @@ -95,16 +97,14 @@ export const useHostOverview = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_HOST_OVERVIEW); + addWarning(i18n.ERROR_HOST_OVERVIEW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_HOST_OVERVIEW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -115,7 +115,7 @@ export const useHostOverview = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx index cf0774a02db3b5..846c40994aac21 100644 --- a/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/containers/overview_network/index.tsx @@ -23,6 +23,7 @@ import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/pl import { getInspectResponse } from '../../../helpers'; import { InspectResponse } from '../../../types'; import * as i18n from './translations'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; export const ID = 'overviewNetworkQuery'; @@ -49,7 +50,7 @@ export const useNetworkOverview = ({ skip = false, startDate, }: UseNetworkOverview): [boolean, NetworkOverviewArgs] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -69,6 +70,7 @@ export const useNetworkOverview = ({ isInspected: false, refetch: refetch.current, }); + const { addError, addWarning } = useAppToasts(); const overviewNetworkSearch = useCallback( (request: NetworkOverviewRequestOptions | null) => { @@ -98,16 +100,14 @@ export const useNetworkOverview = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning(i18n.ERROR_NETWORK_OVERVIEW); + addWarning(i18n.ERROR_NETWORK_OVERVIEW); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_NETWORK_OVERVIEW, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -118,7 +118,7 @@ export const useNetworkOverview = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx index 7e4924eacda4b5..37fdd5a444b2b3 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/index.tsx @@ -20,6 +20,9 @@ import { TimelineEventsDetailsStrategyResponse, } from '../../../../common/search_strategy'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/public'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import * as i18n from './translations'; + export interface EventsArgs { detailsData: TimelineEventsDetailsItem[] | null; } @@ -37,7 +40,7 @@ export const useTimelineEventsDetails = ({ eventId, skip, }: UseTimelineEventsDetailsProps): [boolean, EventsArgs['detailsData']] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -46,6 +49,7 @@ export const useTimelineEventsDetails = ({ timelineDetailsRequest, setTimelineDetailsRequest, ] = useState(null); + const { addError, addWarning } = useAppToasts(); const [timelineDetailsResponse, setTimelineDetailsResponse] = useState( null @@ -77,14 +81,13 @@ export const useTimelineEventsDetails = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - // TODO: Make response error status clearer - notifications.toasts.addWarning('An error has occurred'); + addWarning(i18n.FAIL_TIMELINE_DETAILS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ title: 'Failed to run search', text: msg.message }); + addError(msg, { title: i18n.FAIL_TIMELINE_SEARCH_DETAILS }); searchSubscription$.current.unsubscribe(); }, }); @@ -94,7 +97,7 @@ export const useTimelineEventsDetails = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts, skip] + [data.search, addError, addWarning, skip] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts b/x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts new file mode 100644 index 00000000000000..d11984b967db98 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/details/translations.ts @@ -0,0 +1,22 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const FAIL_TIMELINE_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.failDescription', + { + defaultMessage: 'An error has occurred', + } +); + +export const FAIL_TIMELINE_SEARCH_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.failSearchDescription', + { + defaultMessage: 'Failed to run search', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx index 496107e910d76c..1032d0ec1672ac 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.test.tsx @@ -27,6 +27,11 @@ const mockEvents = mockTimelineData.filter((i, index) => index <= 11); const mockSearch = jest.fn(); jest.mock('../../common/lib/kibana', () => ({ + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + }), useKibana: jest.fn().mockReturnValue({ services: { application: { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 83b511f95bc2a0..92199336b978c1 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -40,6 +40,7 @@ import { TimelineEqlRequestOptions, TimelineEqlResponse, } from '../../../common/search_strategy/timeline/events/eql'; +import { useAppToasts } from '../../common/hooks/use_app_toasts'; export interface TimelineArgs { events: TimelineItem[]; @@ -138,7 +139,7 @@ export const useTimelineEvents = ({ }: UseTimelineEventsProps): [boolean, TimelineArgs] => { const [{ pageName }] = useRouteSpy(); const dispatch = useDispatch(); - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -194,6 +195,7 @@ export const useTimelineEvents = ({ loadPage: wrappedLoadPage, updatedAt: 0, }); + const { addError, addWarning } = useAppToasts(); const timelineSearch = useCallback( (request: TimelineRequest | null) => { @@ -242,15 +244,14 @@ export const useTimelineEvents = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning(i18n.ERROR_TIMELINE_EVENTS); + addWarning(i18n.ERROR_TIMELINE_EVENTS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger({ + addError(msg, { title: i18n.FAIL_TIMELINE_EVENTS, - text: msg.message, }); searchSubscription$.current.unsubscribe(); }, @@ -300,7 +301,7 @@ export const useTimelineEvents = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, id, notifications.toasts, pageName, refetchGrid, skip, wrappedLoadPage] + [data.search, id, addWarning, addError, pageName, refetchGrid, skip, wrappedLoadPage] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx index cf5f44a65ab968..4a6eab13ba4f1a 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/index.tsx @@ -21,6 +21,8 @@ import { } from '../../../../common/search_strategy'; import { ESQuery } from '../../../../common/typed_json'; import { isCompleteResponse, isErrorResponse } from '../../../../../../../src/plugins/data/public'; +import { useAppToasts } from '../../../common/hooks/use_app_toasts'; +import * as i18n from './translations'; export interface UseTimelineKpiProps { timerange: TimerangeInput; @@ -37,7 +39,7 @@ export const useTimelineKpis = ({ defaultIndex, isBlankTimeline, }: UseTimelineKpiProps): [boolean, TimelineKpiStrategyResponse | null] => { - const { data, notifications } = useKibana().services; + const { data } = useKibana().services; const refetch = useRef(noop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); @@ -49,6 +51,8 @@ export const useTimelineKpis = ({ timelineKpiResponse, setTimelineKpiResponse, ] = useState(null); + const { addError, addWarning } = useAppToasts(); + const timelineKpiSearch = useCallback( (request: TimelineRequestBasicOptions | null) => { if (request == null) { @@ -71,13 +75,13 @@ export const useTimelineKpis = ({ searchSubscription$.current.unsubscribe(); } else if (isErrorResponse(response)) { setLoading(false); - notifications.toasts.addWarning('An error has occurred'); + addWarning(i18n.FAIL_TIMELINE_KPI_DETAILS); searchSubscription$.current.unsubscribe(); } }, error: (msg) => { setLoading(false); - notifications.toasts.addDanger('Failed to load KPIs'); + addError(msg, { title: i18n.FAIL_TIMELINE_KPI_SEARCH_DETAILS }); searchSubscription$.current.unsubscribe(); }, }); @@ -87,7 +91,7 @@ export const useTimelineKpis = ({ asyncSearch(); refetch.current = asyncSearch; }, - [data.search, notifications.toasts] + [data.search, addError, addWarning] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts b/x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts new file mode 100644 index 00000000000000..1a487ef8127f2f --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/containers/kpis/translations.ts @@ -0,0 +1,22 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const FAIL_TIMELINE_KPI_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.kpiFailDescription', + { + defaultMessage: 'An error has occurred', + } +); + +export const FAIL_TIMELINE_KPI_SEARCH_DETAILS = i18n.translate( + 'xpack.securitySolution.timeline.kpiFailSearchDescription', + { + defaultMessage: 'Failed to load KPIs', + } +); From 49cdc9066df35d7fae0e5fc55cf3c4be81f9f40d Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 22 Apr 2021 17:16:54 +0200 Subject: [PATCH 50/65] [ML] Improve functional tests for Anomaly detection alert rule (#97998) * [ML] ensureAdvancedSectionOpen for assertion * [ML] delete alert rules after tests execution * [ML] add isAdvancedSectionOpened --- .../test/functional/services/ml/alerting.ts | 30 ++++++++++++++++--- .../apps/ml/alert_flyout.ts | 1 + 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/services/ml/alerting.ts b/x-pack/test/functional/services/ml/alerting.ts index 8d27a75b7b485b..327a0e574f0fde 100644 --- a/x-pack/test/functional/services/ml/alerting.ts +++ b/x-pack/test/functional/services/ml/alerting.ts @@ -8,6 +8,9 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; import { MlCommonUI } from './common_ui'; +import { ML_ALERT_TYPES } from '../../../../plugins/ml/common/constants/alerts'; +import { Alert } from '../../../../plugins/alerting/common'; +import { MlAnomalyDetectionAlertParams } from '../../../../plugins/ml/common/types/alerts'; export function MachineLearningAlertingProvider( { getService }: FtrProviderContext, @@ -17,6 +20,7 @@ export function MachineLearningAlertingProvider( const comboBox = getService('comboBox'); const testSubjects = getService('testSubjects'); const find = getService('find'); + const supertest = getService('supertest'); return { async selectAnomalyDetectionAlertType() { @@ -103,6 +107,7 @@ export function MachineLearningAlertingProvider( }, async assertLookbackInterval(expectedValue: string) { + await this.ensureAdvancedSectionOpen(); const actualValue = await testSubjects.getAttribute( 'mlAnomalyAlertLookbackInterval', 'value' @@ -114,6 +119,7 @@ export function MachineLearningAlertingProvider( }, async assertTopNBuckets(expectedNumberOfBuckets: number) { + await this.ensureAdvancedSectionOpen(); const actualValue = await testSubjects.getAttribute('mlAnomalyAlertTopNBuckets', 'value'); expect(actualValue).to.eql( expectedNumberOfBuckets, @@ -133,15 +139,31 @@ export function MachineLearningAlertingProvider( await this.assertTopNBuckets(numberOfBuckets); }, + async isAdvancedSectionOpened() { + return await find.existsByDisplayedByCssSelector('#mlAnomalyAlertAdvancedSettings'); + }, + async ensureAdvancedSectionOpen() { await retry.tryForTime(5000, async () => { - const isVisible = await find.existsByDisplayedByCssSelector( - '#mlAnomalyAlertAdvancedSettings' - ); - if (!isVisible) { + if (!(await this.isAdvancedSectionOpened())) { await testSubjects.click('mlAnomalyAlertAdvancedSettingsTrigger'); + expect(await this.isAdvancedSectionOpened()).to.eql(true); } }); }, + + async cleanAnomalyDetectionRules() { + const { body: anomalyDetectionRules } = await supertest + .get(`/api/alerting/rules/_find`) + .query({ filter: `alert.attributes.alertTypeId:${ML_ALERT_TYPES.ANOMALY_DETECTION}` }) + .set('kbn-xsrf', 'foo') + .expect(200); + + for (const rule of anomalyDetectionRules.data as Array< + Alert + >) { + await supertest.delete(`/api/alerting/rule/${rule.id}`).set('kbn-xsrf', 'foo').expect(204); + } + }, }; } diff --git a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts index cc0dcff5286635..ee30f3a9eab00e 100644 --- a/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/ml/alert_flyout.ts @@ -93,6 +93,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { after(async () => { await ml.api.cleanMlIndices(); + await ml.alerting.cleanAnomalyDetectionRules(); }); describe('overview page alert flyout controls', () => { From 5c9491154362a0486f8b1c1be514eaafe0085bd6 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Thu, 22 Apr 2021 08:18:49 -0700 Subject: [PATCH 51/65] [ML] Adds tooltip for top influencers (#97762) Co-authored-by: Dima Arnautov --- .../plugins/ml/public/application/explorer/explorer.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.js b/x-pack/plugins/ml/public/application/explorer/explorer.js index 45665b2026db51..e33c09932daab1 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/plugins/ml/public/application/explorer/explorer.js @@ -328,6 +328,15 @@ export class ExplorerUI extends React.Component { id="xpack.ml.explorer.topInfuencersTitle" defaultMessage="Top influencers" /> + + } + position="right" + /> {loading ? ( From 8bbf9c0e280fe3108c0397433ea844b751de3120 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Thu, 22 Apr 2021 11:50:38 -0400 Subject: [PATCH 52/65] [App Search] Disabled Save button when nothing selected (#97896) --- .../result_settings/result_settings.test.tsx | 11 +++++++ .../result_settings/result_settings.tsx | 12 ++++--- .../result_settings_logic.test.ts | 15 +++++++++ .../result_settings/result_settings_logic.ts | 5 +++ .../components/result_settings/utils.test.ts | 32 +++++++++++++++++++ .../components/result_settings/utils.ts | 7 ++++ 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx index 70bc49421a4f1f..48a25d4f1f4bd6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx @@ -86,6 +86,17 @@ describe('ResultSettings', () => { expect(saveButton.prop('disabled')).toBe(true); }); + it('renders the "save" button as disabled if everything is disabled', () => { + setMockValues({ + ...values, + stagedUpdates: true, + resultFieldsEmpty: true, + }); + const buttons = findButtons(subject()); + const saveButton = shallow(buttons[0]); + expect(saveButton.prop('disabled')).toBe(true); + }); + it('renders a "restore defaults" button that will reset all values to their defaults', () => { const buttons = findButtons(subject()); expect(buttons.length).toBe(3); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx index bea5bcc548fab5..51cdc3aea21f24 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx @@ -46,9 +46,13 @@ const UNSAVED_MESSAGE = i18n.translate( ); export const ResultSettings: React.FC = () => { - const { dataLoading, schema, stagedUpdates, resultFieldsAtDefaultSettings } = useValues( - ResultSettingsLogic - ); + const { + dataLoading, + schema, + stagedUpdates, + resultFieldsAtDefaultSettings, + resultFieldsEmpty, + } = useValues(ResultSettingsLogic); const { initializeResultSettingsData, saveResultSettings, @@ -81,7 +85,7 @@ export const ResultSettings: React.FC = () => { color="primary" fill onClick={saveResultSettings} - disabled={!stagedUpdates} + disabled={resultFieldsEmpty || !stagedUpdates} > {SAVE_BUTTON_LABEL} , diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index 437949982cb5aa..e432ba6956094f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -46,6 +46,7 @@ describe('ResultSettingsLogic', () => { const SELECTORS = { serverResultFields: {}, reducedServerResultFields: {}, + resultFieldsEmpty: true, resultFieldsAtDefaultSettings: true, stagedUpdates: false, nonTextResultFields: {}, @@ -333,6 +334,20 @@ describe('ResultSettingsLogic', () => { }); }); + describe('resultFieldsEmpty', () => { + it('should return true if no raw or snippet fields are enabled', () => { + mount({ + resultFields: { + foo: { raw: false }, + bar: {}, + baz: { raw: false, snippet: false }, + }, + }); + + expect(ResultSettingsLogic.values.resultFieldsEmpty).toEqual(true); + }); + }); + describe('stagedUpdates', () => { it('should return true if changes have been made since the last save', () => { mount({ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts index af78543cda2b23..4e738961f5e58e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts @@ -24,6 +24,7 @@ import { import { areFieldsAtDefaultSettings, + areFieldsEmpty, clearAllFields, convertServerResultFieldsToResultFields, convertToServerFieldResultSetting, @@ -197,6 +198,10 @@ export const ResultSettingsLogic = kea [selectors.resultFields], (resultFields) => areFieldsAtDefaultSettings(resultFields), ], + resultFieldsEmpty: [ + () => [selectors.resultFields], + (resultFields) => areFieldsEmpty(resultFields), + ], stagedUpdates: [ () => [selectors.lastSavedResultFields, selectors.resultFields], (lastSavedResultFields, resultFields) => !isEqual(lastSavedResultFields, resultFields), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts index 6fee0a25003575..7e1d3d96c6d3f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.test.ts @@ -14,6 +14,7 @@ import { clearAllFields, resetAllFields, splitResultFields, + areFieldsEmpty, } from './utils'; describe('clearAllFields', () => { @@ -144,6 +145,37 @@ describe('splitResultFields', () => { }); }); +describe('areFieldsEmpty', () => { + it('should return true if all fields are empty or have all properties disabled', () => { + expect( + areFieldsEmpty({ + foo: {}, + bar: { raw: false, snippet: false }, + baz: { raw: false }, + }) + ).toBe(true); + }); + + it('should return false otherwise', () => { + expect( + areFieldsEmpty({ + foo: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + bar: { + raw: true, + rawSize: 5, + snippet: false, + snippetFallback: false, + }, + }) + ).toBe(false); + }); +}); + describe('areFieldsAtDefaultSettings', () => { it('will return true if all settings for all fields are at their defaults', () => { expect( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts index ff88aaac193d78..a67f092a5e7f72 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/utils.ts @@ -112,6 +112,13 @@ export const splitResultFields = (resultFields: FieldResultSettingObject, schema return { textResultFields, nonTextResultFields }; }; +export const areFieldsEmpty = (fields: FieldResultSettingObject) => { + const anyNonEmptyField = Object.values(fields).find((field) => { + return (field as FieldResultSetting).raw || (field as FieldResultSetting).snippet; + }); + return !anyNonEmptyField; +}; + export const areFieldsAtDefaultSettings = (fields: FieldResultSettingObject) => { const anyNonDefaultSettingsValue = Object.values(fields).find((resultSettings) => { return !isEqual(resultSettings, DEFAULT_FIELD_SETTINGS); From d25bd680eb7ce479c11efeaf1b9d7b94f0d59b42 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Thu, 22 Apr 2021 12:01:45 -0400 Subject: [PATCH 53/65] Switch Tinymath to use peggy instead of pegjs (#97906) --- package.json | 1 + packages/kbn-tinymath/BUILD.bazel | 8 ++++---- .../kbn-tinymath/grammar/{grammar.pegjs => grammar.peggy} | 0 yarn.lock | 5 +++++ 4 files changed, 10 insertions(+), 4 deletions(-) rename packages/kbn-tinymath/grammar/{grammar.pegjs => grammar.peggy} (100%) diff --git a/package.json b/package.json index 992433e17e6c15..d4d3706d8b6b8c 100644 --- a/package.json +++ b/package.json @@ -300,6 +300,7 @@ "p-retry": "^4.2.0", "papaparse": "^5.2.0", "pdfmake": "^0.1.65", + "peggy": "^1.0.0", "pegjs": "0.10.0", "pluralize": "3.1.0", "pngjs": "^3.4.0", diff --git a/packages/kbn-tinymath/BUILD.bazel b/packages/kbn-tinymath/BUILD.bazel index 9d521776fb4919..ae029c88774e84 100644 --- a/packages/kbn-tinymath/BUILD.bazel +++ b/packages/kbn-tinymath/BUILD.bazel @@ -1,5 +1,5 @@ load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("@npm//pegjs:index.bzl", "pegjs") +load("@npm//peggy:index.bzl", "peggy") PKG_BASE_NAME = "kbn-tinymath" PKG_REQUIRE_NAME = "@kbn/tinymath" @@ -30,16 +30,16 @@ DEPS = [ "@npm//lodash", ] -pegjs( +peggy( name = "grammar", data = [ - ":grammar/grammar.pegjs" + ":grammar/grammar.peggy" ], output_dir = True, args = [ "-o", "$(@D)/index.js", - "./%s/grammar/grammar.pegjs" % package_name() + "./%s/grammar/grammar.peggy" % package_name() ], ) diff --git a/packages/kbn-tinymath/grammar/grammar.pegjs b/packages/kbn-tinymath/grammar/grammar.peggy similarity index 100% rename from packages/kbn-tinymath/grammar/grammar.pegjs rename to packages/kbn-tinymath/grammar/grammar.peggy diff --git a/yarn.lock b/yarn.lock index f4d76841749673..465667230b6399 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21252,6 +21252,11 @@ pdfmake@^0.1.65: pdfkit "^0.11.0" svg-to-pdfkit "^0.1.8" +peggy@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/peggy/-/peggy-1.0.0.tgz#df6c7816c9df0ef35e071aaf96836cb866fe7eb4" + integrity sha512-lH12sxAXj4Aug+vH6IGoByIQOREIlhH+x4Uzb9kce6DD8wcGeidkC0JYEOwHormKrLt5BFLTbR4PuD/tiMOirQ== + pegjs@0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" From 97ebe11aac997e9df8638a1cfd1191f78f5e8993 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Thu, 22 Apr 2021 11:02:24 -0500 Subject: [PATCH 54/65] [ML] Fix Anomaly Detection influencer filter icons spacing too big (#97713) * [ML] Fix entity button too big * [ML] Consolidate sizing to eui size Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/entity_cell/entity_cell.scss | 5 +++-- .../components/influencers_list/_influencers_list.scss | 2 +- .../explorer_charts/_explorer_chart_tooltip.scss | 2 +- .../explorer_chart_label/_explorer_chart_label.scss | 4 +++- .../{entity_filter.scss => _entity_filter.scss} | 9 ++++++--- .../explorer_chart_label/entity_filter/entity_filter.tsx | 4 +--- 6 files changed, 15 insertions(+), 11 deletions(-) rename x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/{entity_filter.scss => _entity_filter.scss} (51%) diff --git a/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.scss b/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.scss index 60c1a0820fbc90..fc9cf149101380 100644 --- a/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.scss +++ b/x-pack/plugins/ml/public/application/components/entity_cell/entity_cell.scss @@ -9,8 +9,9 @@ } .filter-button { - opacity: .3; - min-width: 14px; + opacity: .5; + width: $euiSize; + height: $euiSize; -webkit-transform: translateY(-1px); transform: translateY(-1px); diff --git a/x-pack/plugins/ml/public/application/components/influencers_list/_influencers_list.scss b/x-pack/plugins/ml/public/application/components/influencers_list/_influencers_list.scss index d2d57544de41a8..e33811aa9a8ccc 100644 --- a/x-pack/plugins/ml/public/application/components/influencers_list/_influencers_list.scss +++ b/x-pack/plugins/ml/public/application/components/influencers_list/_influencers_list.scss @@ -97,7 +97,7 @@ line-height: 14px; border-radius: $euiBorderRadius; padding: $euiSizeXS / 2; - margin-top: 3px; + margin-top: $euiSizeXS; display: inline-block; border: $euiBorderThin; } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/_explorer_chart_tooltip.scss b/x-pack/plugins/ml/public/application/explorer/explorer_charts/_explorer_chart_tooltip.scss index d291ff3d3cade1..80ac69d5b72894 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/_explorer_chart_tooltip.scss +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/_explorer_chart_tooltip.scss @@ -8,7 +8,7 @@ } .ml-explorer-chart-info-tooltip .mlDescriptionList > * { - margin-top: 3px; + margin-top: $euiSizeXS; } .ml-explorer-chart-info-tooltip .mlDescriptionList { diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/_explorer_chart_label.scss b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/_explorer_chart_label.scss index bbe134af0202b4..011937c27758bd 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/_explorer_chart_label.scss +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/_explorer_chart_label.scss @@ -3,5 +3,7 @@ } .ml-explorer-chart-label-badges { - margin-top: 3px; + margin-top: $euiSizeXS; + display: flex; + align-items: center; } diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.scss b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/_entity_filter.scss similarity index 51% rename from x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.scss rename to x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/_entity_filter.scss index 732b71d0565364..800c33e50689d6 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.scss +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/_entity_filter.scss @@ -1,7 +1,10 @@ .filter-button { - opacity: .3; - min-width: 14px; - padding-right: 0; + opacity: .5; + width: $euiSize; + height: $euiSize; + + -webkit-transform: translateY(-1px); + transform: translateY(-1px); .euiIcon { width: $euiFontSizeXS; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx index 079af5827a4b51..2ede9d380f3bf9 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer_charts/components/explorer_chart_label/entity_filter/entity_filter.tsx @@ -12,7 +12,7 @@ import { ENTITY_FIELD_OPERATIONS, EntityFieldOperation, } from '../../../../../../../common/util/anomaly_utils'; -import './entity_filter.scss'; +import './_entity_filter.scss'; interface EntityFilterProps { onFilter: (params: { @@ -39,7 +39,6 @@ export const EntityFilter: FC = ({ } > @@ -65,7 +64,6 @@ export const EntityFilter: FC = ({ } > From 57f84f85934e02ac10b3c1fbcbb89e414ab04097 Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Thu, 22 Apr 2021 11:25:10 -0500 Subject: [PATCH 55/65] [Fleet] Refactor setup to load default packages/policies with preconfiguration (#97328) Co-authored-by: Nicolas Chaulet Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/common/constants/agent_policy.ts | 35 --- x-pack/plugins/fleet/common/constants/epm.ts | 1 + .../common/constants/preconfiguration.ts | 60 +++++ .../common/types/models/preconfiguration.ts | 4 +- .../common/types/rest_spec/ingest_setup.ts | 5 +- .../fleet/public/applications/fleet/app.tsx | 11 +- .../plugins/fleet/server/constants/index.ts | 4 + x-pack/plugins/fleet/server/index.ts | 4 +- .../server/routes/setup/handlers.test.ts | 5 +- .../fleet/server/routes/setup/handlers.ts | 16 +- .../fleet/server/services/agent_policy.ts | 88 +++---- .../epm/packages/bulk_install_packages.ts | 59 ++++- .../ensure_installed_default_packages.test.ts | 147 ------------ .../server/services/epm/packages/install.ts | 102 +++----- .../server/services/preconfiguration.test.ts | 32 ++- .../fleet/server/services/preconfiguration.ts | 226 +++++++++++------- x-pack/plugins/fleet/server/services/setup.ts | 140 ++--------- .../server/types/models/preconfiguration.ts | 19 +- .../apis/preconfiguration/preconfiguration.ts | 1 + 19 files changed, 412 insertions(+), 547 deletions(-) delete mode 100644 x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index 859a96801595a4..d0bde39e9f2d71 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -5,44 +5,9 @@ * 2.0. */ -import type { AgentPolicy } from '../types'; - -import { defaultPackages } from './epm'; - export const AGENT_POLICY_SAVED_OBJECT_TYPE = 'ingest-agent-policies'; export const AGENT_POLICY_INDEX = '.fleet-policies'; export const agentPolicyStatuses = { Active: 'active', Inactive: 'inactive', } as const; - -export const DEFAULT_AGENT_POLICY: Omit< - AgentPolicy, - 'id' | 'updated_at' | 'updated_by' | 'revision' -> = { - name: 'Default policy', - namespace: 'default', - description: 'Default agent policy created by Kibana', - status: agentPolicyStatuses.Active, - package_policies: [], - is_default: true, - is_managed: false, - monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>, -}; - -export const DEFAULT_FLEET_SERVER_AGENT_POLICY: Omit< - AgentPolicy, - 'id' | 'updated_at' | 'updated_by' | 'revision' -> = { - name: 'Default Fleet Server policy', - namespace: 'default', - description: 'Default Fleet Server agent policy created by Kibana', - status: agentPolicyStatuses.Active, - package_policies: [], - is_default: false, - is_default_fleet_server: true, - is_managed: false, - monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>, -}; - -export const DEFAULT_AGENT_POLICIES_PACKAGES = [defaultPackages.System]; diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 7bf3c3e6205ec8..436eaf7cb8ae81 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -15,6 +15,7 @@ export const requiredPackages = { System: 'system', Endpoint: 'endpoint', ElasticAgent: 'elastic_agent', + FleetServer: FLEET_SERVER_PACKAGE, SecurityDetectionEngine: 'security_detection_engine', } as const; diff --git a/x-pack/plugins/fleet/common/constants/preconfiguration.ts b/x-pack/plugins/fleet/common/constants/preconfiguration.ts index da011f31783c34..88ae8530244ca0 100644 --- a/x-pack/plugins/fleet/common/constants/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/constants/preconfiguration.ts @@ -5,7 +5,67 @@ * 2.0. */ +import type { PreconfiguredAgentPolicy } from '../types'; + +import { defaultPackages } from './epm'; + export const PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE = 'fleet-preconfiguration-deletion-record'; export const PRECONFIGURATION_LATEST_KEYWORD = 'latest'; + +type PreconfiguredAgentPolicyWithDefaultInputs = Omit< + PreconfiguredAgentPolicy, + 'package_policies' | 'id' +> & { + package_policies: Array>; +}; + +export const DEFAULT_AGENT_POLICY: PreconfiguredAgentPolicyWithDefaultInputs = { + name: 'Default policy', + namespace: 'default', + description: 'Default agent policy created by Kibana', + package_policies: [ + { + name: `${defaultPackages.System}-1`, + package: { + name: defaultPackages.System, + }, + }, + ], + is_default: true, + is_managed: false, + monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>, +}; + +export const DEFAULT_FLEET_SERVER_AGENT_POLICY: PreconfiguredAgentPolicyWithDefaultInputs = { + name: 'Default Fleet Server policy', + namespace: 'default', + description: 'Default Fleet Server agent policy created by Kibana', + package_policies: [ + { + name: `${defaultPackages.FleetServer}-1`, + package: { + name: defaultPackages.FleetServer, + }, + }, + ], + is_default: false, + is_default_fleet_server: true, + is_managed: false, + monitoring_enabled: ['logs', 'metrics'] as Array<'logs' | 'metrics'>, +}; + +export const DEFAULT_PACKAGES = Object.values(defaultPackages).map((name) => ({ + name, + version: PRECONFIGURATION_LATEST_KEYWORD, +})); + +// these are currently identical. we can separate if they later diverge +export const REQUIRED_PACKAGES = DEFAULT_PACKAGES; + +export interface PreconfigurationError { + package?: { name: string; version: string }; + agentPolicy?: { name: string }; + error: Error; +} diff --git a/x-pack/plugins/fleet/common/types/models/preconfiguration.ts b/x-pack/plugins/fleet/common/types/models/preconfiguration.ts index 61a5cb63400a01..6087c910510cc7 100644 --- a/x-pack/plugins/fleet/common/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/types/models/preconfiguration.ts @@ -20,9 +20,9 @@ export interface PreconfiguredAgentPolicy extends Omit> & { + Partial> & { name: string; - package: Partial; + package: Partial & { name: string }; inputs?: InputsOverride[]; } >; diff --git a/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts b/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts index 6f64f1c48336d1..91a1915c4c5187 100644 --- a/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts +++ b/x-pack/plugins/fleet/common/types/rest_spec/ingest_setup.ts @@ -5,10 +5,7 @@ * 2.0. */ -import type { DefaultPackagesInstallationError } from '../models/epm'; - export interface PostIngestSetupResponse { isInitialized: boolean; - preconfigurationError?: { name: string; message: string }; - nonFatalPackageUpgradeErrors?: DefaultPackagesInstallationError[]; + nonFatalErrors?: Array<{ error: Error }>; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/app.tsx b/x-pack/plugins/fleet/public/applications/fleet/app.tsx index 4a47d39b77934d..5327d4b7cc4a4c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/app.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/app.tsx @@ -83,20 +83,13 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => { if (setupResponse.error) { setInitializationError(setupResponse.error); } - if (setupResponse.data?.preconfigurationError) { - notifications.toasts.addError(setupResponse.data.preconfigurationError, { + if (setupResponse.data?.nonFatalErrors?.length) { + notifications.toasts.addError(setupResponse.data.nonFatalErrors[0], { title: i18n.translate('xpack.fleet.setup.uiPreconfigurationErrorTitle', { defaultMessage: 'Configuration error', }), }); } - if (setupResponse.data?.nonFatalPackageUpgradeErrors) { - notifications.toasts.addError(setupResponse.data.nonFatalPackageUpgradeErrors, { - title: i18n.translate('xpack.fleet.setup.nonFatalPackageErrorsTitle', { - defaultMessage: 'One or more packages could not be successfully upgraded', - }), - }); - } } catch (err) { setInitializationError(err); } diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index aa4fbd9cfeb97b..e2f800f67705db 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -47,11 +47,15 @@ export { GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, // Defaults DEFAULT_AGENT_POLICY, + DEFAULT_FLEET_SERVER_AGENT_POLICY, DEFAULT_OUTPUT, + DEFAULT_PACKAGES, + REQUIRED_PACKAGES, // Fleet Server index FLEET_SERVER_SERVERS_INDEX, ENROLLMENT_API_KEYS_INDEX, AGENTS_INDEX, + // Preconfiguration PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, PRECONFIGURATION_LATEST_KEYWORD, } from '../../common'; diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 25298d991230de..e83617413b7442 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -58,8 +58,8 @@ export const config: PluginConfigDescriptor = { }) ), }), - packages: schema.maybe(PreconfiguredPackagesSchema), - agentPolicies: schema.maybe(PreconfiguredAgentPoliciesSchema), + packages: PreconfiguredPackagesSchema, + agentPolicies: PreconfiguredAgentPoliciesSchema, }), }; diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts index fd32d699ae45e2..809a045478b032 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.test.ts @@ -48,13 +48,12 @@ describe('FleetSetupHandler', () => { mockSetupIngestManager.mockImplementation(() => Promise.resolve({ isInitialized: true, - preconfigurationError: undefined, - nonFatalPackageUpgradeErrors: [], + nonFatalErrors: [], }) ); await fleetSetupHandler(context, request, response); - const expectedBody: PostIngestSetupResponse = { isInitialized: true }; + const expectedBody: PostIngestSetupResponse = { isInitialized: true, nonFatalErrors: [] }; expect(response.customError).toHaveBeenCalledTimes(0); expect(response.ok).toHaveBeenCalledWith({ body: expectedBody }); }); diff --git a/x-pack/plugins/fleet/server/routes/setup/handlers.ts b/x-pack/plugins/fleet/server/routes/setup/handlers.ts index 627f628f7b9fc1..370196cc202cd7 100644 --- a/x-pack/plugins/fleet/server/routes/setup/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/setup/handlers.ts @@ -48,12 +48,18 @@ export const fleetSetupHandler: RequestHandler = async (context, request, respon const esClient = context.core.elasticsearch.client.asCurrentUser; const body: PostIngestSetupResponse = await setupIngestManager(soClient, esClient); - if (body.nonFatalPackageUpgradeErrors?.length === 0) { - delete body.nonFatalPackageUpgradeErrors; - } - return response.ok({ - body, + body: { + ...body, + nonFatalErrors: body.nonFatalErrors?.map((e) => { + // JSONify the error object so it can be displayed properly in the UI + const error = e.error ?? e; + return { + name: error.name, + message: error.message, + }; + }), + }, }); } catch (error) { return defaultIngestErrorHandler({ error, response }); diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index c0a2e80af4bf12..2b9cc4e0723044 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -16,7 +16,6 @@ import type { import type { AuthenticatedUser } from '../../../security/server'; import { - DEFAULT_AGENT_POLICY, AGENT_POLICY_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, @@ -37,7 +36,6 @@ import { dataTypes, packageToPackagePolicy, AGENT_POLICY_INDEX, - DEFAULT_FLEET_SERVER_AGENT_POLICY, } from '../../common'; import type { DeleteAgentPolicyResponse, @@ -106,39 +104,6 @@ class AgentPolicyService { return (await this.get(soClient, id)) as AgentPolicy; } - public async ensureDefaultAgentPolicy( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient - ): Promise<{ - created: boolean; - policy: AgentPolicy; - }> { - const searchParams = { - searchFields: ['is_default'], - search: 'true', - }; - return await this.ensureAgentPolicy(soClient, esClient, DEFAULT_AGENT_POLICY, searchParams); - } - - public async ensureDefaultFleetServerAgentPolicy( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient - ): Promise<{ - created: boolean; - policy: AgentPolicy; - }> { - const searchParams = { - searchFields: ['is_default_fleet_server'], - search: 'true', - }; - return await this.ensureAgentPolicy( - soClient, - esClient, - DEFAULT_FLEET_SERVER_AGENT_POLICY, - searchParams - ); - } - public async ensurePreconfiguredAgentPolicy( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, @@ -148,22 +113,44 @@ class AgentPolicyService { policy?: AgentPolicy; }> { const { id, ...preconfiguredAgentPolicy } = omit(config, 'package_policies'); - const preconfigurationId = String(id); - const searchParams = { - searchFields: ['preconfiguration_id'], - search: escapeSearchQueryPhrase(preconfigurationId), - }; - const newAgentPolicyDefaults: Partial = { namespace: 'default', monitoring_enabled: ['logs', 'metrics'], }; - const newAgentPolicy = { - ...newAgentPolicyDefaults, - ...preconfiguredAgentPolicy, - preconfiguration_id: preconfigurationId, - } as NewAgentPolicy; + let searchParams; + let newAgentPolicy; + if (id) { + const preconfigurationId = String(id); + searchParams = { + searchFields: ['preconfiguration_id'], + search: escapeSearchQueryPhrase(preconfigurationId), + }; + + newAgentPolicy = { + ...newAgentPolicyDefaults, + ...preconfiguredAgentPolicy, + preconfiguration_id: preconfigurationId, + } as NewAgentPolicy; + } else if ( + preconfiguredAgentPolicy.is_default || + preconfiguredAgentPolicy.is_default_fleet_server + ) { + searchParams = { + searchFields: [ + preconfiguredAgentPolicy.is_default_fleet_server + ? 'is_default_fleet_server' + : 'is_default', + ], + search: 'true', + }; + + newAgentPolicy = { + ...newAgentPolicyDefaults, + ...preconfiguredAgentPolicy, + } as NewAgentPolicy; + } + if (!newAgentPolicy || !searchParams) throw new Error('Missing ID'); return await this.ensureAgentPolicy(soClient, esClient, newAgentPolicy, searchParams); } @@ -554,13 +541,14 @@ class AgentPolicyService { throw new HostedAgentPolicyRestrictionRelatedError(`Cannot delete hosted agent policy ${id}`); } - const { - policy: { id: defaultAgentPolicyId }, - } = await this.ensureDefaultAgentPolicy(soClient, esClient); - if (id === defaultAgentPolicyId) { + if (agentPolicy.is_default) { throw new Error('The default agent policy cannot be deleted'); } + if (agentPolicy.is_default_fleet_server) { + throw new Error('The default fleet server agent policy cannot be deleted'); + } + const { total } = await getAgentsByKuery(esClient, { showInactive: false, perPage: 0, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts index 2c5b072aa39796..c77e2a0a22a0a7 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/bulk_install_packages.ts @@ -11,38 +11,68 @@ import { appContextService } from '../../app_context'; import * as Registry from '../registry'; import { installIndexPatterns } from '../kibana/index_pattern/install'; -import { installPackage } from './install'; +import type { InstallResult } from '../../../types'; + +import { installPackage, isPackageVersionOrLaterInstalled } from './install'; import type { BulkInstallResponse, IBulkInstallPackageError } from './install'; interface BulkInstallPackagesParams { savedObjectsClient: SavedObjectsClientContract; - packagesToInstall: string[]; + packagesToInstall: Array; esClient: ElasticsearchClient; + force?: boolean; } export async function bulkInstallPackages({ savedObjectsClient, packagesToInstall, esClient, + force, }: BulkInstallPackagesParams): Promise { const logger = appContextService.getLogger(); const installSource = 'registry'; - const latestPackagesResults = await Promise.allSettled( - packagesToInstall.map((packageName) => Registry.fetchFindLatestPackage(packageName)) + const packagesResults = await Promise.allSettled( + packagesToInstall.map((pkg) => { + if (typeof pkg === 'string') return Registry.fetchFindLatestPackage(pkg); + return Promise.resolve(pkg); + }) ); logger.debug(`kicking off bulk install of ${packagesToInstall.join(', ')} from registry`); const bulkInstallResults = await Promise.allSettled( - latestPackagesResults.map(async (result, index) => { - const packageName = packagesToInstall[index]; + packagesResults.map(async (result, index) => { + const packageName = getNameFromPackagesToInstall(packagesToInstall, index); if (result.status === 'fulfilled') { - const latestPackage = result.value; + const pkgKeyProps = result.value; + const installedPackageResult = await isPackageVersionOrLaterInstalled({ + savedObjectsClient, + pkgName: pkgKeyProps.name, + pkgVersion: pkgKeyProps.version, + }); + if (installedPackageResult) { + const { + name, + version, + installed_es: installedEs, + installed_kibana: installedKibana, + } = installedPackageResult.package; + return { + name, + version, + result: { + assets: [...installedEs, ...installedKibana], + status: 'already_installed', + installType: installedPackageResult.installType, + } as InstallResult, + }; + } const installResult = await installPackage({ savedObjectsClient, esClient, - pkgkey: Registry.pkgToPkgKey(latestPackage), + pkgkey: Registry.pkgToPkgKey(pkgKeyProps), installSource, skipPostInstall: true, + force, }); if (installResult.error) { return { @@ -53,7 +83,7 @@ export async function bulkInstallPackages({ } else { return { name: packageName, - version: latestPackage.version, + version: pkgKeyProps.version, result: installResult, }; } @@ -76,7 +106,7 @@ export async function bulkInstallPackages({ } return bulkInstallResults.map((result, index) => { - const packageName = packagesToInstall[index]; + const packageName = getNameFromPackagesToInstall(packagesToInstall, index); if (result.status === 'fulfilled') { if (result.value && result.value.error) { return { @@ -98,3 +128,12 @@ export function isBulkInstallError( ): installResponse is IBulkInstallPackageError { return 'error' in installResponse && installResponse.error instanceof Error; } + +function getNameFromPackagesToInstall( + packagesToInstall: BulkInstallPackagesParams['packagesToInstall'], + index: number +) { + const entry = packagesToInstall[index]; + if (typeof entry === 'string') return entry; + return entry.name; +} diff --git a/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts deleted file mode 100644 index 60e2e5ea2cbf8b..00000000000000 --- a/x-pack/plugins/fleet/server/services/epm/packages/ensure_installed_default_packages.test.ts +++ /dev/null @@ -1,147 +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 type { SavedObject, SavedObjectsClientContract } from 'src/core/server'; - -import { ElasticsearchAssetType, KibanaSavedObjectType } from '../../../types'; -import type { Installation } from '../../../types'; - -jest.mock('./install'); -jest.mock('./bulk_install_packages'); -jest.mock('./get'); - -const { ensureInstalledDefaultPackages } = jest.requireActual('./install'); -const { isBulkInstallError: actualIsBulkInstallError } = jest.requireActual( - './bulk_install_packages' -); -// eslint-disable-next-line import/order -import { savedObjectsClientMock } from 'src/core/server/mocks'; - -import { appContextService } from '../../app_context'; -import { createAppContextStartContractMock } from '../../../mocks'; - -import { getInstallation } from './get'; -import { bulkInstallPackages, isBulkInstallError } from './bulk_install_packages'; - -// if we add this assertion, TS will type check the return value -// and the editor will also know about .mockImplementation, .mock.calls, etc -const mockedBulkInstallPackages = bulkInstallPackages as jest.MockedFunction< - typeof bulkInstallPackages ->; -const mockedIsBulkInstallError = isBulkInstallError as jest.MockedFunction< - typeof isBulkInstallError ->; -const mockedGetInstallation = getInstallation as jest.MockedFunction; - -// I was unable to get the actual implementation set in the `jest.mock()` call at the top to work -// so this will set the `isBulkInstallError` function back to the actual implementation -mockedIsBulkInstallError.mockImplementation(actualIsBulkInstallError); - -const mockInstallation: SavedObject = { - id: 'test-pkg', - references: [], - type: 'epm-packages', - attributes: { - id: 'test-pkg', - installed_kibana: [{ type: KibanaSavedObjectType.dashboard, id: 'dashboard-1' }], - installed_es: [{ type: ElasticsearchAssetType.ingestPipeline, id: 'pipeline' }], - package_assets: [], - es_index_patterns: { pattern: 'pattern-name' }, - name: 'test package', - version: '1.0.0', - install_status: 'installed', - install_version: '1.0.0', - install_started_at: new Date().toISOString(), - install_source: 'registry', - }, -}; - -describe('ensureInstalledDefaultPackages', () => { - let soClient: jest.Mocked; - beforeEach(async () => { - soClient = savedObjectsClientMock.create(); - appContextService.start(createAppContextStartContractMock()); - }); - afterEach(async () => { - appContextService.stop(); - }); - it('should return an array of Installation objects when successful', async () => { - mockedGetInstallation.mockImplementation(async () => { - return mockInstallation.attributes; - }); - mockedBulkInstallPackages.mockImplementationOnce(async function () { - return [ - { - name: mockInstallation.attributes.name, - result: { assets: [], status: 'installed', installType: 'install' }, - version: '', - statusCode: 200, - }, - ]; - }); - const resp = await ensureInstalledDefaultPackages(soClient, jest.fn()); - expect(resp.installations).toEqual([mockInstallation.attributes]); - }); - it('should throw the first Error it finds', async () => { - class SomeCustomError extends Error {} - mockedGetInstallation.mockImplementation(async () => { - return mockInstallation.attributes; - }); - mockedBulkInstallPackages.mockImplementationOnce(async function () { - return [ - { - name: 'success one', - result: { assets: [], status: 'installed', installType: 'install' }, - version: '', - statusCode: 200, - }, - { - name: 'success two', - result: { assets: [], status: 'installed', installType: 'install' }, - version: '', - statusCode: 200, - }, - { - name: 'failure one', - error: new SomeCustomError('abc 123'), - }, - { - name: 'success three', - result: { assets: [], status: 'installed', installType: 'install' }, - version: '', - statusCode: 200, - }, - { - name: 'failure two', - error: new Error('zzz'), - }, - ]; - }); - const installPromise = ensureInstalledDefaultPackages(soClient, jest.fn()); - expect.assertions(2); - expect(installPromise).rejects.toThrow(SomeCustomError); - expect(installPromise).rejects.toThrow('abc 123'); - }); - it('should throw an error when get installation returns undefined', async () => { - mockedGetInstallation.mockImplementation(async () => { - return undefined; - }); - mockedBulkInstallPackages.mockImplementationOnce(async function () { - return [ - { - name: 'undefined package', - result: { assets: [], status: 'installed', installType: 'install' }, - version: '', - statusCode: 200, - }, - ]; - }); - const installPromise = ensureInstalledDefaultPackages(soClient, jest.fn()); - expect.assertions(1); - expect(installPromise).rejects.toThrow(); - }); -}); diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index ec1cc322475b03..48d66f06e17b94 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -5,19 +5,14 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; import semverLt from 'semver/functions/lt'; import type Boom from '@hapi/boom'; import type { ElasticsearchClient, SavedObject, SavedObjectsClientContract } from 'src/core/server'; import { generateESIndexPatterns } from '../elasticsearch/template/template'; -import { defaultPackages } from '../../../../common'; -import type { - BulkInstallPackageInfo, - InstallablePackage, - InstallSource, - DefaultPackagesInstallationError, -} from '../../../../common'; +import type { BulkInstallPackageInfo, InstallablePackage, InstallSource } from '../../../../common'; import { IngestManagerError, PackageOperationNotSupportedError, @@ -39,71 +34,30 @@ import { toAssetReference } from '../kibana/assets/install'; import type { ArchiveAsset } from '../kibana/assets/install'; import { installIndexPatterns } from '../kibana/index_pattern/install'; -import { - isRequiredPackage, - getInstallation, - getInstallationObject, - bulkInstallPackages, - isBulkInstallError, -} from './index'; +import { isRequiredPackage, getInstallation, getInstallationObject } from './index'; import { removeInstallation } from './remove'; import { getPackageSavedObjects } from './get'; import { _installPackage } from './_install_package'; -export interface DefaultPackagesInstallationResult { - installations: Installation[]; - nonFatalPackageUpgradeErrors: DefaultPackagesInstallationError[]; -} - -export async function ensureInstalledDefaultPackages( - savedObjectsClient: SavedObjectsClientContract, - esClient: ElasticsearchClient -): Promise { - const installations = []; - const nonFatalPackageUpgradeErrors = []; - const bulkResponse = await bulkInstallPackages({ - savedObjectsClient, - packagesToInstall: Object.values(defaultPackages), - esClient, - }); - - for (const resp of bulkResponse) { - if (isBulkInstallError(resp)) { - if (resp.installType && (resp.installType === 'update' || resp.installType === 'reupdate')) { - nonFatalPackageUpgradeErrors.push({ installType: resp.installType, error: resp.error }); - } else { - throw resp.error; - } - } else { - installations.push(getInstallation({ savedObjectsClient, pkgName: resp.name })); - } - } - - const retrievedInstallations = await Promise.all(installations); - const verifiedInstallations = retrievedInstallations.map((installation, index) => { - if (!installation) { - throw new Error(`could not get installation ${bulkResponse[index].name}`); - } - return installation; - }); - return { - installations: verifiedInstallations, - nonFatalPackageUpgradeErrors, - }; -} - -async function isPackageVersionOrLaterInstalled(options: { +export async function isPackageVersionOrLaterInstalled(options: { savedObjectsClient: SavedObjectsClientContract; pkgName: string; pkgVersion: string; -}): Promise { +}): Promise<{ package: Installation; installType: InstallType } | false> { const { savedObjectsClient, pkgName, pkgVersion } = options; - const installedPackage = await getInstallation({ savedObjectsClient, pkgName }); + const installedPackageObject = await getInstallationObject({ savedObjectsClient, pkgName }); + const installedPackage = installedPackageObject?.attributes; if ( installedPackage && (installedPackage.version === pkgVersion || semverLt(pkgVersion, installedPackage.version)) ) { - return installedPackage; + let installType: InstallType; + try { + installType = getInstallType({ pkgVersion, installedPkg: installedPackageObject }); + } catch (e) { + installType = 'unknown'; + } + return { package: installedPackage, installType }; } return false; } @@ -121,16 +75,16 @@ export async function ensureInstalledPackage(options: { ? { name: pkgName, version: pkgVersion } : await Registry.fetchFindLatestPackage(pkgName); - const installedPackage = await isPackageVersionOrLaterInstalled({ + const installedPackageResult = await isPackageVersionOrLaterInstalled({ savedObjectsClient, pkgName: pkgKeyProps.name, pkgVersion: pkgKeyProps.version, }); - if (installedPackage) { - return installedPackage; + if (installedPackageResult) { + return installedPackageResult.package; } const pkgkey = Registry.pkgToPkgKey(pkgKeyProps); - await installPackage({ + const installResult = await installPackage({ installSource: 'registry', savedObjectsClient, pkgkey, @@ -138,6 +92,26 @@ export async function ensureInstalledPackage(options: { force: true, // Always force outdated packages to be installed if a later version isn't installed }); + if (installResult.error) { + const errorPrefix = + installResult.installType === 'update' || installResult.installType === 'reupdate' + ? i18n.translate('xpack.fleet.epm.install.packageUpdateError', { + defaultMessage: 'Error updating {pkgName} to {pkgVersion}', + values: { + pkgName: pkgKeyProps.name, + pkgVersion: pkgKeyProps.version, + }, + }) + : i18n.translate('xpack.fleet.epm.install.packageInstallError', { + defaultMessage: 'Error installing {pkgName} {pkgVersion}', + values: { + pkgName: pkgKeyProps.name, + pkgVersion: pkgKeyProps.version, + }, + }); + throw new Error(`${errorPrefix}: ${installResult.error.message}`); + } + const installation = await getInstallation({ savedObjectsClient, pkgName }); if (!installation) throw new Error(`could not get installation ${pkgName}`); return installation; diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index d60b8fde2aa8d5..f7a4c6d9e670f1 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -71,15 +71,8 @@ function getPutPreconfiguredPackagesMock() { } jest.mock('./epm/packages/install', () => ({ - ensureInstalledPackage({ - pkgName, - pkgVersion, - force, - }: { - pkgName: string; - pkgVersion: string; - force?: boolean; - }) { + installPackage({ pkgkey, force }: { pkgkey: string; force?: boolean }) { + const [pkgName, pkgVersion] = pkgkey.split('-'); const installedPackage = mockInstalledPackages.get(pkgName); if (installedPackage) { if (installedPackage.version === pkgVersion) return installedPackage; @@ -87,8 +80,15 @@ jest.mock('./epm/packages/install', () => ({ const packageInstallation = { name: pkgName, version: pkgVersion, title: pkgName }; mockInstalledPackages.set(pkgName, packageInstallation); + return packageInstallation; }, + ensurePackagesCompletedInstall() { + return []; + }, + isPackageVersionOrLaterInstalled() { + return false; + }, })); jest.mock('./epm/packages/get', () => ({ @@ -117,6 +117,20 @@ jest.mock('./package_policy', () => ({ }, })); +jest.mock('./app_context', () => ({ + appContextService: { + getLogger: () => + new Proxy( + {}, + { + get() { + return jest.fn(); + }, + } + ), + }, +})); + describe('policy preconfiguration', () => { beforeEach(() => { mockInstalledPackages.clear(); diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index ffb16d286c45ae..77230c01cdcb88 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -18,6 +18,7 @@ import type { NewPackagePolicyInputStream, PreconfiguredAgentPolicy, PreconfiguredPackage, + PreconfigurationError, } from '../../common'; import { PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, @@ -28,9 +29,16 @@ import { escapeSearchQueryPhrase } from './saved_object'; import { pkgToPkgKey } from './epm/registry'; import { getInstallation } from './epm/packages'; -import { ensureInstalledPackage } from './epm/packages/install'; +import { ensurePackagesCompletedInstall } from './epm/packages/install'; +import { bulkInstallPackages } from './epm/packages/bulk_install_packages'; import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; +interface PreconfigurationResult { + policies: Array<{ id: string; updated_at: string }>; + packages: string[]; + nonFatalErrors?: PreconfigurationError[]; +} + export type InputsOverride = Partial & { vars?: Array; }; @@ -41,7 +49,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( policies: PreconfiguredAgentPolicy[] = [], packages: PreconfiguredPackage[] = [], defaultOutput: Output -) { +): Promise { // Validate configured packages to ensure there are no version conflicts const packageNames = groupBy(packages, (pkg) => pkg.name); const duplicatePackages = Object.entries(packageNames).filter( @@ -66,28 +74,64 @@ export async function ensurePreconfiguredPackagesAndPolicies( } // Preinstall packages specified in Kibana config - const preconfiguredPackages = await Promise.all( - packages.map(({ name, version }) => - ensureInstalledPreconfiguredPackage(soClient, esClient, name, version) - ) - ); + const preconfiguredPackages = await bulkInstallPackages({ + savedObjectsClient: soClient, + esClient, + packagesToInstall: packages.map((pkg) => + pkg.version === PRECONFIGURATION_LATEST_KEYWORD ? pkg.name : pkg + ), + force: true, // Always force outdated packages to be installed if a later version isn't installed + }); + + const fulfilledPackages = []; + const rejectedPackages = []; + for (let i = 0; i < preconfiguredPackages.length; i++) { + const packageResult = preconfiguredPackages[i]; + if ('error' in packageResult) + rejectedPackages.push({ + package: { name: packages[i].name, version: packages[i].version }, + error: packageResult.error, + } as PreconfigurationError); + else fulfilledPackages.push(packageResult); + } + + // Keeping this outside of the Promise.all because it introduces a race condition. + // If one of the required packages fails to install/upgrade it might get stuck in the installing state. + // On the next call to the /setup API, if there is a upgrade available for one of the required packages a race condition + // will occur between upgrading the package and reinstalling the previously failed package. + // By moving this outside of the Promise.all, the upgrade will occur first, and then we'll attempt to reinstall any + // packages that are stuck in the installing state. + await ensurePackagesCompletedInstall(soClient, esClient); // Create policies specified in Kibana config - const preconfiguredPolicies = await Promise.all( + const preconfiguredPolicies = await Promise.allSettled( policies.map(async (preconfiguredAgentPolicy) => { - // Check to see if a preconfigured policy with the same preconfigurationId was already deleted by the user - const preconfigurationId = String(preconfiguredAgentPolicy.id); - const searchParams = { - searchFields: ['preconfiguration_id'], - search: escapeSearchQueryPhrase(preconfigurationId), - }; - const deletionRecords = await soClient.find({ - type: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, - ...searchParams, - }); - const wasDeleted = deletionRecords.total > 0; - if (wasDeleted) { - return { created: false, deleted: preconfigurationId }; + if (preconfiguredAgentPolicy.id) { + // Check to see if a preconfigured policy with the same preconfigurationId was already deleted by the user + const preconfigurationId = String(preconfiguredAgentPolicy.id); + const searchParams = { + searchFields: ['preconfiguration_id'], + search: escapeSearchQueryPhrase(preconfigurationId), + }; + const deletionRecords = await soClient.find({ + type: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, + ...searchParams, + }); + const wasDeleted = deletionRecords.total > 0; + if (wasDeleted) { + return { created: false, deleted: preconfigurationId }; + } + } else if ( + !preconfiguredAgentPolicy.is_default && + !preconfiguredAgentPolicy.is_default_fleet_server + ) { + throw new Error( + i18n.translate('xpack.fleet.preconfiguration.missingIDError', { + defaultMessage: + '{agentPolicyName} is missing an `id` field. `id` is required, except for policies marked is_default or is_default_fleet_server.', + values: { agentPolicyName: preconfiguredAgentPolicy.name }, + }) + ); } const { created, policy } = await agentPolicyService.ensurePreconfiguredAgentPolicy( @@ -132,13 +176,24 @@ export async function ensurePreconfiguredPackagesAndPolicies( }) ); - for (const preconfiguredPolicy of preconfiguredPolicies) { + const fulfilledPolicies = []; + const rejectedPolicies = []; + for (let i = 0; i < preconfiguredPolicies.length; i++) { + const policyResult = preconfiguredPolicies[i]; + if (policyResult.status === 'rejected') { + rejectedPolicies.push({ + error: policyResult.reason as Error, + agentPolicy: { name: policies[i].name }, + } as PreconfigurationError); + continue; + } + fulfilledPolicies.push(policyResult.value); const { created, policy, installedPackagePolicies, shouldAddIsManagedFlag, - } = preconfiguredPolicy; + } = policyResult.value; if (created) { await addPreconfiguredPolicyPackages( soClient, @@ -155,21 +210,22 @@ export async function ensurePreconfiguredPackagesAndPolicies( } return { - policies: preconfiguredPolicies.map((p) => + policies: fulfilledPolicies.map((p) => p.policy ? { - id: p.policy.id, + id: p.policy.id!, updated_at: p.policy.updated_at, } : { - id: p.deleted, + id: p.deleted!, updated_at: i18n.translate('xpack.fleet.preconfiguration.policyDeleted', { defaultMessage: 'Preconfigured policy {id} was deleted; skipping creation', values: { id: p.deleted }, }), } ), - packages: preconfiguredPackages.map((pkg) => pkgToPkgKey(pkg)), + packages: fulfilledPackages.map((pkg) => pkgToPkgKey(pkg)), + nonFatalErrors: [...rejectedPackages, ...rejectedPolicies], }; } @@ -201,21 +257,6 @@ async function addPreconfiguredPolicyPackages( } } -async function ensureInstalledPreconfiguredPackage( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - pkgName: string, - pkgVersion: string -) { - const isLatest = pkgVersion === PRECONFIGURATION_LATEST_KEYWORD; - return ensureInstalledPackage({ - savedObjectsClient: soClient, - pkgName, - esClient, - pkgVersion: isLatest ? undefined : pkgVersion, - }); -} - function overridePackageInputs( basePackagePolicy: NewPackagePolicy, inputsOverride?: InputsOverride[] @@ -228,15 +269,19 @@ function overridePackageInputs( for (const override of inputsOverride) { const originalInput = inputs.find((i) => i.type === override.type); if (!originalInput) { - throw new Error( - i18n.translate('xpack.fleet.packagePolicyInputOverrideError', { - defaultMessage: 'Input type {inputType} does not exist on package {packageName}', - values: { - inputType: override.type, - packageName, - }, - }) - ); + const e = { + error: new Error( + i18n.translate('xpack.fleet.packagePolicyInputOverrideError', { + defaultMessage: 'Input type {inputType} does not exist on package {packageName}', + values: { + inputType: override.type, + packageName, + }, + }) + ), + package: { name: packageName, version: basePackagePolicy.package!.version }, + }; + throw e; } if (typeof override.enabled !== 'undefined') originalInput.enabled = override.enabled; @@ -245,16 +290,21 @@ function overridePackageInputs( try { deepMergeVars(override, originalInput); } catch (e) { - throw new Error( - i18n.translate('xpack.fleet.packagePolicyVarOverrideError', { - defaultMessage: 'Var {varName} does not exist on {inputType} of package {packageName}', - values: { - varName: e.message, - inputType: override.type, - packageName, - }, - }) - ); + const err = { + error: new Error( + i18n.translate('xpack.fleet.packagePolicyVarOverrideError', { + defaultMessage: + 'Var {varName} does not exist on {inputType} of package {packageName}', + values: { + varName: e.message, + inputType: override.type, + packageName, + }, + }) + ), + package: { name: packageName, version: basePackagePolicy.package!.version }, + }; + throw err; } } @@ -264,17 +314,21 @@ function overridePackageInputs( (s) => s.data_stream.dataset === stream.data_stream.dataset ); if (!originalStream) { - throw new Error( - i18n.translate('xpack.fleet.packagePolicyStreamOverrideError', { - defaultMessage: - 'Data stream {streamSet} does not exist on {inputType} of package {packageName}', - values: { - streamSet: stream.data_stream.dataset, - inputType: override.type, - packageName, - }, - }) - ); + const e = { + error: new Error( + i18n.translate('xpack.fleet.packagePolicyStreamOverrideError', { + defaultMessage: + 'Data stream {streamSet} does not exist on {inputType} of package {packageName}', + values: { + streamSet: stream.data_stream.dataset, + inputType: override.type, + packageName, + }, + }) + ), + package: { name: packageName, version: basePackagePolicy.package!.version }, + }; + throw e; } if (typeof stream.enabled !== 'undefined') originalStream.enabled = stream.enabled; @@ -283,18 +337,22 @@ function overridePackageInputs( try { deepMergeVars(stream as InputsOverride, originalStream); } catch (e) { - throw new Error( - i18n.translate('xpack.fleet.packagePolicyStreamVarOverrideError', { - defaultMessage: - 'Var {varName} does not exist on {streamSet} for {inputType} of package {packageName}', - values: { - varName: e.message, - streamSet: stream.data_stream.dataset, - inputType: override.type, - packageName, - }, - }) - ); + const err = { + error: new Error( + i18n.translate('xpack.fleet.packagePolicyStreamVarOverrideError', { + defaultMessage: + 'Var {varName} does not exist on {streamSet} for {inputType} of package {packageName}', + values: { + varName: e.message, + streamSet: stream.data_stream.dataset, + inputType: override.type, + packageName, + }, + }) + ), + package: { name: packageName, version: basePackagePolicy.package!.version }, + }; + throw err; } } } diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index de6876c7f6fda8..0723186569df8a 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -6,23 +6,15 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; -import { i18n } from '@kbn/i18n'; -import { DEFAULT_AGENT_POLICIES_PACKAGES, FLEET_SERVER_PACKAGE } from '../../common'; - -import type { PackagePolicy, DefaultPackagesInstallationError } from '../../common'; - -import { SO_SEARCH_LIMIT } from '../constants'; +import type { DefaultPackagesInstallationError, PreconfigurationError } from '../../common'; +import { SO_SEARCH_LIMIT, REQUIRED_PACKAGES } from '../constants'; import { appContextService } from './app_context'; -import { agentPolicyService, addPackageToAgentPolicy } from './agent_policy'; +import { agentPolicyService } from './agent_policy'; import { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration'; import { outputService } from './output'; -import { - ensureInstalledDefaultPackages, - ensureInstalledPackage, - ensurePackagesCompletedInstall, -} from './epm/packages/install'; + import { generateEnrollmentAPIKey, hasEnrollementAPIKeysForPolicy } from './api_keys'; import { settingsService } from '.'; import { awaitIfPending } from './setup_utils'; @@ -32,8 +24,7 @@ import { awaitIfFleetServerSetupPending } from './fleet_server'; export interface SetupStatus { isInitialized: boolean; - preconfigurationError: { name: string; message: string } | undefined; - nonFatalPackageUpgradeErrors: DefaultPackagesInstallationError[]; + nonFatalErrors?: Array; } export async function setupIngestManager( @@ -47,9 +38,7 @@ async function createSetupSideEffects( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient ): Promise { - const [defaultPackagesResult, defaultOutput] = await Promise.all([ - // packages installed by default - ensureInstalledDefaultPackages(soClient, esClient), + const [defaultOutput] = await Promise.all([ outputService.ensureDefaultOutput(soClient), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { @@ -61,122 +50,35 @@ async function createSetupSideEffects( }), ]); - // Keeping this outside of the Promise.all because it introduces a race condition. - // If one of the required packages fails to install/upgrade it might get stuck in the installing state. - // On the next call to the /setup API, if there is a upgrade available for one of the required packages a race condition - // will occur between upgrading the package and reinstalling the previously failed package. - // By moving this outside of the Promise.all, the upgrade will occur first, and then we'll attempt to reinstall any - // packages that are stuck in the installing state. - await ensurePackagesCompletedInstall(soClient, esClient); - await awaitIfFleetServerSetupPending(); - const fleetServerPackage = await ensureInstalledPackage({ - savedObjectsClient: soClient, - pkgName: FLEET_SERVER_PACKAGE, - esClient, - }); - const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } = appContextService.getConfig() ?? {}; const policies = policiesOrUndefined ?? []; - const packages = packagesOrUndefined ?? []; - let preconfigurationError; - - try { - await ensurePreconfiguredPackagesAndPolicies( - soClient, - esClient, - policies, - packages, - defaultOutput - ); - } catch (e) { - preconfigurationError = { name: e.name, message: e.message }; - } - // Ensure the predefined default policies AFTER loading preconfigured policies. This allows the kibana config - // to override the default agent policies. - - const [ - { created: defaultAgentPolicyCreated, policy: defaultAgentPolicy }, - { created: defaultFleetServerPolicyCreated, policy: defaultFleetServerPolicy }, - ] = await Promise.all([ - agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient), - agentPolicyService.ensureDefaultFleetServerAgentPolicy(soClient, esClient), - ]); + let packages = packagesOrUndefined ?? []; + // Ensure that required packages are always installed even if they're left out of the config + const preconfiguredPackageNames = new Set(packages.map((pkg) => pkg.name)); + packages = [ + ...packages, + ...REQUIRED_PACKAGES.filter((pkg) => !preconfiguredPackageNames.has(pkg.name)), + ]; - // If we just created the default fleet server policy add the fleet server package - if (defaultFleetServerPolicyCreated) { - await addPackageToAgentPolicy( - soClient, - esClient, - fleetServerPackage, - defaultFleetServerPolicy, - defaultOutput - ); - } - - // If we just created the default policy, ensure default packages are added to it - if (defaultAgentPolicyCreated) { - const agentPolicyWithPackagePolicies = await agentPolicyService.get( - soClient, - defaultAgentPolicy.id, - true - ); - if (!agentPolicyWithPackagePolicies) { - throw new Error( - i18n.translate('xpack.fleet.setup.policyNotFoundError', { - defaultMessage: 'Policy not found', - }) - ); - } - if ( - agentPolicyWithPackagePolicies.package_policies.length && - typeof agentPolicyWithPackagePolicies.package_policies[0] === 'string' - ) { - throw new Error( - i18n.translate('xpack.fleet.setup.policyNotFoundError', { - defaultMessage: 'Policy not found', - }) - ); - } - - for (const installedPackage of defaultPackagesResult.installations) { - const packageShouldBeInstalled = DEFAULT_AGENT_POLICIES_PACKAGES.some( - (packageName) => installedPackage.name === packageName - ); - if (!packageShouldBeInstalled) { - continue; - } - - const isInstalled = agentPolicyWithPackagePolicies.package_policies.some( - (d: PackagePolicy | string) => { - return typeof d !== 'string' && d.package?.name === installedPackage.name; - } - ); - - if (!isInstalled) { - await addPackageToAgentPolicy( - soClient, - esClient, - installedPackage, - agentPolicyWithPackagePolicies, - defaultOutput - ); - } - } - } + const { nonFatalErrors } = await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + policies, + packages, + defaultOutput + ); await ensureDefaultEnrollmentAPIKeysExists(soClient, esClient); - await ensureAgentActionPolicyChangeExists(soClient, esClient); return { isInitialized: true, - preconfigurationError, - nonFatalPackageUpgradeErrors: defaultPackagesResult.nonFatalPackageUpgradeErrors, + nonFatalErrors, }; } diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 11336af6c2635c..5b871b80a6bbd6 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -8,7 +8,12 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import semverValid from 'semver/functions/valid'; -import { PRECONFIGURATION_LATEST_KEYWORD } from '../../constants'; +import { + PRECONFIGURATION_LATEST_KEYWORD, + DEFAULT_AGENT_POLICY, + DEFAULT_FLEET_SERVER_AGENT_POLICY, + DEFAULT_PACKAGES, +} from '../../constants'; import { AgentPolicyBaseSchema } from './agent_policy'; import { NamespaceSchema } from './package_policy'; @@ -36,14 +41,17 @@ export const PreconfiguredPackagesSchema = schema.arrayOf( } }, }), - }) + }), + { + defaultValue: DEFAULT_PACKAGES, + } ); export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( schema.object({ ...AgentPolicyBaseSchema, namespace: schema.maybe(NamespaceSchema), - id: schema.oneOf([schema.string(), schema.number()]), + id: schema.maybe(schema.oneOf([schema.string(), schema.number()])), is_default: schema.maybe(schema.boolean()), is_default_fleet_server: schema.maybe(schema.boolean()), package_policies: schema.arrayOf( @@ -77,5 +85,8 @@ export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( ), }) ), - }) + }), + { + defaultValue: [DEFAULT_AGENT_POLICY, DEFAULT_FLEET_SERVER_AGENT_POLICY], + } ); diff --git a/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts b/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts index a6ae2d34ed8dae..7d9534cae364a6 100644 --- a/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts +++ b/x-pack/test/fleet_api_integration/apis/preconfiguration/preconfiguration.ts @@ -41,6 +41,7 @@ export default function (providerContext: FtrProviderContext) { expect(body).to.eql({ packages: [], policies: [], + nonFatalErrors: [], }); }); }); From a8a8289b2005c49848062daf32c6e283ff7f7753 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Thu, 22 Apr 2021 17:55:38 +0100 Subject: [PATCH 56/65] chore(NA): chore(NA): moving @kbn/utils into bazel (#97833) * chore(NA): chore(NA): moving @kbn/utils into bazel * chore(NA): run kbn-test integration test with preserve-symlinks --- .../monorepo-packages.asciidoc | 1 + package.json | 2 +- packages/BUILD.bazel | 1 + packages/kbn-apm-config-loader/package.json | 3 +- packages/kbn-cli-dev-mode/package.json | 3 +- packages/kbn-config-schema/BUILD.bazel | 3 +- packages/kbn-dev-utils/package.json | 3 - packages/kbn-docs-utils/package.json | 1 - packages/kbn-es-archiver/package.json | 3 +- packages/kbn-legacy-logging/package.json | 3 - packages/kbn-pm/package.json | 3 - packages/kbn-test/package.json | 3 +- .../integration_tests/junit_reporter.test.ts | 9 +- packages/kbn-utils/BUILD.bazel | 82 +++++++++++++++++++ packages/kbn-utils/package.json | 7 +- packages/kbn-utils/tsconfig.json | 2 +- yarn.lock | 2 +- 17 files changed, 100 insertions(+), 31 deletions(-) create mode 100644 packages/kbn-utils/BUILD.bazel diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index 610d78bacccd4b..86f9f7562434e4 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -68,4 +68,5 @@ yarn kbn watch-bazel - @kbn/std - @kbn/tinymath - @kbn/utility-types +- @kbn/utils diff --git a/package.json b/package.json index d4d3706d8b6b8c..b21ad0021656e1 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "@kbn/ui-framework": "link:packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types/npm_module", - "@kbn/utils": "link:packages/kbn-utils", + "@kbn/utils": "link:bazel-bin/packages/kbn-utils/npm_module", "@loaders.gl/core": "^2.3.1", "@loaders.gl/json": "^2.3.1", "@mapbox/geojson-rewind": "^0.5.0", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 552eed64d418cd..5c3172a6c636a2 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -10,5 +10,6 @@ filegroup( "//packages/kbn-std:build", "//packages/kbn-tinymath:build", "//packages/kbn-utility-types:build", + "//packages/kbn-utils:build", ], ) diff --git a/packages/kbn-apm-config-loader/package.json b/packages/kbn-apm-config-loader/package.json index 214b8510ba69c6..d198ee57c619d4 100644 --- a/packages/kbn-apm-config-loader/package.json +++ b/packages/kbn-apm-config-loader/package.json @@ -11,7 +11,6 @@ "kbn:watch": "yarn build --watch" }, "dependencies": { - "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set", - "@kbn/utils": "link:../kbn-utils" + "@elastic/safer-lodash-set": "link:../elastic-safer-lodash-set" } } \ No newline at end of file diff --git a/packages/kbn-cli-dev-mode/package.json b/packages/kbn-cli-dev-mode/package.json index 2ffa09d7e1604a..cc91be0df45508 100644 --- a/packages/kbn-cli-dev-mode/package.json +++ b/packages/kbn-cli-dev-mode/package.json @@ -18,7 +18,6 @@ "@kbn/logging": "link:../kbn-logging", "@kbn/server-http-tools": "link:../kbn-server-http-tools", "@kbn/optimizer": "link:../kbn-optimizer", - "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/utils": "link:../kbn-utils" + "@kbn/dev-utils": "link:../kbn-dev-utils" } } \ No newline at end of file diff --git a/packages/kbn-config-schema/BUILD.bazel b/packages/kbn-config-schema/BUILD.bazel index 5dcbd9e5a802a2..0c6b3c10db4fd9 100644 --- a/packages/kbn-config-schema/BUILD.bazel +++ b/packages/kbn-config-schema/BUILD.bazel @@ -63,7 +63,7 @@ ts_project( js_library( name = PKG_BASE_NAME, - srcs = [], + srcs = NPM_MODULE_EXTRA_FILES, deps = [":tsc"] + DEPS, package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], @@ -71,7 +71,6 @@ js_library( pkg_npm( name = "npm_module", - srcs = NPM_MODULE_EXTRA_FILES, deps = [ ":%s" % PKG_BASE_NAME, ] diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 87e142c3bece7d..4ce2880afbbdad 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -13,9 +13,6 @@ "kibana": { "devOnly": true }, - "dependencies": { - "@kbn/utils": "link:../kbn-utils" - }, "devDependencies": { "@kbn/expect": "link:../kbn-expect" } diff --git a/packages/kbn-docs-utils/package.json b/packages/kbn-docs-utils/package.json index 26a7fa0e8c9576..e2db07001b5432 100644 --- a/packages/kbn-docs-utils/package.json +++ b/packages/kbn-docs-utils/package.json @@ -13,7 +13,6 @@ "kbn:watch": "../../node_modules/.bin/tsc --watch" }, "dependencies": { - "@kbn/utils": "link:../kbn-utils", "@kbn/config": "link:../kbn-config", "@kbn/dev-utils": "link:../kbn-dev-utils" } diff --git a/packages/kbn-es-archiver/package.json b/packages/kbn-es-archiver/package.json index 047d1dd675d263..0e4c9884d2c390 100644 --- a/packages/kbn-es-archiver/package.json +++ b/packages/kbn-es-archiver/package.json @@ -14,7 +14,6 @@ }, "dependencies": { "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/test": "link:../kbn-test", - "@kbn/utils": "link:../kbn-utils" + "@kbn/test": "link:../kbn-test" } } \ No newline at end of file diff --git a/packages/kbn-legacy-logging/package.json b/packages/kbn-legacy-logging/package.json index 9450fd39607ea9..8c26535b9b48f8 100644 --- a/packages/kbn-legacy-logging/package.json +++ b/packages/kbn-legacy-logging/package.json @@ -9,8 +9,5 @@ "build": "tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/utils": "link:../kbn-utils" } } diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index 050aadd402d8a5..c46906112b2e22 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -14,8 +14,5 @@ }, "devDependencies": { "@kbn/dev-utils": "link:../kbn-dev-utils" - }, - "dependencies": { - "@kbn/utils": "link:../kbn-utils" } } \ No newline at end of file diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 2afbe41e0e00ec..9bf8a01e031cc0 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -20,7 +20,6 @@ }, "devDependencies": { "@kbn/dev-utils": "link:../kbn-dev-utils", - "@kbn/expect": "link:../kbn-expect", - "@kbn/utils": "link:../kbn-utils" + "@kbn/expect": "link:../kbn-expect" } } \ No newline at end of file diff --git a/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts b/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts index 9a94ff41eb6b98..f2bf25067a9bdf 100644 --- a/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts +++ b/packages/kbn-test/src/jest/integration_tests/junit_reporter.test.ts @@ -30,8 +30,13 @@ it( 'produces a valid junit report for failures', async () => { const result = await execa( - './node_modules/.bin/jest', - ['--config', 'packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js'], + 'node', + [ + '--preserve-symlinks', + './node_modules/.bin/jest', + '--config', + 'packages/kbn-test/src/jest/integration_tests/__fixtures__/jest.config.js', + ], { cwd: REPO_ROOT, env: { diff --git a/packages/kbn-utils/BUILD.bazel b/packages/kbn-utils/BUILD.bazel new file mode 100644 index 00000000000000..57aee048746b44 --- /dev/null +++ b/packages/kbn-utils/BUILD.bazel @@ -0,0 +1,82 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-utils" +PKG_REQUIRE_NAME = "@kbn/utils" + +SOURCE_FILES = glob( + [ + "src/**/*.ts", + ], + exclude = ["**/*.test.*"], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/kbn-config-schema", + "@npm//load-json-file", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = [":tsc"] + DEPS, + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-utils/package.json b/packages/kbn-utils/package.json index 2c3c0c11b65ab8..c404613d2c8e10 100644 --- a/packages/kbn-utils/package.json +++ b/packages/kbn-utils/package.json @@ -4,10 +4,5 @@ "types": "./target/index.d.ts", "version": "1.0.0", "license": "SSPL-1.0 OR Elastic License 2.0", - "private": true, - "scripts": { - "build": "rm -rf target && ../../node_modules/.bin/tsc", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" - } + "private": true } \ No newline at end of file diff --git a/packages/kbn-utils/tsconfig.json b/packages/kbn-utils/tsconfig.json index e6c83767c30dc0..0c7657ba55ee60 100644 --- a/packages/kbn-utils/tsconfig.json +++ b/packages/kbn-utils/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "incremental": false, + "incremental": true, "outDir": "target", "declaration": true, "declarationMap": true, diff --git a/yarn.lock b/yarn.lock index 465667230b6399..bc87ff50c55e8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2721,7 +2721,7 @@ version "0.0.0" uid "" -"@kbn/utils@link:packages/kbn-utils": +"@kbn/utils@link:bazel-bin/packages/kbn-utils/npm_module": version "0.0.0" uid "" From 55f3b8975e168be930b75eada0533985b3245254 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Thu, 22 Apr 2021 18:02:21 +0100 Subject: [PATCH 57/65] [Discover Index Pattern management] Add more unit tests (#97749) * [Discover Index Pattern management] Add more unit tests * [Discover Index Pattern management] Fix typo * Fix failing unit test --- ...ver_index_pattern_management.test.tsx.snap | 79 ++++++++++++++++--- ...discover_index_pattern_management.test.tsx | 55 +++++++++++-- .../discover_index_pattern_management.tsx | 1 + 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/plugins/discover/public/application/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap b/src/plugins/discover/public/application/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap index 44b8cbb8b839a3..f94a73ae05a554 100644 --- a/src/plugins/discover/public/application/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap +++ b/src/plugins/discover/public/application/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap @@ -634,21 +634,16 @@ exports[`Discover IndexPattern Management renders correctly 1`] = ` "show": true, }, }, + "core": Object { + "application": Object { + "navigateToApp": [MockFunction], + }, + }, "history": [Function], "indexPatternFieldEditor": Object { "openEditor": [MockFunction], "userPermissions": Object { - "editIndexPattern": [MockFunction] { - "calls": Array [ - Array [], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - }, + "editIndexPattern": [Function], }, }, "uiSettings": Object { @@ -657,5 +652,65 @@ exports[`Discover IndexPattern Management renders correctly 1`] = ` } } useNewFieldsApi={true} -/> +> + + } + closePopover={[Function]} + data-test-subj="discover-addRuntimeField-popover" + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + > +
+
+ + + +
+
+
+ `; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.test.tsx index 88644dc213fd66..5a954270fdf587 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.test.tsx @@ -14,6 +14,8 @@ import stubbedLogstashFields from '../../../__fixtures__/logstash_fields'; import { mountWithIntl } from '@kbn/test/jest'; import React from 'react'; import { DiscoverIndexPatternManagement } from './discover_index_pattern_management'; +import { EuiContextMenuPanel, EuiPopover, EuiContextMenuItem } from '@elastic/eui'; +import { findTestSubject } from '@kbn/test/jest'; const mockServices = ({ history: () => ({ @@ -29,6 +31,11 @@ const mockServices = ({ save: false, }, }, + core: { + application: { + navigateToApp: jest.fn(), + }, + }, uiSettings: { get: (key: string) => { if (key === 'fields:popularLimit') { @@ -39,15 +46,13 @@ const mockServices = ({ indexPatternFieldEditor: { openEditor: jest.fn(), userPermissions: { - editIndexPattern: jest.fn(), + editIndexPattern: () => { + return true; + }, }, }, } as unknown) as DiscoverServices; -jest.mock('../../../kibana_services', () => ({ - getServices: () => mockServices, -})); - describe('Discover IndexPattern Management', () => { const indexPattern = getStubIndexPattern( 'logstash-*', @@ -59,8 +64,8 @@ describe('Discover IndexPattern Management', () => { const editField = jest.fn(); - test('renders correctly', () => { - const component = mountWithIntl( + const mountComponent = () => { + return mountWithIntl( { useNewFieldsApi={true} /> ); + }; + + test('renders correctly', () => { + const component = mountComponent(); expect(component).toMatchSnapshot(); + expect(component.find(EuiPopover).length).toBe(1); + }); + + test('click on a button opens popover', () => { + const component = mountComponent(); + expect(component.find(EuiContextMenuPanel).length).toBe(0); + + const button = findTestSubject(component, 'discoverIndexPatternActions'); + button.simulate('click'); + + expect(component.find(EuiContextMenuPanel).length).toBe(1); + expect(component.find(EuiContextMenuItem).length).toBe(2); + }); + + test('click on an add button executes editField callback', () => { + const component = mountComponent(); + const button = findTestSubject(component, 'discoverIndexPatternActions'); + button.simulate('click'); + + const addButton = findTestSubject(component, 'indexPattern-add-field'); + addButton.simulate('click'); + expect(editField).toHaveBeenCalledWith(undefined); + }); + + test('click on a manage button navigates away from discover', () => { + const component = mountComponent(); + const button = findTestSubject(component, 'discoverIndexPatternActions'); + button.simulate('click'); + + const manageButton = findTestSubject(component, 'indexPattern-manage-field'); + manageButton.simulate('click'); + expect(mockServices.core.application.navigateToApp).toHaveBeenCalled(); }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.tsx b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.tsx index 38681d75a4e1d0..9a9dfd579b96d5 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_index_pattern_management.tsx @@ -89,6 +89,7 @@ export function DiscoverIndexPatternManagement(props: DiscoverIndexPatternManage { setIsAddIndexPatternFieldPopoverOpen(false); core.application.navigateToApp('management', { From dc8786604a749b54bfa4e7eff1d5cf025e0b9910 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Thu, 22 Apr 2021 13:08:26 -0400 Subject: [PATCH 58/65] Create privilege action to allow for decrypted telemetry payload (#96571) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../privileges/privileges.test.ts | 885 ++++++++++-------- .../authorization/privileges/privileges.ts | 8 +- 2 files changed, 484 insertions(+), 409 deletions(-) diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts index 4da0020e94b158..ecbbb637f4da08 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts @@ -212,438 +212,485 @@ describe('features', () => { expectManageSpaces: true, expectGetFeatures: true, expectEnterpriseSearch: true, + expectDecryptedTelemetry: true, }, { group: 'space', expectManageSpaces: false, expectGetFeatures: false, expectEnterpriseSearch: false, + expectDecryptedTelemetry: false, }, -].forEach(({ group, expectManageSpaces, expectGetFeatures, expectEnterpriseSearch }) => { - describe(`${group}`, () => { - test('actions defined in any feature privilege are included in `all`', () => { - const features: KibanaFeature[] = [ - new KibanaFeature({ - id: 'foo', - name: 'Foo KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - catalogue: ['ignore-me-1', 'ignore-me-2'], - management: { - foo: ['ignore-me-1', 'ignore-me-2'], - }, - privileges: { - all: { - management: { - 'all-management': ['all-management-1', 'all-management-2'], - }, - catalogue: ['all-catalogue-1', 'all-catalogue-2'], - savedObject: { - all: ['all-savedObject-all-1', 'all-savedObject-all-2'], - read: ['all-savedObject-read-1', 'all-savedObject-read-2'], - }, - ui: ['all-ui-1', 'all-ui-2'], +].forEach( + ({ + group, + expectManageSpaces, + expectGetFeatures, + expectEnterpriseSearch, + expectDecryptedTelemetry, + }) => { + describe(`${group}`, () => { + test('actions defined in any feature privilege are included in `all`', () => { + const features: KibanaFeature[] = [ + new KibanaFeature({ + id: 'foo', + name: 'Foo KibanaFeature', + app: [], + category: { id: 'foo', label: 'foo' }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], }, - read: { - management: { - 'read-management': ['read-management-1', 'read-management-2'], + privileges: { + all: { + management: { + 'all-management': ['all-management-1', 'all-management-2'], + }, + catalogue: ['all-catalogue-1', 'all-catalogue-2'], + savedObject: { + all: ['all-savedObject-all-1', 'all-savedObject-all-2'], + read: ['all-savedObject-read-1', 'all-savedObject-read-2'], + }, + ui: ['all-ui-1', 'all-ui-2'], }, - catalogue: ['read-catalogue-1', 'read-catalogue-2'], - savedObject: { - all: ['read-savedObject-all-1', 'read-savedObject-all-2'], - read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + read: { + management: { + 'read-management': ['read-management-1', 'read-management-2'], + }, + catalogue: ['read-catalogue-1', 'read-catalogue-2'], + savedObject: { + all: ['read-savedObject-all-1', 'read-savedObject-all-2'], + read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + }, + ui: ['read-ui-1', 'read-ui-2'], }, - ui: ['read-ui-1', 'read-ui-2'], }, - }, - }), - ]; - - const mockFeaturesPlugin = { - getKibanaFeatures: jest.fn().mockReturnValue(features), - }; - const mockLicenseService = { - getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), - getType: jest.fn().mockReturnValue('basic'), - }; - const privileges = privilegesFactory(actions, mockFeaturesPlugin as any, mockLicenseService); - - const actual = privileges.get(); - expect(actual).toHaveProperty(`${group}.all`, [ - actions.login, - actions.version, - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectManageSpaces - ? [ - actions.space.manage, - actions.ui.get('spaces', 'manage'), - actions.ui.get('management', 'kibana', 'spaces'), - actions.ui.get('catalogue', 'spaces'), - ] - : []), - ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), - actions.ui.get('catalogue', 'all-catalogue-1'), - actions.ui.get('catalogue', 'all-catalogue-2'), - actions.ui.get('management', 'all-management', 'all-management-1'), - actions.ui.get('management', 'all-management', 'all-management-2'), - actions.savedObject.get('all-savedObject-all-1', 'bulk_get'), - actions.savedObject.get('all-savedObject-all-1', 'get'), - actions.savedObject.get('all-savedObject-all-1', 'find'), - actions.savedObject.get('all-savedObject-all-1', 'open_point_in_time'), - actions.savedObject.get('all-savedObject-all-1', 'close_point_in_time'), - actions.savedObject.get('all-savedObject-all-1', 'create'), - actions.savedObject.get('all-savedObject-all-1', 'bulk_create'), - actions.savedObject.get('all-savedObject-all-1', 'update'), - actions.savedObject.get('all-savedObject-all-1', 'bulk_update'), - actions.savedObject.get('all-savedObject-all-1', 'delete'), - actions.savedObject.get('all-savedObject-all-1', 'share_to_space'), - actions.savedObject.get('all-savedObject-all-2', 'bulk_get'), - actions.savedObject.get('all-savedObject-all-2', 'get'), - actions.savedObject.get('all-savedObject-all-2', 'find'), - actions.savedObject.get('all-savedObject-all-2', 'open_point_in_time'), - actions.savedObject.get('all-savedObject-all-2', 'close_point_in_time'), - actions.savedObject.get('all-savedObject-all-2', 'create'), - actions.savedObject.get('all-savedObject-all-2', 'bulk_create'), - actions.savedObject.get('all-savedObject-all-2', 'update'), - actions.savedObject.get('all-savedObject-all-2', 'bulk_update'), - actions.savedObject.get('all-savedObject-all-2', 'delete'), - actions.savedObject.get('all-savedObject-all-2', 'share_to_space'), - actions.savedObject.get('all-savedObject-read-1', 'bulk_get'), - actions.savedObject.get('all-savedObject-read-1', 'get'), - actions.savedObject.get('all-savedObject-read-1', 'find'), - actions.savedObject.get('all-savedObject-read-1', 'open_point_in_time'), - actions.savedObject.get('all-savedObject-read-1', 'close_point_in_time'), - actions.savedObject.get('all-savedObject-read-2', 'bulk_get'), - actions.savedObject.get('all-savedObject-read-2', 'get'), - actions.savedObject.get('all-savedObject-read-2', 'find'), - actions.savedObject.get('all-savedObject-read-2', 'open_point_in_time'), - actions.savedObject.get('all-savedObject-read-2', 'close_point_in_time'), - actions.ui.get('foo', 'all-ui-1'), - actions.ui.get('foo', 'all-ui-2'), - actions.ui.get('catalogue', 'read-catalogue-1'), - actions.ui.get('catalogue', 'read-catalogue-2'), - actions.ui.get('management', 'read-management', 'read-management-1'), - actions.ui.get('management', 'read-management', 'read-management-2'), - actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), - actions.savedObject.get('read-savedObject-all-1', 'get'), - actions.savedObject.get('read-savedObject-all-1', 'find'), - actions.savedObject.get('read-savedObject-all-1', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-all-1', 'close_point_in_time'), - actions.savedObject.get('read-savedObject-all-1', 'create'), - actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), - actions.savedObject.get('read-savedObject-all-1', 'update'), - actions.savedObject.get('read-savedObject-all-1', 'bulk_update'), - actions.savedObject.get('read-savedObject-all-1', 'delete'), - actions.savedObject.get('read-savedObject-all-1', 'share_to_space'), - actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), - actions.savedObject.get('read-savedObject-all-2', 'get'), - actions.savedObject.get('read-savedObject-all-2', 'find'), - actions.savedObject.get('read-savedObject-all-2', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-all-2', 'close_point_in_time'), - actions.savedObject.get('read-savedObject-all-2', 'create'), - actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), - actions.savedObject.get('read-savedObject-all-2', 'update'), - actions.savedObject.get('read-savedObject-all-2', 'bulk_update'), - actions.savedObject.get('read-savedObject-all-2', 'delete'), - actions.savedObject.get('read-savedObject-all-2', 'share_to_space'), - actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), - actions.savedObject.get('read-savedObject-read-1', 'get'), - actions.savedObject.get('read-savedObject-read-1', 'find'), - actions.savedObject.get('read-savedObject-read-1', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-read-1', 'close_point_in_time'), - actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), - actions.savedObject.get('read-savedObject-read-2', 'get'), - actions.savedObject.get('read-savedObject-read-2', 'find'), - actions.savedObject.get('read-savedObject-read-2', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-read-2', 'close_point_in_time'), - actions.ui.get('foo', 'read-ui-1'), - actions.ui.get('foo', 'read-ui-2'), - ]); - }); - - test('actions defined in a feature privilege with name `read` are included in `read`', () => { - const features: KibanaFeature[] = [ - new KibanaFeature({ - id: 'foo', - name: 'Foo KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - catalogue: ['ignore-me-1', 'ignore-me-2'], - management: { - foo: ['ignore-me-1', 'ignore-me-2'], - }, - privileges: { - all: { - management: { - 'ignore-me': ['ignore-me-1', 'ignore-me-2'], - }, - catalogue: ['ignore-me-1', 'ignore-me-2'], - savedObject: { - all: ['ignore-me-1', 'ignore-me-2'], - read: ['ignore-me-1', 'ignore-me-2'], - }, - ui: ['ignore-me-1', 'ignore-me-2'], + }), + ]; + + const mockFeaturesPlugin = { + getKibanaFeatures: jest.fn().mockReturnValue(features), + }; + const mockLicenseService = { + getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), + getType: jest.fn().mockReturnValue('basic'), + }; + const privileges = privilegesFactory( + actions, + mockFeaturesPlugin as any, + mockLicenseService + ); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.all`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectGetFeatures ? [actions.api.get('features')] : []), + ...(expectManageSpaces + ? [ + actions.space.manage, + actions.ui.get('spaces', 'manage'), + actions.ui.get('management', 'kibana', 'spaces'), + actions.ui.get('catalogue', 'spaces'), + ] + : []), + ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), + actions.ui.get('catalogue', 'all-catalogue-1'), + actions.ui.get('catalogue', 'all-catalogue-2'), + actions.ui.get('management', 'all-management', 'all-management-1'), + actions.ui.get('management', 'all-management', 'all-management-2'), + actions.savedObject.get('all-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('all-savedObject-all-1', 'get'), + actions.savedObject.get('all-savedObject-all-1', 'find'), + actions.savedObject.get('all-savedObject-all-1', 'open_point_in_time'), + actions.savedObject.get('all-savedObject-all-1', 'close_point_in_time'), + actions.savedObject.get('all-savedObject-all-1', 'create'), + actions.savedObject.get('all-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('all-savedObject-all-1', 'update'), + actions.savedObject.get('all-savedObject-all-1', 'bulk_update'), + actions.savedObject.get('all-savedObject-all-1', 'delete'), + actions.savedObject.get('all-savedObject-all-1', 'share_to_space'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('all-savedObject-all-2', 'get'), + actions.savedObject.get('all-savedObject-all-2', 'find'), + actions.savedObject.get('all-savedObject-all-2', 'open_point_in_time'), + actions.savedObject.get('all-savedObject-all-2', 'close_point_in_time'), + actions.savedObject.get('all-savedObject-all-2', 'create'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('all-savedObject-all-2', 'update'), + actions.savedObject.get('all-savedObject-all-2', 'bulk_update'), + actions.savedObject.get('all-savedObject-all-2', 'delete'), + actions.savedObject.get('all-savedObject-all-2', 'share_to_space'), + actions.savedObject.get('all-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('all-savedObject-read-1', 'get'), + actions.savedObject.get('all-savedObject-read-1', 'find'), + actions.savedObject.get('all-savedObject-read-1', 'open_point_in_time'), + actions.savedObject.get('all-savedObject-read-1', 'close_point_in_time'), + actions.savedObject.get('all-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('all-savedObject-read-2', 'get'), + actions.savedObject.get('all-savedObject-read-2', 'find'), + actions.savedObject.get('all-savedObject-read-2', 'open_point_in_time'), + actions.savedObject.get('all-savedObject-read-2', 'close_point_in_time'), + actions.ui.get('foo', 'all-ui-1'), + actions.ui.get('foo', 'all-ui-2'), + actions.ui.get('catalogue', 'read-catalogue-1'), + actions.ui.get('catalogue', 'read-catalogue-2'), + actions.ui.get('management', 'read-management', 'read-management-1'), + actions.ui.get('management', 'read-management', 'read-management-2'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-1', 'get'), + actions.savedObject.get('read-savedObject-all-1', 'find'), + actions.savedObject.get('read-savedObject-all-1', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-all-1', 'close_point_in_time'), + actions.savedObject.get('read-savedObject-all-1', 'create'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-1', 'update'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_update'), + actions.savedObject.get('read-savedObject-all-1', 'delete'), + actions.savedObject.get('read-savedObject-all-1', 'share_to_space'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-2', 'get'), + actions.savedObject.get('read-savedObject-all-2', 'find'), + actions.savedObject.get('read-savedObject-all-2', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-all-2', 'close_point_in_time'), + actions.savedObject.get('read-savedObject-all-2', 'create'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-2', 'update'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_update'), + actions.savedObject.get('read-savedObject-all-2', 'delete'), + actions.savedObject.get('read-savedObject-all-2', 'share_to_space'), + actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-1', 'get'), + actions.savedObject.get('read-savedObject-read-1', 'find'), + actions.savedObject.get('read-savedObject-read-1', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-read-1', 'close_point_in_time'), + actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-2', 'get'), + actions.savedObject.get('read-savedObject-read-2', 'find'), + actions.savedObject.get('read-savedObject-read-2', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-read-2', 'close_point_in_time'), + actions.ui.get('foo', 'read-ui-1'), + actions.ui.get('foo', 'read-ui-2'), + ]); + }); + + test('actions defined in a feature privilege with name `read` are included in `read`', () => { + const features: KibanaFeature[] = [ + new KibanaFeature({ + id: 'foo', + name: 'Foo KibanaFeature', + app: [], + category: { id: 'foo', label: 'foo' }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], }, - read: { - management: { - 'read-management': ['read-management-1', 'read-management-2'], + privileges: { + all: { + management: { + 'ignore-me': ['ignore-me-1', 'ignore-me-2'], + }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1', 'ignore-me-2'], }, - catalogue: ['read-catalogue-1', 'read-catalogue-2'], - savedObject: { - all: ['read-savedObject-all-1', 'read-savedObject-all-2'], - read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + read: { + management: { + 'read-management': ['read-management-1', 'read-management-2'], + }, + catalogue: ['read-catalogue-1', 'read-catalogue-2'], + savedObject: { + all: ['read-savedObject-all-1', 'read-savedObject-all-2'], + read: ['read-savedObject-read-1', 'read-savedObject-read-2'], + }, + ui: ['read-ui-1', 'read-ui-2'], }, - ui: ['read-ui-1', 'read-ui-2'], }, - }, - }), - ]; - - const mockFeaturesPlugin = { - getKibanaFeatures: jest.fn().mockReturnValue(features), - }; - const mockLicenseService = { - getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), - getType: jest.fn().mockReturnValue('basic'), - }; - const privileges = privilegesFactory(actions, mockFeaturesPlugin as any, mockLicenseService); - - const actual = privileges.get(); - expect(actual).toHaveProperty(`${group}.read`, [ - actions.login, - actions.version, - actions.ui.get('catalogue', 'read-catalogue-1'), - actions.ui.get('catalogue', 'read-catalogue-2'), - actions.ui.get('management', 'read-management', 'read-management-1'), - actions.ui.get('management', 'read-management', 'read-management-2'), - actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), - actions.savedObject.get('read-savedObject-all-1', 'get'), - actions.savedObject.get('read-savedObject-all-1', 'find'), - actions.savedObject.get('read-savedObject-all-1', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-all-1', 'close_point_in_time'), - actions.savedObject.get('read-savedObject-all-1', 'create'), - actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), - actions.savedObject.get('read-savedObject-all-1', 'update'), - actions.savedObject.get('read-savedObject-all-1', 'bulk_update'), - actions.savedObject.get('read-savedObject-all-1', 'delete'), - actions.savedObject.get('read-savedObject-all-1', 'share_to_space'), - actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), - actions.savedObject.get('read-savedObject-all-2', 'get'), - actions.savedObject.get('read-savedObject-all-2', 'find'), - actions.savedObject.get('read-savedObject-all-2', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-all-2', 'close_point_in_time'), - actions.savedObject.get('read-savedObject-all-2', 'create'), - actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), - actions.savedObject.get('read-savedObject-all-2', 'update'), - actions.savedObject.get('read-savedObject-all-2', 'bulk_update'), - actions.savedObject.get('read-savedObject-all-2', 'delete'), - actions.savedObject.get('read-savedObject-all-2', 'share_to_space'), - actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), - actions.savedObject.get('read-savedObject-read-1', 'get'), - actions.savedObject.get('read-savedObject-read-1', 'find'), - actions.savedObject.get('read-savedObject-read-1', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-read-1', 'close_point_in_time'), - actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), - actions.savedObject.get('read-savedObject-read-2', 'get'), - actions.savedObject.get('read-savedObject-read-2', 'find'), - actions.savedObject.get('read-savedObject-read-2', 'open_point_in_time'), - actions.savedObject.get('read-savedObject-read-2', 'close_point_in_time'), - actions.ui.get('foo', 'read-ui-1'), - actions.ui.get('foo', 'read-ui-2'), - ]); - }); - - test('actions defined in a reserved privilege are not included in `all` or `read`', () => { - const features: KibanaFeature[] = [ - new KibanaFeature({ - id: 'foo', - name: 'Foo KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - catalogue: ['ignore-me-1', 'ignore-me-2'], - management: { - foo: ['ignore-me-1', 'ignore-me-2'], - }, - privileges: null, - reserved: { - privileges: [ - { - id: 'reserved', - privilege: { - savedObject: { - all: ['ignore-me-1', 'ignore-me-2'], - read: ['ignore-me-1', 'ignore-me-2'], + }), + ]; + + const mockFeaturesPlugin = { + getKibanaFeatures: jest.fn().mockReturnValue(features), + }; + const mockLicenseService = { + getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), + getType: jest.fn().mockReturnValue('basic'), + }; + const privileges = privilegesFactory( + actions, + mockFeaturesPlugin as any, + mockLicenseService + ); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.read`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + actions.ui.get('catalogue', 'read-catalogue-1'), + actions.ui.get('catalogue', 'read-catalogue-2'), + actions.ui.get('management', 'read-management', 'read-management-1'), + actions.ui.get('management', 'read-management', 'read-management-2'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-1', 'get'), + actions.savedObject.get('read-savedObject-all-1', 'find'), + actions.savedObject.get('read-savedObject-all-1', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-all-1', 'close_point_in_time'), + actions.savedObject.get('read-savedObject-all-1', 'create'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-1', 'update'), + actions.savedObject.get('read-savedObject-all-1', 'bulk_update'), + actions.savedObject.get('read-savedObject-all-1', 'delete'), + actions.savedObject.get('read-savedObject-all-1', 'share_to_space'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-all-2', 'get'), + actions.savedObject.get('read-savedObject-all-2', 'find'), + actions.savedObject.get('read-savedObject-all-2', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-all-2', 'close_point_in_time'), + actions.savedObject.get('read-savedObject-all-2', 'create'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_create'), + actions.savedObject.get('read-savedObject-all-2', 'update'), + actions.savedObject.get('read-savedObject-all-2', 'bulk_update'), + actions.savedObject.get('read-savedObject-all-2', 'delete'), + actions.savedObject.get('read-savedObject-all-2', 'share_to_space'), + actions.savedObject.get('read-savedObject-read-1', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-1', 'get'), + actions.savedObject.get('read-savedObject-read-1', 'find'), + actions.savedObject.get('read-savedObject-read-1', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-read-1', 'close_point_in_time'), + actions.savedObject.get('read-savedObject-read-2', 'bulk_get'), + actions.savedObject.get('read-savedObject-read-2', 'get'), + actions.savedObject.get('read-savedObject-read-2', 'find'), + actions.savedObject.get('read-savedObject-read-2', 'open_point_in_time'), + actions.savedObject.get('read-savedObject-read-2', 'close_point_in_time'), + actions.ui.get('foo', 'read-ui-1'), + actions.ui.get('foo', 'read-ui-2'), + ]); + }); + + test('actions defined in a reserved privilege are not included in `all` or `read`', () => { + const features: KibanaFeature[] = [ + new KibanaFeature({ + id: 'foo', + name: 'Foo KibanaFeature', + app: [], + category: { id: 'foo', label: 'foo' }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: null, + reserved: { + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1'], }, - ui: ['ignore-me-1'], }, - }, - ], - description: '', - }, - }), - ]; - - const mockFeaturesPlugin = { - getKibanaFeatures: jest.fn().mockReturnValue(features), - }; - const mockLicenseService = { - getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), - getType: jest.fn().mockReturnValue('basic'), - }; - const privileges = privilegesFactory(actions, mockFeaturesPlugin as any, mockLicenseService); - - const actual = privileges.get(); - expect(actual).toHaveProperty(`${group}.all`, [ - actions.login, - actions.version, - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectManageSpaces - ? [ - actions.space.manage, - actions.ui.get('spaces', 'manage'), - actions.ui.get('management', 'kibana', 'spaces'), - actions.ui.get('catalogue', 'spaces'), - ] - : []), - ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), - ]); - expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]); - }); - - test('actions defined in a feature with excludeFromBasePrivileges are not included in `all` or `read', () => { - const features: KibanaFeature[] = [ - new KibanaFeature({ - id: 'foo', - name: 'Foo KibanaFeature', - excludeFromBasePrivileges: true, - app: [], - category: { id: 'foo', label: 'foo' }, - catalogue: ['ignore-me-1', 'ignore-me-2'], - management: { - foo: ['ignore-me-1', 'ignore-me-2'], - }, - privileges: { - all: { - management: { - 'all-management': ['all-management-1'], - }, - catalogue: ['all-catalogue-1'], - savedObject: { - all: ['all-savedObject-all-1'], - read: ['all-savedObject-read-1'], - }, - ui: ['all-ui-1'], + ], + description: '', }, - read: { - management: { - 'read-management': ['read-management-1'], - }, - catalogue: ['read-catalogue-1'], - savedObject: { - all: ['read-savedObject-all-1'], - read: ['read-savedObject-read-1'], - }, - ui: ['read-ui-1'], + }), + ]; + + const mockFeaturesPlugin = { + getKibanaFeatures: jest.fn().mockReturnValue(features), + }; + const mockLicenseService = { + getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), + getType: jest.fn().mockReturnValue('basic'), + }; + const privileges = privilegesFactory( + actions, + mockFeaturesPlugin as any, + mockLicenseService + ); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.all`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectGetFeatures ? [actions.api.get('features')] : []), + ...(expectManageSpaces + ? [ + actions.space.manage, + actions.ui.get('spaces', 'manage'), + actions.ui.get('management', 'kibana', 'spaces'), + actions.ui.get('catalogue', 'spaces'), + ] + : []), + ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), + ]); + expect(actual).toHaveProperty(`${group}.read`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ]); + }); + + test('actions defined in a feature with excludeFromBasePrivileges are not included in `all` or `read', () => { + const features: KibanaFeature[] = [ + new KibanaFeature({ + id: 'foo', + name: 'Foo KibanaFeature', + excludeFromBasePrivileges: true, + app: [], + category: { id: 'foo', label: 'foo' }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], }, - }, - }), - ]; - - const mockFeaturesPlugin = { - getKibanaFeatures: jest.fn().mockReturnValue(features), - }; - const mockLicenseService = { - getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), - getType: jest.fn().mockReturnValue('basic'), - }; - const privileges = privilegesFactory(actions, mockFeaturesPlugin as any, mockLicenseService); - - const actual = privileges.get(); - expect(actual).toHaveProperty(`${group}.all`, [ - actions.login, - actions.version, - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectManageSpaces - ? [ - actions.space.manage, - actions.ui.get('spaces', 'manage'), - actions.ui.get('management', 'kibana', 'spaces'), - actions.ui.get('catalogue', 'spaces'), - ] - : []), - ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), - ]); - expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]); - }); - - test('actions defined in an individual feature privilege with excludeFromBasePrivileges are not included in `all` or `read`', () => { - const features: KibanaFeature[] = [ - new KibanaFeature({ - id: 'foo', - name: 'Foo KibanaFeature', - app: [], - category: { id: 'foo', label: 'foo' }, - catalogue: ['ignore-me-1', 'ignore-me-2'], - management: { - foo: ['ignore-me-1', 'ignore-me-2'], - }, - privileges: { - all: { - excludeFromBasePrivileges: true, - management: { - 'all-management': ['all-management-1'], + privileges: { + all: { + management: { + 'all-management': ['all-management-1'], + }, + catalogue: ['all-catalogue-1'], + savedObject: { + all: ['all-savedObject-all-1'], + read: ['all-savedObject-read-1'], + }, + ui: ['all-ui-1'], }, - catalogue: ['all-catalogue-1'], - savedObject: { - all: ['all-savedObject-all-1'], - read: ['all-savedObject-read-1'], + read: { + management: { + 'read-management': ['read-management-1'], + }, + catalogue: ['read-catalogue-1'], + savedObject: { + all: ['read-savedObject-all-1'], + read: ['read-savedObject-read-1'], + }, + ui: ['read-ui-1'], }, - ui: ['all-ui-1'], }, - read: { - excludeFromBasePrivileges: true, - management: { - 'read-management': ['read-management-1'], + }), + ]; + + const mockFeaturesPlugin = { + getKibanaFeatures: jest.fn().mockReturnValue(features), + }; + const mockLicenseService = { + getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), + getType: jest.fn().mockReturnValue('basic'), + }; + const privileges = privilegesFactory( + actions, + mockFeaturesPlugin as any, + mockLicenseService + ); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.all`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectGetFeatures ? [actions.api.get('features')] : []), + ...(expectManageSpaces + ? [ + actions.space.manage, + actions.ui.get('spaces', 'manage'), + actions.ui.get('management', 'kibana', 'spaces'), + actions.ui.get('catalogue', 'spaces'), + ] + : []), + ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), + ]); + expect(actual).toHaveProperty(`${group}.read`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ]); + }); + + test('actions defined in an individual feature privilege with excludeFromBasePrivileges are not included in `all` or `read`', () => { + const features: KibanaFeature[] = [ + new KibanaFeature({ + id: 'foo', + name: 'Foo KibanaFeature', + app: [], + category: { id: 'foo', label: 'foo' }, + catalogue: ['ignore-me-1', 'ignore-me-2'], + management: { + foo: ['ignore-me-1', 'ignore-me-2'], + }, + privileges: { + all: { + excludeFromBasePrivileges: true, + management: { + 'all-management': ['all-management-1'], + }, + catalogue: ['all-catalogue-1'], + savedObject: { + all: ['all-savedObject-all-1'], + read: ['all-savedObject-read-1'], + }, + ui: ['all-ui-1'], }, - catalogue: ['read-catalogue-1'], - savedObject: { - all: ['read-savedObject-all-1'], - read: ['read-savedObject-read-1'], + read: { + excludeFromBasePrivileges: true, + management: { + 'read-management': ['read-management-1'], + }, + catalogue: ['read-catalogue-1'], + savedObject: { + all: ['read-savedObject-all-1'], + read: ['read-savedObject-read-1'], + }, + ui: ['read-ui-1'], }, - ui: ['read-ui-1'], }, - }, - }), - ]; - - const mockFeaturesPlugin = { - getKibanaFeatures: jest.fn().mockReturnValue(features), - }; - const mockLicenseService = { - getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), - getType: jest.fn().mockReturnValue('basic'), - }; - const privileges = privilegesFactory(actions, mockFeaturesPlugin as any, mockLicenseService); - - const actual = privileges.get(); - expect(actual).toHaveProperty(`${group}.all`, [ - actions.login, - actions.version, - ...(expectGetFeatures ? [actions.api.get('features')] : []), - ...(expectManageSpaces - ? [ - actions.space.manage, - actions.ui.get('spaces', 'manage'), - actions.ui.get('management', 'kibana', 'spaces'), - actions.ui.get('catalogue', 'spaces'), - ] - : []), - ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), - ]); - expect(actual).toHaveProperty(`${group}.read`, [actions.login, actions.version]); + }), + ]; + + const mockFeaturesPlugin = { + getKibanaFeatures: jest.fn().mockReturnValue(features), + }; + const mockLicenseService = { + getFeatures: jest.fn().mockReturnValue({ allowSubFeaturePrivileges: true }), + getType: jest.fn().mockReturnValue('basic'), + }; + const privileges = privilegesFactory( + actions, + mockFeaturesPlugin as any, + mockLicenseService + ); + + const actual = privileges.get(); + expect(actual).toHaveProperty(`${group}.all`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ...(expectGetFeatures ? [actions.api.get('features')] : []), + ...(expectManageSpaces + ? [ + actions.space.manage, + actions.ui.get('spaces', 'manage'), + actions.ui.get('management', 'kibana', 'spaces'), + actions.ui.get('catalogue', 'spaces'), + ] + : []), + ...(expectEnterpriseSearch ? [actions.ui.get('enterpriseSearch', 'all')] : []), + ]); + expect(actual).toHaveProperty(`${group}.read`, [ + actions.login, + actions.version, + ...(expectDecryptedTelemetry ? [actions.api.get('decryptedTelemetry')] : []), + ]); + }); }); - }); -}); + } +); describe('reserved', () => { test('actions defined at the feature do not cascade to the privileges', () => { @@ -911,6 +958,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -922,6 +970,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.read', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.ui.get('foo', 'foo'), ]); @@ -1080,6 +1129,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -1108,6 +1158,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.read', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), actions.savedObject.get('all-sub-feature-type', 'find'), @@ -1316,6 +1367,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -1323,7 +1375,11 @@ describe('subFeatures', () => { actions.ui.get('catalogue', 'spaces'), actions.ui.get('enterpriseSearch', 'all'), ]); - expect(actual).toHaveProperty('global.read', [actions.login, actions.version]); + expect(actual).toHaveProperty('global.read', [ + actions.login, + actions.version, + actions.api.get('decryptedTelemetry'), + ]); expect(actual).toHaveProperty('space.all', [actions.login, actions.version]); expect(actual).toHaveProperty('space.read', [actions.login, actions.version]); @@ -1455,6 +1511,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -1483,6 +1540,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.read', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.ui.get('foo', 'foo'), ]); @@ -1640,6 +1698,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -1647,7 +1706,11 @@ describe('subFeatures', () => { actions.ui.get('catalogue', 'spaces'), actions.ui.get('enterpriseSearch', 'all'), ]); - expect(actual).toHaveProperty('global.read', [actions.login, actions.version]); + expect(actual).toHaveProperty('global.read', [ + actions.login, + actions.version, + actions.api.get('decryptedTelemetry'), + ]); expect(actual).toHaveProperty('space.all', [actions.login, actions.version]); expect(actual).toHaveProperty('space.read', [actions.login, actions.version]); @@ -1768,6 +1831,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -1796,6 +1860,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.read', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), actions.savedObject.get('all-sub-feature-type', 'find'), @@ -2002,6 +2067,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -2030,6 +2096,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.read', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), actions.savedObject.get('all-sub-feature-type', 'find'), @@ -2270,6 +2337,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.all', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -2315,6 +2383,7 @@ describe('subFeatures', () => { expect(actual).toHaveProperty('global.read', [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.savedObject.get('all-sub-feature-type', 'bulk_get'), actions.savedObject.get('all-sub-feature-type', 'get'), actions.savedObject.get('all-sub-feature-type', 'find'), diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.ts index 28d3ddefc62b59..1826b853ce6687 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.ts @@ -105,6 +105,7 @@ export function privilegesFactory( all: [ actions.login, actions.version, + actions.api.get('decryptedTelemetry'), actions.api.get('features'), actions.space.manage, actions.ui.get('spaces', 'manage'), @@ -113,7 +114,12 @@ export function privilegesFactory( actions.ui.get('enterpriseSearch', 'all'), ...allActions, ], - read: [actions.login, actions.version, ...readActions], + read: [ + actions.login, + actions.version, + actions.api.get('decryptedTelemetry'), + ...readActions, + ], }, space: { all: [actions.login, actions.version, ...allActions], From 2f25047cbd789887733084035c086315fce76186 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Thu, 22 Apr 2021 13:25:16 -0400 Subject: [PATCH 59/65] [ML] DF Analytics map: deselect node after node action or flyout close (#97922) * deselect node after node action or flyout close * remove unnecessary wrapping function for deselect --- .../pages/job_map/components/controls.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx index 7093c3f7a88c74..eb4aa8e4f09fe0 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/components/controls.tsx @@ -249,6 +249,9 @@ export const Controls: FC = React.memo( icon="branch" onClick={() => { getNodeData({ id: nodeLabel, type: nodeType }); + if (cy) { + cy.elements().unselect(); + } setShowFlyout(false); setPopover(false); }} @@ -264,12 +267,7 @@ export const Controls: FC = React.memo( return ( - setShowFlyout(false)} - data-test-subj="mlAnalyticsJobMapFlyout" - > + From 6bb289368b313430cfb45d4144205220e10484ee Mon Sep 17 00:00:00 2001 From: Spencer Date: Thu, 22 Apr 2021 11:30:27 -0700 Subject: [PATCH 60/65] Revert skips added while triaging ES OOMs (#97876) * Revert "skip flaky suite (#97382)" This reverts commit e321f57f64657ffff91df8ed96f4e9fdbe5dcde7. * Revert "skip flaky suite (#97387)" This reverts commit a89b75671000d6c8431ff150b4f555e1f00f361e. * Revert "Skip test to try and stabilize master" (#97378) This reverts commit 194355fdd3969f567f43ad4b7f63d72dcf7974a9. * upload heap dumps when they are created Co-authored-by: spalger Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- vars/kibanaPipeline.groovy | 1 + x-pack/test/api_integration/apis/lens/existing_fields.ts | 3 +-- .../apis/security_solution/matrix_dns_histogram.ts | 3 +-- .../test/api_integration/apis/short_urls/feature_controls.ts | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index b8afdb9cde3ef7..76ed71ebbf2708 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -201,6 +201,7 @@ def withGcsArtifactUpload(workerName, closure) { 'x-pack/test/**/screenshots/session/*.png', 'x-pack/test/functional/apps/reporting/reports/session/*.pdf', 'x-pack/test/functional/failure_debug/html/*.html', + '.es/**/*.hprof' ] withEnv([ diff --git a/x-pack/test/api_integration/apis/lens/existing_fields.ts b/x-pack/test/api_integration/apis/lens/existing_fields.ts index 03587869939196..88949401f102ad 100644 --- a/x-pack/test/api_integration/apis/lens/existing_fields.ts +++ b/x-pack/test/api_integration/apis/lens/existing_fields.ts @@ -160,8 +160,7 @@ export default ({ getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); - // FLAKY: https://github.com/elastic/kibana/issues/97387 - describe.skip('existing_fields apis', () => { + describe('existing_fields apis', () => { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.loadIfNeeded('visualize/default'); diff --git a/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts b/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts index 27a7a5a5396077..69beb65dec670f 100644 --- a/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts +++ b/x-pack/test/api_integration/apis/security_solution/matrix_dns_histogram.ts @@ -33,8 +33,7 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const retry = getService('retry'); - // FIX: https://github.com/elastic/kibana/issues/97378 - describe.skip('Matrix DNS Histogram', () => { + describe('Matrix DNS Histogram', () => { describe('Large data set', () => { before(() => esArchiver.load('security_solution/matrix_dns_histogram/large_dns_query')); after(() => esArchiver.unload('security_solution/matrix_dns_histogram/large_dns_query')); diff --git a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts index e55fcf10b7fac9..a2596e9eaedaf5 100644 --- a/x-pack/test/api_integration/apis/short_urls/feature_controls.ts +++ b/x-pack/test/api_integration/apis/short_urls/feature_controls.ts @@ -12,8 +12,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const supertest = getService('supertestWithoutAuth'); const security = getService('security'); - // FLAKY: https://github.com/elastic/kibana/issues/97382 - describe.skip('feature controls', () => { + describe('feature controls', () => { const kibanaUsername = 'kibana_admin'; const kibanaUserRoleName = 'kibana_admin'; From 49a18483d38c7430d63e6fb774efd7de47997603 Mon Sep 17 00:00:00 2001 From: Michael Dokolin Date: Thu, 22 Apr 2021 20:36:25 +0200 Subject: [PATCH 61/65] Refactor execution service to use observables underneath (#96065) * Refactor execution service to use observables underneath * Fix canvas plugin to initialize workspace after assets * Update expression functions implementations to resolve observables instead of promises --- ...-expressions-public.execution.interpret.md | 4 +- ...xpressions-public.execution.invokechain.md | 4 +- ...essions-public.execution.invokefunction.md | 4 +- ...in-plugins-expressions-public.execution.md | 2 +- ...xpressions-public.execution.resolveargs.md | 4 +- ...ins-expressions-public.execution.result.md | 4 +- ...gins-expressions-public.execution.start.md | 4 +- ...plugins-expressions-public.executor.run.md | 4 +- ...n-plugins-expressions-public.typestring.md | 2 +- ...-expressions-server.execution.interpret.md | 4 +- ...xpressions-server.execution.invokechain.md | 4 +- ...essions-server.execution.invokefunction.md | 4 +- ...in-plugins-expressions-server.execution.md | 2 +- ...xpressions-server.execution.resolveargs.md | 4 +- ...ins-expressions-server.execution.result.md | 4 +- ...gins-expressions-server.execution.start.md | 4 +- ...plugins-expressions-server.executor.run.md | 4 +- ...n-plugins-expressions-server.typestring.md | 2 +- .../execution/execution.abortion.test.ts | 11 +- .../common/execution/execution.test.ts | 314 ++++++++++-- .../expressions/common/execution/execution.ts | 463 +++++++++--------- .../execution/execution_contract.test.ts | 5 +- .../common/execution/execution_contract.ts | 29 +- .../expressions/common/executor/executor.ts | 11 +- .../expression_function.ts | 4 +- .../expression_functions/specs/map_column.ts | 10 +- .../specs/tests/map_column.test.ts | 176 ++++--- .../common/service/expressions_services.ts | 3 +- .../expression_functions/index.ts | 2 + .../test_helpers/expression_functions/sum.ts | 23 + .../expressions/common/types/common.ts | 12 +- src/plugins/expressions/public/loader.test.ts | 7 +- src/plugins/expressions/public/public.api.md | 19 +- src/plugins/expressions/server/server.api.md | 19 +- .../functions/common/case.test.js | 33 +- .../functions/common/case.ts | 21 +- .../functions/common/filterrows.test.js | 27 +- .../functions/common/filterrows.ts | 6 +- .../functions/common/if.test.js | 67 ++- .../canvas_plugin_src/functions/common/if.ts | 20 +- .../functions/common/ply.test.js | 75 +-- .../canvas_plugin_src/functions/common/ply.ts | 6 +- .../functions/common/switch.test.js | 25 +- .../functions/common/switch.ts | 16 +- .../canvas/public/apps/workpad/routes.ts | 2 +- 45 files changed, 904 insertions(+), 566 deletions(-) create mode 100644 src/plugins/expressions/common/test_helpers/expression_functions/sum.ts diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md index 24dee04861b4ee..46934e119aee09 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.interpret.md @@ -7,7 +7,7 @@ Signature: ```typescript -interpret(ast: ExpressionAstNode, input: T): Promise; +interpret(ast: ExpressionAstNode, input: T): Observable; ``` ## Parameters @@ -19,5 +19,5 @@ interpret(ast: ExpressionAstNode, input: T): Promise; Returns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokechain.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokechain.md index 5078baf2ca526e..99768f0ddd533d 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokechain.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokechain.md @@ -7,7 +7,7 @@ Signature: ```typescript -invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise; +invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; ``` ## Parameters @@ -19,5 +19,5 @@ invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise; Returns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokefunction.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokefunction.md index e90cee8b626d61..2c3c2173e08331 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokefunction.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.invokefunction.md @@ -7,7 +7,7 @@ Signature: ```typescript -invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Promise; +invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; ``` ## Parameters @@ -20,5 +20,5 @@ invokeFunction(fn: ExpressionFunction, input: unknown, args: RecordReturns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md index 56b14e005adfb8..30fe9f497f7eeb 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md @@ -26,7 +26,7 @@ export declare class Executionstring | | | [input](./kibana-plugin-plugins-expressions-public.execution.input.md) | | Input | Initial input of the execution.N.B. It is initialized to null rather than undefined for legacy reasons, because in legacy interpreter it was set to null by default. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.execution.inspectoradapters.md) | | InspectorAdapters | | -| [result](./kibana-plugin-plugins-expressions-public.execution.result.md) | | Promise<Output | ExpressionValueError> | | +| [result](./kibana-plugin-plugins-expressions-public.execution.result.md) | | Observable<Output | ExpressionValueError> | Future that tracks result or error of this execution. | | [state](./kibana-plugin-plugins-expressions-public.execution.state.md) | | ExecutionContainer<Output | ExpressionValueError> | Dynamic state of the execution. | ## Methods diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.resolveargs.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.resolveargs.md index ab67dff604a867..fc11af42c5febd 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.resolveargs.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.resolveargs.md @@ -7,7 +7,7 @@ Signature: ```typescript -resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Promise; +resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; ``` ## Parameters @@ -20,5 +20,5 @@ resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): PromiseReturns:
-`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md index e0167a3a378fef..94f60ccee0f009 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.result.md @@ -4,8 +4,10 @@ ## Execution.result property +Future that tracks result or error of this execution. + Signature: ```typescript -get result(): Promise; +readonly result: Observable; ``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md index c6edc43d423dc4..64cf81b376948a 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.start.md @@ -11,7 +11,7 @@ N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons Signature: ```typescript -start(input?: Input): void; +start(input?: Input): Observable; ``` ## Parameters @@ -22,5 +22,5 @@ start(input?: Input): void; Returns: -`void` +`Observable` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md index 81fc8aa8658ca0..307e6b6bcd5c80 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executor.run.md @@ -9,7 +9,7 @@ Execute expression and return result. Signature: ```typescript -run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Promise; +run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; ``` ## Parameters @@ -22,5 +22,5 @@ run(ast: string | ExpressionAstExpression, input: Input, params?: Returns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.typestring.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.typestring.md index 1e85625907bb07..08dc2d6208d341 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.typestring.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.typestring.md @@ -11,5 +11,5 @@ If the type extends a Promise, we still need to return the string representation Signature: ```typescript -export declare type TypeString = KnownTypeToString>; +export declare type TypeString = KnownTypeToString ? UnwrapObservable : UnwrapPromiseOrReturn>; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md index e425bdc70e3491..936e98be589a35 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.interpret.md @@ -7,7 +7,7 @@ Signature: ```typescript -interpret(ast: ExpressionAstNode, input: T): Promise; +interpret(ast: ExpressionAstNode, input: T): Observable; ``` ## Parameters @@ -19,5 +19,5 @@ interpret(ast: ExpressionAstNode, input: T): Promise; Returns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokechain.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokechain.md index 9ada611f32bf2e..003702ff845b20 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokechain.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokechain.md @@ -7,7 +7,7 @@ Signature: ```typescript -invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise; +invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; ``` ## Parameters @@ -19,5 +19,5 @@ invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise; Returns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokefunction.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokefunction.md index 4519d21ee250af..91839172c31f42 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokefunction.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.invokefunction.md @@ -7,7 +7,7 @@ Signature: ```typescript -invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Promise; +invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; ``` ## Parameters @@ -20,5 +20,5 @@ invokeFunction(fn: ExpressionFunction, input: unknown, args: RecordReturns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md index c94ae9bcfe9466..a4e324eef6674c 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.md @@ -26,7 +26,7 @@ export declare class Executionstring | | | [input](./kibana-plugin-plugins-expressions-server.execution.input.md) | | Input | Initial input of the execution.N.B. It is initialized to null rather than undefined for legacy reasons, because in legacy interpreter it was set to null by default. | | [inspectorAdapters](./kibana-plugin-plugins-expressions-server.execution.inspectoradapters.md) | | InspectorAdapters | | -| [result](./kibana-plugin-plugins-expressions-server.execution.result.md) | | Promise<Output | ExpressionValueError> | | +| [result](./kibana-plugin-plugins-expressions-server.execution.result.md) | | Observable<Output | ExpressionValueError> | Future that tracks result or error of this execution. | | [state](./kibana-plugin-plugins-expressions-server.execution.state.md) | | ExecutionContainer<Output | ExpressionValueError> | Dynamic state of the execution. | ## Methods diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.resolveargs.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.resolveargs.md index 48cc43b2d7767b..784818f2fb8e3d 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.resolveargs.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.resolveargs.md @@ -7,7 +7,7 @@ Signature: ```typescript -resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Promise; +resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; ``` ## Parameters @@ -20,5 +20,5 @@ resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): PromiseReturns:
-`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md index be0134cd2542e3..06cf047ac4160f 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.result.md @@ -4,8 +4,10 @@ ## Execution.result property +Future that tracks result or error of this execution. + Signature: ```typescript -get result(): Promise; +readonly result: Observable; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md index 9a4e93fe6a9af0..dd0456ac09950e 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.execution.start.md @@ -11,7 +11,7 @@ N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons Signature: ```typescript -start(input?: Input): void; +start(input?: Input): Observable; ``` ## Parameters @@ -22,5 +22,5 @@ start(input?: Input): void; Returns: -`void` +`Observable` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md index de5ce1ed37f12a..2ab534eac2f3a2 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executor.run.md @@ -9,7 +9,7 @@ Execute expression and return result. Signature: ```typescript -run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Promise; +run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; ``` ## Parameters @@ -22,5 +22,5 @@ run(ast: string | ExpressionAstExpression, input: Input, params?: Returns: -`Promise` +`Observable` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.typestring.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.typestring.md index af4d5ae0bf8149..adf2c52490de44 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.typestring.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.typestring.md @@ -11,5 +11,5 @@ If the type extends a Promise, we still need to return the string representation Signature: ```typescript -export declare type TypeString = KnownTypeToString>; +export declare type TypeString = KnownTypeToString ? UnwrapObservable : UnwrapPromiseOrReturn>; ``` diff --git a/src/plugins/expressions/common/execution/execution.abortion.test.ts b/src/plugins/expressions/common/execution/execution.abortion.test.ts index 33bb7826917473..514086e9b19eee 100644 --- a/src/plugins/expressions/common/execution/execution.abortion.test.ts +++ b/src/plugins/expressions/common/execution/execution.abortion.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { first } from 'rxjs/operators'; import { waitFor } from '@testing-library/react'; import { Execution } from './execution'; import { parseExpression } from '../ast'; @@ -39,7 +40,7 @@ describe('Execution abortion tests', () => { execution.start(); execution.cancel(); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toMatchObject({ type: 'error', @@ -57,7 +58,7 @@ describe('Execution abortion tests', () => { jest.advanceTimersByTime(100); execution.cancel(); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toMatchObject({ type: 'error', @@ -75,7 +76,7 @@ describe('Execution abortion tests', () => { execution.start(); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); execution.cancel(); @@ -130,12 +131,12 @@ describe('Execution abortion tests', () => { params: {}, }); - execution.start(); + execution.start().toPromise(); await waitFor(() => expect(started).toHaveBeenCalledTimes(1)); execution.cancel(); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toMatchObject({ type: 'error', error: { diff --git a/src/plugins/expressions/common/execution/execution.test.ts b/src/plugins/expressions/common/execution/execution.test.ts index b9fa693c1056b1..343ea9ef7f03c8 100644 --- a/src/plugins/expressions/common/execution/execution.test.ts +++ b/src/plugins/expressions/common/execution/execution.test.ts @@ -6,6 +6,9 @@ * Side Public License, v 1. */ +import { of } from 'rxjs'; +import { first, scan } from 'rxjs/operators'; +import { TestScheduler } from 'rxjs/testing'; import { Execution } from './execution'; import { parseExpression, ExpressionAstExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; @@ -42,10 +45,18 @@ const run = async ( ) => { const execution = createExecution(expression, context); execution.start(input); - return await execution.result; + return await execution.result.pipe(first()).toPromise(); }; +let testScheduler: TestScheduler; + describe('Execution', () => { + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => { + return expect(actual).toStrictEqual(expected); + }); + }); + test('can instantiate', () => { const execution = createExecution('foo bar=123'); expect(execution.state.get().ast.chain[0].arguments.bar).toEqual([123]); @@ -73,7 +84,7 @@ describe('Execution', () => { /* eslint-enable no-console */ execution.start(123); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toBe(123); expect(spy).toHaveBeenCalledTimes(1); @@ -91,7 +102,7 @@ describe('Execution', () => { value: -1, }); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toEqual({ type: 'num', @@ -106,7 +117,7 @@ describe('Execution', () => { value: 0, }); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toEqual({ type: 'num', @@ -114,16 +125,102 @@ describe('Execution', () => { }); }); - test('casts input to correct type', async () => { - const execution = createExecution('add val=1'); + describe('.input', () => { + test('casts input to correct type', async () => { + const execution = createExecution('add val=1'); - // Below 1 is cast to { type: 'num', value: 1 }. - execution.start(1); - const result = await execution.result; + // Below 1 is cast to { type: 'num', value: 1 }. + execution.start(1); + const result = await execution.result.pipe(first()).toPromise(); - expect(result).toEqual({ - type: 'num', - value: 2, + expect(result).toEqual({ + type: 'num', + value: 2, + }); + }); + + test('supports promises on input', async () => { + const execution = createExecution('add val=1'); + + execution.start(Promise.resolve(1)); + const result = await execution.result.pipe(first()).toPromise(); + + expect(result).toEqual({ + type: 'num', + value: 2, + }); + }); + + test('supports observables on input', async () => { + const execution = createExecution('add val=1'); + + execution.start(of(1)); + const result = await execution.result.pipe(first()).toPromise(); + + expect(result).toEqual({ + type: 'num', + value: 2, + }); + }); + + test('handles observables on input', () => { + const execution = createExecution('add val=1'); + + testScheduler.run(({ cold, expectObservable }) => { + const input = cold(' -a--b-c-', { a: 1, b: 2, c: 3 }); + const subscription = ' ---^---!'; + const expected = ' ---ab-c-'; + + expectObservable(execution.start(input), subscription).toBe(expected, { + a: { type: 'num', value: 2 }, + b: { type: 'num', value: 3 }, + c: { type: 'num', value: 4 }, + }); + }); + }); + + test('stops when input errors', () => { + const execution = createExecution('add val=1'); + + testScheduler.run(({ cold, expectObservable }) => { + const input = cold('-a-#-b-', { a: 1, b: 2 }); + const expected = ' -a-#'; + + expectObservable(execution.start(input)).toBe(expected, { + a: { type: 'num', value: 2 }, + }); + }); + }); + + test('does not complete when input completes', () => { + const execution = createExecution('add val=1'); + + testScheduler.run(({ cold, expectObservable }) => { + const input = cold('-a-b|', { a: 1, b: 2 }); + const expected = ' -a-b-'; + + expectObservable(execution.start(input)).toBe(expected, { + a: { type: 'num', value: 2 }, + b: { type: 'num', value: 3 }, + }); + }); + }); + + test('handles partial results', () => { + const execution = createExecution('sum'); + + testScheduler.run(({ cold, expectObservable }) => { + const items = cold(' -a--b-c-', { a: 1, b: 2, c: 3 }); + const subscription = ' ---^---!'; + const expected = ' ---ab-c-'; + const input = items.pipe(scan((result, value) => [...result, value], new Array())); + + expectObservable(execution.start(input), subscription).toBe(expected, { + a: { type: 'num', value: 1 }, + b: { type: 'num', value: 3 }, + c: { type: 'num', value: 6 }, + }); + }); }); }); @@ -251,7 +348,7 @@ describe('Execution', () => { value: 0, }); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toEqual({ type: 'num', @@ -267,13 +364,32 @@ describe('Execution', () => { test('result is undefined until execution completes', async () => { const execution = createExecution('sleep 10'); expect(execution.state.get().result).toBe(undefined); - execution.start(null); + execution.start(null).subscribe(jest.fn()); expect(execution.state.get().result).toBe(undefined); await new Promise((r) => setTimeout(r, 1)); expect(execution.state.get().result).toBe(undefined); await new Promise((r) => setTimeout(r, 11)); expect(execution.state.get().result).toBe(null); }); + + test('handles functions returning observables', () => { + testScheduler.run(({ cold, expectObservable }) => { + const arg = cold(' -a-b-c|', { a: 1, b: 2, c: 3 }); + const expected = ' -a-b-c-'; + const observable: ExpressionFunctionDefinition<'observable', any, {}, any> = { + name: 'observable', + args: {}, + help: '', + fn: () => arg, + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(observable); + + const result = executor.run('observable', null, {}); + + expectObservable(result).toBe(expected, { a: 1, b: 2, c: 3 }); + }); + }); }); describe('when function throws', () => { @@ -309,7 +425,7 @@ describe('Execution', () => { const execution = await createExecution('error "foo"'); execution.start(null); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toMatchObject({ type: 'error', @@ -330,7 +446,7 @@ describe('Execution', () => { const executor = createUnitTestExecutor(); executor.registerFunction(spy); - await executor.run('error "..." | spy', null); + await executor.run('error "..." | spy', null).pipe(first()).toPromise(); expect(spy.fn).toHaveBeenCalledTimes(0); }); @@ -360,14 +476,14 @@ describe('Execution', () => { test('execution state is "result" when execution successfully completes', async () => { const execution = createExecution('sleep 1'); execution.start(null); - await execution.result; + await execution.result.pipe(first()).toPromise(); expect(execution.state.get().state).toBe('result'); }); test('execution state is "result" when execution successfully completes - 2', async () => { const execution = createExecution('var foo'); execution.start(null); - await execution.result; + await execution.result.pipe(first()).toPromise(); expect(execution.state.get().state).toBe('result'); }); }); @@ -413,10 +529,142 @@ describe('Execution', () => { expect(result).toBe(66); }); + + test('supports observables in arguments', () => { + const observable = { + name: 'observable', + args: {}, + help: '', + fn: () => of(1), + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(observable); + + expect( + executor.run('add val={observable}', 1, {}).pipe(first()).toPromise() + ).resolves.toEqual({ + type: 'num', + value: 2, + }); + }); + + test('supports observables in arguments emitting multiple values', () => { + testScheduler.run(({ cold, expectObservable }) => { + const arg = cold('-a-b-c-', { a: 1, b: 2, c: 3 }); + const expected = '-a-b-c-'; + const observable = { + name: 'observable', + args: {}, + help: '', + fn: () => arg, + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(observable); + + const result = executor.run('add val={observable}', 1, {}); + + expectObservable(result).toBe(expected, { + a: { type: 'num', value: 2 }, + b: { type: 'num', value: 3 }, + c: { type: 'num', value: 4 }, + }); + }); + }); + + test('combines multiple observables in arguments', () => { + testScheduler.run(({ cold, expectObservable }) => { + const arg1 = cold('--ab-c-', { a: 0, b: 2, c: 4 }); + const arg2 = cold('-a--bc-', { a: 1, b: 3, c: 5 }); + const expected = ' --abc(de)-'; + const observable1 = { + name: 'observable1', + args: {}, + help: '', + fn: () => arg1, + }; + const observable2 = { + name: 'observable2', + args: {}, + help: '', + fn: () => arg2, + }; + const max: ExpressionFunctionDefinition<'max', any, { val1: number; val2: number }, any> = { + name: 'max', + args: { + val1: { help: '', types: ['number'] }, + val2: { help: '', types: ['number'] }, + }, + help: '', + fn: (input, { val1, val2 }) => ({ type: 'num', value: Math.max(val1, val2) }), + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(observable1); + executor.registerFunction(observable2); + executor.registerFunction(max); + + const result = executor.run('max val1={observable1} val2={observable2}', {}); + + expectObservable(result).toBe(expected, { + a: { type: 'num', value: 1 }, + b: { type: 'num', value: 2 }, + c: { type: 'num', value: 3 }, + d: { type: 'num', value: 4 }, + e: { type: 'num', value: 5 }, + }); + }); + }); + + test('does not complete when an argument completes', () => { + testScheduler.run(({ cold, expectObservable }) => { + const arg = cold('-a|', { a: 1 }); + const expected = '-a-'; + const observable = { + name: 'observable', + args: {}, + help: '', + fn: () => arg, + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(observable); + + const result = executor.run('add val={observable}', 1, {}); + + expectObservable(result).toBe(expected, { + a: { type: 'num', value: 2 }, + }); + }); + }); + + test('handles error in observable arguments', () => { + testScheduler.run(({ cold, expectObservable }) => { + const arg = cold('-a-#', { a: 1 }, new Error('some error')); + const expected = '-a-b'; + const observable = { + name: 'observable', + args: {}, + help: '', + fn: () => arg, + }; + const executor = createUnitTestExecutor(); + executor.registerFunction(observable); + + const result = executor.run('add val={observable}', 1, {}); + + expectObservable(result).toBe(expected, { + a: { type: 'num', value: 2 }, + b: { + error: expect.objectContaining({ + message: '[add] > [observable] > some error', + }), + type: 'error', + }, + }); + }); + }); }); describe('when arguments are missing', () => { - test('when required argument is missing and has not alias, returns error', async () => { + it('when required argument is missing and has not alias, returns error', async () => { const requiredArg: ExpressionFunctionDefinition<'requiredArg', any, { arg: any }, any> = { name: 'requiredArg', args: { @@ -430,7 +678,7 @@ describe('Execution', () => { }; const executor = createUnitTestExecutor(); executor.registerFunction(requiredArg); - const result = await executor.run('requiredArg', null, {}); + const result = await executor.run('requiredArg', null, {}).pipe(first()).toPromise(); expect(result).toMatchObject({ type: 'error', @@ -456,7 +704,7 @@ describe('Execution', () => { test('can execute expression in debug mode', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toEqual({ type: 'num', @@ -471,7 +719,7 @@ describe('Execution', () => { true ); execution.start(0); - const result = await execution.result; + const result = await execution.result.pipe(first()).toPromise(); expect(result).toEqual({ type: 'num', @@ -483,7 +731,7 @@ describe('Execution', () => { test('sets "success" flag on all functions to true', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); for (const node of execution.state.get().ast.chain) { expect(node.debug?.success).toBe(true); @@ -493,7 +741,7 @@ describe('Execution', () => { test('stores "fn" reference to the function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); for (const node of execution.state.get().ast.chain) { expect(node.debug?.fn).toBe('add'); @@ -503,7 +751,7 @@ describe('Execution', () => { test('saves duration it took to execute each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); for (const node of execution.state.get().ast.chain) { expect(typeof node.debug?.duration).toBe('number'); @@ -515,7 +763,7 @@ describe('Execution', () => { test('adds .debug field in expression AST on each executed function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); for (const node of execution.state.get().ast.chain) { expect(typeof node.debug).toBe('object'); @@ -526,7 +774,7 @@ describe('Execution', () => { test('stores input of each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); const { chain } = execution.state.get().ast; @@ -544,7 +792,7 @@ describe('Execution', () => { test('stores output of each function', async () => { const execution = createExecution('add val=1 | add val=2 | add val=3', {}, true); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); const { chain } = execution.state.get().ast; @@ -569,7 +817,7 @@ describe('Execution', () => { true ); execution.start(-1); - await execution.result; + await execution.result.pipe(first()).toPromise(); const { chain } = execution.state.get().ast; @@ -592,7 +840,7 @@ describe('Execution', () => { true ); execution.start(0); - await execution.result; + await execution.result.pipe(first()).toPromise(); const { chain } = execution.state.get().ast.chain[0].arguments .val[0] as ExpressionAstExpression; @@ -627,7 +875,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result; + await execution.result.pipe(first()).toPromise(); const node1 = execution.state.get().ast.chain[0]; const node2 = execution.state.get().ast.chain[1]; @@ -645,7 +893,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result; + await execution.result.pipe(first()).toPromise(); const node2 = execution.state.get().ast.chain[1]; @@ -666,7 +914,7 @@ describe('Execution', () => { params: { debug: true }, }); execution.start(0); - await execution.result; + await execution.result.pipe(first()).toPromise(); const node2 = execution.state.get().ast.chain[1]; diff --git a/src/plugins/expressions/common/execution/execution.ts b/src/plugins/expressions/common/execution/execution.ts index bf545a0075bed7..b70f261ea4b201 100644 --- a/src/plugins/expressions/common/execution/execution.ts +++ b/src/plugins/expressions/common/execution/execution.ts @@ -7,14 +7,28 @@ */ import { i18n } from '@kbn/i18n'; +import { isPromise } from '@kbn/std'; import { keys, last, mapValues, reduce, zipObject } from 'lodash'; +import { + combineLatest, + defer, + from, + isObservable, + of, + race, + throwError, + Observable, + ReplaySubject, +} from 'rxjs'; +import { catchError, finalize, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { Executor } from '../executor'; import { createExecutionContainer, ExecutionContainer } from './container'; import { createError } from '../util'; -import { abortSignalToPromise, Defer, now } from '../../../kibana_utils/common'; +import { abortSignalToPromise, now } from '../../../kibana_utils/common'; import { RequestAdapter, Adapters } from '../../../inspector/common'; import { isExpressionValueError, ExpressionValueError } from '../expression_types/specs/error'; import { + ExpressionAstArgument, ExpressionAstExpression, ExpressionAstFunction, parse, @@ -23,8 +37,8 @@ import { ExpressionAstNode, } from '../ast'; import { ExecutionContext, DefaultInspectorAdapters } from './types'; -import { getType, ExpressionValue, Datatable } from '../expression_types'; -import { ArgumentType, ExpressionFunction } from '../expression_functions'; +import { getType, Datatable } from '../expression_types'; +import { ExpressionFunction } from '../expression_functions'; import { getByAlias } from '../util/get_by_alias'; import { ExecutionContract } from './execution_contract'; import { ExpressionExecutionParams } from '../service'; @@ -87,6 +101,11 @@ export class Execution< */ public input: Input = null as any; + /** + * Input of the started execution. + */ + private input$ = new ReplaySubject(1); + /** * Execution context - object that allows to do side-effects. Context is passed * to every function. @@ -104,10 +123,10 @@ export class Execution< private readonly abortRejection = abortSignalToPromise(this.abortController.signal); /** - * Races a given promise against the "abort" event of `abortController`. + * Races a given observable against the "abort" event of `abortController`. */ - private race(promise: Promise): Promise { - return Promise.race([this.abortRejection.promise, promise]); + private race(observable: Observable): Observable { + return race(from(this.abortRejection.promise), observable); } /** @@ -118,7 +137,7 @@ export class Execution< /** * Future that tracks result or error of this execution. */ - private readonly firstResultFuture = new Defer(); + public readonly result: Observable; /** * Keeping track of any child executions @@ -139,10 +158,6 @@ export class Execution< public readonly expression: string; - public get result(): Promise { - return this.firstResultFuture.promise; - } - public get inspectorAdapters(): InspectorAdapters { return this.context.inspectorAdapters; } @@ -184,6 +199,28 @@ export class Execution< isSyncColorsEnabled: () => execution.params.syncColors, ...(execution.params as any).extraContext, }; + + this.result = this.input$.pipe( + switchMap((input) => this.race(this.invokeChain(this.state.get().ast.chain, input))), + catchError((error) => { + if (this.abortController.signal.aborted) { + this.childExecutions.forEach((childExecution) => childExecution.cancel()); + + return of(createAbortErrorValue()); + } + + return throwError(error); + }), + tap({ + next: (result) => { + this.context.inspectorAdapters.expression?.logAST(this.state.get().ast); + this.state.transitions.setResult(result); + }, + error: (error) => this.state.transitions.setError(error), + }), + finalize(() => this.abortRejection.cleanup()), + shareReplay(1) + ); } /** @@ -199,150 +236,139 @@ export class Execution< * N.B. `input` is initialized to `null` rather than `undefined` for legacy reasons, * because in legacy interpreter it was set to `null` by default. */ - public start(input: Input = null as any) { + public start(input: Input = null as any): Observable { if (this.hasStarted) throw new Error('Execution already started.'); this.hasStarted = true; - this.input = input; this.state.transitions.start(); - const { resolve, reject } = this.firstResultFuture; - const chainPromise = this.invokeChain(this.state.get().ast.chain, input); - - this.race(chainPromise).then(resolve, (error) => { - if (this.abortController.signal.aborted) { - this.childExecutions.forEach((ex) => ex.cancel()); - resolve(createAbortErrorValue()); - } else reject(error); - }); + if (isObservable(input)) { + // `input$` should never complete + input.subscribe( + (value) => this.input$.next(value), + (error) => this.input$.error(error) + ); + } else if (isPromise(input)) { + input.then( + (value) => this.input$.next(value), + (error) => this.input$.error(error) + ); + } else { + this.input$.next(input); + } - this.firstResultFuture.promise - .then( - (result) => { - if (this.context.inspectorAdapters.expression) { - this.context.inspectorAdapters.expression.logAST(this.state.get().ast); - } - this.state.transitions.setResult(result); - }, - (error) => { - this.state.transitions.setError(error); - } - ) - .finally(() => { - this.abortRejection.cleanup(); - }); + return this.result; } - async invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise { - if (!chainArr.length) return input; - - for (const link of chainArr) { - const { function: fnName, arguments: fnArgs } = link; - const fn = getByAlias(this.state.get().functions, fnName); - - if (!fn) { - return createError({ - name: 'fn not found', - message: i18n.translate('expressions.execution.functionNotFound', { - defaultMessage: `Function {fnName} could not be found.`, - values: { - fnName, - }, - }), - }); - } - - if (fn.disabled) { - return createError({ - name: 'fn is disabled', - message: i18n.translate('expressions.execution.functionDisabled', { - defaultMessage: `Function {fnName} is disabled.`, - values: { - fnName, - }, - }), - }); - } - - let args: Record = {}; - let timeStart: number | undefined; - - try { - // `resolveArgs` returns an object because the arguments themselves might - // actually have a `then` function which would be treated as a `Promise`. - const { resolvedArgs } = await this.race(this.resolveArgs(fn, input, fnArgs)); - args = resolvedArgs; - timeStart = this.execution.params.debug ? now() : 0; - const output = await this.race(this.invokeFunction(fn, input, resolvedArgs)); - - if (this.execution.params.debug) { - const timeEnd: number = now(); - (link as ExpressionAstFunction).debug = { - success: true, - fn: fn.name, - input, - args: resolvedArgs, - output, - duration: timeEnd - timeStart, - }; - } + invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable { + return of(input).pipe( + ...(chainArr.map((link) => + switchMap((currentInput) => { + const { function: fnName, arguments: fnArgs } = link; + const fn = getByAlias(this.state.get().functions, fnName); + + if (!fn) { + throw createError({ + name: 'fn not found', + message: i18n.translate('expressions.execution.functionNotFound', { + defaultMessage: `Function {fnName} could not be found.`, + values: { + fnName, + }, + }), + }); + } - if (getType(output) === 'error') return output; - input = output; - } catch (rawError) { - const timeEnd: number = this.execution.params.debug ? now() : 0; - const error = createError(rawError) as ExpressionValueError; - error.error.message = `[${fnName}] > ${error.error.message}`; - - if (this.execution.params.debug) { - (link as ExpressionAstFunction).debug = { - success: false, - fn: fn.name, - input, - args, - error, - rawError, - duration: timeStart ? timeEnd - timeStart : undefined, - }; - } + if (fn.disabled) { + throw createError({ + name: 'fn is disabled', + message: i18n.translate('expressions.execution.functionDisabled', { + defaultMessage: `Function {fnName} is disabled.`, + values: { + fnName, + }, + }), + }); + } - return error; - } - } + if (this.execution.params.debug) { + link.debug = { + args: {}, + duration: 0, + fn: fn.name, + input: currentInput, + success: true, + }; + } - return input; + const timeStart = this.execution.params.debug ? now() : 0; + + // `resolveArgs` returns an object because the arguments themselves might + // actually have `then` or `subscribe` methods which would be treated as a `Promise` + // or an `Observable` accordingly. + return this.race(this.resolveArgs(fn, currentInput, fnArgs)).pipe( + tap((args) => this.execution.params.debug && Object.assign(link.debug, { args })), + switchMap((args) => this.race(this.invokeFunction(fn, currentInput, args))), + switchMap((output) => (getType(output) === 'error' ? throwError(output) : of(output))), + tap((output) => this.execution.params.debug && Object.assign(link.debug, { output })), + catchError((rawError) => { + const error = createError(rawError); + error.error.message = `[${fnName}] > ${error.error.message}`; + + if (this.execution.params.debug) { + Object.assign(link.debug, { error, rawError, success: false }); + } + + return throwError(error); + }), + finalize(() => { + if (this.execution.params.debug) { + Object.assign(link.debug, { duration: now() - timeStart }); + } + }) + ); + }) + ) as Parameters['pipe']>), + catchError((error) => of(error)) + ); } - async invokeFunction( + invokeFunction( fn: ExpressionFunction, input: unknown, args: Record - ): Promise { - const normalizedInput = this.cast(input, fn.inputTypes); - const output = await this.race(fn.fn(normalizedInput, args, this.context)); - - // Validate that the function returned the type it said it would. - // This isn't required, but it keeps function developers honest. - const returnType = getType(output); - const expectedType = fn.type; - if (expectedType && returnType !== expectedType) { - throw new Error( - `Function '${fn.name}' should return '${expectedType}',` + - ` actually returned '${returnType}'` - ); - } + ): Observable { + return of(input).pipe( + map((currentInput) => this.cast(currentInput, fn.inputTypes)), + switchMap((normalizedInput) => this.race(of(fn.fn(normalizedInput, args, this.context)))), + switchMap((fnResult: any) => + isObservable(fnResult) ? fnResult : from(isPromise(fnResult) ? fnResult : [fnResult]) + ), + map((output) => { + // Validate that the function returned the type it said it would. + // This isn't required, but it keeps function developers honest. + const returnType = getType(output); + const expectedType = fn.type; + if (expectedType && returnType !== expectedType) { + throw new Error( + `Function '${fn.name}' should return '${expectedType}',` + + ` actually returned '${returnType}'` + ); + } - // Validate the function output against the type definition's validate function. - const type = this.context.types[fn.type]; - if (type && type.validate) { - try { - type.validate(output); - } catch (e) { - throw new Error(`Output of '${fn.name}' is not a valid type '${fn.type}': ${e}`); - } - } + // Validate the function output against the type definition's validate function. + const type = this.context.types[fn.type]; + if (type && type.validate) { + try { + type.validate(output); + } catch (e) { + throw new Error(`Output of '${fn.name}' is not a valid type '${fn.type}': ${e}`); + } + } - return output; + return output; + }) + ); } public cast(value: any, toTypeNames?: string[]) { @@ -371,98 +397,96 @@ export class Execution< } // Processes the multi-valued AST argument values into arguments that can be passed to the function - async resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Promise { - const argDefs = fnDef.args; - - // Use the non-alias name from the argument definition - const dealiasedArgAsts = reduce( - argAsts, - (acc, argAst, argName) => { - const argDef = getByAlias(argDefs, argName); - if (!argDef) { - throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); - } - acc[argDef.name] = (acc[argDef.name] || []).concat(argAst); - return acc; - }, - {} as any - ); + resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable { + return defer(() => { + const { args: argDefs } = fnDef; + + // Use the non-alias name from the argument definition + const dealiasedArgAsts = reduce( + argAsts as Record, + (acc, argAst, argName) => { + const argDef = getByAlias(argDefs, argName); + if (!argDef) { + throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); + } + acc[argDef.name] = (acc[argDef.name] || []).concat(argAst); + return acc; + }, + {} as Record + ); - // Check for missing required arguments. - for (const argDef of Object.values(argDefs)) { - const { - aliases, - default: argDefault, - name: argName, - required, - } = argDef as ArgumentType & { name: string }; - if ( - typeof argDefault !== 'undefined' || - !required || - typeof dealiasedArgAsts[argName] !== 'undefined' - ) - continue; - - if (!aliases || aliases.length === 0) { - throw new Error(`${fnDef.name} requires an argument`); - } + // Check for missing required arguments. + for (const { aliases, default: argDefault, name, required } of Object.values(argDefs)) { + if (!(name in dealiasedArgAsts) && typeof argDefault !== 'undefined') { + dealiasedArgAsts[name] = [parse(argDefault, 'argument')]; + } - // use an alias if _ is the missing arg - const errorArg = argName === '_' ? aliases[0] : argName; - throw new Error(`${fnDef.name} requires an "${errorArg}" argument`); - } + if (!required || name in dealiasedArgAsts) { + continue; + } - // Fill in default values from argument definition - const argAstsWithDefaults = reduce( - argDefs, - (acc: any, argDef: any, argName: any) => { - if (typeof acc[argName] === 'undefined' && typeof argDef.default !== 'undefined') { - acc[argName] = [parse(argDef.default, 'argument')]; + if (!aliases?.length) { + throw new Error(`${fnDef.name} requires an argument`); } - return acc; - }, - dealiasedArgAsts - ); + // use an alias if _ is the missing arg + const errorArg = name === '_' ? aliases[0] : name; + throw new Error(`${fnDef.name} requires an "${errorArg}" argument`); + } - // Create the functions to resolve the argument ASTs into values - // These are what are passed to the actual functions if you opt out of resolving - const resolveArgFns = mapValues(argAstsWithDefaults, (asts, argName) => { - return asts.map((item: ExpressionAstExpression) => { - return async (subInput = input) => { - const output = await this.interpret(item, subInput); - if (isExpressionValueError(output)) throw output.error; - const casted = this.cast(output, argDefs[argName as any].types); - return casted; - }; - }); - }); + // Create the functions to resolve the argument ASTs into values + // These are what are passed to the actual functions if you opt out of resolving + const resolveArgFns = mapValues(dealiasedArgAsts, (asts, argName) => + asts.map((item) => (subInput = input) => + this.interpret(item, subInput).pipe( + map((output) => { + if (isExpressionValueError(output)) { + throw output.error; + } + + return this.cast(output, argDefs[argName].types); + }) + ) + ) + ); - const argNames = keys(resolveArgFns); + const argNames = keys(resolveArgFns); - // Actually resolve unless the argument definition says not to - const resolvedArgValues = await Promise.all( - argNames.map((argName) => { - const interpretFns = resolveArgFns[argName]; - if (!argDefs[argName].resolve) return interpretFns; - return Promise.all(interpretFns.map((fn: any) => fn())); - }) - ); + if (!argNames.length) { + return from([[]]); + } - const resolvedMultiArgs = zipObject(argNames, resolvedArgValues); + const resolvedArgValuesObservable = combineLatest( + argNames.map((argName) => { + const interpretFns = resolveArgFns[argName]; - // Just return the last unless the argument definition allows multiple - const resolvedArgs = mapValues(resolvedMultiArgs, (argValues, argName) => { - if (argDefs[argName as any].multi) return argValues; - return last(argValues as any); - }); + // `combineLatest` does not emit a value on an empty collection + // @see https://github.com/ReactiveX/RxSwift/issues/1879 + if (!interpretFns.length) { + return of([]); + } - // Return an object here because the arguments themselves might actually have a 'then' - // function which would be treated as a promise - return { resolvedArgs }; + return argDefs[argName].resolve + ? combineLatest(interpretFns.map((fn) => fn())) + : of(interpretFns); + }) + ); + + return resolvedArgValuesObservable.pipe( + map((resolvedArgValues) => + mapValues( + // Return an object here because the arguments themselves might actually have a 'then' + // function which would be treated as a promise + zipObject(argNames, resolvedArgValues), + // Just return the last unless the argument definition allows multiple + (argValues, argName) => (argDefs[argName].multi ? argValues : last(argValues)) + ) + ) + ); + }); } - public async interpret(ast: ExpressionAstNode, input: T): Promise { + public interpret(ast: ExpressionAstNode, input: T): Observable { switch (getType(ast)) { case 'expression': const execution = this.execution.executor.createExecution( @@ -470,15 +494,14 @@ export class Execution< this.execution.params ); this.childExecutions.push(execution); - execution.start(input); - return await execution.result; + return execution.start(input); case 'string': case 'number': case 'null': case 'boolean': - return ast; + return of(ast); default: - throw new Error(`Unknown AST object: ${JSON.stringify(ast)}`); + return throwError(new Error(`Unknown AST object: ${JSON.stringify(ast)}`)); } } } diff --git a/src/plugins/expressions/common/execution/execution_contract.test.ts b/src/plugins/expressions/common/execution/execution_contract.test.ts index 111dc0d735ebb7..99a5c80de3c462 100644 --- a/src/plugins/expressions/common/execution/execution_contract.test.ts +++ b/src/plugins/expressions/common/execution/execution_contract.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { first } from 'rxjs/operators'; import { Execution } from './execution'; import { parseExpression } from '../ast'; import { createUnitTestExecutor } from '../test_helpers'; @@ -108,7 +109,7 @@ describe('ExecutionContract', () => { const contract = new ExecutionContract(execution); execution.start(); - await execution.result; + await execution.result.pipe(first()).toPromise(); expect(contract.isPending).toBe(false); expect(execution.state.get().state).toBe('result'); @@ -119,7 +120,7 @@ describe('ExecutionContract', () => { const contract = new ExecutionContract(execution); execution.start(); - await execution.result; + await execution.result.pipe(first()).toPromise(); execution.state.get().state = 'error'; expect(contract.isPending).toBe(false); diff --git a/src/plugins/expressions/common/execution/execution_contract.ts b/src/plugins/expressions/common/execution/execution_contract.ts index 9ef2aa79f520c8..3cad9cef5e09ae 100644 --- a/src/plugins/expressions/common/execution/execution_contract.ts +++ b/src/plugins/expressions/common/execution/execution_contract.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { of } from 'rxjs'; +import { catchError, take } from 'rxjs/operators'; import { Execution } from './execution'; import { ExpressionValueError } from '../expression_types/specs'; import { ExpressionAstExpression } from '../ast'; @@ -38,18 +40,21 @@ export class ExecutionContract => { - try { - return await this.execution.result; - } catch (e) { - return { - type: 'error', - error: { - name: e.name, - message: e.message, - stack: e.stack, - }, - }; - } + return this.execution.result + .pipe( + take(1), + catchError(({ name, message, stack }) => + of({ + type: 'error', + error: { + name, + message, + stack, + }, + } as ExpressionValueError) + ) + ) + .toPromise(); }; /** diff --git a/src/plugins/expressions/common/executor/executor.ts b/src/plugins/expressions/common/executor/executor.ts index 255de31f7239b1..1eea51a0e1ec45 100644 --- a/src/plugins/expressions/common/executor/executor.ts +++ b/src/plugins/expressions/common/executor/executor.ts @@ -9,6 +9,7 @@ /* eslint-disable max-classes-per-file */ import { cloneDeep, mapValues } from 'lodash'; +import { Observable } from 'rxjs'; import { ExecutorState, ExecutorContainer } from './container'; import { createExecutorContainer } from './container'; import { AnyExpressionFunctionDefinition, ExpressionFunction } from '../expression_functions'; @@ -17,7 +18,7 @@ import { IRegistry } from '../types'; import { ExpressionType } from '../expression_types/expression_type'; import { AnyExpressionTypeDefinition } from '../expression_types/types'; import { ExpressionAstExpression, ExpressionAstFunction } from '../ast'; -import { typeSpecs } from '../expression_types/specs'; +import { ExpressionValueError, typeSpecs } from '../expression_types/specs'; import { functionSpecs } from '../expression_functions/specs'; import { getByAlias } from '../util'; import { SavedObjectReference } from '../../../../core/types'; @@ -156,14 +157,12 @@ export class Executor = Record( + public run( ast: string | ExpressionAstExpression, input: Input, params: ExpressionExecutionParams = {} - ) { - const execution = this.createExecution(ast, params); - execution.start(input); - return (await execution.result) as Output; + ): Observable { + return this.createExecution(ast, params).start(input); } public createExecution( diff --git a/src/plugins/expressions/common/expression_functions/expression_function.ts b/src/plugins/expressions/common/expression_functions/expression_function.ts index 6eb1762a5d3c39..a4cb1141104984 100644 --- a/src/plugins/expressions/common/expression_functions/expression_function.ts +++ b/src/plugins/expressions/common/expression_functions/expression_function.ts @@ -10,7 +10,6 @@ import { identity } from 'lodash'; import { AnyExpressionFunctionDefinition } from './types'; import { ExpressionFunctionParameter } from './expression_function_parameter'; import { ExpressionValue } from '../expression_types/types'; -import { ExecutionContext } from '../execution'; import { ExpressionAstFunction } from '../ast'; import { SavedObjectReference } from '../../../../core/types'; import { PersistableState, SerializableState } from '../../../kibana_utils/common'; @@ -89,8 +88,7 @@ export class ExpressionFunction implements PersistableState - Promise.resolve(fn(input, params, handlers as ExecutionContext)); + this.fn = fn as ExpressionFunction['fn']; this.help = help || ''; this.inputTypes = inputTypes || context?.types; this.disabled = disabled || false; diff --git a/src/plugins/expressions/common/expression_functions/specs/map_column.ts b/src/plugins/expressions/common/expression_functions/specs/map_column.ts index e2605e5ddf38d2..c570206670dde5 100644 --- a/src/plugins/expressions/common/expression_functions/specs/map_column.ts +++ b/src/plugins/expressions/common/expression_functions/specs/map_column.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../types'; import { Datatable, getType } from '../../expression_types'; @@ -13,7 +15,7 @@ import { Datatable, getType } from '../../expression_types'; export interface MapColumnArguments { id?: string | null; name: string; - expression?: (datatable: Datatable) => Promise; + expression?(datatable: Datatable): Observable; copyMetaFrom?: string | null; } @@ -79,7 +81,11 @@ export const mapColumn: ExpressionFunctionDefinition< }, }, fn: (input, args) => { - const expression = args.expression || (() => Promise.resolve(null)); + const expression = (...params: Parameters['expression']>) => + args + .expression?.(...params) + .pipe(take(1)) + .toPromise() ?? Promise.resolve(null); const columnId = args.id != null ? args.id : args.name; const columns = [...input.columns]; diff --git a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts index 6b0dce4ff9a2a1..b2966b010b4790 100644 --- a/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts +++ b/src/plugins/expressions/common/expression_functions/specs/tests/map_column.test.ts @@ -6,85 +6,80 @@ * Side Public License, v 1. */ +import { of } from 'rxjs'; import { Datatable } from '../../../expression_types'; import { mapColumn, MapColumnArguments } from '../map_column'; import { emptyTable, functionWrapper, testTable } from './utils'; -const pricePlusTwo = (datatable: Datatable) => Promise.resolve(datatable.rows[0].price + 2); +const pricePlusTwo = (datatable: Datatable) => of(datatable.rows[0].price + 2); describe('mapColumn', () => { const fn = functionWrapper(mapColumn); const runFn = (input: Datatable, args: MapColumnArguments) => fn(input, args) as Promise; - it('returns a datatable with a new column with the values from mapping a function over each row in a datatable', () => { - return runFn(testTable, { + it('returns a datatable with a new column with the values from mapping a function over each row in a datatable', async () => { + const arbitraryRowIndex = 2; + const result = await runFn(testTable, { id: 'pricePlusTwo', name: 'pricePlusTwo', expression: pricePlusTwo, - }).then((result) => { - const arbitraryRowIndex = 2; - - expect(result.type).toBe('datatable'); - expect(result.columns).toEqual([ - ...testTable.columns, - { id: 'pricePlusTwo', name: 'pricePlusTwo', meta: { type: 'number' } }, - ]); - expect(result.columns[result.columns.length - 1]).toHaveProperty('name', 'pricePlusTwo'); - expect(result.rows[arbitraryRowIndex]).toHaveProperty('pricePlusTwo'); }); + + expect(result.type).toBe('datatable'); + expect(result.columns).toEqual([ + ...testTable.columns, + { id: 'pricePlusTwo', name: 'pricePlusTwo', meta: { type: 'number' } }, + ]); + expect(result.columns[result.columns.length - 1]).toHaveProperty('name', 'pricePlusTwo'); + expect(result.rows[arbitraryRowIndex]).toHaveProperty('pricePlusTwo'); }); - it('overwrites existing column with the new column if an existing column name is provided', () => { - return runFn(testTable, { name: 'name', expression: pricePlusTwo }).then((result) => { - const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); - const arbitraryRowIndex = 4; + it('overwrites existing column with the new column if an existing column name is provided', async () => { + const result = await runFn(testTable, { name: 'name', expression: pricePlusTwo }); + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + const arbitraryRowIndex = 4; - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(testTable.columns.length); - expect(result.columns[nameColumnIndex]).toHaveProperty('name', 'name'); - expect(result.columns[nameColumnIndex].meta).toHaveProperty('type', 'number'); - expect(result.rows[arbitraryRowIndex]).toHaveProperty('name', 202); - }); + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(testTable.columns.length); + expect(result.columns[nameColumnIndex]).toHaveProperty('name', 'name'); + expect(result.columns[nameColumnIndex].meta).toHaveProperty('type', 'number'); + expect(result.rows[arbitraryRowIndex]).toHaveProperty('name', 202); }); - it('adds a column to empty tables', () => { - return runFn(emptyTable, { name: 'name', expression: pricePlusTwo }).then((result) => { - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(1); - expect(result.columns[0]).toHaveProperty('name', 'name'); - expect(result.columns[0].meta).toHaveProperty('type', 'null'); - }); + it('adds a column to empty tables', async () => { + const result = await runFn(emptyTable, { name: 'name', expression: pricePlusTwo }); + + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'name'); + expect(result.columns[0].meta).toHaveProperty('type', 'null'); }); - it('should assign specific id, different from name, when id arg is passed for new columns', () => { - return runFn(emptyTable, { name: 'name', id: 'myid', expression: pricePlusTwo }).then( - (result) => { - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(1); - expect(result.columns[0]).toHaveProperty('name', 'name'); - expect(result.columns[0]).toHaveProperty('id', 'myid'); - expect(result.columns[0].meta).toHaveProperty('type', 'null'); - } - ); + it('should assign specific id, different from name, when id arg is passed for new columns', async () => { + const result = await runFn(emptyTable, { name: 'name', id: 'myid', expression: pricePlusTwo }); + + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'name'); + expect(result.columns[0]).toHaveProperty('id', 'myid'); + expect(result.columns[0].meta).toHaveProperty('type', 'null'); }); - it('should assign specific id, different from name, when id arg is passed for copied column', () => { - return runFn(testTable, { name: 'name', id: 'myid', expression: pricePlusTwo }).then( - (result) => { - const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); - expect(result.type).toBe('datatable'); - expect(result.columns[nameColumnIndex]).toEqual({ - id: 'myid', - name: 'name', - meta: { type: 'number' }, - }); - } - ); + it('should assign specific id, different from name, when id arg is passed for copied column', async () => { + const result = await runFn(testTable, { name: 'name', id: 'myid', expression: pricePlusTwo }); + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + + expect(result.type).toBe('datatable'); + expect(result.columns[nameColumnIndex]).toEqual({ + id: 'myid', + name: 'name', + meta: { type: 'number' }, + }); }); - it('should copy over the meta information from the specified column', () => { - return runFn( + it('should copy over the meta information from the specified column', async () => { + const result = await runFn( { ...testTable, columns: [ @@ -99,52 +94,53 @@ describe('mapColumn', () => { rows: testTable.rows.map((row) => ({ ...row, myId: Date.now() })), }, { name: 'name', copyMetaFrom: 'myId', expression: pricePlusTwo } - ).then((result) => { - const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); - expect(result.type).toBe('datatable'); - expect(result.columns[nameColumnIndex]).toEqual({ - id: 'name', - name: 'name', - meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, - }); + ); + const nameColumnIndex = result.columns.findIndex(({ name }) => name === 'name'); + + expect(result.type).toBe('datatable'); + expect(result.columns[nameColumnIndex]).toEqual({ + id: 'name', + name: 'name', + meta: { type: 'date', params: { id: 'number', params: { digits: 2 } } }, }); }); - it('should be resilient if the references column for meta information does not exists', () => { - return runFn(emptyTable, { name: 'name', copyMetaFrom: 'time', expression: pricePlusTwo }).then( - (result) => { - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(1); - expect(result.columns[0]).toHaveProperty('name', 'name'); - expect(result.columns[0]).toHaveProperty('id', 'name'); - expect(result.columns[0].meta).toHaveProperty('type', 'null'); - } - ); + it('should be resilient if the references column for meta information does not exists', async () => { + const result = await runFn(emptyTable, { + name: 'name', + copyMetaFrom: 'time', + expression: pricePlusTwo, + }); + + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'name'); + expect(result.columns[0]).toHaveProperty('id', 'name'); + expect(result.columns[0].meta).toHaveProperty('type', 'null'); }); - it('should correctly infer the type fromt he first row if the references column for meta information does not exists', () => { - return runFn( + it('should correctly infer the type fromt he first row if the references column for meta information does not exists', async () => { + const result = await runFn( { ...emptyTable, rows: [...emptyTable.rows, { value: 5 }] }, { name: 'value', copyMetaFrom: 'time', expression: pricePlusTwo } - ).then((result) => { - expect(result.type).toBe('datatable'); - expect(result.columns).toHaveLength(1); - expect(result.columns[0]).toHaveProperty('name', 'value'); - expect(result.columns[0]).toHaveProperty('id', 'value'); - expect(result.columns[0].meta).toHaveProperty('type', 'number'); - }); + ); + + expect(result.type).toBe('datatable'); + expect(result.columns).toHaveLength(1); + expect(result.columns[0]).toHaveProperty('name', 'value'); + expect(result.columns[0]).toHaveProperty('id', 'value'); + expect(result.columns[0].meta).toHaveProperty('type', 'number'); }); describe('expression', () => { - it('maps null values to the new column', () => { - return runFn(testTable, { name: 'empty' }).then((result) => { - const emptyColumnIndex = result.columns.findIndex(({ name }) => name === 'empty'); - const arbitraryRowIndex = 8; - - expect(result.columns[emptyColumnIndex]).toHaveProperty('name', 'empty'); - expect(result.columns[emptyColumnIndex].meta).toHaveProperty('type', 'null'); - expect(result.rows[arbitraryRowIndex]).toHaveProperty('empty', null); - }); + it('maps null values to the new column', async () => { + const result = await runFn(testTable, { name: 'empty' }); + const emptyColumnIndex = result.columns.findIndex(({ name }) => name === 'empty'); + const arbitraryRowIndex = 8; + + expect(result.columns[emptyColumnIndex]).toHaveProperty('name', 'empty'); + expect(result.columns[emptyColumnIndex].meta).toHaveProperty('type', 'null'); + expect(result.rows[arbitraryRowIndex]).toHaveProperty('empty', null); }); }); }); diff --git a/src/plugins/expressions/common/service/expressions_services.ts b/src/plugins/expressions/common/service/expressions_services.ts index 28df6d69818299..d57c1748954abf 100644 --- a/src/plugins/expressions/common/service/expressions_services.ts +++ b/src/plugins/expressions/common/service/expressions_services.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { take } from 'rxjs/operators'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import type { KibanaRequest } from 'src/core/server'; @@ -228,7 +229,7 @@ export class ExpressionsService implements PersistableStateService this.renderers.register(definition); public readonly run: ExpressionsServiceStart['run'] = (ast, input, params) => - this.executor.run(ast, input, params); + this.executor.run(ast, input, params).pipe(take(1)).toPromise(); public readonly getFunction: ExpressionsServiceStart['getFunction'] = (name) => this.executor.getFunction(name); diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/index.ts b/src/plugins/expressions/common/test_helpers/expression_functions/index.ts index 3e5f93e5830817..b73d1e0208f892 100644 --- a/src/plugins/expressions/common/test_helpers/expression_functions/index.ts +++ b/src/plugins/expressions/common/test_helpers/expression_functions/index.ts @@ -12,6 +12,7 @@ import { error } from './error'; import { introspectContext } from './introspect_context'; import { mult } from './mult'; import { sleep } from './sleep'; +import { sum } from './sum'; import { AnyExpressionFunctionDefinition } from '../../expression_functions'; export const functionTestSpecs: AnyExpressionFunctionDefinition[] = [ @@ -21,4 +22,5 @@ export const functionTestSpecs: AnyExpressionFunctionDefinition[] = [ introspectContext, mult, sleep, + sum, ]; diff --git a/src/plugins/expressions/common/test_helpers/expression_functions/sum.ts b/src/plugins/expressions/common/test_helpers/expression_functions/sum.ts new file mode 100644 index 00000000000000..54b0207364ffd9 --- /dev/null +++ b/src/plugins/expressions/common/test_helpers/expression_functions/sum.ts @@ -0,0 +1,23 @@ +/* + * 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 { ExpressionFunctionDefinition } from '../../expression_functions'; +import { ExpressionValueNum } from '../../expression_types'; + +export const sum: ExpressionFunctionDefinition<'sum', unknown[], {}, ExpressionValueNum> = { + name: 'sum', + help: 'This function summarizes the input', + inputTypes: [], + args: {}, + fn: (values) => { + return { + type: 'num', + value: Array.isArray(values) ? values.reduce((a, b) => a + b) : values, + }; + }, +}; diff --git a/src/plugins/expressions/common/types/common.ts b/src/plugins/expressions/common/types/common.ts index a2f1ed9a0d569f..d8d1a9a4b256a1 100644 --- a/src/plugins/expressions/common/types/common.ts +++ b/src/plugins/expressions/common/types/common.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; +import { ObservableLike, UnwrapObservable, UnwrapPromiseOrReturn } from '@kbn/utility-types'; /** * This can convert a type into a known Expression string representation of @@ -23,9 +23,9 @@ export type TypeToString = KnownTypeToString | UnmappedTypeStrings; * the `type` key as a string literal type for it. */ // prettier-ignore -export type KnownTypeToString = - T extends string ? 'string' : - T extends boolean ? 'boolean' : +export type KnownTypeToString = + T extends string ? 'string' : + T extends boolean ? 'boolean' : T extends number ? 'number' : T extends null ? 'null' : T extends { type: string } ? T['type'] : @@ -36,7 +36,9 @@ export type KnownTypeToString = * * `someArgument: Promise` results in `types: ['boolean', 'string']` */ -export type TypeString = KnownTypeToString>; +export type TypeString = KnownTypeToString< + T extends ObservableLike ? UnwrapObservable : UnwrapPromiseOrReturn +>; /** * Types used in Expressions that don't map to a primitive cleanly: diff --git a/src/plugins/expressions/public/loader.test.ts b/src/plugins/expressions/public/loader.test.ts index 2177966fb4d7a3..98adec285afd5e 100644 --- a/src/plugins/expressions/public/loader.test.ts +++ b/src/plugins/expressions/public/loader.test.ts @@ -43,6 +43,11 @@ jest.mock('./services', () => { }; service.registerFunction(testFn); + // eslint-disable-next-line @typescript-eslint/no-var-requires + for (const func of require('../common/test_helpers/expression_functions').functionTestSpecs) { + service.registerFunction(func); + } + const moduleMock = { __execution: undefined, __getLastExecution: () => moduleMock.__execution, @@ -144,7 +149,7 @@ describe('ExpressionLoader', () => { }); it('cancels the previous request when the expression is updated', () => { - const expressionLoader = new ExpressionLoader(element, 'var foo', {}); + const expressionLoader = new ExpressionLoader(element, 'sleep 10', {}); const execution = __getLastExecution(); jest.spyOn(execution, 'cancel'); diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index b3e7803f97c38c..9c17a2753cfc93 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -11,10 +11,12 @@ import { EnvironmentMode } from '@kbn/config'; import { EventEmitter } from 'events'; import { KibanaRequest } from 'src/core/server'; import { Observable } from 'rxjs'; +import { ObservableLike } from '@kbn/utility-types'; import { PackageInfo } from '@kbn/config'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import React from 'react'; +import { UnwrapObservable } from '@kbn/utility-types'; import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; // Warning: (ae-missing-release-tag) "AnyExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -110,16 +112,15 @@ export class Execution(ast: ExpressionAstNode, input: T): Promise; + interpret(ast: ExpressionAstNode, input: T): Observable; // (undocumented) - invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise; + invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; // (undocumented) - invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Promise; + invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; // (undocumented) - resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Promise; - // (undocumented) - get result(): Promise; - start(input?: Input): void; + resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; + readonly result: Observable; + start(input?: Input): Observable; readonly state: ExecutionContainer; } @@ -229,7 +230,7 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) registerType(typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)): void; - run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Promise; + run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; // (undocumented) readonly state: ExecutorContainer; // (undocumented) @@ -1160,7 +1161,7 @@ export class TypesRegistry implements IRegistry { // Warning: (ae-missing-release-tag) "TypeString" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export type TypeString = KnownTypeToString>; +export type TypeString = KnownTypeToString ? UnwrapObservable : UnwrapPromiseOrReturn>; // Warning: (ae-missing-release-tag) "TypeToString" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 2d873fa5183069..12af0480fac93f 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -10,8 +10,10 @@ import { Ensure } from '@kbn/utility-types'; import { EventEmitter } from 'events'; import { KibanaRequest } from 'src/core/server'; import { Observable } from 'rxjs'; +import { ObservableLike } from '@kbn/utility-types'; import { Plugin as Plugin_2 } from 'src/core/server'; import { PluginInitializerContext } from 'src/core/server'; +import { UnwrapObservable } from '@kbn/utility-types'; import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; // Warning: (ae-missing-release-tag) "AnyExpressionFunctionDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -108,16 +110,15 @@ export class Execution(ast: ExpressionAstNode, input: T): Promise; + interpret(ast: ExpressionAstNode, input: T): Observable; // (undocumented) - invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Promise; + invokeChain(chainArr: ExpressionAstFunction[], input: unknown): Observable; // (undocumented) - invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Promise; + invokeFunction(fn: ExpressionFunction, input: unknown, args: Record): Observable; // (undocumented) - resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Promise; - // (undocumented) - get result(): Promise; - start(input?: Input): void; + resolveArgs(fnDef: ExpressionFunction, input: unknown, argAsts: any): Observable; + readonly result: Observable; + start(input?: Input): Observable; readonly state: ExecutionContainer; } @@ -211,7 +212,7 @@ export class Executor = Record AnyExpressionFunctionDefinition)): void; // (undocumented) registerType(typeDefinition: AnyExpressionTypeDefinition | (() => AnyExpressionTypeDefinition)): void; - run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Promise; + run(ast: string | ExpressionAstExpression, input: Input, params?: ExpressionExecutionParams): Observable; // (undocumented) readonly state: ExecutorContainer; // (undocumented) @@ -919,7 +920,7 @@ export class TypesRegistry implements IRegistry { // Warning: (ae-missing-release-tag) "TypeString" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public -export type TypeString = KnownTypeToString>; +export type TypeString = KnownTypeToString ? UnwrapObservable : UnwrapPromiseOrReturn>; // Warning: (ae-missing-release-tag) "TypeToString" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js index 997afdbddeafab..fc5d03190d4f8d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.test.js @@ -5,6 +5,7 @@ * 2.0. */ +import { of } from 'rxjs'; import { functionWrapper } from '../../../test_helpers/function_wrapper'; import { caseFn } from './case'; @@ -19,10 +20,10 @@ describe('case', () => { describe('function', () => { describe('no args', () => { - it('should return a case object that matches with the result as the context', async () => { + it('should return a case object that matches with the result as the context', () => { const context = null; const args = {}; - expect(await fn(context, args)).toEqual({ + expect(fn(context, args)).resolves.toEqual({ type: 'case', matches: true, result: context, @@ -31,24 +32,24 @@ describe('case', () => { }); describe('no if or value', () => { - it('should return the result if provided', async () => { + it('should return the result if provided', () => { const context = null; const args = { - then: () => 'foo', + then: () => of('foo'), }; - expect(await fn(context, args)).toEqual({ + expect(fn(context, args)).resolves.toEqual({ type: 'case', matches: true, - result: args.then(), + result: 'foo', }); }); }); describe('with if', () => { - it('should return as the matches prop', async () => { + it('should return as the matches prop', () => { const context = null; const args = { if: false }; - expect(await fn(context, args)).toEqual({ + expect(fn(context, args)).resolves.toEqual({ type: 'case', matches: args.if, result: context, @@ -57,17 +58,17 @@ describe('case', () => { }); describe('with value', () => { - it('should return whether it matches the context as the matches prop', async () => { + it('should return whether it matches the context as the matches prop', () => { const args = { - when: () => 'foo', - then: () => 'bar', + when: () => of('foo'), + then: () => of('bar'), }; - expect(await fn('foo', args)).toEqual({ + expect(fn('foo', args)).resolves.toEqual({ type: 'case', matches: true, - result: args.then(), + result: 'bar', }); - expect(await fn('bar', args)).toEqual({ + expect(fn('bar', args)).resolves.toEqual({ type: 'case', matches: false, result: null, @@ -76,13 +77,13 @@ describe('case', () => { }); describe('with if and value', () => { - it('should return the if as the matches prop', async () => { + it('should return the if as the matches prop', () => { const context = null; const args = { when: () => 'foo', if: true, }; - expect(await fn(context, args)).toEqual({ + expect(fn(context, args)).resolves.toEqual({ type: 'case', matches: args.if, result: context, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.ts index ce2d6586acc3ff..7fba5b74e9b207 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/case.ts @@ -5,13 +5,15 @@ * 2.0. */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { - when: () => any; - if: boolean; - then: () => any; + when?(): Observable; + if?: boolean; + then?(): Observable; } interface Case { @@ -31,16 +33,16 @@ export function caseFn(): ExpressionFunctionDefinition<'case', any, Arguments, P when: { aliases: ['_'], resolve: false, - help: argHelp.when, + help: argHelp.when!, }, if: { types: ['boolean'], - help: argHelp.if, + help: argHelp.if!, }, then: { resolve: false, required: true, - help: argHelp.then, + help: argHelp.then!, }, }, fn: async (input, args) => { @@ -56,14 +58,11 @@ async function doesMatch(context: any, args: Arguments) { return args.if; } if (typeof args.when !== 'undefined') { - return (await args.when()) === context; + return (await args.when().pipe(take(1)).toPromise()) === context; } return true; } async function getResult(context: any, args: Arguments) { - if (typeof args.then !== 'undefined') { - return await args.then(); - } - return context; + return args.then?.().pipe(take(1)).toPromise() ?? context; } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js index 9be713e0154476..fdea4faa4ece28 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.test.js @@ -5,35 +5,38 @@ * 2.0. */ +import { of } from 'rxjs'; import { functionWrapper } from '../../../test_helpers/function_wrapper'; import { testTable } from './__fixtures__/test_tables'; import { filterrows } from './filterrows'; -const inStock = (datatable) => datatable.rows[0].in_stock; -const returnFalse = () => false; +const inStock = (datatable) => of(datatable.rows[0].in_stock); +const returnFalse = () => of(false); describe('filterrows', () => { const fn = functionWrapper(filterrows); it('returns a datable', () => { - return fn(testTable, { fn: inStock }).then((result) => { - expect(result).toHaveProperty('type', 'datatable'); - }); + expect(fn(testTable, { fn: inStock })).resolves.toHaveProperty('type', 'datatable'); }); it('keeps rows that evaluate to true and removes rows that evaluate to false', () => { const inStockRows = testTable.rows.filter((row) => row.in_stock); - return fn(testTable, { fn: inStock }).then((result) => { - expect(result.columns).toEqual(testTable.columns); - expect(result.rows).toEqual(inStockRows); - }); + expect(fn(testTable, { fn: inStock })).resolves.toEqual( + expect.objectContaining({ + columns: testTable.columns, + rows: inStockRows, + }) + ); }); it('returns datatable with no rows when no rows meet function condition', () => { - return fn(testTable, { fn: returnFalse }).then((result) => { - expect(result.rows).toEqual([]); - }); + expect(fn(testTable, { fn: returnFalse })).resolves.toEqual( + expect.objectContaining({ + rows: [], + }) + ); }); it('throws when no function is provided', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts index 90bf668f4503c3..082506f58e86fa 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/filterrows.ts @@ -5,11 +5,13 @@ * 2.0. */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { Datatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { - fn: (datatable: Datatable) => Promise; + fn: (datatable: Datatable) => Observable; } export function filterrows(): ExpressionFunctionDefinition< @@ -41,6 +43,8 @@ export function filterrows(): ExpressionFunctionDefinition< ...input, rows: [row], }) + .pipe(take(1)) + .toPromise() ); return Promise.all(checks) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js index 458447ca07c4b7..8e1106644105e4 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.test.js @@ -5,6 +5,7 @@ * 2.0. */ +import { of } from 'rxjs'; import { functionWrapper } from '../../../test_helpers/function_wrapper'; import { ifFn } from './if'; @@ -19,52 +20,68 @@ describe('if', () => { describe('function', () => { describe('condition passed', () => { - it('with then', async () => { - expect(await fn(null, { condition: true, then: () => 'foo' })).toBe('foo'); - expect(await fn(null, { condition: true, then: () => 'foo', else: () => 'bar' })).toBe( - 'foo' - ); + it('with then', () => { + expect(fn(null, { condition: true, then: () => of('foo') })).resolves.toBe('foo'); + expect( + fn(null, { condition: true, then: () => of('foo'), else: () => of('bar') }) + ).resolves.toBe('foo'); }); - it('without then', async () => { - expect(await fn(null, { condition: true })).toBe(null); - expect(await fn('some context', { condition: true })).toBe('some context'); + it('without then', () => { + expect(fn(null, { condition: true })).resolves.toBe(null); + expect(fn('some context', { condition: true })).resolves.toBe('some context'); }); }); describe('condition failed', () => { - it('with else', async () => + it('with else', () => expect( - await fn('some context', { condition: false, then: () => 'foo', else: () => 'bar' }) - ).toBe('bar')); + fn('some context', { + condition: false, + then: () => of('foo'), + else: () => of('bar'), + }) + ).resolves.toBe('bar')); - it('without else', async () => - expect(await fn('some context', { condition: false, then: () => 'foo' })).toBe( + it('without else', () => + expect(fn('some context', { condition: false, then: () => of('foo') })).resolves.toBe( 'some context' )); }); describe('falsy values', () => { describe('for then', () => { - it('with null', async () => - expect(await fn('some context', { condition: true, then: () => null })).toBe(null)); + it('with null', () => { + expect(fn('some context', { condition: true, then: () => of(null) })).resolves.toBe(null); + }); - it('with false', async () => - expect(await fn('some context', { condition: true, then: () => false })).toBe(false)); + it('with false', () => { + expect(fn('some context', { condition: true, then: () => of(false) })).resolves.toBe( + false + ); + }); - it('with 0', async () => - expect(await fn('some context', { condition: true, then: () => 0 })).toBe(0)); + it('with 0', () => { + expect(fn('some context', { condition: true, then: () => of(0) })).resolves.toBe(0); + }); }); describe('for else', () => { - it('with null', async () => - expect(await fn('some context', { condition: false, else: () => null })).toBe(null)); + it('with null', () => { + expect(fn('some context', { condition: false, else: () => of(null) })).resolves.toBe( + null + ); + }); - it('with false', async () => - expect(await fn('some context', { condition: false, else: () => false })).toBe(false)); + it('with false', () => { + expect(fn('some context', { condition: false, else: () => of(false) })).resolves.toBe( + false + ); + }); - it('with 0', async () => - expect(await fn('some context', { condition: false, else: () => 0 })).toBe(0)); + it('with 0', () => { + expect(fn('some context', { condition: false, else: () => of(0) })).resolves.toBe(0); + }); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.ts index fca98d3b9a725b..6d7665db551e4b 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/if.ts @@ -5,13 +5,15 @@ * 2.0. */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { condition: boolean | null; - then: () => Promise; - else: () => Promise; + then?(): Observable; + else?(): Observable; } export function ifFn(): ExpressionFunctionDefinition<'if', unknown, Arguments, unknown> { @@ -29,24 +31,18 @@ export function ifFn(): ExpressionFunctionDefinition<'if', unknown, Arguments, u }, then: { resolve: false, - help: argHelp.then, + help: argHelp.then!, }, else: { resolve: false, - help: argHelp.else, + help: argHelp.else!, }, }, fn: async (input, args) => { if (args.condition) { - if (typeof args.then === 'undefined') { - return input; - } - return await args.then(); + return args.then?.().pipe(take(1)).toPromise() ?? input; } else { - if (typeof args.else === 'undefined') { - return input; - } - return await args.else(); + return args.else?.().pipe(take(1)).toPromise() ?? input; } }, }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js index 588a897453be4f..74eca79395a103 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.test.js @@ -5,6 +5,7 @@ * 2.0. */ +import { of } from 'rxjs'; import { functionWrapper } from '../../../test_helpers/function_wrapper'; import { getFunctionErrors } from '../../../i18n'; import { testTable } from './__fixtures__/test_tables'; @@ -15,7 +16,7 @@ const errors = getFunctionErrors().ply; const averagePrice = (datatable) => { const average = datatable.rows.reduce((sum, row) => sum + row.price, 0) / datatable.rows.length; - return Promise.resolve({ + return of({ type: 'datatable', columns: [{ id: 'average_price', name: 'average_price', meta: { type: 'number' } }], rows: [{ average_price: average }], @@ -23,17 +24,17 @@ const averagePrice = (datatable) => { }; const doublePrice = (datatable) => { - const newRows = datatable.rows.map((row) => ({ double_price: row.price * 2 })); + const newRows = datatable.rows.map((row) => of({ double_price: row.price * 2 })); - return Promise.resolve({ + return of({ type: 'datatable', columns: [{ id: 'double_price', name: 'double_price', meta: { type: 'number' } }], rows: newRows, }); }; -const rowCount = (datatable) => { - return Promise.resolve({ +const rowCount = (datatable) => + of({ type: 'datatable', columns: [{ id: 'row_count', name: 'row_count', meta: { type: 'number' } }], rows: [ @@ -42,43 +43,40 @@ const rowCount = (datatable) => { }, ], }); -}; describe('ply', () => { const fn = functionWrapper(ply); - it('maps a function over sub datatables grouped by specified columns and merges results into one datatable', () => { + it('maps a function over sub datatables grouped by specified columns and merges results into one datatable', async () => { const arbitaryRowIndex = 0; + const result = await fn(testTable, { + by: ['name', 'in_stock'], + expression: [averagePrice, rowCount], + }); - return fn(testTable, { by: ['name', 'in_stock'], expression: [averagePrice, rowCount] }).then( - (result) => { - expect(result.type).toBe('datatable'); - expect(result.columns).toEqual([ - { id: 'name', name: 'name', meta: { type: 'string' } }, - { id: 'in_stock', name: 'in_stock', meta: { type: 'boolean' } }, - { id: 'average_price', name: 'average_price', meta: { type: 'number' } }, - { id: 'row_count', name: 'row_count', meta: { type: 'number' } }, - ]); - expect(result.rows[arbitaryRowIndex]).toHaveProperty('average_price'); - expect(result.rows[arbitaryRowIndex]).toHaveProperty('row_count'); - } - ); + expect(result.type).toBe('datatable'); + expect(result.columns).toEqual([ + { id: 'name', name: 'name', meta: { type: 'string' } }, + { id: 'in_stock', name: 'in_stock', meta: { type: 'boolean' } }, + { id: 'average_price', name: 'average_price', meta: { type: 'number' } }, + { id: 'row_count', name: 'row_count', meta: { type: 'number' } }, + ]); + expect(result.rows[arbitaryRowIndex]).toHaveProperty('average_price'); + expect(result.rows[arbitaryRowIndex]).toHaveProperty('row_count'); }); describe('missing args', () => { it('returns the original datatable if both args are missing', () => { - return fn(testTable).then((result) => expect(result).toEqual(testTable)); + expect(fn(testTable)).resolves.toEqual(testTable); }); describe('by', () => { it('passes the entire context into the expression when no columns are provided', () => { - return fn(testTable, { expression: [rowCount] }).then((result) => - expect(result).toEqual({ - type: 'datatable', - rows: [{ row_count: testTable.rows.length }], - columns: [{ id: 'row_count', name: 'row_count', meta: { type: 'number' } }], - }) - ); + expect(fn(testTable, { expression: [rowCount] })).resolves.toEqual({ + type: 'datatable', + rows: [{ row_count: testTable.rows.length }], + columns: [{ id: 'row_count', name: 'row_count', meta: { type: 'number' } }], + }); }); it('throws when by is an invalid column', () => { @@ -93,20 +91,23 @@ describe('ply', () => { }); describe('expression', () => { - it('returns the original datatable grouped by the specified columns', () => { + it('returns the original datatable grouped by the specified columns', async () => { const arbitaryRowIndex = 6; + const result = await fn(testTable, { by: ['price', 'quantity'] }); - return fn(testTable, { by: ['price', 'quantity'] }).then((result) => { - expect(result.columns[0]).toHaveProperty('name', 'price'); - expect(result.columns[1]).toHaveProperty('name', 'quantity'); - expect(result.rows[arbitaryRowIndex]).toHaveProperty('price'); - expect(result.rows[arbitaryRowIndex]).toHaveProperty('quantity'); - }); + expect(result.columns[0]).toHaveProperty('name', 'price'); + expect(result.columns[1]).toHaveProperty('name', 'quantity'); + expect(result.rows[arbitaryRowIndex]).toHaveProperty('price'); + expect(result.rows[arbitaryRowIndex]).toHaveProperty('quantity'); }); it('throws when row counts do not match across resulting datatables', () => { - return fn(testTable, { by: ['name'], expression: [doublePrice, rowCount] }).catch((e) => - expect(e.message).toBe(errors.rowCountMismatch().message) + expect( + fn(testTable, { by: ['name'], expression: [doublePrice, rowCount] }) + ).rejects.toEqual( + expect.objectContaining({ + message: errors.rowCountMismatch().message, + }) ); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts index 0dd6590ce1b3f3..514d7f73d48e47 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/ply.ts @@ -5,13 +5,15 @@ * 2.0. */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { groupBy, flatten, pick, map } from 'lodash'; import { Datatable, DatatableColumn, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; interface Arguments { by: string[]; - expression: Array<(datatable: Datatable) => Promise>; + expression: Array<(datatable: Datatable) => Observable>; } type Output = Datatable | Promise; @@ -73,7 +75,7 @@ export function ply(): ExpressionFunctionDefinition<'ply', Datatable, Arguments, if (args.expression) { expressionResultPromises = args.expression.map((expression) => - expression(originalDatatable) + expression(originalDatatable).pipe(take(1)).toPromise() ); } else { expressionResultPromises.push(Promise.resolve(originalDatatable)); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js index 6371198b2db92d..6d9a20dfeb4873 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.test.js @@ -5,12 +5,13 @@ * 2.0. */ +import { of } from 'rxjs'; import { functionWrapper } from '../../../test_helpers/function_wrapper'; import { switchFn } from './switch'; describe('switch', () => { const fn = functionWrapper(switchFn); - const getter = (value) => () => value; + const getter = (value) => () => of(value); const mockCases = [ { type: 'case', @@ -48,32 +49,32 @@ describe('switch', () => { describe('function', () => { describe('with no cases', () => { - it('should return the context if no default is provided', async () => { + it('should return the context if no default is provided', () => { const context = 'foo'; - expect(await fn(context, {})).toBe(context); + expect(fn(context, {})).resolves.toBe(context); }); - it('should return the default if provided', async () => { + it('should return the default if provided', () => { const context = 'foo'; - const args = { default: () => 'bar' }; - expect(await fn(context, args)).toBe(args.default()); + const args = { default: () => of('bar') }; + expect(fn(context, args)).resolves.toBe('bar'); }); }); describe('with no matching cases', () => { - it('should return the context if no default is provided', async () => { + it('should return the context if no default is provided', () => { const context = 'foo'; const args = { case: nonMatchingCases.map(getter) }; - expect(await fn(context, args)).toBe(context); + expect(fn(context, args)).resolves.toBe(context); }); - it('should return the default if provided', async () => { + it('should return the default if provided', () => { const context = 'foo'; const args = { case: nonMatchingCases.map(getter), - default: () => 'bar', + default: () => of('bar'), }; - expect(await fn(context, args)).toBe(args.default()); + expect(fn(context, args)).resolves.toBe('bar'); }); }); @@ -82,7 +83,7 @@ describe('switch', () => { const context = 'foo'; const args = { case: mockCases.map(getter) }; const firstMatch = mockCases.find((c) => c.matches); - expect(await fn(context, args)).toBe(firstMatch.result); + expect(fn(context, args)).resolves.toBe(firstMatch.result); }); }); }); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.ts index 57cfca4ab2787a..4258f56ec4cf5f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/switch.ts @@ -5,13 +5,15 @@ * 2.0. */ +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { Case } from '../../../types'; import { getFunctionHelp } from '../../../i18n'; interface Arguments { - case: Array<() => Promise>; - default: () => any; + case: Array<() => Observable>; + default?(): Observable; } export function switchFn(): ExpressionFunctionDefinition<'switch', unknown, Arguments, unknown> { @@ -32,25 +34,21 @@ export function switchFn(): ExpressionFunctionDefinition<'switch', unknown, Argu default: { aliases: ['finally'], resolve: false, - help: argHelp.default, + help: argHelp.default!, }, }, fn: async (input, args) => { const cases = args.case || []; for (let i = 0; i < cases.length; i++) { - const { matches, result } = await cases[i](); + const { matches, result } = await cases[i]().pipe(take(1)).toPromise(); if (matches) { return result; } } - if (typeof args.default !== 'undefined') { - return await args.default(); - } - - return input; + return args.default?.().pipe(take(1)).toPromise() ?? input; }, }; } diff --git a/x-pack/plugins/canvas/public/apps/workpad/routes.ts b/x-pack/plugins/canvas/public/apps/workpad/routes.ts index d2586b1d89ec9d..8ecba5e1833434 100644 --- a/x-pack/plugins/canvas/public/apps/workpad/routes.ts +++ b/x-pack/plugins/canvas/public/apps/workpad/routes.ts @@ -70,8 +70,8 @@ export const routes = [ const fetchedWorkpad = await workpadService.get(params.id); const { assets, ...workpad } = fetchedWorkpad; - dispatch(setWorkpad(workpad)); dispatch(setAssets(assets)); + dispatch(setWorkpad(workpad)); // reset transient properties when changing workpads dispatch(setZoomScale(1)); From aee7787db9d25690dba861049045a906b0415090 Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Thu, 22 Apr 2021 15:31:04 -0400 Subject: [PATCH 62/65] [Uptime] open synthetics beta disclaimer in a new tab (#97822) --- .../components/monitor/monitor_title.test.tsx | 14 ++++++++++---- .../public/components/monitor/monitor_title.tsx | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx index 4bf4e9193de7e6..5e77e68720c528 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.test.tsx @@ -139,7 +139,7 @@ describe('MonitorTitle component', () => { state: { monitorStatus: { status: defaultBrowserMonitorStatus, loading: false } }, }); const betaLink = screen.getByRole('link', { - name: 'See more External link', + name: 'See more External link (opens in a new tab or window)', }) as HTMLAnchorElement; expect(betaLink).toBeInTheDocument(); expect(betaLink.href).toBe('https://www.elastic.co/what-is/synthetic-monitoring'); @@ -152,7 +152,9 @@ describe('MonitorTitle component', () => { }); expect(screen.getByText('HTTP ping')).toBeInTheDocument(); expect(screen.queryByText(/BETA/)).not.toBeInTheDocument(); - expect(screen.queryByRole('link', { name: 'See more External link' })).not.toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: 'See more External link (opens in a new tab or window)' }) + ).not.toBeInTheDocument(); }); it('does not render beta disclaimer for tcp', () => { @@ -161,7 +163,9 @@ describe('MonitorTitle component', () => { }); expect(screen.getByText('TCP ping')).toBeInTheDocument(); expect(screen.queryByText(/BETA/)).not.toBeInTheDocument(); - expect(screen.queryByRole('link', { name: 'See more External link' })).not.toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: 'See more External link (opens in a new tab or window)' }) + ).not.toBeInTheDocument(); }); it('renders badge and does not render beta disclaimer for icmp', () => { @@ -170,6 +174,8 @@ describe('MonitorTitle component', () => { }); expect(screen.getByText('ICMP ping')).toBeInTheDocument(); expect(screen.queryByText(/BETA/)).not.toBeInTheDocument(); - expect(screen.queryByRole('link', { name: 'See more External link' })).not.toBeInTheDocument(); + expect( + screen.queryByRole('link', { name: 'See more External link (opens in a new tab or window)' }) + ).not.toBeInTheDocument(); }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx index d25d7eca333cf0..d6f8b23229f6c5 100644 --- a/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/monitor_title.tsx @@ -112,7 +112,7 @@ export const MonitorPageTitle: React.FC = () => { {isBrowser && ( - + Date: Thu, 22 Apr 2021 15:31:35 -0400 Subject: [PATCH 63/65] [Uptime] - monitors - update ExpandRowColumn to use isDisabled prop (#97883) --- .../ping_list/columns/expand_row.test.tsx | 80 +++++++++++++++++++ .../monitor/ping_list/columns/expand_row.tsx | 2 +- 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.test.tsx diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.test.tsx new file mode 100644 index 00000000000000..e3ac1f2e171254 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.test.tsx @@ -0,0 +1,80 @@ +/* + * 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 { screen } from '@testing-library/react'; + +import { makePing } from '../../../../../common/runtime_types'; + +import { render } from '../../../../lib/helper/rtl_helpers'; +import { ExpandRowColumn } from './expand_row'; + +import { Ping } from '../../../../../common/runtime_types/ping'; + +describe('ExpandRowColumn', () => { + const defaultPing = makePing({ + docId: 'test', + }); + const pingWithError = { + ...defaultPing, + error: true, + }; + const pingWithoutResponseBody = { + ...defaultPing, + http: { + response: { + body: { + bytes: 0, + }, + }, + }, + }; + const pingWithResponseBody = { + ...defaultPing, + http: { + response: { + body: { + bytes: 1, + }, + }, + }, + }; + const browserPing = { + ...defaultPing, + type: 'browser', + }; + const onChange = jest.fn(); + const defaultExpandedRows = { + test:

Test row

, + }; + + it.each([ + [defaultExpandedRows, 'Collapse'], + [{}, 'Expand'], + ])('renders correctly', (expandedRows, labelText) => { + render( + + ); + expect(screen.getByRole('button', { name: labelText })); + }); + + it.each([[defaultPing], [pingWithoutResponseBody], [browserPing]])( + 'disables expand button for pings without error, without response body, or browser pings', + (ping) => { + render(); + expect(screen.getByRole('button', { name: 'Expand' })).toHaveAttribute('disabled'); + } + ); + + it.each([[pingWithError], [pingWithResponseBody]])( + 'enables expand button for pings with error and response body', + (ping) => { + render(); + expect(screen.getByRole('button', { name: 'Expand' })).not.toHaveAttribute('disabled'); + } + ); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.tsx index 4655efc77729e1..b0453dd8f26808 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/expand_row.tsx @@ -47,7 +47,7 @@ export const ExpandRowColumn = ({ item, expandedRows, setExpandedRows }: Props) toggleDetails(item, expandedRows, setExpandedRows)} - disabled={!rowShouldExpand(item)} + isDisabled={!rowShouldExpand(item)} aria-label={ expandedRows[item.docId] ? i18n.translate('xpack.uptime.pingList.collapseRow', { From 94e5acd1476df6ee5e8795f132a03c986df98a2d Mon Sep 17 00:00:00 2001 From: Constance Date: Thu, 22 Apr 2021 13:24:36 -0700 Subject: [PATCH 64/65] Update synonyms API routes (#98046) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/routes/app_search/index.ts | 2 + .../server/routes/app_search/synonyms.test.ts | 86 ------------------- .../server/routes/app_search/synonyms.ts | 17 ++-- 3 files changed, 7 insertions(+), 98 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index 1d48614e733741..6b6886cbbb75dd 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -18,6 +18,7 @@ import { registerResultSettingsRoutes } from './result_settings'; import { registerRoleMappingsRoutes } from './role_mappings'; import { registerSearchSettingsRoutes } from './search_settings'; import { registerSettingsRoutes } from './settings'; +import { registerSynonymsRoutes } from './synonyms'; export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerEnginesRoutes(dependencies); @@ -27,6 +28,7 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerDocumentsRoutes(dependencies); registerDocumentRoutes(dependencies); registerCurationsRoutes(dependencies); + registerSynonymsRoutes(dependencies); registerSearchSettingsRoutes(dependencies); registerRoleMappingsRoutes(dependencies); registerResultSettingsRoutes(dependencies); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.test.ts index 26b44b5ad88896..53ceefc736d207 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.test.ts @@ -71,49 +71,6 @@ describe('synonyms routes', () => { path: '/as/engines/:engineName/synonyms/collection', }); }); - - describe('validates', () => { - it('with synonyms', () => { - const request = { - body: { - synonyms: ['a', 'b', 'c'], - }, - }; - mockRouter.shouldValidate(request); - }); - - it('empty synonyms array', () => { - const request = { - body: { - queries: [], - }, - }; - mockRouter.shouldThrow(request); - }); - - it('only one synonym', () => { - const request = { - body: { - queries: ['a'], - }, - }; - mockRouter.shouldThrow(request); - }); - - it('empty synonym strings', () => { - const request = { - body: { - queries: ['', '', ''], - }, - }; - mockRouter.shouldThrow(request); - }); - - it('missing synonyms', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); describe('PUT /api/app_search/engines/{engineName}/synonyms/{synonymId}', () => { @@ -137,49 +94,6 @@ describe('synonyms routes', () => { path: '/as/engines/:engineName/synonyms/:synonymId', }); }); - - describe('validates', () => { - it('with synonyms', () => { - const request = { - body: { - synonyms: ['a', 'b', 'c'], - }, - }; - mockRouter.shouldValidate(request); - }); - - it('empty synonyms array', () => { - const request = { - body: { - queries: [], - }, - }; - mockRouter.shouldThrow(request); - }); - - it('only one synonym', () => { - const request = { - body: { - queries: ['a'], - }, - }; - mockRouter.shouldThrow(request); - }); - - it('empty synonym strings', () => { - const request = { - body: { - queries: ['', '', ''], - }, - }; - mockRouter.shouldThrow(request); - }); - - it('missing synonyms', () => { - const request = { body: {} }; - mockRouter.shouldThrow(request); - }); - }); }); describe('DELETE /api/app_search/engines/{engineName}/synonyms/{synonymId}', () => { diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.ts index 1be58f00c476a4..d9b0cf1b9289fb 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/synonyms.ts @@ -7,10 +7,9 @@ import { schema } from '@kbn/config-schema'; +import { skipBodyValidation } from '../../lib/route_config_helpers'; import { RouteDependencies } from '../../plugin'; -const synonymsSchema = schema.arrayOf(schema.string({ minLength: 1 }), { minSize: 2 }); - export function registerSynonymsRoutes({ router, enterpriseSearchRequestHandler, @@ -34,35 +33,29 @@ export function registerSynonymsRoutes({ ); router.post( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/synonyms', validate: { params: schema.object({ engineName: schema.string(), }), - body: schema.object({ - synonyms: synonymsSchema, - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/synonyms/collection', }) ); router.put( - { + skipBodyValidation({ path: '/api/app_search/engines/{engineName}/synonyms/{synonymId}', validate: { params: schema.object({ engineName: schema.string(), synonymId: schema.string(), }), - body: schema.object({ - synonyms: synonymsSchema, - }), }, - }, + }), enterpriseSearchRequestHandler.createRequest({ path: '/as/engines/:engineName/synonyms/:synonymId', }) From fc45de9fd0158667e83b942ef8546b0c738cc379 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Thu, 22 Apr 2021 16:25:07 -0400 Subject: [PATCH 65/65] Don't hungry mock within mocks (#98040) --- .../public/applications/__mocks__/kibana_logic.mock.ts | 4 ++-- .../public/applications/__mocks__/mount_async.mock.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts index 2325ddcf2b2704..1ebd61df388c5a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kibana_logic.mock.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { chartPluginMock } from '../../../../../../src/plugins/charts/public/mocks'; +import { mockHistory } from './react_router_history.mock'; -import { mockHistory } from './'; +import { chartPluginMock } from '../../../../../../src/plugins/charts/public/mocks'; export const mockKibanaValues = { config: { host: 'http://localhost:3002' }, diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx index 2b5c06df37e8cd..886effcd540573 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/mount_async.mock.tsx @@ -5,13 +5,13 @@ * 2.0. */ +import { mountWithIntl } from './mount_with_i18n.mock'; + import React from 'react'; import { mount, ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from './'; - /** * This helper is intended for components that have async effects * (e.g. http fetches) on mount. It mostly adds act/update boilerplate