From 29aca9f55480a441dfeceb2ee50f7358de04736c Mon Sep 17 00:00:00 2001 From: Marshall Main <55718608+marshallmain@users.noreply.github.com> Date: Tue, 7 Dec 2021 11:30:56 -0800 Subject: [PATCH 001/145] AlertWithPersistence return only alerts that were actually indexed (#120439) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../rule_data_client/rule_data_client.ts | 2 +- .../create_persistence_rule_type_wrapper.ts | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts b/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts index d7ec6ea41ac8f..8ffe4e4db753f 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts @@ -192,7 +192,7 @@ export class RuleDataClient implements IRuleDataClient { return clusterClient.bulk(requestWithDefaultParameters).then((response) => { if (response.body.errors) { const error = new errors.ResponseError(response); - throw error; + this.options.logger.error(error); } return response; }); diff --git a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts index de1193771dd95..2d914e5e0945e 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_persistence_rule_type_wrapper.ts @@ -106,14 +106,16 @@ export const createPersistenceRuleTypeWrapper: CreatePersistenceRuleTypeWrapper } return { - createdAlerts: augmentedAlerts.map((alert, idx) => { - const responseItem = response.body.items[idx].create; - return { - _id: responseItem?._id ?? '', - _index: responseItem?._index ?? '', - ...alert._source, - }; - }), + createdAlerts: augmentedAlerts + .map((alert, idx) => { + const responseItem = response.body.items[idx].create; + return { + _id: responseItem?._id ?? '', + _index: responseItem?._index ?? '', + ...alert._source, + }; + }) + .filter((_, idx) => response.body.items[idx].create?.status === 201), }; } else { logger.debug('Writing is disabled.'); From 80dc3b64f2bea93041f3a0b97623b577f1dd5270 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Tue, 7 Dec 2021 13:04:03 -0700 Subject: [PATCH 002/145] [Logging]metrics ops logs include event loop delay histogram data (#120451) --- .../logging/get_ops_metrics_log.test.ts | 62 ++++++++++--------- .../metrics/logging/get_ops_metrics_log.ts | 32 +++++++++- .../server/metrics/metrics_service.test.ts | 1 + 3 files changed, 63 insertions(+), 32 deletions(-) diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts index cba188c94c74e..3fd3c4a7a24d6 100644 --- a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts +++ b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts @@ -42,6 +42,7 @@ const testMetrics = { memory: { heap: { used_in_bytes: 100 } }, uptime_in_millis: 1500, event_loop_delay: 50, + event_loop_delay_histogram: { percentiles: { '50': 50, '75': 75, '95': 95, '99': 99 } }, }, os: { load: { @@ -56,7 +57,7 @@ describe('getEcsOpsMetricsLog', () => { it('provides correctly formatted message', () => { const result = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); expect(result.message).toMatchInlineSnapshot( - `"memory: 100.0B uptime: 0:00:01 load: [10.00,20.00,30.00] delay: 50.000"` + `"memory: 100.0B uptime: 0:00:01 load: [10.00,20.00,30.00] mean delay: 50.000 delay histogram: { 50: 50.000; 95: 95.000; 99: 99.000 }"` ); }); @@ -70,6 +71,7 @@ describe('getEcsOpsMetricsLog', () => { const missingMetrics = { ...baseMetrics, process: {}, + processes: [], os: {}, } as unknown as OpsMetrics; const logMeta = getEcsOpsMetricsLog(missingMetrics); @@ -77,39 +79,41 @@ describe('getEcsOpsMetricsLog', () => { }); it('provides an ECS-compatible response', () => { - const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); - expect(logMeta).toMatchInlineSnapshot(` + const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); + expect(logMeta.meta).toMatchInlineSnapshot(` Object { - "message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000", - "meta": Object { - "event": Object { - "category": Array [ - "process", - "host", - ], - "kind": "metric", - "type": Array [ - "info", - ], - }, - "host": Object { - "os": Object { - "load": Object { - "15m": 1, - "1m": 1, - "5m": 1, - }, + "event": Object { + "category": Array [ + "process", + "host", + ], + "kind": "metric", + "type": Array [ + "info", + ], + }, + "host": Object { + "os": Object { + "load": Object { + "15m": 30, + "1m": 10, + "5m": 20, }, }, - "process": Object { - "eventLoopDelay": 1, - "memory": Object { - "heap": Object { - "usedInBytes": 1, - }, + }, + "process": Object { + "eventLoopDelay": 50, + "eventLoopDelayHistogram": Object { + "50": 50, + "95": 95, + "99": 99, + }, + "memory": Object { + "heap": Object { + "usedInBytes": 100, }, - "uptime": 0, }, + "uptime": 1, }, } `); diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.ts b/src/core/server/metrics/logging/get_ops_metrics_log.ts index 7e13f35889ec7..6211407ae86f0 100644 --- a/src/core/server/metrics/logging/get_ops_metrics_log.ts +++ b/src/core/server/metrics/logging/get_ops_metrics_log.ts @@ -30,10 +30,29 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics) { // HH:mm:ss message format for backward compatibility const uptimeValMsg = uptimeVal ? `uptime: ${numeral(uptimeVal).format('00:00:00')} ` : ''; - // Event loop delay is in ms + // Event loop delay metrics are in ms const eventLoopDelayVal = process?.event_loop_delay; const eventLoopDelayValMsg = eventLoopDelayVal - ? `delay: ${numeral(process?.event_loop_delay).format('0.000')}` + ? `mean delay: ${numeral(process?.event_loop_delay).format('0.000')}` + : ''; + + const eventLoopDelayPercentiles = process?.event_loop_delay_histogram?.percentiles; + + // Extract 50th, 95th and 99th percentiles for log meta + const eventLoopDelayHistVals = eventLoopDelayPercentiles + ? { + 50: eventLoopDelayPercentiles[50], + 95: eventLoopDelayPercentiles[95], + 99: eventLoopDelayPercentiles[99], + } + : undefined; + // Format message from 50th, 95th and 99th percentiles + const eventLoopDelayHistMsg = eventLoopDelayPercentiles + ? ` delay histogram: { 50: ${numeral(eventLoopDelayPercentiles['50']).format( + '0.000' + )}; 95: ${numeral(eventLoopDelayPercentiles['95']).format('0.000')}; 99: ${numeral( + eventLoopDelayPercentiles['99'] + ).format('0.000')} }` : ''; const loadEntries = { @@ -65,6 +84,7 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics) { }, }, eventLoopDelay: eventLoopDelayVal, + eventLoopDelayHistogram: eventLoopDelayHistVals, }, host: { os: { @@ -75,7 +95,13 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics) { }; return { - message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`, + message: [ + processMemoryUsedInBytesMsg, + uptimeValMsg, + loadValsMsg, + eventLoopDelayValMsg, + eventLoopDelayHistMsg, + ].join(''), meta, }; } diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index d7de41fd7ccf7..27043b8fa2c8a 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -203,6 +203,7 @@ describe('MetricsService', () => { }, "process": Object { "eventLoopDelay": undefined, + "eventLoopDelayHistogram": undefined, "memory": Object { "heap": Object { "usedInBytes": undefined, From d955835a2cf0727378d594606f1fd0cdd4ae0701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= Date: Tue, 7 Dec 2021 15:07:12 -0500 Subject: [PATCH 003/145] Make rule execute events `@timestamp` represent the end of the event rather than the start (#119761) * Fix event log timestamp for execute events * commit using @elastic.co * Fix failing jest tests * Fix typo * Fix failing jest tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/task_runner/task_runner.test.ts | 36 ------------------- .../server/task_runner/task_runner.ts | 2 -- .../task_runner/task_runner_cancel.test.ts | 9 ----- .../spaces_only/tests/alerting/event_log.ts | 20 +++-------- 4 files changed, 4 insertions(+), 63 deletions(-) diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index d370a278e0a5c..df650b11abfc0 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -299,7 +299,6 @@ describe('Task Runner', () => { expect(eventLogger.startTiming).toHaveBeenCalledTimes(1); expect(eventLogger.logEvent.mock.calls[0][0]).toMatchInlineSnapshot(` Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -450,7 +449,6 @@ describe('Task Runner', () => { const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(5); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-start', category: ['alerts'], @@ -582,7 +580,6 @@ describe('Task Runner', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(5, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute', category: ['alerts'], kind: 'alert', outcome: 'success' }, kibana: { alerting: { @@ -671,7 +668,6 @@ describe('Task Runner', () => { expect(eventLogger.startTiming).toHaveBeenCalledTimes(1); expect(eventLogger.logEvent).toHaveBeenCalledTimes(4); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-start', category: ['alerts'], @@ -767,7 +763,6 @@ describe('Task Runner', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(4, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute', category: ['alerts'], @@ -931,7 +926,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -1001,7 +995,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -1272,7 +1265,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -1418,7 +1410,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -1569,7 +1560,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -1755,7 +1745,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -2139,7 +2128,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -2246,7 +2234,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -2481,7 +2468,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -2515,7 +2501,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "error": Object { "message": "OMG", }, @@ -2590,7 +2575,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -2624,7 +2608,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "error": Object { "message": "OMG", }, @@ -2708,7 +2691,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -2742,7 +2724,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "error": Object { "message": "OMG", }, @@ -2826,7 +2807,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -2860,7 +2840,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "error": Object { "message": "OMG", }, @@ -2943,7 +2922,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -2977,7 +2955,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "error": Object { "message": "OMG", }, @@ -3238,7 +3215,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -3416,7 +3392,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -3525,7 +3500,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -3631,7 +3605,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -3732,7 +3705,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -3834,7 +3806,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -3930,7 +3901,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -4036,7 +4006,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -4134,7 +4103,6 @@ describe('Task Runner', () => { Array [ Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -4234,7 +4202,6 @@ describe('Task Runner', () => { ], Array [ Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute", "category": Array [ @@ -4383,7 +4350,6 @@ describe('Task Runner', () => { expect(eventLogger.startTiming).toHaveBeenCalledTimes(1); expect(eventLogger.logEvent.mock.calls[0][0]).toMatchInlineSnapshot(` Object { - "@timestamp": "1970-01-01T00:00:00.000Z", "event": Object { "action": "execute-start", "category": Array [ @@ -4463,7 +4429,6 @@ describe('Task Runner', () => { const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); expect(eventLogger.logEvent.mock.calls[0][0]).toStrictEqual({ - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-start', kind: 'alert', @@ -4484,7 +4449,6 @@ describe('Task Runner', () => { message: 'alert execution start: "1"', }); expect(eventLogger.logEvent.mock.calls[1][0]).toStrictEqual({ - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute', kind: 'alert', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 0cf5202787392..3cd1a2d1217dc 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -602,7 +602,6 @@ export class TaskRunner< const scheduleDelay = runDate.getTime() - this.taskInstance.runAt.getTime(); const event = createAlertEventLogRecordObject({ - timestamp: runDateString, ruleId: alertId, ruleType: this.alertType as UntypedNormalizedAlertType, action: EVENT_LOG_ACTIONS.execute, @@ -747,7 +746,6 @@ export class TaskRunner< const eventLogger = this.context.eventLogger; const event: IEvent = { - '@timestamp': new Date().toISOString(), event: { action: EVENT_LOG_ACTIONS.executeTimeout, kind: 'alert', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index c82cc0a7f21e8..eb3e22f348ed7 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -196,7 +196,6 @@ describe('Task Runner Cancel', () => { expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); expect(eventLogger.startTiming).toHaveBeenCalledTimes(1); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-start', category: ['alerts'], @@ -225,7 +224,6 @@ describe('Task Runner Cancel', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(2, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-timeout', category: ['alerts'], @@ -250,7 +248,6 @@ describe('Task Runner Cancel', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(3, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute', category: ['alerts'], @@ -424,7 +421,6 @@ describe('Task Runner Cancel', () => { expect(eventLogger.startTiming).toHaveBeenCalledTimes(1); expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-start', category: ['alerts'], @@ -453,7 +449,6 @@ describe('Task Runner Cancel', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(2, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-timeout', category: ['alerts'], @@ -479,7 +474,6 @@ describe('Task Runner Cancel', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(3, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute', category: ['alerts'], @@ -539,7 +533,6 @@ describe('Task Runner Cancel', () => { const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(6); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(1, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-start', category: ['alerts'], @@ -569,7 +562,6 @@ describe('Task Runner Cancel', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(2, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute-timeout', category: ['alerts'], @@ -689,7 +681,6 @@ describe('Task Runner Cancel', () => { }, }); expect(eventLogger.logEvent).toHaveBeenNthCalledWith(6, { - '@timestamp': '1970-01-01T00:00:00.000Z', event: { action: 'execute', category: ['alerts'], kind: 'alert', outcome: 'success' }, kibana: { alerting: { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index 806c1fa3a4ea7..18d21679d3341 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -107,26 +107,19 @@ export default function eventLogTests({ getService }: FtrProviderContext) { const executeEvents = getEventsByAction(events, 'execute'); const executeStartEvents = getEventsByAction(events, 'execute-start'); - const executeActionEvents = getEventsByAction(events, 'execute-action'); const newInstanceEvents = getEventsByAction(events, 'new-instance'); const recoveredInstanceEvents = getEventsByAction(events, 'recovered-instance'); // make sure the events are in the right temporal order const executeTimes = getTimestamps(executeEvents); const executeStartTimes = getTimestamps(executeStartEvents); - const executeActionTimes = getTimestamps(executeActionEvents); const newInstanceTimes = getTimestamps(newInstanceEvents); const recoveredInstanceTimes = getTimestamps(recoveredInstanceEvents); expect(executeTimes[0] < newInstanceTimes[0]).to.be(true); - expect(executeTimes[1] <= newInstanceTimes[0]).to.be(true); + expect(executeTimes[1] >= newInstanceTimes[0]).to.be(true); expect(executeTimes[2] > newInstanceTimes[0]).to.be(true); - expect(executeTimes[1] <= executeActionTimes[0]).to.be(true); - expect(executeTimes[2] > executeActionTimes[0]).to.be(true); expect(executeStartTimes.length === executeTimes.length).to.be(true); - executeStartTimes.forEach((est, index) => - expect(est === executeTimes[index]).to.be(true) - ); expect(recoveredInstanceTimes[0] > newInstanceTimes[0]).to.be(true); // validate each event @@ -325,26 +318,19 @@ export default function eventLogTests({ getService }: FtrProviderContext) { const executeEvents = getEventsByAction(events, 'execute'); const executeStartEvents = getEventsByAction(events, 'execute-start'); - const executeActionEvents = getEventsByAction(events, 'execute-action'); const newInstanceEvents = getEventsByAction(events, 'new-instance'); const recoveredInstanceEvents = getEventsByAction(events, 'recovered-instance'); // make sure the events are in the right temporal order const executeTimes = getTimestamps(executeEvents); const executeStartTimes = getTimestamps(executeStartEvents); - const executeActionTimes = getTimestamps(executeActionEvents); const newInstanceTimes = getTimestamps(newInstanceEvents); const recoveredInstanceTimes = getTimestamps(recoveredInstanceEvents); expect(executeTimes[0] < newInstanceTimes[0]).to.be(true); - expect(executeTimes[1] <= newInstanceTimes[0]).to.be(true); + expect(executeTimes[1] >= newInstanceTimes[0]).to.be(true); expect(executeTimes[2] > newInstanceTimes[0]).to.be(true); - expect(executeTimes[1] <= executeActionTimes[0]).to.be(true); - expect(executeTimes[2] > executeActionTimes[0]).to.be(true); expect(executeStartTimes.length === executeTimes.length).to.be(true); - executeStartTimes.forEach((est, index) => - expect(est === executeTimes[index]).to.be(true) - ); expect(recoveredInstanceTimes[0] > newInstanceTimes[0]).to.be(true); // validate each event @@ -589,6 +575,7 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa } const duration = event?.event?.duration; + const timestamp = Date.parse(event?.['@timestamp'] || 'undefined'); const eventStart = Date.parse(event?.event?.start || 'undefined'); const eventEnd = Date.parse(event?.event?.end || 'undefined'); const dateNow = Date.now(); @@ -608,6 +595,7 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa expect(durationDiff < 1).to.equal(true); expect(eventStart <= eventEnd).to.equal(true); expect(eventEnd <= dateNow).to.equal(true); + expect(eventEnd <= timestamp).to.equal(true); } if (shouldHaveEventEnd === false) { From de5dd787b13ee0098c2964c92e044930d5e90bde Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 7 Dec 2021 20:12:18 +0000 Subject: [PATCH 004/145] chore(NA): splits types from code on @kbn/dev-utils (#120531) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 1 + packages/BUILD.bazel | 1 + .../elastic-eslint-config-kibana/react.js | 2 +- .../typescript.js | 2 +- packages/kbn-cli-dev-mode/BUILD.bazel | 2 +- .../kbn-cli-dev-mode/src/cli_dev_mode.test.ts | 8 ++---- packages/kbn-cli-dev-mode/src/cli_dev_mode.ts | 3 +- .../src/get_server_watch_paths.test.ts | 3 +- .../src/get_server_watch_paths.ts | 2 +- packages/kbn-crypto/BUILD.bazel | 2 +- packages/kbn-dev-utils/BUILD.bazel | 28 +++++++++++++++---- packages/kbn-dev-utils/package.json | 1 - packages/kbn-dev-utils/src/index.ts | 1 - packages/kbn-docs-utils/BUILD.bazel | 2 +- .../src/api_docs/build_api_docs_cli.ts | 5 ++-- .../src/api_docs/find_plugins.ts | 3 +- packages/kbn-es-archiver/BUILD.bazel | 2 +- packages/kbn-es-archiver/src/actions/load.ts | 3 +- .../src/actions/rebuild_all.ts | 4 +-- packages/kbn-es-archiver/src/actions/save.ts | 4 +-- .../kbn-es-archiver/src/actions/unload.ts | 4 +-- packages/kbn-es-archiver/src/es_archiver.ts | 3 +- .../docs/generate_doc_records_stream.test.ts | 5 ++-- .../lib/docs/index_doc_records_stream.test.ts | 9 ++---- .../helpers/exports.js | 2 +- packages/kbn-optimizer/BUILD.bazel | 6 +++- ..._babel_runtime_helpers_in_entry_bundles.ts | 3 +- .../src/node/node_auto_tranpilation.ts | 2 +- .../src/optimizer/get_changes.test.ts | 3 +- .../src/optimizer/get_changes.ts | 2 +- packages/kbn-plugin-generator/BUILD.bazel | 2 +- packages/kbn-plugin-helpers/BUILD.bazel | 2 +- packages/kbn-storybook/BUILD.bazel | 4 ++- packages/kbn-storybook/src/lib/constants.ts | 2 +- packages/kbn-telemetry-tools/BUILD.bazel | 3 +- packages/kbn-test/BUILD.bazel | 11 +++++++- packages/kbn-test/src/es/es_test_config.ts | 2 +- .../lib/mocha/validate_ci_group_tags.js | 2 +- .../lib/suite_tracker.test.ts | 2 +- .../lib/babel_register_for_test_plugins.js | 2 +- .../kbn-test/src/functional_tests/tasks.ts | 3 +- .../kbn_client/kbn_client_import_export.ts | 3 +- .../integration_tests/invalid_config.test.ts | 2 +- .../capabilities_service.test.ts | 2 +- src/core/server/core_context.mock.ts | 2 +- .../elasticsearch_service.test.ts | 2 +- .../http/cookie_session_storage.test.ts | 2 +- src/core/server/http/http_service.test.ts | 2 +- src/core/server/http/test_utils.ts | 2 +- .../discovery/plugins_discovery.test.ts | 2 +- .../integration_tests/plugins_service.test.ts | 2 +- src/core/server/plugins/plugin.test.ts | 2 +- .../server/plugins/plugin_context.test.ts | 2 +- .../server/plugins/plugins_config.test.ts | 2 +- .../server/plugins/plugins_service.test.ts | 3 +- .../server/plugins/plugins_system.test.ts | 2 +- .../server/preboot/preboot_service.test.ts | 2 +- src/core/server/root/index.test.ts | 2 +- .../7.7.2_xpack_100k.test.ts | 2 +- .../migration_from_older_v1.test.ts | 2 +- .../migration_from_same_v1.test.ts | 2 +- .../saved_objects_service.test.ts | 2 +- src/core/server/server.test.ts | 2 +- .../integration_tests/index.test.ts | 2 +- src/core/test_helpers/kbn_server.ts | 3 +- .../integration_tests/version_info.test.ts | 2 +- .../docker_generator/bundle_dockerfiles.ts | 3 +- .../tasks/os_packages/docker_generator/run.ts | 3 +- src/dev/chromium_version.ts | 3 +- .../__tests__/enumerate_patterns.test.js | 3 +- .../ingest_coverage/team_assignment/index.js | 3 +- src/dev/ensure_all_tests_in_ci_group.ts | 3 +- src/dev/eslint/run_eslint_with_types.ts | 3 +- src/dev/plugin_discovery/find_plugins.ts | 8 ++---- src/dev/run_build_docs_cli.ts | 3 +- .../run_find_plugins_with_circular_deps.ts | 3 +- src/dev/run_precommit_hook.js | 3 +- src/dev/typescript/build_ts_refs.ts | 3 +- src/dev/typescript/build_ts_refs_cli.ts | 3 +- .../ref_output_cache/ref_output_cache.ts | 3 +- src/dev/typescript/root_refs_config.ts | 3 +- .../server/deprecations/deprecations.test.ts | 2 +- .../apps/metricbeat/_metricbeat_dashboard.ts | 2 +- yarn.lock | 4 +++ 84 files changed, 160 insertions(+), 104 deletions(-) diff --git a/package.json b/package.json index 6b7d6662eb70b..bd38699b6966a 100644 --- a/package.json +++ b/package.json @@ -567,6 +567,7 @@ "@types/kbn__config": "link:bazel-bin/packages/kbn-config/npm_module_types", "@types/kbn__config-schema": "link:bazel-bin/packages/kbn-config-schema/npm_module_types", "@types/kbn__crypto": "link:bazel-bin/packages/kbn-crypto/npm_module_types", + "@types/kbn__dev-utils": "link:bazel-bin/packages/kbn-dev-utils/npm_module_types", "@types/kbn__docs-utils": "link:bazel-bin/packages/kbn-docs-utils/npm_module_types", "@types/kbn__i18n": "link:bazel-bin/packages/kbn-i18n/npm_module_types", "@types/kbn__i18n-react": "link:bazel-bin/packages/kbn-i18n-react/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index aa90c3c122171..02e8bcf8ea144 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -86,6 +86,7 @@ filegroup( "//packages/kbn-config:build_types", "//packages/kbn-config-schema:build_types", "//packages/kbn-crypto:build_types", + "//packages/kbn-dev-utils:build_types", "//packages/kbn-docs-utils:build_types", "//packages/kbn-i18n:build_types", "//packages/kbn-i18n-react:build_types", diff --git a/packages/elastic-eslint-config-kibana/react.js b/packages/elastic-eslint-config-kibana/react.js index 29000bdb15684..0b1cce15de9ad 100644 --- a/packages/elastic-eslint-config-kibana/react.js +++ b/packages/elastic-eslint-config-kibana/react.js @@ -1,5 +1,5 @@ const semver = require('semver'); -const { kibanaPackageJson: PKG } = require('@kbn/dev-utils'); +const { kibanaPackageJson: PKG } = require('@kbn/utils'); module.exports = { plugins: [ diff --git a/packages/elastic-eslint-config-kibana/typescript.js b/packages/elastic-eslint-config-kibana/typescript.js index 1a0ef81ae2f1e..3ada725cb1805 100644 --- a/packages/elastic-eslint-config-kibana/typescript.js +++ b/packages/elastic-eslint-config-kibana/typescript.js @@ -4,7 +4,7 @@ // as this package was moved from typescript-eslint-parser to @typescript-eslint/parser const semver = require('semver'); -const { kibanaPackageJson: PKG } = require('@kbn/dev-utils'); +const { kibanaPackageJson: PKG } = require('@kbn/utils'); const eslintConfigPrettierTypescriptEslintRules = require('eslint-config-prettier/@typescript-eslint').rules; diff --git a/packages/kbn-cli-dev-mode/BUILD.bazel b/packages/kbn-cli-dev-mode/BUILD.bazel index dfb441dffc6ef..cdc40e85c972a 100644 --- a/packages/kbn-cli-dev-mode/BUILD.bazel +++ b/packages/kbn-cli-dev-mode/BUILD.bazel @@ -50,7 +50,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", "//packages/kbn-config-schema:npm_module_types", - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-logging", "//packages/kbn-optimizer", "//packages/kbn-server-http-tools", diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts index e5e009e51e69e..0066644d0825a 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.test.ts @@ -8,11 +8,9 @@ import Path from 'path'; import * as Rx from 'rxjs'; -import { - REPO_ROOT, - createAbsolutePathSerializer, - createAnyInstanceSerializer, -} from '@kbn/dev-utils'; +import { createAbsolutePathSerializer, createAnyInstanceSerializer } from '@kbn/dev-utils'; + +import { REPO_ROOT } from '@kbn/utils'; import { TestLog } from './log'; import { CliDevMode, SomeCliArgs } from './cli_dev_mode'; diff --git a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts index 2396b316aa3a2..9cf688b675e67 100644 --- a/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts +++ b/packages/kbn-cli-dev-mode/src/cli_dev_mode.ts @@ -22,7 +22,8 @@ import { takeUntil, } from 'rxjs/operators'; import { CliArgs } from '@kbn/config'; -import { REPO_ROOT, CiStatsReporter } from '@kbn/dev-utils'; +import { CiStatsReporter } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Log, CliLog } from './log'; import { Optimizer } from './optimizer'; diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts index 06ded8d8bf526..25bc59bf78458 100644 --- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.test.ts @@ -8,7 +8,8 @@ import Path from 'path'; -import { REPO_ROOT, createAbsolutePathSerializer } from '@kbn/dev-utils'; +import { createAbsolutePathSerializer } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { getServerWatchPaths } from './get_server_watch_paths'; diff --git a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts index f075dc806b6ec..acfc9aeecdc80 100644 --- a/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts +++ b/packages/kbn-cli-dev-mode/src/get_server_watch_paths.ts @@ -9,7 +9,7 @@ import Path from 'path'; import Fs from 'fs'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; interface Options { pluginPaths: string[]; diff --git a/packages/kbn-crypto/BUILD.bazel b/packages/kbn-crypto/BUILD.bazel index 81ee6d770103c..f71c8b866fd5d 100644 --- a/packages/kbn-crypto/BUILD.bazel +++ b/packages/kbn-crypto/BUILD.bazel @@ -34,7 +34,7 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "@npm//@types/flot", "@npm//@types/jest", "@npm//@types/node", diff --git a/packages/kbn-dev-utils/BUILD.bazel b/packages/kbn-dev-utils/BUILD.bazel index 4fd99e0144cb6..89df1870a3cec 100644 --- a/packages/kbn-dev-utils/BUILD.bazel +++ b/packages/kbn-dev-utils/BUILD.bazel @@ -1,9 +1,10 @@ -load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") PKG_BASE_NAME = "kbn-dev-utils" PKG_REQUIRE_NAME = "@kbn/dev-utils" +TYPES_PKG_REQUIRE_NAME = "@types/kbn__dev-utils" SOURCE_FILES = glob( [ @@ -43,7 +44,6 @@ NPM_MODULE_EXTRA_FILES = [ ] RUNTIME_DEPS = [ - "//packages/kbn-std", "//packages/kbn-utils", "@npm//@babel/core", "@npm//axios", @@ -66,7 +66,6 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-std", "//packages/kbn-utils", "@npm//@babel/parser", "@npm//@babel/types", @@ -124,7 +123,7 @@ ts_project( js_library( name = PKG_BASE_NAME, srcs = NPM_MODULE_EXTRA_FILES, - deps = RUNTIME_DEPS + [":target_node", ":tsc_types"], + deps = RUNTIME_DEPS + [":target_node"], package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) @@ -143,3 +142,20 @@ filegroup( ], visibility = ["//visibility:public"], ) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = TYPES_PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [ + ":npm_module_types", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 9d6e6dde86fac..ab4f489e7d345 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -4,7 +4,6 @@ "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", "main": "./target_node/index.js", - "types": "./target_types/index.d.ts", "kibana": { "devOnly": true } diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 381e99ac677f5..9b207ad9e9966 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -export * from '@kbn/utils'; export { withProcRunner, ProcRunner } from './proc_runner'; export * from './tooling_log'; export * from './serializers'; diff --git a/packages/kbn-docs-utils/BUILD.bazel b/packages/kbn-docs-utils/BUILD.bazel index 37e5bb06377cc..edfd3ee96c181 100644 --- a/packages/kbn-docs-utils/BUILD.bazel +++ b/packages/kbn-docs-utils/BUILD.bazel @@ -38,7 +38,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-utils", "@npm//ts-morph", "@npm//@types/dedent", diff --git a/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts b/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts index 2e4ce08540714..3c9137b260a3e 100644 --- a/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts +++ b/packages/kbn-docs-utils/src/api_docs/build_api_docs_cli.ts @@ -9,7 +9,8 @@ import Fs from 'fs'; import Path from 'path'; -import { REPO_ROOT, run, CiStatsReporter, createFlagError } from '@kbn/dev-utils'; +import { run, CiStatsReporter, createFlagError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Project } from 'ts-morph'; import { writePluginDocs } from './mdx/write_plugin_mdx_docs'; @@ -241,7 +242,7 @@ export function runBuildApiDocsCli() { boolean: ['references'], help: ` --plugin Optionally, run for only a specific plugin - --stats Optionally print API stats. Must be one or more of: any, comments or exports. + --stats Optionally print API stats. Must be one or more of: any, comments or exports. --references Collect references for API items `, }, diff --git a/packages/kbn-docs-utils/src/api_docs/find_plugins.ts b/packages/kbn-docs-utils/src/api_docs/find_plugins.ts index 78cba3f3a9476..774452a6f1f9f 100644 --- a/packages/kbn-docs-utils/src/api_docs/find_plugins.ts +++ b/packages/kbn-docs-utils/src/api_docs/find_plugins.ts @@ -12,7 +12,8 @@ import globby from 'globby'; import loadJsonFile from 'load-json-file'; import { getPluginSearchPaths } from '@kbn/config'; -import { simpleKibanaPlatformPluginDiscovery, REPO_ROOT } from '@kbn/dev-utils'; +import { simpleKibanaPlatformPluginDiscovery } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { ApiScope, PluginOrPackage } from './types'; export function findPlugins(): PluginOrPackage[] { diff --git a/packages/kbn-es-archiver/BUILD.bazel b/packages/kbn-es-archiver/BUILD.bazel index 2dc311ed74406..f50a5b98f6811 100644 --- a/packages/kbn-es-archiver/BUILD.bazel +++ b/packages/kbn-es-archiver/BUILD.bazel @@ -43,7 +43,7 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-test", "//packages/kbn-utils", "@npm//@elastic/elasticsearch", diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index 0a7235c566b52..c5bea5e29a687 100644 --- a/packages/kbn-es-archiver/src/actions/load.ts +++ b/packages/kbn-es-archiver/src/actions/load.ts @@ -9,7 +9,8 @@ import { resolve, relative } from 'path'; import { createReadStream } from 'fs'; import { Readable } from 'stream'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { KbnClient } from '@kbn/test'; import type { Client } from '@elastic/elasticsearch'; import { createPromiseFromStreams, concatStreamProviders } from '@kbn/utils'; diff --git a/packages/kbn-es-archiver/src/actions/rebuild_all.ts b/packages/kbn-es-archiver/src/actions/rebuild_all.ts index 360fdb438f2db..27fcae0c7cec5 100644 --- a/packages/kbn-es-archiver/src/actions/rebuild_all.ts +++ b/packages/kbn-es-archiver/src/actions/rebuild_all.ts @@ -10,8 +10,8 @@ import { resolve, relative } from 'path'; import { Stats, createReadStream, createWriteStream } from 'fs'; import { stat, rename } from 'fs/promises'; import { Readable, Writable } from 'stream'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; -import { createPromiseFromStreams } from '@kbn/utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { createPromiseFromStreams, REPO_ROOT } from '@kbn/utils'; import { prioritizeMappings, readDirectory, diff --git a/packages/kbn-es-archiver/src/actions/save.ts b/packages/kbn-es-archiver/src/actions/save.ts index 9cb5be05ac060..e5e3f06b8436d 100644 --- a/packages/kbn-es-archiver/src/actions/save.ts +++ b/packages/kbn-es-archiver/src/actions/save.ts @@ -10,8 +10,8 @@ import { resolve, relative } from 'path'; import { createWriteStream, mkdirSync } from 'fs'; import { Readable, Writable } from 'stream'; import type { Client } from '@elastic/elasticsearch'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; -import { createListStream, createPromiseFromStreams } from '@kbn/utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { createListStream, createPromiseFromStreams, REPO_ROOT } from '@kbn/utils'; import { createStats, diff --git a/packages/kbn-es-archiver/src/actions/unload.ts b/packages/kbn-es-archiver/src/actions/unload.ts index 1c5f4cd5d7d03..22830b7289174 100644 --- a/packages/kbn-es-archiver/src/actions/unload.ts +++ b/packages/kbn-es-archiver/src/actions/unload.ts @@ -10,9 +10,9 @@ import { resolve, relative } from 'path'; import { createReadStream } from 'fs'; import { Readable, Writable } from 'stream'; import type { Client } from '@elastic/elasticsearch'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; import { KbnClient } from '@kbn/test'; -import { createPromiseFromStreams } from '@kbn/utils'; +import { createPromiseFromStreams, REPO_ROOT } from '@kbn/utils'; import { isGzip, diff --git a/packages/kbn-es-archiver/src/es_archiver.ts b/packages/kbn-es-archiver/src/es_archiver.ts index 354197a98fa46..e13e20f25a703 100644 --- a/packages/kbn-es-archiver/src/es_archiver.ts +++ b/packages/kbn-es-archiver/src/es_archiver.ts @@ -10,7 +10,8 @@ import Fs from 'fs'; import Path from 'path'; import type { Client } from '@elastic/elasticsearch'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { KbnClient } from '@kbn/test'; import { diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts index ae21649690a99..2590074a25411 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ +import { ToolingLog } from '@kbn/dev-utils'; + import { createListStream, createPromiseFromStreams, createConcatStream, createMapStream, - ToolingLog, -} from '@kbn/dev-utils'; +} from '@kbn/utils'; import { createGenerateDocRecordsStream } from './generate_doc_records_stream'; import { Progress } from '../progress'; diff --git a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts index bcf28a4976a1c..9c0ff4a8f91ec 100644 --- a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts @@ -6,12 +6,9 @@ * Side Public License, v 1. */ -import { - createListStream, - createPromiseFromStreams, - ToolingLog, - createRecursiveSerializer, -} from '@kbn/dev-utils'; +import { ToolingLog, createRecursiveSerializer } from '@kbn/dev-utils'; + +import { createListStream, createPromiseFromStreams } from '@kbn/utils'; import { Progress } from '../progress'; import { createIndexDocRecordsStream } from './index_doc_records_stream'; diff --git a/packages/kbn-eslint-plugin-eslint/helpers/exports.js b/packages/kbn-eslint-plugin-eslint/helpers/exports.js index b7af8e83d7661..971364633356c 100644 --- a/packages/kbn-eslint-plugin-eslint/helpers/exports.js +++ b/packages/kbn-eslint-plugin-eslint/helpers/exports.js @@ -9,7 +9,7 @@ const Fs = require('fs'); const Path = require('path'); const ts = require('typescript'); -const { REPO_ROOT } = require('@kbn/dev-utils'); +const { REPO_ROOT } = require('@kbn/utils'); const { ExportSet } = require('./export_set'); /** @typedef {import("@typescript-eslint/types").TSESTree.ExportAllDeclaration} ExportAllDeclaration */ diff --git a/packages/kbn-optimizer/BUILD.bazel b/packages/kbn-optimizer/BUILD.bazel index a389086c9ee3c..3bd41249e2d51 100644 --- a/packages/kbn-optimizer/BUILD.bazel +++ b/packages/kbn-optimizer/BUILD.bazel @@ -38,10 +38,12 @@ RUNTIME_DEPS = [ "//packages/kbn-ui-shared-deps-npm", "//packages/kbn-ui-shared-deps-src", "//packages/kbn-utils", + "@npm//@babel/core", "@npm//chalk", "@npm//clean-webpack-plugin", "@npm//compression-webpack-plugin", "@npm//cpy", + "@npm//dedent", "@npm//del", "@npm//execa", "@npm//jest-diff", @@ -64,7 +66,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-config:npm_module_types", "//packages/kbn-config-schema:npm_module_types", - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-std", "//packages/kbn-ui-shared-deps-npm", "//packages/kbn-ui-shared-deps-src", @@ -79,7 +81,9 @@ TYPES_DEPS = [ "@npm//pirates", "@npm//rxjs", "@npm//zlib", + "@npm//@types/babel__core", "@npm//@types/compression-webpack-plugin", + "@npm//@types/dedent", "@npm//@types/jest", "@npm//@types/json-stable-stringify", "@npm//@types/js-yaml", diff --git a/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts b/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts index f00905f3f4920..c07a9764af76f 100644 --- a/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts +++ b/packages/kbn-optimizer/src/babel_runtime_helpers/find_babel_runtime_helpers_in_entry_bundles.ts @@ -8,7 +8,8 @@ import Path from 'path'; -import { run, REPO_ROOT } from '@kbn/dev-utils'; +import { run } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { OptimizerConfig } from '../optimizer'; import { parseStats, inAnyEntryChunk } from './parse_stats'; diff --git a/packages/kbn-optimizer/src/node/node_auto_tranpilation.ts b/packages/kbn-optimizer/src/node/node_auto_tranpilation.ts index 6f5dabf410ffa..2710ba8a54210 100644 --- a/packages/kbn-optimizer/src/node/node_auto_tranpilation.ts +++ b/packages/kbn-optimizer/src/node/node_auto_tranpilation.ts @@ -39,7 +39,7 @@ import Crypto from 'crypto'; import * as babel from '@babel/core'; import { addHook } from 'pirates'; -import { REPO_ROOT, UPSTREAM_BRANCH } from '@kbn/dev-utils'; +import { REPO_ROOT, UPSTREAM_BRANCH } from '@kbn/utils'; import sourceMapSupport from 'source-map-support'; import { Cache } from './cache'; diff --git a/packages/kbn-optimizer/src/optimizer/get_changes.test.ts b/packages/kbn-optimizer/src/optimizer/get_changes.test.ts index d3cc5cceefddf..d1754248dba17 100644 --- a/packages/kbn-optimizer/src/optimizer/get_changes.test.ts +++ b/packages/kbn-optimizer/src/optimizer/get_changes.test.ts @@ -9,7 +9,8 @@ jest.mock('execa'); import { getChanges } from './get_changes'; -import { REPO_ROOT, createAbsolutePathSerializer } from '@kbn/dev-utils'; +import { createAbsolutePathSerializer } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; const execa: jest.Mock = jest.requireMock('execa'); diff --git a/packages/kbn-optimizer/src/optimizer/get_changes.ts b/packages/kbn-optimizer/src/optimizer/get_changes.ts index c5f8abe99c322..b59f938eb8c37 100644 --- a/packages/kbn-optimizer/src/optimizer/get_changes.ts +++ b/packages/kbn-optimizer/src/optimizer/get_changes.ts @@ -10,7 +10,7 @@ import Path from 'path'; import execa from 'execa'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; export type Changes = Map; diff --git a/packages/kbn-plugin-generator/BUILD.bazel b/packages/kbn-plugin-generator/BUILD.bazel index c935d1763dae8..488f09bdd5d52 100644 --- a/packages/kbn-plugin-generator/BUILD.bazel +++ b/packages/kbn-plugin-generator/BUILD.bazel @@ -51,7 +51,7 @@ RUNTIME_DEPS = [ TYPES_DEPS = [ "//packages/kbn-utils", - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "@npm//del", "@npm//execa", "@npm//globby", diff --git a/packages/kbn-plugin-helpers/BUILD.bazel b/packages/kbn-plugin-helpers/BUILD.bazel index d7744aecac26e..47f205f1530b7 100644 --- a/packages/kbn-plugin-helpers/BUILD.bazel +++ b/packages/kbn-plugin-helpers/BUILD.bazel @@ -42,7 +42,7 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-optimizer", "//packages/kbn-utils", "@npm//del", diff --git a/packages/kbn-storybook/BUILD.bazel b/packages/kbn-storybook/BUILD.bazel index f2a7bf25fb407..5dbe22b56c63f 100644 --- a/packages/kbn-storybook/BUILD.bazel +++ b/packages/kbn-storybook/BUILD.bazel @@ -32,6 +32,7 @@ RUNTIME_DEPS = [ "//packages/kbn-dev-utils", "//packages/kbn-ui-shared-deps-npm", "//packages/kbn-ui-shared-deps-src", + "//packages/kbn-utils", "@npm//@storybook/addons", "@npm//@storybook/api", "@npm//@storybook/components", @@ -47,9 +48,10 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-ui-shared-deps-npm", "//packages/kbn-ui-shared-deps-src", + "//packages/kbn-utils", "@npm//@storybook/addons", "@npm//@storybook/api", "@npm//@storybook/components", diff --git a/packages/kbn-storybook/src/lib/constants.ts b/packages/kbn-storybook/src/lib/constants.ts index 722f789fde786..69b05c94ea1b0 100644 --- a/packages/kbn-storybook/src/lib/constants.ts +++ b/packages/kbn-storybook/src/lib/constants.ts @@ -7,7 +7,7 @@ */ import { resolve } from 'path'; -import { REPO_ROOT as KIBANA_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT as KIBANA_ROOT } from '@kbn/utils'; export const REPO_ROOT = KIBANA_ROOT; export const ASSET_DIR = resolve(KIBANA_ROOT, 'built_assets/storybook'); diff --git a/packages/kbn-telemetry-tools/BUILD.bazel b/packages/kbn-telemetry-tools/BUILD.bazel index 1183de2586424..d2ea3a704f154 100644 --- a/packages/kbn-telemetry-tools/BUILD.bazel +++ b/packages/kbn-telemetry-tools/BUILD.bazel @@ -38,8 +38,9 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-utility-types", + "@npm//tslib", "@npm//@types/glob", "@npm//@types/jest", "@npm//@types/listr", diff --git a/packages/kbn-test/BUILD.bazel b/packages/kbn-test/BUILD.bazel index 1d1d95d639861..eae0fe2cdf5dc 100644 --- a/packages/kbn-test/BUILD.bazel +++ b/packages/kbn-test/BUILD.bazel @@ -44,11 +44,13 @@ RUNTIME_DEPS = [ "@npm//axios", "@npm//@babel/traverse", "@npm//chance", + "@npm//dedent", "@npm//del", "@npm//enzyme", "@npm//execa", "@npm//exit-hook", "@npm//form-data", + "@npm//getopts", "@npm//globby", "@npm//he", "@npm//history", @@ -59,6 +61,7 @@ RUNTIME_DEPS = [ "@npm//@jest/reporters", "@npm//joi", "@npm//mustache", + "@npm//normalize-path", "@npm//parse-link-header", "@npm//prettier", "@npm//react-dom", @@ -72,13 +75,17 @@ RUNTIME_DEPS = [ ] TYPES_DEPS = [ - "//packages/kbn-dev-utils", + "//packages/kbn-dev-utils:npm_module_types", "//packages/kbn-i18n-react:npm_module_types", + "//packages/kbn-std", "//packages/kbn-utils", "@npm//@elastic/elasticsearch", + "@npm//axios", "@npm//elastic-apm-node", "@npm//del", + "@npm//exit-hook", "@npm//form-data", + "@npm//getopts", "@npm//jest", "@npm//jest-cli", "@npm//jest-snapshot", @@ -86,6 +93,7 @@ TYPES_DEPS = [ "@npm//rxjs", "@npm//xmlbuilder", "@npm//@types/chance", + "@npm//@types/dedent", "@npm//@types/enzyme", "@npm//@types/he", "@npm//@types/history", @@ -93,6 +101,7 @@ TYPES_DEPS = [ "@npm//@types/joi", "@npm//@types/lodash", "@npm//@types/mustache", + "@npm//@types/normalize-path", "@npm//@types/node", "@npm//@types/parse-link-header", "@npm//@types/prettier", diff --git a/packages/kbn-test/src/es/es_test_config.ts b/packages/kbn-test/src/es/es_test_config.ts index db5d705710a75..70000c8068e9f 100644 --- a/packages/kbn-test/src/es/es_test_config.ts +++ b/packages/kbn-test/src/es/es_test_config.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { kibanaPackageJson as pkg } from '@kbn/dev-utils'; +import { kibanaPackageJson as pkg } from '@kbn/utils'; import Url from 'url'; import { adminTestUser } from '../kbn'; diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/validate_ci_group_tags.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/validate_ci_group_tags.js index 3446c5be5d4a7..4f798839d7231 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/validate_ci_group_tags.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/validate_ci_group_tags.js @@ -8,7 +8,7 @@ import Path from 'path'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; /** * Traverse the suites configured and ensure that each suite has no more than one ciGroup assigned diff --git a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts index e87f316a100a7..53ce4c74c1388 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts @@ -14,7 +14,7 @@ jest.mock('@kbn/utils', () => { return { REPO_ROOT: '/dev/null/root' }; }); -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Lifecycle } from './lifecycle'; import { SuiteTracker } from './suite_tracker'; import { Suite } from '../fake_mocha_types'; diff --git a/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js b/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js index 03947f7e267ba..63d2b56350ba1 100644 --- a/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js +++ b/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js @@ -9,7 +9,7 @@ const Fs = require('fs'); const Path = require('path'); -const { REPO_ROOT: REPO_ROOT_FOLLOWING_SYMLINKS } = require('@kbn/dev-utils'); +const { REPO_ROOT: REPO_ROOT_FOLLOWING_SYMLINKS } = require('@kbn/utils'); const BASE_REPO_ROOT = Path.resolve( Fs.realpathSync(Path.resolve(REPO_ROOT_FOLLOWING_SYMLINKS, 'package.json')), '..' diff --git a/packages/kbn-test/src/functional_tests/tasks.ts b/packages/kbn-test/src/functional_tests/tasks.ts index 6dde114d3a98e..6a6c7edb98c79 100644 --- a/packages/kbn-test/src/functional_tests/tasks.ts +++ b/packages/kbn-test/src/functional_tests/tasks.ts @@ -9,7 +9,8 @@ import { relative } from 'path'; import * as Rx from 'rxjs'; import { startWith, switchMap, take } from 'rxjs/operators'; -import { withProcRunner, ToolingLog, REPO_ROOT, getTimeReporter } from '@kbn/dev-utils'; +import { withProcRunner, ToolingLog, getTimeReporter } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import dedent from 'dedent'; import { 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 4adae7d1cd031..6da34228bbe7f 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 @@ -12,7 +12,8 @@ import { existsSync } from 'fs'; import Path from 'path'; import FormData from 'form-data'; -import { ToolingLog, isAxiosResponseError, createFailError, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog, isAxiosResponseError, createFailError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { KbnClientRequester, uriencode, ReqOptions } from './kbn_client_requester'; import { KbnClientSavedObjects } from './kbn_client_saved_objects'; diff --git a/src/cli/serve/integration_tests/invalid_config.test.ts b/src/cli/serve/integration_tests/invalid_config.test.ts index 2de902582a548..ca051f37a816e 100644 --- a/src/cli/serve/integration_tests/invalid_config.test.ts +++ b/src/cli/serve/integration_tests/invalid_config.test.ts @@ -8,7 +8,7 @@ import { spawnSync } from 'child_process'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; const INVALID_CONFIG_PATH = require.resolve('./__fixtures__/invalid_config.yml'); diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index 2e80fbb9d20c0..c1f6ffb5add77 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -7,7 +7,7 @@ */ import supertest from 'supertest'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { HttpService, InternalHttpServicePreboot, InternalHttpServiceSetup } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; diff --git a/src/core/server/core_context.mock.ts b/src/core/server/core_context.mock.ts index ddb87d31383c8..4d7b4e1ba5548 100644 --- a/src/core/server/core_context.mock.ts +++ b/src/core/server/core_context.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { CoreContext } from './core_context'; import { Env, IConfigService } from './config'; diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 3b75d19b80a10..ce5672ad30519 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -21,7 +21,7 @@ import { MockClusterClient, isScriptingEnabledMock } from './elasticsearch_servi import type { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { BehaviorSubject } from 'rxjs'; import { first } from 'rxjs/operators'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Env } from '../config'; import { configServiceMock, getEnvOptions } from '../config/mocks'; import { CoreContext } from '../core_context'; diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index ad05d37c81e99..8e2cd58733faf 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -8,7 +8,7 @@ import { parse as parseCookie } from 'tough-cookie'; import supertest from 'supertest'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { ByteSizeValue } from '@kbn/config-schema'; import { BehaviorSubject } from 'rxjs'; diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index 4955d19668580..3a387cdfd5e35 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -10,7 +10,7 @@ import { mockHttpServer } from './http_service.test.mocks'; import { noop } from 'lodash'; import { BehaviorSubject } from 'rxjs'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { getEnvOptions } from '../config/mocks'; import { HttpService } from '.'; import { HttpConfigType, config } from './http_config'; diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index 4e1a88e967f8f..8a8c545b365b3 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -8,7 +8,7 @@ import { BehaviorSubject } from 'rxjs'; import moment from 'moment'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { ByteSizeValue } from '@kbn/config-schema'; import { Env } from '../config'; import { HttpService } from './http_service'; diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index 958e051d0476d..a6ffdff4422be 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -7,7 +7,7 @@ */ // must be before mocks imports to avoid conflicting with `REPO_ROOT` accessor. -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { mockPackage, scanPluginSearchPathsMock } from './plugins_discovery.test.mocks'; import mockFs from 'mock-fs'; import { loggingSystemMock } from '../../logging/logging_system.mock'; diff --git a/src/core/server/plugins/integration_tests/plugins_service.test.ts b/src/core/server/plugins/integration_tests/plugins_service.test.ts index 4170d9422f277..ebbb3fa473b6d 100644 --- a/src/core/server/plugins/integration_tests/plugins_service.test.ts +++ b/src/core/server/plugins/integration_tests/plugins_service.test.ts @@ -7,7 +7,7 @@ */ // must be before mocks imports to avoid conflicting with `REPO_ROOT` accessor. -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { mockPackage, mockDiscover } from './plugins_service.test.mocks'; import { join } from 'path'; diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 513e893992005..92cbda2a69cfe 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -8,7 +8,7 @@ import { join } from 'path'; import { BehaviorSubject } from 'rxjs'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { schema } from '@kbn/config-schema'; import { Env } from '../config'; diff --git a/src/core/server/plugins/plugin_context.test.ts b/src/core/server/plugins/plugin_context.test.ts index 867d4d978314b..7bcf392ed510b 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/src/core/server/plugins/plugin_context.test.ts @@ -8,7 +8,7 @@ import { duration } from 'moment'; import { first } from 'rxjs/operators'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { fromRoot } from '@kbn/utils'; import { createPluginInitializerContext, diff --git a/src/core/server/plugins/plugins_config.test.ts b/src/core/server/plugins/plugins_config.test.ts index d65b057fb65c0..b9225054e63ef 100644 --- a/src/core/server/plugins/plugins_config.test.ts +++ b/src/core/server/plugins/plugins_config.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { getEnvOptions } from '../config/mocks'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { Env } from '../config'; diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 0c077d732c67b..5a05817d2111f 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -11,7 +11,8 @@ import { mockDiscover, mockPackage } from './plugins_service.test.mocks'; import { resolve, join } from 'path'; import { BehaviorSubject, from } from 'rxjs'; import { schema } from '@kbn/config-schema'; -import { createAbsolutePathSerializer, REPO_ROOT } from '@kbn/dev-utils'; +import { createAbsolutePathSerializer } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { ConfigPath, ConfigService, Env } from '../config'; import { rawConfigServiceMock, getEnvOptions } from '../config/mocks'; diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 4cd8e4c551bea..3d8a47005b362 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -14,7 +14,7 @@ import { import { BehaviorSubject } from 'rxjs'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Env } from '../config'; import { configServiceMock, getEnvOptions } from '../config/mocks'; import { CoreContext } from '../core_context'; diff --git a/src/core/server/preboot/preboot_service.test.ts b/src/core/server/preboot/preboot_service.test.ts index dd4b1cb7d1df0..77242f0c5765f 100644 --- a/src/core/server/preboot/preboot_service.test.ts +++ b/src/core/server/preboot/preboot_service.test.ts @@ -7,7 +7,7 @@ */ import { nextTick } from '@kbn/test/jest'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { LoggerFactory } from '@kbn/logging'; import { Env } from '@kbn/config'; import { getEnvOptions } from '../config/mocks'; diff --git a/src/core/server/root/index.test.ts b/src/core/server/root/index.test.ts index 7eba051a128f0..6ea3e05b9c2c2 100644 --- a/src/core/server/root/index.test.ts +++ b/src/core/server/root/index.test.ts @@ -10,7 +10,7 @@ import { rawConfigService, configService, logger, mockServer } from './index.tes import { BehaviorSubject } from 'rxjs'; import { filter, first } from 'rxjs/operators'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { getEnvOptions } from '../config/mocks'; import { Root } from '.'; import { Env } from '../config'; diff --git a/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts index c22c6154c2605..139cd298d28ed 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/7.7.2_xpack_100k.test.ts @@ -8,7 +8,7 @@ import path from 'path'; import { unlink } from 'fs/promises'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Env } from '@kbn/config'; import { getEnvOptions } from '../../../config/mocks'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; diff --git a/src/core/server/saved_objects/migrations/integration_tests/migration_from_older_v1.test.ts b/src/core/server/saved_objects/migrations/integration_tests/migration_from_older_v1.test.ts index 0ed9262017263..c341463b78910 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/migration_from_older_v1.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/migration_from_older_v1.test.ts @@ -10,7 +10,7 @@ import Path from 'path'; import Fs from 'fs'; import Util from 'util'; import Semver from 'semver'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Env } from '@kbn/config'; import { getEnvOptions } from '../../../config/mocks'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; diff --git a/src/core/server/saved_objects/migrations/integration_tests/migration_from_same_v1.test.ts b/src/core/server/saved_objects/migrations/integration_tests/migration_from_same_v1.test.ts index 15d985daccba6..34d1317755c14 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/migration_from_same_v1.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/migration_from_same_v1.test.ts @@ -10,7 +10,7 @@ import Path from 'path'; import Fs from 'fs'; import Util from 'util'; import Semver from 'semver'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { Env } from '@kbn/config'; import { getEnvOptions } from '../../../config/mocks'; import * as kbnTestServer from '../../../../test_helpers/kbn_server'; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index a4f6c019c9624..a8bda95af46f9 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -19,7 +19,7 @@ import { import { BehaviorSubject } from 'rxjs'; import { RawPackageInfo } from '@kbn/config'; import { ByteSizeValue } from '@kbn/config-schema'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { SavedObjectsService } from './saved_objects_service'; import { mockCoreContext } from '../core_context.mock'; diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 112693aae0279..48547883d5f67 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -26,7 +26,7 @@ import { } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { rawConfigServiceMock, getEnvOptions } from './config/mocks'; import { Env } from './config'; import { Server } from './server'; diff --git a/src/core/server/ui_settings/integration_tests/index.test.ts b/src/core/server/ui_settings/integration_tests/index.test.ts index ef635e90dac70..3f85beb2acec6 100644 --- a/src/core/server/ui_settings/integration_tests/index.test.ts +++ b/src/core/server/ui_settings/integration_tests/index.test.ts @@ -7,7 +7,7 @@ */ import { Env } from '@kbn/config'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { getEnvOptions } from '../../config/mocks'; import { startServers, stopServers } from './lib'; import { docExistsSuite } from './doc_exists'; diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index 58720be637e2f..c326c7a35df63 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { createTestEsCluster, CreateTestEsClusterOptions, diff --git a/src/dev/build/lib/integration_tests/version_info.test.ts b/src/dev/build/lib/integration_tests/version_info.test.ts index e7a3a04c04734..9385de6e00a4f 100644 --- a/src/dev/build/lib/integration_tests/version_info.test.ts +++ b/src/dev/build/lib/integration_tests/version_info.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { kibanaPackageJson as pkg } from '@kbn/dev-utils'; +import { kibanaPackageJson as pkg } from '@kbn/utils'; import { getVersionInfo } from '../version_info'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts index 02b469820f900..cc1ffb5f3e301 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts @@ -10,7 +10,8 @@ import { resolve } from 'path'; import { readFileSync } from 'fs'; import { copyFile } from 'fs/promises'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import Mustache from 'mustache'; import { compressTar, copyAll, mkdirp, write, Config } from '../../../lib'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 6a192baed3fa3..085b4393caa66 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -10,7 +10,8 @@ import { access, link, unlink, chmod } from 'fs'; import { resolve, basename } from 'path'; import { promisify } from 'util'; -import { ToolingLog, kibanaPackageJson } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { kibanaPackageJson } from '@kbn/utils'; import { write, copyAll, mkdirp, exec, Config, Build } from '../../../lib'; import * as dockerTemplates from './templates'; diff --git a/src/dev/chromium_version.ts b/src/dev/chromium_version.ts index 410fcc72fbc0f..1f55330a92bb6 100644 --- a/src/dev/chromium_version.ts +++ b/src/dev/chromium_version.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { run, REPO_ROOT, ToolingLog } from '@kbn/dev-utils'; +import { run, ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import chalk from 'chalk'; import cheerio from 'cheerio'; import fs from 'fs'; diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js index 57467d84f1f61..40d36ed46ea34 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js +++ b/src/dev/code_coverage/ingest_coverage/__tests__/enumerate_patterns.test.js @@ -7,7 +7,8 @@ */ import { enumeratePatterns } from '../team_assignment/enumerate_patterns'; -import { ToolingLog, REPO_ROOT } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; const log = new ToolingLog({ level: 'info', diff --git a/src/dev/code_coverage/ingest_coverage/team_assignment/index.js b/src/dev/code_coverage/ingest_coverage/team_assignment/index.js index 0e341a3aac1dc..a38c4ee50b40a 100644 --- a/src/dev/code_coverage/ingest_coverage/team_assignment/index.js +++ b/src/dev/code_coverage/ingest_coverage/team_assignment/index.js @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { run, createFlagError, REPO_ROOT } from '@kbn/dev-utils'; +import { run, createFlagError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { parse } from './parse_owners'; import { flush } from './flush'; import { enumeratePatterns } from './enumerate_patterns'; diff --git a/src/dev/ensure_all_tests_in_ci_group.ts b/src/dev/ensure_all_tests_in_ci_group.ts index aeccefae05d2c..a2d9729d3352b 100644 --- a/src/dev/ensure_all_tests_in_ci_group.ts +++ b/src/dev/ensure_all_tests_in_ci_group.ts @@ -12,7 +12,8 @@ import Fs from 'fs/promises'; import execa from 'execa'; import { safeLoad } from 'js-yaml'; -import { run, REPO_ROOT } from '@kbn/dev-utils'; +import { run } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { schema } from '@kbn/config-schema'; const RELATIVE_JOBS_YAML_PATH = '.ci/ci_groups.yml'; diff --git a/src/dev/eslint/run_eslint_with_types.ts b/src/dev/eslint/run_eslint_with_types.ts index 750011dea1031..0f2a10d07d681 100644 --- a/src/dev/eslint/run_eslint_with_types.ts +++ b/src/dev/eslint/run_eslint_with_types.ts @@ -14,7 +14,8 @@ import execa from 'execa'; import * as Rx from 'rxjs'; import { mergeMap, reduce } from 'rxjs/operators'; import { supportsColor } from 'chalk'; -import { REPO_ROOT, run, createFailError } from '@kbn/dev-utils'; +import { run, createFailError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { lastValueFrom } from '@kbn/std'; import { PROJECTS } from '../typescript/projects'; diff --git a/src/dev/plugin_discovery/find_plugins.ts b/src/dev/plugin_discovery/find_plugins.ts index f1725f34d1f8e..53a53bc08e15b 100644 --- a/src/dev/plugin_discovery/find_plugins.ts +++ b/src/dev/plugin_discovery/find_plugins.ts @@ -8,11 +8,9 @@ import Path from 'path'; import { getPluginSearchPaths } from '@kbn/config'; -import { - KibanaPlatformPlugin, - REPO_ROOT, - simpleKibanaPlatformPluginDiscovery, -} from '@kbn/dev-utils'; +import { KibanaPlatformPlugin, simpleKibanaPlatformPluginDiscovery } from '@kbn/dev-utils'; + +import { REPO_ROOT } from '@kbn/utils'; export interface SearchOptions { oss: boolean; diff --git a/src/dev/run_build_docs_cli.ts b/src/dev/run_build_docs_cli.ts index aad524b4437d3..8ee75912c1a7e 100644 --- a/src/dev/run_build_docs_cli.ts +++ b/src/dev/run_build_docs_cli.ts @@ -9,7 +9,8 @@ import Path from 'path'; import dedent from 'dedent'; -import { run, REPO_ROOT, createFailError } from '@kbn/dev-utils'; +import { run, createFailError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; const DEFAULT_DOC_REPO_PATH = Path.resolve(REPO_ROOT, '..', 'docs'); diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts index f7974b464fcaf..f9ee7bd84c54f 100644 --- a/src/dev/run_find_plugins_with_circular_deps.ts +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -10,7 +10,8 @@ import dedent from 'dedent'; import { parseDependencyTree, parseCircular, prettyCircular } from 'dpdm'; import { relative } from 'path'; import { getPluginSearchPaths } from '@kbn/config'; -import { REPO_ROOT, run } from '@kbn/dev-utils'; +import { run } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; interface Options { debug?: boolean; diff --git a/src/dev/run_precommit_hook.js b/src/dev/run_precommit_hook.js index a7bd0a9f57f6e..dfa3a94426bb2 100644 --- a/src/dev/run_precommit_hook.js +++ b/src/dev/run_precommit_hook.js @@ -8,7 +8,8 @@ import SimpleGit from 'simple-git/promise'; -import { run, combineErrors, createFlagError, REPO_ROOT } from '@kbn/dev-utils'; +import { run, combineErrors, createFlagError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import * as Eslint from './eslint'; import * as Stylelint from './stylelint'; import { getFilesForCommit, checkFileCasing } from './precommit_hook'; diff --git a/src/dev/typescript/build_ts_refs.ts b/src/dev/typescript/build_ts_refs.ts index aaa8c0d12fa4d..f3896cf676e27 100644 --- a/src/dev/typescript/build_ts_refs.ts +++ b/src/dev/typescript/build_ts_refs.ts @@ -8,7 +8,8 @@ import Path from 'path'; -import { ToolingLog, REPO_ROOT, ProcRunner } from '@kbn/dev-utils'; +import { ToolingLog, ProcRunner } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { ROOT_REFS_CONFIG_PATH } from './root_refs_config'; import { Project } from './project'; diff --git a/src/dev/typescript/build_ts_refs_cli.ts b/src/dev/typescript/build_ts_refs_cli.ts index c68424c2a98f7..09866315fc8dd 100644 --- a/src/dev/typescript/build_ts_refs_cli.ts +++ b/src/dev/typescript/build_ts_refs_cli.ts @@ -8,7 +8,8 @@ import Path from 'path'; -import { run, REPO_ROOT, createFlagError } from '@kbn/dev-utils'; +import { run, createFlagError } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import del from 'del'; import { RefOutputCache } from './ref_output_cache'; diff --git a/src/dev/typescript/ref_output_cache/ref_output_cache.ts b/src/dev/typescript/ref_output_cache/ref_output_cache.ts index b7e641ceb33d5..32b08ec1ba0df 100644 --- a/src/dev/typescript/ref_output_cache/ref_output_cache.ts +++ b/src/dev/typescript/ref_output_cache/ref_output_cache.ts @@ -9,7 +9,8 @@ import Path from 'path'; import Fs from 'fs/promises'; -import { ToolingLog, kibanaPackageJson, extract } from '@kbn/dev-utils'; +import { ToolingLog, extract } from '@kbn/dev-utils'; +import { kibanaPackageJson } from '@kbn/utils'; import del from 'del'; import tempy from 'tempy'; diff --git a/src/dev/typescript/root_refs_config.ts b/src/dev/typescript/root_refs_config.ts index f4aa88f1ea6b2..e20b1ab46cd82 100644 --- a/src/dev/typescript/root_refs_config.ts +++ b/src/dev/typescript/root_refs_config.ts @@ -10,7 +10,8 @@ import Path from 'path'; import Fs from 'fs/promises'; import dedent from 'dedent'; -import { REPO_ROOT, ToolingLog } from '@kbn/dev-utils'; +import { ToolingLog } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import normalize from 'normalize-path'; import { PROJECTS } from './projects'; diff --git a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts index 11deff82de572..6b00c5cdd9a2b 100644 --- a/x-pack/plugins/apm/server/deprecations/deprecations.test.ts +++ b/x-pack/plugins/apm/server/deprecations/deprecations.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { kibanaPackageJson } from '@kbn/dev-utils'; +import { kibanaPackageJson } from '@kbn/utils'; import { GetDeprecationsContext } from '../../../../../src/core/server'; import { CloudSetup } from '../../../cloud/server'; diff --git a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.ts b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.ts index d2e9adbfd2683..66da887b5067c 100644 --- a/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.ts +++ b/x-pack/test/stack_functional_integration/apps/metricbeat/_metricbeat_dashboard.ts @@ -7,7 +7,7 @@ import expect from '@kbn/expect'; import { resolve } from 'path'; -import { REPO_ROOT } from '@kbn/dev-utils'; +import { REPO_ROOT } from '@kbn/utils'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; const INTEGRATION_TEST_ROOT = process.env.WORKSPACE || resolve(REPO_ROOT, '../integration-test'); diff --git a/yarn.lock b/yarn.lock index 68eb7893fb00f..e838d0ec297a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5846,6 +5846,10 @@ version "0.0.0" uid "" +"@types/kbn__dev-utils@link:bazel-bin/packages/kbn-dev-utils/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__docs-utils@link:bazel-bin/packages/kbn-docs-utils/npm_module_types": version "0.0.0" uid "" From f5141e43906b940f5fd759b9807a4ac89757a9d7 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Tue, 7 Dec 2021 14:35:05 -0600 Subject: [PATCH 005/145] [APM] Handle other values popup when correlated value is not in top 10 (#118069) * [ML] Add stats for field value not in top 10 * [ML] Fix tests * [ML] Add message * [ML] Reverse label, remove Fragment, switch to i18n * Fix sample shard size import, subdued text * Fix sample shard size import, subdued text * Move routes after refactor * Add loading spinner * Fix sampler shard size Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/correlations/field_stats_types.ts | 6 +- .../context_popover/context_popover.tsx | 31 +- .../context_popover/top_values.tsx | 334 ++++++++++++------ .../field_stats/get_boolean_field_stats.ts | 19 +- .../field_stats/get_field_stats.test.ts | 52 +-- .../field_stats/get_field_value_stats.ts | 76 ++++ .../queries/field_stats/get_fields_stats.ts | 13 +- .../field_stats/get_keyword_field_stats.ts | 20 +- .../field_stats/get_numeric_field_stats.ts | 52 +-- .../routes/correlations/queries/index.ts | 1 + .../apm/server/routes/correlations/route.ts | 53 ++- .../correlations/utils/field_stats_utils.ts | 21 -- 12 files changed, 414 insertions(+), 264 deletions(-) create mode 100644 x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_value_stats.ts diff --git a/x-pack/plugins/apm/common/correlations/field_stats_types.ts b/x-pack/plugins/apm/common/correlations/field_stats_types.ts index 50dc7919fbd00..41f7e3c3c6649 100644 --- a/x-pack/plugins/apm/common/correlations/field_stats_types.ts +++ b/x-pack/plugins/apm/common/correlations/field_stats_types.ts @@ -8,9 +8,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { CorrelationsParams } from './types'; -export interface FieldStatsCommonRequestParams extends CorrelationsParams { - samplerShardSize: number; -} +export type FieldStatsCommonRequestParams = CorrelationsParams; export interface Field { fieldName: string; @@ -55,3 +53,5 @@ export type FieldStats = | NumericFieldStats | KeywordFieldStats | BooleanFieldStats; + +export type FieldValueFieldStats = TopValuesStats; diff --git a/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx b/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx index f1d0d194749c5..d7043ea669a03 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/context_popover/context_popover.tsx @@ -11,14 +11,11 @@ import { EuiFlexItem, EuiPopover, EuiPopoverTitle, - EuiSpacer, - EuiText, EuiTitle, EuiToolTip, } from '@elastic/eui'; -import React, { Fragment, useState } from 'react'; +import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { FieldStats } from '../../../../../common/correlations/field_stats_types'; import { OnAddFilter, TopValues } from './top_values'; import { useTheme } from '../../../../hooks/use_theme'; @@ -97,27 +94,11 @@ export function CorrelationsContextPopover({ {infoIsOpen ? ( - <> - - {topValueStats.topValuesSampleSize !== undefined && ( - - - - - - - )} - + ) : null} ); diff --git a/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx b/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx index 05b4f6d56fa45..fbf33899a2de2 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/context_popover/top_values.tsx @@ -12,11 +12,21 @@ import { EuiProgress, EuiSpacer, EuiToolTip, + EuiText, + EuiHorizontalRule, + EuiLoadingSpinner, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { FieldStats } from '../../../../../common/correlations/field_stats_types'; +import numeral from '@elastic/numeral'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + FieldStats, + TopValueBucket, +} from '../../../../../common/correlations/field_stats_types'; import { asPercent } from '../../../../../common/utils/formatters'; import { useTheme } from '../../../../hooks/use_theme'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useFetchParams } from '../use_fetch_params'; export type OnAddFilter = ({ fieldName, @@ -28,23 +38,179 @@ export type OnAddFilter = ({ include: boolean; }) => void; -interface Props { +interface TopValueProps { + progressBarMax: number; + barColor: string; + value: TopValueBucket; + isHighlighted: boolean; + fieldName: string; + onAddFilter?: OnAddFilter; + valueText?: string; + reverseLabel?: boolean; +} +export function TopValue({ + progressBarMax, + barColor, + value, + isHighlighted, + fieldName, + onAddFilter, + valueText, + reverseLabel = false, +}: TopValueProps) { + const theme = useTheme(); + return ( + + + + {value.key} + + } + className="eui-textTruncate" + aria-label={value.key.toString()} + valueText={valueText} + labelProps={ + isHighlighted + ? { + style: { fontWeight: 'bold' }, + } + : undefined + } + /> + + {fieldName !== undefined && + value.key !== undefined && + onAddFilter !== undefined ? ( + <> + { + onAddFilter({ + fieldName, + fieldValue: + typeof value.key === 'number' + ? value.key.toString() + : value.key, + include: true, + }); + }} + aria-label={i18n.translate( + 'xpack.apm.correlations.fieldContextPopover.addFilterAriaLabel', + { + defaultMessage: 'Filter for {fieldName}: "{value}"', + values: { fieldName, value: value.key }, + } + )} + data-test-subj={`apmFieldContextTopValuesAddFilterButton-${value.key}-${value.key}`} + style={{ + minHeight: 'auto', + width: theme.eui.euiSizeL, + paddingRight: 2, + paddingLeft: 2, + paddingTop: 0, + paddingBottom: 0, + }} + /> + { + onAddFilter({ + fieldName, + fieldValue: + typeof value.key === 'number' + ? value.key.toString() + : value.key, + include: false, + }); + }} + aria-label={i18n.translate( + 'xpack.apm.correlations.fieldContextPopover.removeFilterAriaLabel', + { + defaultMessage: 'Filter out {fieldName}: "{value}"', + values: { fieldName, value: value.key }, + } + )} + data-test-subj={`apmFieldContextTopValuesExcludeFilterButton-${value.key}-${value.key}`} + style={{ + minHeight: 'auto', + width: theme.eui.euiSizeL, + paddingTop: 0, + paddingBottom: 0, + paddingRight: 2, + paddingLeft: 2, + }} + /> + + ) : null} + + ); +} + +interface TopValuesProps { topValueStats: FieldStats; compressed?: boolean; onAddFilter?: OnAddFilter; fieldValue?: string | number; } -export function TopValues({ topValueStats, onAddFilter, fieldValue }: Props) { +export function TopValues({ + topValueStats, + onAddFilter, + fieldValue, +}: TopValuesProps) { const { topValues, topValuesSampleSize, count, fieldName } = topValueStats; const theme = useTheme(); - if (!Array.isArray(topValues) || topValues.length === 0) return null; + const idxToHighlight = Array.isArray(topValues) + ? topValues.findIndex((value) => value.key === fieldValue) + : null; + + const params = useFetchParams(); + const { data: fieldValueStats, status } = useFetcher( + (callApmApi) => { + if ( + idxToHighlight === -1 && + fieldName !== undefined && + fieldValue !== undefined + ) { + return callApmApi({ + endpoint: 'GET /internal/apm/correlations/field_value_stats', + params: { + query: { + ...params, + fieldName, + fieldValue, + }, + }, + }); + } + }, + [params, fieldName, fieldValue, idxToHighlight] + ); + if ( + !Array.isArray(topValues) || + topValues?.length === 0 || + fieldValue === undefined + ) + return null; const sampledSize = typeof topValuesSampleSize === 'string' ? parseInt(topValuesSampleSize, 10) : topValuesSampleSize; + const progressBarMax = sampledSize ?? count; return (
- - - - {value.key} - - } - className="eui-textTruncate" - aria-label={value.key.toString()} - valueText={valueText} - labelProps={ - isHighlighted - ? { - style: { fontWeight: 'bold' }, - } - : undefined - } - /> - - {fieldName !== undefined && - value.key !== undefined && - onAddFilter !== undefined ? ( - <> - { - onAddFilter({ - fieldName, - fieldValue: - typeof value.key === 'number' - ? value.key.toString() - : value.key, - include: true, - }); - }} - aria-label={i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.addFilterAriaLabel', - { - defaultMessage: 'Filter for {fieldName}: "{value}"', - values: { fieldName, value: value.key }, - } - )} - data-test-subj={`apmFieldContextTopValuesAddFilterButton-${value.key}-${value.key}`} - style={{ - minHeight: 'auto', - width: theme.eui.euiSizeL, - paddingRight: 2, - paddingLeft: 2, - paddingTop: 0, - paddingBottom: 0, - }} - /> - { - onAddFilter({ - fieldName, - fieldValue: - typeof value.key === 'number' - ? value.key.toString() - : value.key, - include: false, - }); - }} - aria-label={i18n.translate( - 'xpack.apm.correlations.fieldContextPopover.removeFilterAriaLabel', - { - defaultMessage: 'Filter out {fieldName}: "{value}"', - values: { fieldName, value: value.key }, - } - )} - data-test-subj={`apmFieldContextTopValuesExcludeFilterButton-${value.key}-${value.key}`} - style={{ - minHeight: 'auto', - width: theme.eui.euiSizeL, - paddingTop: 0, - paddingBottom: 0, - paddingRight: 2, - paddingLeft: 2, - }} - /> - - ) : null} - + ); })} + + {idxToHighlight === -1 && ( + <> + + + + + + {status === FETCH_STATUS.SUCCESS && + Array.isArray(fieldValueStats?.topValues) ? ( + fieldValueStats?.topValues.map((value) => { + const valueText = + progressBarMax !== undefined + ? asPercent(value.doc_count, progressBarMax) + : undefined; + + return ( + + ); + }) + ) : ( + + + + )} + + )} + + {topValueStats.topValuesSampleSize !== undefined && ( + <> + + + {i18n.translate( + 'xpack.apm.correlations.fieldContextPopover.calculatedFromSampleDescription', + { + defaultMessage: + 'Calculated from sample of {sampleSize} documents', + values: { sampleSize: topValueStats.topValuesSampleSize }, + } + )} + + + )}
); } diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_boolean_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_boolean_field_stats.ts index c936e626a5599..a41e3370c1063 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_boolean_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_boolean_field_stats.ts @@ -7,8 +7,6 @@ import { ElasticsearchClient } from 'kibana/server'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - -import { buildSamplerAggregation } from '../../utils/field_stats_utils'; import { FieldValuePair } from '../../../../../common/correlations/types'; import { FieldStatsCommonRequestParams, @@ -25,7 +23,7 @@ export const getBooleanFieldStatsRequest = ( ): estypes.SearchRequest => { const query = getQueryWithParams({ params, termFilters }); - const { index, samplerShardSize } = params; + const { index } = params; const size = 0; const aggs: Aggs = { @@ -42,14 +40,13 @@ export const getBooleanFieldStatsRequest = ( const searchBody = { query, - aggs: { - sample: buildSamplerAggregation(aggs, samplerShardSize), - }, + aggs, }; return { index, size, + track_total_hits: false, body: searchBody, }; }; @@ -67,19 +64,17 @@ export const fetchBooleanFieldStats = async ( ); const { body } = await esClient.search(request); const aggregations = body.aggregations as { - sample: { - sampled_value_count: estypes.AggregationsFiltersBucketItemKeys; - sampled_values: estypes.AggregationsTermsAggregate; - }; + sampled_value_count: estypes.AggregationsFiltersBucketItemKeys; + sampled_values: estypes.AggregationsTermsAggregate; }; const stats: BooleanFieldStats = { fieldName: field.fieldName, - count: aggregations?.sample.sampled_value_count.doc_count ?? 0, + count: aggregations?.sampled_value_count.doc_count ?? 0, }; const valueBuckets: TopValueBucket[] = - aggregations?.sample.sampled_values?.buckets ?? []; + aggregations?.sampled_values?.buckets ?? []; valueBuckets.forEach((bucket) => { stats[`${bucket.key.toString()}Count`] = bucket.doc_count; }); diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_stats.test.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_stats.test.ts index 2775d755c9907..30bebc4c24774 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_stats.test.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_stats.test.ts @@ -20,7 +20,6 @@ const params = { includeFrozen: false, environment: ENVIRONMENT_ALL.value, kuery: '', - samplerShardSize: 5000, }; export const getExpectedQuery = (aggs: any) => { @@ -46,6 +45,7 @@ export const getExpectedQuery = (aggs: any) => { }, index: 'apm-*', size: 0, + track_total_hits: false, }; }; @@ -55,28 +55,16 @@ describe('field_stats', () => { const req = getNumericFieldStatsRequest(params, 'url.path'); const expectedAggs = { - sample: { - aggs: { - sampled_field_stats: { - aggs: { actual_stats: { stats: { field: 'url.path' } } }, - filter: { exists: { field: 'url.path' } }, - }, - sampled_percentiles: { - percentiles: { - field: 'url.path', - keyed: false, - percents: [50], - }, - }, - sampled_top: { - terms: { - field: 'url.path', - order: { _count: 'desc' }, - size: 10, - }, - }, + sampled_field_stats: { + aggs: { actual_stats: { stats: { field: 'url.path' } } }, + filter: { exists: { field: 'url.path' } }, + }, + sampled_top: { + terms: { + field: 'url.path', + order: { _count: 'desc' }, + size: 10, }, - sampler: { shard_size: 5000 }, }, }; expect(req).toEqual(getExpectedQuery(expectedAggs)); @@ -87,13 +75,8 @@ describe('field_stats', () => { const req = getKeywordFieldStatsRequest(params, 'url.path'); const expectedAggs = { - sample: { - sampler: { shard_size: 5000 }, - aggs: { - sampled_top: { - terms: { field: 'url.path', size: 10, order: { _count: 'desc' } }, - }, - }, + sampled_top: { + terms: { field: 'url.path', size: 10 }, }, }; expect(req).toEqual(getExpectedQuery(expectedAggs)); @@ -104,15 +87,10 @@ describe('field_stats', () => { const req = getBooleanFieldStatsRequest(params, 'url.path'); const expectedAggs = { - sample: { - sampler: { shard_size: 5000 }, - aggs: { - sampled_value_count: { - filter: { exists: { field: 'url.path' } }, - }, - sampled_values: { terms: { field: 'url.path', size: 2 } }, - }, + sampled_value_count: { + filter: { exists: { field: 'url.path' } }, }, + sampled_values: { terms: { field: 'url.path', size: 2 } }, }; expect(req).toEqual(getExpectedQuery(expectedAggs)); }); diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_value_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_value_stats.ts new file mode 100644 index 0000000000000..0fa508eff508c --- /dev/null +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_field_value_stats.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from 'kibana/server'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { FieldValuePair } from '../../../../../common/correlations/types'; +import { + FieldStatsCommonRequestParams, + FieldValueFieldStats, + Aggs, + TopValueBucket, +} from '../../../../../common/correlations/field_stats_types'; +import { getQueryWithParams } from '../get_query_with_params'; + +export const getFieldValueFieldStatsRequest = ( + params: FieldStatsCommonRequestParams, + field?: FieldValuePair +): estypes.SearchRequest => { + const query = getQueryWithParams({ params }); + + const { index } = params; + + const size = 0; + const aggs: Aggs = { + filtered_count: { + filter: { + term: { + [`${field?.fieldName}`]: field?.fieldValue, + }, + }, + }, + }; + + const searchBody = { + query, + aggs, + }; + + return { + index, + size, + track_total_hits: false, + body: searchBody, + }; +}; + +export const fetchFieldValueFieldStats = async ( + esClient: ElasticsearchClient, + params: FieldStatsCommonRequestParams, + field: FieldValuePair +): Promise => { + const request = getFieldValueFieldStatsRequest(params, field); + + const { body } = await esClient.search(request); + const aggregations = body.aggregations as { + filtered_count: estypes.AggregationsFiltersBucketItemKeys; + }; + const topValues: TopValueBucket[] = [ + { + key: field.fieldValue, + doc_count: aggregations.filtered_count.doc_count, + }, + ]; + + const stats = { + fieldName: field.fieldName, + topValues, + topValuesSampleSize: aggregations.filtered_count.doc_count ?? 0, + }; + + return stats; +}; diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_fields_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_fields_stats.ts index 8b41f7662679c..513252ee65e11 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_fields_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_fields_stats.ts @@ -8,10 +8,7 @@ import { ElasticsearchClient } from 'kibana/server'; import { chunk } from 'lodash'; import { ES_FIELD_TYPES } from '@kbn/field-types'; -import { - FieldValuePair, - CorrelationsParams, -} from '../../../../../common/correlations/types'; +import { FieldValuePair } from '../../../../../common/correlations/types'; import { FieldStats, FieldStatsCommonRequestParams, @@ -23,7 +20,7 @@ import { fetchBooleanFieldStats } from './get_boolean_field_stats'; export const fetchFieldsStats = async ( esClient: ElasticsearchClient, - params: CorrelationsParams, + fieldStatsParams: FieldStatsCommonRequestParams, fieldsToSample: string[], termFilters?: FieldValuePair[] ): Promise<{ stats: FieldStats[]; errors: any[] }> => { @@ -33,14 +30,10 @@ export const fetchFieldsStats = async ( if (fieldsToSample.length === 0) return { stats, errors }; const respMapping = await esClient.fieldCaps({ - ...getRequestBase(params), + ...getRequestBase(fieldStatsParams), fields: fieldsToSample, }); - const fieldStatsParams: FieldStatsCommonRequestParams = { - ...params, - samplerShardSize: 5000, - }; const fieldStatsPromises = Object.entries(respMapping.body.fields) .map(([key, value], idx) => { const field: FieldValuePair = { fieldName: key, fieldValue: '' }; diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_keyword_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_keyword_field_stats.ts index c64bbc6678779..16ba4f24f5e93 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_keyword_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_keyword_field_stats.ts @@ -14,7 +14,6 @@ import { Aggs, TopValueBucket, } from '../../../../../common/correlations/field_stats_types'; -import { buildSamplerAggregation } from '../../utils/field_stats_utils'; import { getQueryWithParams } from '../get_query_with_params'; export const getKeywordFieldStatsRequest = ( @@ -24,7 +23,7 @@ export const getKeywordFieldStatsRequest = ( ): estypes.SearchRequest => { const query = getQueryWithParams({ params, termFilters }); - const { index, samplerShardSize } = params; + const { index } = params; const size = 0; const aggs: Aggs = { @@ -32,23 +31,19 @@ export const getKeywordFieldStatsRequest = ( terms: { field: fieldName, size: 10, - order: { - _count: 'desc', - }, }, }, }; const searchBody = { query, - aggs: { - sample: buildSamplerAggregation(aggs, samplerShardSize), - }, + aggs, }; return { index, size, + track_total_hits: false, body: searchBody, }; }; @@ -66,19 +61,16 @@ export const fetchKeywordFieldStats = async ( ); const { body } = await esClient.search(request); const aggregations = body.aggregations as { - sample: { - sampled_top: estypes.AggregationsTermsAggregate; - }; + sampled_top: estypes.AggregationsTermsAggregate; }; - const topValues: TopValueBucket[] = - aggregations?.sample.sampled_top?.buckets ?? []; + const topValues: TopValueBucket[] = aggregations?.sampled_top?.buckets ?? []; const stats = { fieldName: field.fieldName, topValues, topValuesSampleSize: topValues.reduce( (acc, curr) => acc + curr.doc_count, - aggregations.sample.sampled_top?.sum_other_doc_count ?? 0 + aggregations.sampled_top?.sum_other_doc_count ?? 0 ), }; diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_numeric_field_stats.ts b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_numeric_field_stats.ts index 21e6559fdda25..197ed66c4fe70 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_numeric_field_stats.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/field_stats/get_numeric_field_stats.ts @@ -6,7 +6,7 @@ */ import { ElasticsearchClient } from 'kibana/server'; -import { find, get } from 'lodash'; +import { get } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { NumericFieldStats, @@ -16,10 +16,6 @@ import { } from '../../../../../common/correlations/field_stats_types'; import { FieldValuePair } from '../../../../../common/correlations/types'; import { getQueryWithParams } from '../get_query_with_params'; -import { buildSamplerAggregation } from '../../utils/field_stats_utils'; - -// Only need 50th percentile for the median -const PERCENTILES = [50]; export const getNumericFieldStatsRequest = ( params: FieldStatsCommonRequestParams, @@ -29,9 +25,8 @@ export const getNumericFieldStatsRequest = ( const query = getQueryWithParams({ params, termFilters }); const size = 0; - const { index, samplerShardSize } = params; + const { index } = params; - const percents = PERCENTILES; const aggs: Aggs = { sampled_field_stats: { filter: { exists: { field: fieldName } }, @@ -41,13 +36,6 @@ export const getNumericFieldStatsRequest = ( }, }, }, - sampled_percentiles: { - percentiles: { - field: fieldName, - percents, - keyed: false, - }, - }, sampled_top: { terms: { field: fieldName, @@ -61,14 +49,13 @@ export const getNumericFieldStatsRequest = ( const searchBody = { query, - aggs: { - sample: buildSamplerAggregation(aggs, samplerShardSize), - }, + aggs, }; return { index, size, + track_total_hits: false, body: searchBody, }; }; @@ -87,19 +74,15 @@ export const fetchNumericFieldStats = async ( const { body } = await esClient.search(request); const aggregations = body.aggregations as { - sample: { - sampled_top: estypes.AggregationsTermsAggregate; - sampled_percentiles: estypes.AggregationsHdrPercentilesAggregate; - sampled_field_stats: { - doc_count: number; - actual_stats: estypes.AggregationsStatsAggregate; - }; + sampled_top: estypes.AggregationsTermsAggregate; + sampled_field_stats: { + doc_count: number; + actual_stats: estypes.AggregationsStatsAggregate; }; }; - const docCount = aggregations?.sample.sampled_field_stats?.doc_count ?? 0; - const fieldStatsResp = - aggregations?.sample.sampled_field_stats?.actual_stats ?? {}; - const topValues = aggregations?.sample.sampled_top?.buckets ?? []; + const docCount = aggregations?.sampled_field_stats?.doc_count ?? 0; + const fieldStatsResp = aggregations?.sampled_field_stats?.actual_stats ?? {}; + const topValues = aggregations?.sampled_top?.buckets ?? []; const stats: NumericFieldStats = { fieldName: field.fieldName, @@ -110,20 +93,9 @@ export const fetchNumericFieldStats = async ( topValues, topValuesSampleSize: topValues.reduce( (acc: number, curr: TopValueBucket) => acc + curr.doc_count, - aggregations.sample.sampled_top?.sum_other_doc_count ?? 0 + aggregations.sampled_top?.sum_other_doc_count ?? 0 ), }; - if (stats.count !== undefined && stats.count > 0) { - const percentiles = aggregations?.sample.sampled_percentiles.values ?? []; - const medianPercentile: { value: number; key: number } | undefined = find( - percentiles, - { - key: 50, - } - ); - stats.median = medianPercentile !== undefined ? medianPercentile!.value : 0; - } - return stats; }; diff --git a/x-pack/plugins/apm/server/routes/correlations/queries/index.ts b/x-pack/plugins/apm/server/routes/correlations/queries/index.ts index 548127eb7647d..d2a86a20bd5c6 100644 --- a/x-pack/plugins/apm/server/routes/correlations/queries/index.ts +++ b/x-pack/plugins/apm/server/routes/correlations/queries/index.ts @@ -16,3 +16,4 @@ export { fetchTransactionDurationCorrelation } from './query_correlation'; export { fetchTransactionDurationCorrelationWithHistogram } from './query_correlation_with_histogram'; export { fetchTransactionDurationHistogramRangeSteps } from './query_histogram_range_steps'; export { fetchTransactionDurationRanges } from './query_ranges'; +export { fetchFieldValueFieldStats } from './field_stats/get_field_value_stats'; diff --git a/x-pack/plugins/apm/server/routes/correlations/route.ts b/x-pack/plugins/apm/server/routes/correlations/route.ts index b02a6fbc6b7a6..377fedf9d1813 100644 --- a/x-pack/plugins/apm/server/routes/correlations/route.ts +++ b/x-pack/plugins/apm/server/routes/correlations/route.ts @@ -19,6 +19,7 @@ import { fetchSignificantCorrelations, fetchTransactionDurationFieldCandidates, fetchTransactionDurationFieldValuePairs, + fetchFieldValueFieldStats, } from './queries'; import { fetchFieldsStats } from './queries/field_stats/get_fields_stats'; @@ -77,12 +78,12 @@ const fieldStatsRoute = createApmServerRoute({ transactionName: t.string, transactionType: t.string, }), - environmentRt, - kueryRt, - rangeRt, t.type({ fieldsToSample: t.array(t.string), }), + environmentRt, + kueryRt, + rangeRt, ]), }), options: { tags: ['access:apm'] }, @@ -112,6 +113,51 @@ const fieldStatsRoute = createApmServerRoute({ }, }); +const fieldValueStatsRoute = createApmServerRoute({ + endpoint: 'GET /internal/apm/correlations/field_value_stats', + params: t.type({ + query: t.intersection([ + t.partial({ + serviceName: t.string, + transactionName: t.string, + transactionType: t.string, + }), + environmentRt, + kueryRt, + rangeRt, + t.type({ + fieldName: t.string, + fieldValue: t.union([t.string, t.number]), + }), + ]), + }), + options: { tags: ['access:apm'] }, + handler: async (resources) => { + const { context } = resources; + if (!isActivePlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(INVALID_LICENSE); + } + + const { indices } = await setupRequest(resources); + const esClient = resources.context.core.elasticsearch.client.asCurrentUser; + + const { fieldName, fieldValue, ...params } = resources.params.query; + + return withApmSpan( + 'get_correlations_field_value_stats', + async () => + await fetchFieldValueFieldStats( + esClient, + { + ...params, + index: indices.transaction, + }, + { fieldName, fieldValue } + ) + ); + }, +}); + const fieldValuePairsRoute = createApmServerRoute({ endpoint: 'POST /internal/apm/correlations/field_value_pairs', params: t.type({ @@ -252,5 +298,6 @@ export const correlationsRouteRepository = createApmServerRouteRepository() .add(pValuesRoute) .add(fieldCandidatesRoute) .add(fieldStatsRoute) + .add(fieldValueStatsRoute) .add(fieldValuePairsRoute) .add(significantCorrelationsRoute); diff --git a/x-pack/plugins/apm/server/routes/correlations/utils/field_stats_utils.ts b/x-pack/plugins/apm/server/routes/correlations/utils/field_stats_utils.ts index 7f98f771c50e2..a60622583781b 100644 --- a/x-pack/plugins/apm/server/routes/correlations/utils/field_stats_utils.ts +++ b/x-pack/plugins/apm/server/routes/correlations/utils/field_stats_utils.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; - /* * Contains utility functions for building and processing queries. */ @@ -38,22 +36,3 @@ export function buildBaseFilterCriteria( return filterCriteria; } - -// Wraps the supplied aggregations in a sampler aggregation. -// A supplied samplerShardSize (the shard_size parameter of the sampler aggregation) -// of less than 1 indicates no sampling, and the aggs are returned as-is. -export function buildSamplerAggregation( - aggs: any, - samplerShardSize: number -): estypes.AggregationsAggregationContainer { - if (samplerShardSize < 1) { - return aggs; - } - - return { - sampler: { - shard_size: samplerShardSize, - }, - aggs, - }; -} From 27ef4b4a6d36e27b5f9e04bf3fdbda4147a81d79 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Tue, 7 Dec 2021 16:03:25 -0500 Subject: [PATCH 006/145] [Fleet] Handle ID's for preconfigured package policies (#120664) * Handle ID's for preconfigured package policies - For Fleet's default policies, add a hard-coded ID to the generated package policies - Require an ID value for all preconfigured package policies Resolves #120612 * Add required package_policies.id field to docs * Specify that id is unique in docs * Tweak docs * Fall back to UUID value if no preconfigured ID * Fix default fleet server policy id --- docs/settings/fleet-settings.asciidoc | 2 ++ .../fleet/common/constants/preconfiguration.ts | 6 ++++++ .../fleet/common/types/models/package_policy.ts | 1 + .../fleet/common/types/models/preconfiguration.ts | 1 + .../plugins/fleet/server/services/agent_policy.ts | 13 +++++++++++++ .../fleet/server/services/preconfiguration.test.ts | 1 + .../fleet/server/services/preconfiguration.ts | 4 +++- .../fleet/server/types/models/preconfiguration.ts | 1 + 8 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/settings/fleet-settings.asciidoc b/docs/settings/fleet-settings.asciidoc index f0dfeb619bb38..a088f31937cc8 100644 --- a/docs/settings/fleet-settings.asciidoc +++ b/docs/settings/fleet-settings.asciidoc @@ -87,6 +87,7 @@ Optional properties are: `data_output_id`:: ID of the output to send data (Need to be identical to `monitoring_output_id`) `monitoring_output_id`:: ID of the output to send monitoring data. (Need to be identical to `data_output_id`) `package_policies`:: List of integration policies to add to this policy. + `id`::: Unique ID of the integration policy. The ID may be a number or string. `name`::: (required) Name of the integration policy. `package`::: (required) Integration that this policy configures `name`:::: Name of the integration associated with this policy. @@ -128,6 +129,7 @@ xpack.fleet.agentPolicies: - package: name: system name: System Integration + id: preconfigured-system inputs: - type: system/metrics enabled: true diff --git a/x-pack/plugins/fleet/common/constants/preconfiguration.ts b/x-pack/plugins/fleet/common/constants/preconfiguration.ts index 3e7377477c93e..635345585f925 100644 --- a/x-pack/plugins/fleet/common/constants/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/constants/preconfiguration.ts @@ -30,12 +30,15 @@ type PreconfiguredAgentPolicyWithDefaultInputs = Omit< package_policies: Array>; }; +export const DEFAULT_SYSTEM_PACKAGE_POLICY_ID = 'default-system-policy'; + export const DEFAULT_AGENT_POLICY: PreconfiguredAgentPolicyWithDefaultInputs = { name: 'Default policy', namespace: 'default', description: 'Default agent policy created by Kibana', package_policies: [ { + id: DEFAULT_SYSTEM_PACKAGE_POLICY_ID, name: `${FLEET_SYSTEM_PACKAGE}-1`, package: { name: FLEET_SYSTEM_PACKAGE, @@ -47,12 +50,15 @@ export const DEFAULT_AGENT_POLICY: PreconfiguredAgentPolicyWithDefaultInputs = { monitoring_enabled: monitoringTypes, }; +export const DEFAULT_FLEET_SERVER_POLICY_ID = 'default-fleet-server-policy'; + 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: [ { + id: DEFAULT_FLEET_SERVER_POLICY_ID, name: `${FLEET_SERVER_PACKAGE}-1`, package: { name: FLEET_SERVER_PACKAGE, diff --git a/x-pack/plugins/fleet/common/types/models/package_policy.ts b/x-pack/plugins/fleet/common/types/models/package_policy.ts index df484646ef66b..75932fd4a790a 100644 --- a/x-pack/plugins/fleet/common/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/package_policy.ts @@ -56,6 +56,7 @@ export interface PackagePolicyInput extends Omit> & { + id?: string | number; name: string; package: Partial & { name: string }; inputs?: InputsOverride[]; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 69859855d74f0..1e0864f2f8338 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -7,6 +7,7 @@ import { uniq, omit } from 'lodash'; import uuid from 'uuid/v4'; +import uuidv5 from 'uuid/v5'; import type { ElasticsearchClient, SavedObjectsClientContract, @@ -57,8 +58,12 @@ import { agentPolicyUpdateEventHandler } from './agent_policy_update'; import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object'; import { appContextService } from './app_context'; import { getFullAgentPolicy } from './agent_policies'; + const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; +// UUID v5 values require a namespace +const UUID_V5_NAMESPACE = 'dde7c2de-1370-4c19-9975-b473d0e03508'; + class AgentPolicyService { private triggerAgentPolicyUpdatedEvent = async ( soClient: SavedObjectsClientContract, @@ -780,6 +785,7 @@ export async function addPackageToAgentPolicy( agentPolicy: AgentPolicy, defaultOutput: Output, packagePolicyName?: string, + packagePolicyId?: string | number, packagePolicyDescription?: string, transformPackagePolicy?: (p: NewPackagePolicy) => NewPackagePolicy, bumpAgentPolicyRevison = false @@ -803,7 +809,14 @@ export async function addPackageToAgentPolicy( ? transformPackagePolicy(basePackagePolicy) : basePackagePolicy; + // If an ID is provided via preconfiguration, use that value. Otherwise fall back to + // a UUID v5 value seeded from the agent policy's ID and the provided package policy name. + const id = packagePolicyId + ? String(packagePolicyId) + : uuidv5(`${agentPolicy.id}-${packagePolicyName}`, UUID_V5_NAMESPACE); + await packagePolicyService.create(soClient, esClient, newPackagePolicy, { + id, bumpRevision: bumpAgentPolicyRevison, skipEnsureInstalled: true, skipUniqueNameVerification: true, diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 4b87c0957c961..8324079e10da8 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -446,6 +446,7 @@ describe('policy preconfiguration', () => { id: 'test-id', package_policies: [ { + id: 'test-package', package: { name: 'test_package' }, name: 'Test package', }, diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 76fa7778eafa2..84c5c73524f17 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -404,6 +404,7 @@ async function addPreconfiguredPolicyPackages( agentPolicy: AgentPolicy, installedPackagePolicies: Array< Partial> & { + id?: string | number; name: string; installedPackage: Installation; inputs?: InputsOverride[]; @@ -413,7 +414,7 @@ async function addPreconfiguredPolicyPackages( bumpAgentPolicyRevison = false ) { // Add packages synchronously to avoid overwriting - for (const { installedPackage, name, description, inputs } of installedPackagePolicies) { + for (const { installedPackage, id, name, description, inputs } of installedPackagePolicies) { const packageInfo = await getPackageInfo({ savedObjectsClient: soClient, pkgName: installedPackage.name, @@ -427,6 +428,7 @@ async function addPreconfiguredPolicyPackages( agentPolicy, defaultOutput, name, + id, description, (policy) => preconfigurePackageInputs(policy, packageInfo, inputs), bumpAgentPolicyRevison diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index 3ba89f1e526b3..64ab8f8ee3a81 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -106,6 +106,7 @@ export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( monitoring_output_id: schema.maybe(schema.string()), package_policies: schema.arrayOf( schema.object({ + id: schema.maybe(schema.oneOf([schema.string(), schema.number()])), name: schema.string(), package: schema.object({ name: schema.string(), From 069f35bc3b63ea2dd4786ad69d7bdbea9d5f4f6e Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 7 Dec 2021 15:37:30 -0600 Subject: [PATCH 007/145] [Workplace Search] Refactor documentation links to use `docLinks` service directly (#120675) * Replace `sourceData` links with `docLinks` * Replace Getting Started link * Replace Custom Source Permissions link * Replace Indexing Schedule link * Replace Sync link * Replace Permissions link * Replace Sync link (Objects & Assets) * Replace Custom Source links * Replace External Identities links * Replace Doc Permissions links * Replace License Management links * Replace Security links --- .../license_callout/license_callout.tsx | 5 +- .../applications/workplace_search/routes.ts | 31 ----------- .../add_source/config_completed.tsx | 10 +--- .../add_source/configure_custom.tsx | 4 +- .../document_permissions_callout.tsx | 4 +- .../add_source/document_permissions_field.tsx | 6 +- .../components/add_source/save_custom.tsx | 10 ++-- .../content_sources/components/overview.tsx | 13 ++--- .../components/source_content.tsx | 4 +- .../components/source_layout.tsx | 4 +- .../components/synchronization/frequency.tsx | 4 +- .../synchronization/objects_and_assets.tsx | 4 +- .../synchronization/synchronization.tsx | 4 +- .../views/content_sources/source_data.tsx | 55 +++++++------------ .../views/content_sources/sources_view.tsx | 6 +- .../views/role_mappings/role_mappings.tsx | 6 +- .../settings/components/oauth_application.tsx | 4 +- .../views/setup_guide/setup_guide.tsx | 4 +- 18 files changed, 64 insertions(+), 114 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx index 270daf195bd38..7bf80b5ff9180 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/components/shared/license_callout/license_callout.tsx @@ -9,8 +9,9 @@ import React from 'react'; import { EuiLink, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; + import { EXPLORE_PLATINUM_FEATURES_LINK } from '../../../constants'; -import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../routes'; interface LicenseCalloutProps { message?: string; @@ -20,7 +21,7 @@ export const LicenseCallout: React.FC = ({ message }) => { const title = ( <> {message}{' '} - + {EXPLORE_PLATINUM_FEATURES_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts index 1b630a47e2f86..ee180ae52e0b7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/routes.ts @@ -7,8 +7,6 @@ import { generatePath } from 'react-router-dom'; -import { docLinks } from '../shared/doc_links'; - import { GITHUB_VIA_APP_SERVICE_TYPE, GITHUB_ENTERPRISE_SERVER_VIA_APP_SERVICE_TYPE, @@ -22,35 +20,6 @@ export const LOGOUT_ROUTE = '/logout'; export const LEAVE_FEEDBACK_EMAIL = 'support@elastic.co'; export const LEAVE_FEEDBACK_URL = `mailto:${LEAVE_FEEDBACK_EMAIL}?Subject=Elastic%20Workplace%20Search%20Feedback`; -export const BOX_DOCS_URL = docLinks.workplaceSearchBox; -export const CONFLUENCE_DOCS_URL = docLinks.workplaceSearchConfluenceCloud; -export const CONFLUENCE_SERVER_DOCS_URL = docLinks.workplaceSearchConfluenceServer; -export const CUSTOM_SOURCE_DOCS_URL = docLinks.workplaceSearchCustomSources; -export const CUSTOM_API_DOCUMENT_PERMISSIONS_DOCS_URL = - docLinks.workplaceSearchCustomSourcePermissions; -export const DIFFERENT_SYNC_TYPES_DOCS_URL = docLinks.workplaceSearchIndexingSchedule; -export const DOCUMENT_PERMISSIONS_DOCS_URL = docLinks.workplaceSearchDocumentPermissions; -export const DROPBOX_DOCS_URL = docLinks.workplaceSearchDropbox; -export const ENT_SEARCH_LICENSE_MANAGEMENT = docLinks.licenseManagement; -export const EXTERNAL_IDENTITIES_DOCS_URL = docLinks.workplaceSearchExternalIdentities; -export const GETTING_STARTED_DOCS_URL = docLinks.workplaceSearchGettingStarted; -export const GITHUB_DOCS_URL = docLinks.workplaceSearchGitHub; -export const GITHUB_ENTERPRISE_DOCS_URL = docLinks.workplaceSearchGitHub; -export const GMAIL_DOCS_URL = docLinks.workplaceSearchGmail; -export const GOOGLE_DRIVE_DOCS_URL = docLinks.workplaceSearchGoogleDrive; -export const JIRA_DOCS_URL = docLinks.workplaceSearchJiraCloud; -export const JIRA_SERVER_DOCS_URL = docLinks.workplaceSearchJiraServer; -export const OBJECTS_AND_ASSETS_DOCS_URL = docLinks.workplaceSearchSynch; -export const ONEDRIVE_DOCS_URL = docLinks.workplaceSearchOneDrive; -export const PRIVATE_SOURCES_DOCS_URL = docLinks.workplaceSearchPermissions; -export const SALESFORCE_DOCS_URL = docLinks.workplaceSearchSalesforce; -export const SECURITY_DOCS_URL = docLinks.workplaceSearchSecurity; -export const SERVICENOW_DOCS_URL = docLinks.workplaceSearchServiceNow; -export const SHAREPOINT_DOCS_URL = docLinks.workplaceSearchSharePoint; -export const SLACK_DOCS_URL = docLinks.workplaceSearchSlack; -export const SYNCHRONIZATION_DOCS_URL = docLinks.workplaceSearchSynch; -export const ZENDESK_DOCS_URL = docLinks.workplaceSearchZendesk; - export const PERSONAL_PATH = '/p'; export const OAUTH_AUTHORIZE_PATH = `${PERSONAL_PATH}/oauth/authorize`; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx index 167bf1af4b9b1..9b34053bfe524 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx @@ -21,13 +21,9 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../../shared/doc_links'; import { EuiLinkTo, EuiButtonTo } from '../../../../../shared/react_router_helpers'; -import { - getSourcesPath, - ADD_SOURCE_PATH, - SECURITY_PATH, - PRIVATE_SOURCES_DOCS_URL, -} from '../../../../routes'; +import { getSourcesPath, ADD_SOURCE_PATH, SECURITY_PATH } from '../../../../routes'; import { CONFIG_COMPLETED_PRIVATE_SOURCES_DISABLED_LINK, @@ -126,7 +122,7 @@ export const ConfigCompleted: React.FC = ({ {CONFIG_COMPLETED_PRIVATE_SOURCES_DOCS_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx index 4682d4329a964..e794323dc169e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configure_custom.tsx @@ -20,7 +20,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { CUSTOM_SOURCE_DOCS_URL } from '../../../../routes'; +import { docLinks } from '../../../../../shared/doc_links'; import { SOURCE_NAME_LABEL } from '../../constants'; @@ -63,7 +63,7 @@ export const ConfigureCustom: React.FC = ({ defaultMessage="{link} to learn more about Custom API Sources." values={{ link: ( - + {CONFIG_CUSTOM_LINK_TEXT} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_callout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_callout.tsx index 3c6980f74bcf5..d3879eabe08de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_callout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_callout.tsx @@ -17,8 +17,8 @@ import { EuiText, } from '@elastic/eui'; +import { docLinks } from '../../../../../shared/doc_links'; import { EXPLORE_PLATINUM_FEATURES_LINK } from '../../../../constants'; -import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../../routes'; import { SOURCE_FEATURES_DOCUMENT_LEVEL_PERMISSIONS_FEATURE, @@ -45,7 +45,7 @@ export const DocumentPermissionsCallout: React.FC = () => { - + {EXPLORE_PLATINUM_FEATURES_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_field.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_field.tsx index 1b1043ecbc3d2..1cc953ee7c2ea 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_field.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/document_permissions_field.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { DOCUMENT_PERMISSIONS_DOCS_URL } from '../../../../routes'; +import { docLinks } from '../../../../../shared/doc_links'; import { LEARN_MORE_LINK } from '../../constants'; import { @@ -42,7 +42,7 @@ export const DocumentPermissionsField: React.FC = ({ setValue, }) => { const whichDocsLink = ( - + {CONNECT_WHICH_OPTION_LINK} ); @@ -64,7 +64,7 @@ export const DocumentPermissionsField: React.FC = ({ defaultMessage="Document-level permissions are not yet available for this source. {link}" values={{ link: ( - + {LEARN_MORE_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index bbf1b66277c70..9dbbcc537fa31 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -24,14 +24,13 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../../shared/doc_links'; import { LicensingLogic } from '../../../../../shared/licensing'; import { EuiLinkTo } from '../../../../../shared/react_router_helpers'; import { LicenseBadge } from '../../../../components/shared/license_badge'; import { SOURCES_PATH, SOURCE_DISPLAY_SETTINGS_PATH, - CUSTOM_API_DOCUMENT_PERMISSIONS_DOCS_URL, - ENT_SEARCH_LICENSE_MANAGEMENT, getContentSourcePath, getSourcesPath, } from '../../../../routes'; @@ -178,7 +177,10 @@ export const SaveCustom: React.FC = ({ defaultMessage="{link} manage content access content on individual or group attributes. Allow or deny access to specific documents." values={{ link: ( - + {SAVE_CUSTOM_DOC_PERMISSIONS_LINK} ), @@ -189,7 +191,7 @@ export const SaveCustom: React.FC = ({ {!hasPlatinumLicense && ( - + {LEARN_CUSTOM_FEATURES_BUTTON} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index 29abbf94db397..d3714c2174b66 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -33,6 +33,7 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { CANCEL_BUTTON_LABEL, START_BUTTON_LABEL } from '../../../../shared/constants'; +import { docLinks } from '../../../../shared/doc_links'; import { EuiListGroupItemTo, EuiLinkTo } from '../../../../shared/react_router_helpers'; import { AppLogic } from '../../../app_logic'; import aclImage from '../../../assets/supports_acl.svg'; @@ -46,10 +47,6 @@ import { DOCUMENTATION_LINK_TITLE, } from '../../../constants'; import { - CUSTOM_SOURCE_DOCS_URL, - DOCUMENT_PERMISSIONS_DOCS_URL, - ENT_SEARCH_LICENSE_MANAGEMENT, - EXTERNAL_IDENTITIES_DOCS_URL, SYNC_FREQUENCY_PATH, BLOCKED_TIME_WINDOWS_PATH, getGroupPath, @@ -347,7 +344,7 @@ export const Overview: React.FC = () => { defaultMessage="{learnMoreLink} about permissions" values={{ learnMoreLink: ( - + {LEARN_MORE_LINK} ), @@ -408,7 +405,7 @@ export const Overview: React.FC = () => { defaultMessage="The {externalIdentitiesLink} must be used to configure user access mappings. Read the guide to learn more." values={{ externalIdentitiesLink: ( - + {EXTERNAL_IDENTITIES_LINK} ), @@ -466,7 +463,7 @@ export const Overview: React.FC = () => { - + {LEARN_CUSTOM_FEATURES_BUTTON} @@ -569,7 +566,7 @@ export const Overview: React.FC = () => { defaultMessage="{learnMoreLink} about custom sources." values={{ learnMoreLink: ( - + {LEARN_MORE_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx index 6b0e43fbce0c4..e37849033a144 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx @@ -31,12 +31,12 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../../shared/doc_links'; import { TruncatedContent } from '../../../../shared/truncate'; import { ComponentLoader } from '../../../components/shared/component_loader'; import { TablePaginationBar } from '../../../components/shared/table_pagination_bar'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; import { NAV, CUSTOM_SERVICE_TYPE } from '../../../constants'; -import { CUSTOM_SOURCE_DOCS_URL } from '../../../routes'; import { SourceContentItem } from '../../../types'; import { NO_CONTENT_MESSAGE, @@ -110,7 +110,7 @@ export const SourceContent: React.FC = () => { defaultMessage="Learn more about adding content in our {documentationLink}" values={{ documentationLink: ( - + {CUSTOM_DOCUMENTATION_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx index 663088f797c18..f741cfdc538fc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx @@ -12,11 +12,11 @@ import moment from 'moment'; import { EuiButton, EuiCallOut, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import { docLinks } from '../../../../shared/doc_links'; import { PageTemplateProps } from '../../../../shared/layout'; import { AppLogic } from '../../../app_logic'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../../components/layout'; import { NAV } from '../../../constants'; -import { ENT_SEARCH_LICENSE_MANAGEMENT } from '../../../routes'; import { SOURCE_DISABLED_CALLOUT_TITLE, @@ -53,7 +53,7 @@ export const SourceLayout: React.FC = ({ <>

{SOURCE_DISABLED_CALLOUT_DESCRIPTION}

- + {SOURCE_DISABLED_CALLOUT_BUTTON}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx index db4f80dc37f4b..50651bdeb3e75 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/frequency.tsx @@ -21,10 +21,10 @@ import { } from '@elastic/eui'; import { SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; +import { docLinks } from '../../../../../shared/doc_links'; import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { NAV, RESET_BUTTON } from '../../../../constants'; -import { DIFFERENT_SYNC_TYPES_DOCS_URL } from '../../../../routes'; import { LEARN_MORE_LINK, SOURCE_FREQUENCY_DESCRIPTION, @@ -102,7 +102,7 @@ export const Frequency: React.FC = ({ tabId }) => { description={ <> {SOURCE_FREQUENCY_DESCRIPTION}{' '} - + {LEARN_MORE_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx index 2dfa2a6420f7f..460f7e7f42055 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/objects_and_assets.tsx @@ -22,10 +22,10 @@ import { } from '@elastic/eui'; import { SAVE_BUTTON_LABEL } from '../../../../../shared/constants'; +import { docLinks } from '../../../../../shared/doc_links'; import { UnsavedChangesPrompt } from '../../../../../shared/unsaved_changes_prompt'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { NAV, RESET_BUTTON } from '../../../../constants'; -import { OBJECTS_AND_ASSETS_DOCS_URL } from '../../../../routes'; import { LEARN_MORE_LINK, SYNC_MANAGEMENT_CONTENT_EXTRACTION_LABEL, @@ -87,7 +87,7 @@ export const ObjectsAndAssets: React.FC = () => { description={ <> {SOURCE_OBJECTS_AND_ASSETS_DESCRIPTION}{' '} - + {LEARN_MORE_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx index dec275adb3c50..2e777fa906dd6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/synchronization/synchronization.tsx @@ -11,9 +11,9 @@ import { useActions, useValues } from 'kea'; import { EuiCallOut, EuiLink, EuiPanel, EuiSwitch, EuiSpacer, EuiText } from '@elastic/eui'; +import { docLinks } from '../../../../../shared/doc_links'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; import { NAV } from '../../../../constants'; -import { SYNCHRONIZATION_DOCS_URL } from '../../../../routes'; import { LEARN_MORE_LINK, SOURCE_SYNCHRONIZATION_DESCRIPTION, @@ -68,7 +68,7 @@ export const Synchronization: React.FC = () => { description={ <> {SOURCE_SYNCHRONIZATION_DESCRIPTION}{' '} - + {LEARN_MORE_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx index 687461296ac9e..20a0673709b5a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_data.tsx @@ -7,6 +7,8 @@ import { i18n } from '@kbn/i18n'; +import { docLinks } from '../../../shared/doc_links'; + import { SOURCE_NAMES, SOURCE_OBJ_TYPES, GITHUB_LINK_TITLE } from '../../constants'; import { ADD_BOX_PATH, @@ -45,23 +47,6 @@ import { EDIT_SLACK_PATH, EDIT_ZENDESK_PATH, EDIT_CUSTOM_PATH, - BOX_DOCS_URL, - CONFLUENCE_DOCS_URL, - CONFLUENCE_SERVER_DOCS_URL, - GITHUB_ENTERPRISE_DOCS_URL, - DROPBOX_DOCS_URL, - GITHUB_DOCS_URL, - GMAIL_DOCS_URL, - GOOGLE_DRIVE_DOCS_URL, - JIRA_DOCS_URL, - JIRA_SERVER_DOCS_URL, - ONEDRIVE_DOCS_URL, - SALESFORCE_DOCS_URL, - SERVICENOW_DOCS_URL, - SHAREPOINT_DOCS_URL, - SLACK_DOCS_URL, - ZENDESK_DOCS_URL, - CUSTOM_SOURCE_DOCS_URL, } from '../../routes'; import { FeatureIds, SourceDataItem } from '../../types'; @@ -75,7 +60,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: BOX_DOCS_URL, + documentationUrl: docLinks.workplaceSearchBox, applicationPortalUrl: 'https://app.box.com/developers/console', }, objTypes: [SOURCE_OBJ_TYPES.FOLDERS, SOURCE_OBJ_TYPES.ALL_FILES], @@ -104,7 +89,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: true, - documentationUrl: CONFLUENCE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchConfluenceCloud, applicationPortalUrl: 'https://developer.atlassian.com/console/myapps/', }, objTypes: [ @@ -138,7 +123,7 @@ export const staticSourceData = [ isPublicKey: true, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: CONFLUENCE_SERVER_DOCS_URL, + documentationUrl: docLinks.workplaceSearchConfluenceServer, }, objTypes: [ SOURCE_OBJ_TYPES.PAGES, @@ -170,7 +155,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: DROPBOX_DOCS_URL, + documentationUrl: docLinks.workplaceSearchDropbox, applicationPortalUrl: 'https://www.dropbox.com/developers/apps', }, objTypes: [SOURCE_OBJ_TYPES.FOLDERS, SOURCE_OBJ_TYPES.ALL_FILES], @@ -200,7 +185,7 @@ export const staticSourceData = [ hasOauthRedirect: true, needsBaseUrl: false, needsConfiguration: true, - documentationUrl: GITHUB_DOCS_URL, + documentationUrl: docLinks.workplaceSearchGitHub, applicationPortalUrl: 'https://github.com/settings/developers', applicationLinkTitle: GITHUB_LINK_TITLE, }, @@ -242,7 +227,7 @@ export const staticSourceData = [ defaultMessage: 'GitHub Enterprise URL', } ), - documentationUrl: GITHUB_ENTERPRISE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchGitHub, applicationPortalUrl: 'https://github.com/settings/developers', applicationLinkTitle: GITHUB_LINK_TITLE, }, @@ -277,7 +262,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: GMAIL_DOCS_URL, + documentationUrl: docLinks.workplaceSearchGmail, applicationPortalUrl: 'https://console.developers.google.com/', }, objTypes: [SOURCE_OBJ_TYPES.EMAILS], @@ -295,7 +280,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: GOOGLE_DRIVE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchGoogleDrive, applicationPortalUrl: 'https://console.developers.google.com/', }, objTypes: [ @@ -328,7 +313,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: true, - documentationUrl: JIRA_DOCS_URL, + documentationUrl: docLinks.workplaceSearchJiraCloud, applicationPortalUrl: 'https://developer.atlassian.com/console/myapps/', }, objTypes: [ @@ -364,7 +349,7 @@ export const staticSourceData = [ isPublicKey: true, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: JIRA_SERVER_DOCS_URL, + documentationUrl: docLinks.workplaceSearchJiraServer, applicationPortalUrl: '', }, objTypes: [ @@ -399,7 +384,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: ONEDRIVE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchOneDrive, applicationPortalUrl: 'https://portal.azure.com/', }, objTypes: [SOURCE_OBJ_TYPES.FOLDERS, SOURCE_OBJ_TYPES.ALL_FILES], @@ -428,7 +413,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: SALESFORCE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchSalesforce, applicationPortalUrl: 'https://salesforce.com/', }, objTypes: [ @@ -464,7 +449,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: SALESFORCE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchSalesforce, applicationPortalUrl: 'https://test.salesforce.com/', }, objTypes: [ @@ -500,7 +485,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: false, needsBaseUrl: true, - documentationUrl: SERVICENOW_DOCS_URL, + documentationUrl: docLinks.workplaceSearchServiceNow, applicationPortalUrl: 'https://www.servicenow.com/my-account/sign-in.html', }, objTypes: [ @@ -533,7 +518,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: SHAREPOINT_DOCS_URL, + documentationUrl: docLinks.workplaceSearchSharePoint, applicationPortalUrl: 'https://portal.azure.com/', }, objTypes: [SOURCE_OBJ_TYPES.FOLDERS, SOURCE_OBJ_TYPES.SITES, SOURCE_OBJ_TYPES.ALL_FILES], @@ -562,7 +547,7 @@ export const staticSourceData = [ isPublicKey: false, hasOauthRedirect: true, needsBaseUrl: false, - documentationUrl: SLACK_DOCS_URL, + documentationUrl: docLinks.workplaceSearchSlack, applicationPortalUrl: 'https://api.slack.com/apps/', }, objTypes: [ @@ -585,7 +570,7 @@ export const staticSourceData = [ hasOauthRedirect: true, needsBaseUrl: false, needsSubdomain: true, - documentationUrl: ZENDESK_DOCS_URL, + documentationUrl: docLinks.workplaceSearchZendesk, applicationPortalUrl: 'https://www.zendesk.com/login/', }, objTypes: [SOURCE_OBJ_TYPES.TICKETS], @@ -617,7 +602,7 @@ export const staticSourceData = [ defaultMessage: 'To create a Custom API Source, provide a human-readable and descriptive name. The name will appear as-is in the various search experiences and management interfaces.', }), - documentationUrl: CUSTOM_SOURCE_DOCS_URL, + documentationUrl: docLinks.workplaceSearchCustomSources, applicationPortalUrl: '', }, accountContextOnly: false, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx index 8697f10f8afaf..a7c981dad9103 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx @@ -24,9 +24,9 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { docLinks } from '../../../shared/doc_links'; import { Loading } from '../../../shared/loading'; import { SourceIcon } from '../../components/shared/source_icon'; -import { EXTERNAL_IDENTITIES_DOCS_URL, DOCUMENT_PERMISSIONS_DOCS_URL } from '../../routes'; import { EXTERNAL_IDENTITIES_LINK, @@ -82,7 +82,7 @@ export const SourcesView: React.FC = ({ children }) => { values={{ addedSourceName, externalIdentitiesLink: ( - + {EXTERNAL_IDENTITIES_LINK} ), @@ -96,7 +96,7 @@ export const SourcesView: React.FC = ({ children }) => { defaultMessage="Documents will not be searchable from Workplace Search until user and group mappings have been configured. {documentPermissionsLink}." values={{ documentPermissionsLink: ( - + {DOCUMENT_PERMISSIONS_LINK} ), diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx index f7e578b1b4d23..c0362b44b618b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx @@ -12,6 +12,7 @@ import { useActions, useValues } from 'kea'; import { EuiSpacer } from '@elastic/eui'; import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { docLinks } from '../../../shared/doc_links'; import { RoleMappingsTable, RoleMappingsHeading, @@ -22,7 +23,6 @@ import { } from '../../../shared/role_mapping'; import { ROLE_MAPPINGS_TITLE } from '../../../shared/role_mapping/constants'; import { WorkplaceSearchPageTemplate } from '../../components/layout'; -import { SECURITY_DOCS_URL } from '../../routes'; import { ROLE_MAPPINGS_TABLE_HEADER } from './constants'; @@ -56,7 +56,7 @@ export const RoleMappings: React.FC = () => { const rolesEmptyState = ( ); @@ -65,7 +65,7 @@ export const RoleMappings: React.FC = () => {
initializeRoleMapping()} /> { @@ -100,7 +100,7 @@ export const OauthApplication: React.FC = () => { <> {NON_PLATINUM_OAUTH_DESCRIPTION} - + {EXPLORE_PLATINUM_FEATURES_LINK} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx index 009dbffafebd8..3c3a7085d7116 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/setup_guide/setup_guide.tsx @@ -12,14 +12,14 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { WORKPLACE_SEARCH_PLUGIN } from '../../../../../common/constants'; +import { docLinks } from '../../../shared/doc_links'; import { SetWorkplaceSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { SetupGuideLayout, SETUP_GUIDE_TITLE } from '../../../shared/setup_guide'; import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/telemetry'; -import { GETTING_STARTED_DOCS_URL } from '../../routes'; import GettingStarted from './assets/getting_started.png'; -const GETTING_STARTED_LINK_URL = GETTING_STARTED_DOCS_URL; +const GETTING_STARTED_LINK_URL = docLinks.workplaceSearchGettingStarted; export const SetupGuide: React.FC = () => { return ( From 2a937d0e03dea8a8f6035f2c620313e3d28e3e8f Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Tue, 7 Dec 2021 16:45:11 -0500 Subject: [PATCH 008/145] [APM] Auto attachment for java agent beta in APM integration settings (#119131) * wip * wip * wip * wip * wip * wip * wip * wip * adds initial discovery rule for `include-vmargs elastic.apm.attach=true` * adds support for type descriptions * adding java_attacher_agent_version field * fixing some stuff * adding link to doc * adding internationalization * updating user description * fixing default version * setting to null when disabled * - fixes encoding and decoding discovery rules yaml - adds workaround for extra 'elasticsearch' field on integration policy updates - updates migration package version from 7.16.0 to 8.0.0-dev4 * addressing pr comments * fixing ci * fixing elements not visible while dragging * addressing PR changes * beta * adding tooltip back * addressing pr comments Co-authored-by: cauemarcondes Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/apm/common/fleet.ts | 2 +- .../agent_instructions_accordion.tsx | 219 +++++--- .../apm_agents/agent_instructions_mappings.ts | 15 + .../fleet_integration/apm_agents/index.tsx | 15 +- ...template_strings.ts => render_mustache.ts} | 17 +- .../default_discovery_rule.tsx | 30 ++ .../runtime_attachment/discovery_rule.tsx | 125 +++++ .../edit_discovery_rule.tsx | 181 +++++++ .../apm_agents/runtime_attachment/index.tsx | 327 ++++++++++++ .../runtime_attachment.stories.tsx | 484 ++++++++++++++++++ .../runtime_attachment/runtime_attachment.tsx | 235 +++++++++ .../java_runtime_attachment.tsx | 276 ++++++++++ .../edit_package_policy_page/index.tsx | 3 +- 13 files changed, 1853 insertions(+), 76 deletions(-) rename x-pack/plugins/apm/public/components/fleet_integration/apm_agents/{replace_template_strings.ts => render_mustache.ts} (65%) create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/default_discovery_rule.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/discovery_rule.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx create mode 100644 x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/supported_agents/java_runtime_attachment.tsx diff --git a/x-pack/plugins/apm/common/fleet.ts b/x-pack/plugins/apm/common/fleet.ts index 00a958952d2de..bd8c6cf2653c2 100644 --- a/x-pack/plugins/apm/common/fleet.ts +++ b/x-pack/plugins/apm/common/fleet.ts @@ -8,7 +8,7 @@ import semverParse from 'semver/functions/parse'; export const POLICY_ELASTIC_AGENT_ON_CLOUD = 'policy-elastic-agent-on-cloud'; -export const SUPPORTED_APM_PACKAGE_VERSION = '7.16.0'; +export const SUPPORTED_APM_PACKAGE_VERSION = '8.0.0-dev4'; // TODO update to just '8.0.0' once published export function isPrereleaseVersion(version: string) { return semverParse(version)?.prerelease?.length ?? 0 > 0; diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx index 8f66658785b97..a82fa3121bb3b 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_accordion.tsx @@ -12,19 +12,29 @@ import { EuiSpacer, EuiText, EuiCodeBlock, + EuiTabbedContent, + EuiBetaBadge, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { CreateAgentInstructions } from './agent_instructions_mappings'; +import React, { ComponentType } from 'react'; +import styled from 'styled-components'; +import { + AgentRuntimeAttachmentProps, + CreateAgentInstructions, +} from './agent_instructions_mappings'; import { Markdown, useKibana, } from '../../../../../../../src/plugins/kibana_react/public'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; import { AgentIcon } from '../../shared/agent_icon'; -import { NewPackagePolicy } from '../apm_policy_form/typings'; +import type { + NewPackagePolicy, + PackagePolicy, + PackagePolicyEditExtensionComponentProps, +} from '../apm_policy_form/typings'; import { getCommands } from '../../../tutorial/config_agent/commands/get_commands'; -import { replaceTemplateStrings } from './replace_template_strings'; +import { renderMustache } from './render_mustache'; function AccordionButtonContent({ agentName, @@ -97,96 +107,175 @@ function TutorialConfigAgent({ } interface Props { + policy: PackagePolicy; newPolicy: NewPackagePolicy; + onChange: PackagePolicyEditExtensionComponentProps['onChange']; agentName: AgentName; title: string; variantId: string; createAgentInstructions: CreateAgentInstructions; + AgentRuntimeAttachment?: ComponentType; } +const StyledEuiAccordion = styled(EuiAccordion)` + // This is an alternative fix suggested by the EUI team to fix drag elements inside EuiAccordion + // This Issue tracks the fix on the Eui side https://github.com/elastic/eui/issues/3548#issuecomment-639041283 + .euiAccordion__childWrapper { + transform: none; + } +`; + export function AgentInstructionsAccordion({ + policy, newPolicy, + onChange, agentName, title, createAgentInstructions, variantId, + AgentRuntimeAttachment, }: Props) { const docLinks = useKibana().services.docLinks; const vars = newPolicy?.inputs?.[0]?.vars; const apmServerUrl = vars?.url.value; const secretToken = vars?.secret_token.value; const steps = createAgentInstructions(apmServerUrl, secretToken); + const stepsElements = steps.map( + ( + { title: stepTitle, textPre, textPost, customComponentName, commands }, + index + ) => { + const commandBlock = commands + ? renderMustache({ + text: commands, + docLinks, + }) + : ''; + + return ( +
+ +

{stepTitle}

+
+ + + {textPre && ( + + )} + {commandBlock && ( + <> + + + {commandBlock} + + + )} + {customComponentName === 'TutorialConfigAgent' && ( + + )} + {customComponentName === 'TutorialConfigAgentRumScript' && ( + + )} + {textPost && ( + <> + + + + )} + + +
+ ); + } + ); + + const manualInstrumentationContent = ( + <> + + {stepsElements} + + ); + return ( - } > - - {steps.map( - ( - { - title: stepTitle, - textPre, - textPost, - customComponentName, - commands, - }, - index - ) => { - const commandBlock = replaceTemplateStrings( - Array.isArray(commands) ? commands.join('\n') : commands || '', - docLinks - ); - return ( -
- -

{stepTitle}

-
- - - {textPre && ( - - )} - {commandBlock && ( - <> - - - {commandBlock} - - - )} - {customComponentName === 'TutorialConfigAgent' && ( - - )} - {customComponentName === 'TutorialConfigAgentRumScript' && ( - - )} - {textPost && ( + {AgentRuntimeAttachment ? ( + <> + + + + {i18n.translate( + 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.autoAttachment', + { defaultMessage: 'Auto-Attachment' } + )} + + + + + + ), + content: ( <> - - )} - - -
- ); - } + ), + }, + ]} + /> + + ) : ( + manualInstrumentationContent )} -
+ ); } diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts index 8bfdafe61d44e..5e992094ac64c 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/agent_instructions_mappings.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ComponentType } from 'react'; import { createDotNetAgentInstructions, createDjangoAgentInstructions, @@ -18,6 +19,18 @@ import { createRackAgentInstructions, } from '../../../../common/tutorial/instructions/apm_agent_instructions'; import { AgentName } from '../../../../typings/es_schemas/ui/fields/agent'; +import { JavaRuntimeAttachment } from './runtime_attachment/supported_agents/java_runtime_attachment'; +import { + NewPackagePolicy, + PackagePolicy, + PackagePolicyEditExtensionComponentProps, +} from '../apm_policy_form/typings'; + +export interface AgentRuntimeAttachmentProps { + policy: PackagePolicy; + newPolicy: NewPackagePolicy; + onChange: PackagePolicyEditExtensionComponentProps['onChange']; +} export type CreateAgentInstructions = ( apmServerUrl?: string, @@ -35,12 +48,14 @@ export const ApmAgentInstructionsMappings: Array<{ title: string; variantId: string; createAgentInstructions: CreateAgentInstructions; + AgentRuntimeAttachment?: ComponentType; }> = [ { agentName: 'java', title: 'Java', variantId: 'java', createAgentInstructions: createJavaAgentInstructions, + AgentRuntimeAttachment: JavaRuntimeAttachment, }, { agentName: 'rum-js', diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx index d6a43a1e1268a..09b638fb184df 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/index.tsx @@ -21,19 +21,28 @@ interface Props { onChange: PackagePolicyEditExtensionComponentProps['onChange']; } -export function ApmAgents({ newPolicy }: Props) { +export function ApmAgents({ policy, newPolicy, onChange }: Props) { return (
{ApmAgentInstructionsMappings.map( - ({ agentName, title, createAgentInstructions, variantId }) => ( + ({ + agentName, + title, + createAgentInstructions, + variantId, + AgentRuntimeAttachment, + }) => ( diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/render_mustache.ts similarity index 65% rename from x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts rename to x-pack/plugins/apm/public/components/fleet_integration/apm_agents/render_mustache.ts index d36d76d466308..ebf5fea7f2b85 100644 --- a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/replace_template_strings.ts +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/render_mustache.ts @@ -10,12 +10,17 @@ import Mustache from 'mustache'; const TEMPLATE_TAGS = ['{', '}']; -export function replaceTemplateStrings( - text: string, - docLinks?: CoreStart['docLinks'] -) { - Mustache.parse(text, TEMPLATE_TAGS); - return Mustache.render(text, { +export function renderMustache({ + text, + docLinks, +}: { + text: string | string[]; + docLinks?: CoreStart['docLinks']; +}) { + const template = Array.isArray(text) ? text.join('\n') : text; + + Mustache.parse(template, TEMPLATE_TAGS); + return Mustache.render(template, { config: { docs: { base_url: docLinks?.ELASTIC_WEBSITE_URL, diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/default_discovery_rule.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/default_discovery_rule.tsx new file mode 100644 index 0000000000000..848582bb3feb6 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/default_discovery_rule.tsx @@ -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 { + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiBadge, +} from '@elastic/eui'; +import React from 'react'; + +export function DefaultDiscoveryRule() { + return ( + + + + Exclude + + + Everything else + + + + ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/discovery_rule.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/discovery_rule.tsx new file mode 100644 index 0000000000000..f7b1b3db3a4c4 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/discovery_rule.tsx @@ -0,0 +1,125 @@ +/* + * 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 { + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiBadge, + EuiPanel, + DraggableProvidedDragHandleProps, + EuiButtonIcon, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { Operation } from '.'; + +interface Props { + id: string; + order: number; + operation: string; + type: string; + probe: string; + providedDragHandleProps?: DraggableProvidedDragHandleProps; + onDelete: (discoveryItemId: string) => void; + onEdit: (discoveryItemId: string) => void; + operationTypes: Operation[]; +} + +export function DiscoveryRule({ + id, + order, + operation, + type, + probe, + providedDragHandleProps, + onDelete, + onEdit, + operationTypes, +}: Props) { + const operationTypesLabels = useMemo(() => { + return operationTypes.reduce<{ + [operationValue: string]: { + label: string; + types: { [typeValue: string]: string }; + }; + }>((acc, current) => { + return { + ...acc, + [current.operation.value]: { + label: current.operation.label, + types: current.types.reduce((memo, { value, label }) => { + return { ...memo, [value]: label }; + }, {}), + }, + }; + }, {}); + }, [operationTypes]); + return ( + + + +
+ +
+
+ + + + {order} + + + + {operationTypesLabels[operation].label} + + + + + + +

{operationTypesLabels[operation].types[type]}

+
+
+ + {probe} + +
+
+ + + + { + onEdit(id); + }} + /> + + + { + onDelete(id); + }} + /> + + + +
+
+
+
+ ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx new file mode 100644 index 0000000000000..5059bbabfce91 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/edit_discovery_rule.tsx @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiButton, + EuiButtonEmpty, + EuiFormFieldset, + EuiSelect, + EuiFieldText, + EuiFormRow, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { + Operation, + DISCOVERY_RULE_TYPE_ALL, + STAGED_DISCOVERY_RULE_ID, +} from '.'; + +interface Props { + id: string; + onChangeOperation: (discoveryItemId: string) => void; + operation: string; + onChangeType: (discoveryItemId: string) => void; + type: string; + onChangeProbe: (discoveryItemId: string) => void; + probe: string; + onCancel: () => void; + onSubmit: () => void; + operationTypes: Operation[]; +} + +export function EditDiscoveryRule({ + id, + onChangeOperation, + operation, + onChangeType, + type, + onChangeProbe, + probe, + onCancel, + onSubmit, + operationTypes, +}: Props) { + return ( + + + + + ({ + text: item.operation.label, + value: item.operation.value, + }))} + value={operation} + onChange={(e) => { + onChangeOperation(e.target.value); + }} + /> + + + + + + + + + definedOperation.value === operation + ) + ?.types.map((item) => ({ + inputDisplay: item.label, + value: item.value, + dropdownDisplay: ( + <> + {item.label} + +

{item.description}

+
+ + ), + })) ?? [] + } + valueOfSelected={type} + onChange={onChangeType} + /> +
+
+
+
+ {type !== DISCOVERY_RULE_TYPE_ALL && ( + + + + + onChangeProbe(e.target.value)} + /> + + + + + )} + + + Cancel + + + + {id === STAGED_DISCOVERY_RULE_ID + ? i18n.translate( + 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.editRule.add', + { defaultMessage: 'Add' } + ) + : i18n.translate( + 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.editRule.save', + { defaultMessage: 'Save' } + )} + + + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx new file mode 100644 index 0000000000000..8f2a1d3d1dea1 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/index.tsx @@ -0,0 +1,327 @@ +/* + * 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 { + htmlIdGenerator, + euiDragDropReorder, + DropResult, + EuiComboBoxOptionOption, +} from '@elastic/eui'; +import React, { useState, useCallback, ReactNode } from 'react'; +import { RuntimeAttachment as RuntimeAttachmentStateless } from './runtime_attachment'; + +export const STAGED_DISCOVERY_RULE_ID = 'STAGED_DISCOVERY_RULE_ID'; +export const DISCOVERY_RULE_TYPE_ALL = 'all'; + +export interface IDiscoveryRule { + operation: string; + type: string; + probe: string; +} + +export type IDiscoveryRuleList = Array<{ + id: string; + discoveryRule: IDiscoveryRule; +}>; + +export interface RuntimeAttachmentSettings { + enabled: boolean; + discoveryRules: IDiscoveryRule[]; + version: string | null; +} + +interface Props { + onChange?: (runtimeAttachmentSettings: RuntimeAttachmentSettings) => void; + toggleDescription: ReactNode; + discoveryRulesDescription: ReactNode; + showUnsavedWarning?: boolean; + initialIsEnabled?: boolean; + initialDiscoveryRules?: IDiscoveryRule[]; + operationTypes: Operation[]; + selectedVersion: string; + versions: string[]; +} + +interface Option { + value: string; + label: string; + description?: string; +} + +export interface Operation { + operation: Option; + types: Option[]; +} + +const versionRegex = new RegExp(/^\d+\.\d+\.\d+$/); +function validateVersion(version: string) { + return versionRegex.test(version); +} + +export function RuntimeAttachment(props: Props) { + const { initialDiscoveryRules = [], onChange = () => {} } = props; + const [isEnabled, setIsEnabled] = useState(Boolean(props.initialIsEnabled)); + const [discoveryRuleList, setDiscoveryRuleList] = + useState( + initialDiscoveryRules.map((discoveryRule) => ({ + id: generateId(), + discoveryRule, + })) + ); + const [editDiscoveryRuleId, setEditDiscoveryRuleId] = useState( + null + ); + const [version, setVersion] = useState(props.selectedVersion); + const [versions, setVersions] = useState(props.versions); + const [isValidVersion, setIsValidVersion] = useState( + validateVersion(version) + ); + + const onToggleEnable = useCallback(() => { + const nextIsEnabled = !isEnabled; + setIsEnabled(nextIsEnabled); + onChange({ + enabled: nextIsEnabled, + discoveryRules: nextIsEnabled + ? discoveryRuleList.map(({ discoveryRule }) => discoveryRule) + : [], + version: nextIsEnabled ? version : null, + }); + }, [isEnabled, onChange, discoveryRuleList, version]); + + const onDelete = useCallback( + (discoveryRuleId: string) => { + const filteredDiscoveryRuleList = discoveryRuleList.filter( + ({ id }) => id !== discoveryRuleId + ); + setDiscoveryRuleList(filteredDiscoveryRuleList); + onChange({ + enabled: isEnabled, + discoveryRules: filteredDiscoveryRuleList.map( + ({ discoveryRule }) => discoveryRule + ), + version, + }); + }, + [isEnabled, discoveryRuleList, onChange, version] + ); + + const onEdit = useCallback( + (discoveryRuleId: string) => { + const editingDiscoveryRule = discoveryRuleList.find( + ({ id }) => id === discoveryRuleId + ); + if (editingDiscoveryRule) { + const { + discoveryRule: { operation, type, probe }, + } = editingDiscoveryRule; + setStagedOperationText(operation); + setStagedTypeText(type); + setStagedProbeText(probe); + setEditDiscoveryRuleId(discoveryRuleId); + } + }, + [discoveryRuleList] + ); + + const [stagedOperationText, setStagedOperationText] = useState(''); + const [stagedTypeText, setStagedTypeText] = useState(''); + const [stagedProbeText, setStagedProbeText] = useState(''); + + const onChangeOperation = useCallback( + (operationText: string) => { + setStagedOperationText(operationText); + const selectedOperationTypes = props.operationTypes.find( + ({ operation }) => operationText === operation.value + ); + const selectedTypeAvailable = selectedOperationTypes?.types.some( + ({ value }) => stagedTypeText === value + ); + if (!selectedTypeAvailable) { + setStagedTypeText(selectedOperationTypes?.types[0].value ?? ''); + } + }, + [props.operationTypes, stagedTypeText] + ); + + const onChangeType = useCallback((operationText: string) => { + setStagedTypeText(operationText); + if (operationText === DISCOVERY_RULE_TYPE_ALL) { + setStagedProbeText(''); + } + }, []); + + const onChangeProbe = useCallback((operationText: string) => { + setStagedProbeText(operationText); + }, []); + + const onCancel = useCallback(() => { + if (editDiscoveryRuleId === STAGED_DISCOVERY_RULE_ID) { + onDelete(STAGED_DISCOVERY_RULE_ID); + } + setEditDiscoveryRuleId(null); + }, [editDiscoveryRuleId, onDelete]); + + const onSubmit = useCallback(() => { + const editDiscoveryRuleIndex = discoveryRuleList.findIndex( + ({ id }) => id === editDiscoveryRuleId + ); + const editDiscoveryRule = discoveryRuleList[editDiscoveryRuleIndex]; + const nextDiscoveryRuleList = [ + ...discoveryRuleList.slice(0, editDiscoveryRuleIndex), + { + id: + editDiscoveryRule.id === STAGED_DISCOVERY_RULE_ID + ? generateId() + : editDiscoveryRule.id, + discoveryRule: { + operation: stagedOperationText, + type: stagedTypeText, + probe: stagedProbeText, + }, + }, + ...discoveryRuleList.slice(editDiscoveryRuleIndex + 1), + ]; + setDiscoveryRuleList(nextDiscoveryRuleList); + setEditDiscoveryRuleId(null); + onChange({ + enabled: isEnabled, + discoveryRules: nextDiscoveryRuleList.map( + ({ discoveryRule }) => discoveryRule + ), + version, + }); + }, [ + isEnabled, + editDiscoveryRuleId, + stagedOperationText, + stagedTypeText, + stagedProbeText, + discoveryRuleList, + onChange, + version, + ]); + + const onAddRule = useCallback(() => { + const firstOperationType = props.operationTypes[0]; + const operationText = firstOperationType.operation.value; + const typeText = firstOperationType.types[0].value; + const valueText = ''; + setStagedOperationText(operationText); + setStagedTypeText(typeText); + setStagedProbeText(valueText); + const nextDiscoveryRuleList = [ + { + id: STAGED_DISCOVERY_RULE_ID, + discoveryRule: { + operation: operationText, + type: typeText, + probe: valueText, + }, + }, + ...discoveryRuleList, + ]; + setDiscoveryRuleList(nextDiscoveryRuleList); + setEditDiscoveryRuleId(STAGED_DISCOVERY_RULE_ID); + }, [discoveryRuleList, props.operationTypes]); + + const onDragEnd = useCallback( + ({ source, destination }: DropResult) => { + if (source && destination) { + const nextDiscoveryRuleList = euiDragDropReorder( + discoveryRuleList, + source.index, + destination.index + ); + setDiscoveryRuleList(nextDiscoveryRuleList); + onChange({ + enabled: isEnabled, + discoveryRules: nextDiscoveryRuleList.map( + ({ discoveryRule }) => discoveryRule + ), + version, + }); + } + }, + [isEnabled, discoveryRuleList, onChange, version] + ); + + function onChangeVersion(nextVersion?: string) { + if (!nextVersion) { + return; + } + setVersion(nextVersion); + onChange({ + enabled: isEnabled, + discoveryRules: isEnabled + ? discoveryRuleList.map(({ discoveryRule }) => discoveryRule) + : [], + version: nextVersion, + }); + } + + function onCreateNewVersion( + newVersion: string, + flattenedOptions: Array> + ) { + const normalizedNewVersion = newVersion.trim().toLowerCase(); + const isNextVersionValid = validateVersion(normalizedNewVersion); + setIsValidVersion(isNextVersionValid); + if (!normalizedNewVersion || !isNextVersionValid) { + return; + } + + // Create the option if it doesn't exist. + if ( + flattenedOptions.findIndex( + (option) => option.label.trim().toLowerCase() === normalizedNewVersion + ) === -1 + ) { + setVersions([...versions, newVersion]); + } + + onChangeVersion(newVersion); + } + + return ( + { + const nextVersion: string | undefined = selectedVersions[0]?.label; + const isNextVersionValid = validateVersion(nextVersion); + setIsValidVersion(isNextVersionValid); + onChangeVersion(nextVersion); + }} + onCreateNewVersion={onCreateNewVersion} + isValidVersion={isValidVersion} + /> + ); +} + +const generateId = htmlIdGenerator(); diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx new file mode 100644 index 0000000000000..12f6705284ff9 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.stories.tsx @@ -0,0 +1,484 @@ +/* + * 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 { Meta, Story } from '@storybook/react'; +import React, { useState } from 'react'; +import { RuntimeAttachment } from '.'; +import { JavaRuntimeAttachment } from './supported_agents/java_runtime_attachment'; + +const stories: Meta<{}> = { + title: 'fleet/Runtime agent attachment', + component: RuntimeAttachment, + decorators: [ + (StoryComponent) => { + return ( +
+ +
+ ); + }, + ], +}; +export default stories; + +const excludeOptions = [ + { value: 'main', label: 'main class / jar name' }, + { value: 'vmarg', label: 'vmarg' }, + { value: 'user', label: 'user' }, +]; +const includeOptions = [{ value: 'all', label: 'All' }, ...excludeOptions]; + +const versions = ['1.27.1', '1.27.0', '1.26.0', '1.25.0']; + +export const RuntimeAttachmentExample: Story = () => { + const [runtimeAttachmentSettings, setRuntimeAttachmentSettings] = useState( + {} + ); + return ( + <> + { + setRuntimeAttachmentSettings(settings); + }} + toggleDescription="Attach the Java agent to running and starting Java applications." + discoveryRulesDescription="For every running JVM, the discovery rules are evaluated in the order they are provided. The first matching rule determines the outcome. Learn more in the docs" + showUnsavedWarning={true} + initialIsEnabled={true} + initialDiscoveryRules={[ + { + operation: 'include', + type: 'main', + probe: 'java-opbeans-10010', + }, + { + operation: 'exclude', + type: 'vmarg', + probe: '10948653898867', + }, + ]} + versions={versions} + selectedVersion={versions[0]} + /> +
+
{JSON.stringify(runtimeAttachmentSettings, null, 4)}
+ + ); +}; + +export const JavaRuntimeAttachmentExample: Story = () => { + return ( + {}} + /> + ); +}; + +const policy = { + id: 'cc380ec5-d84e-40e1-885a-d706edbdc968', + version: 'WzM0MzA2LDJd', + name: 'apm-1', + description: '', + namespace: 'default', + policy_id: 'policy-elastic-agent-on-cloud', + enabled: true, + output_id: '', + inputs: [ + { + type: 'apm', + policy_template: 'apmserver', + enabled: true, + streams: [], + vars: { + host: { + value: 'localhost:8200', + type: 'text', + }, + url: { + value: 'http://localhost:8200', + type: 'text', + }, + secret_token: { + type: 'text', + }, + api_key_enabled: { + value: false, + type: 'bool', + }, + enable_rum: { + value: true, + type: 'bool', + }, + anonymous_enabled: { + value: true, + type: 'bool', + }, + anonymous_allow_agent: { + value: ['rum-js', 'js-base', 'iOS/swift'], + type: 'text', + }, + anonymous_allow_service: { + value: [], + type: 'text', + }, + anonymous_rate_limit_event_limit: { + value: 10, + type: 'integer', + }, + anonymous_rate_limit_ip_limit: { + value: 10000, + type: 'integer', + }, + default_service_environment: { + type: 'text', + }, + rum_allow_origins: { + value: ['"*"'], + type: 'text', + }, + rum_allow_headers: { + value: [], + type: 'text', + }, + rum_response_headers: { + type: 'yaml', + }, + rum_library_pattern: { + value: '"node_modules|bower_components|~"', + type: 'text', + }, + rum_exclude_from_grouping: { + value: '"^/webpack"', + type: 'text', + }, + api_key_limit: { + value: 100, + type: 'integer', + }, + max_event_bytes: { + value: 307200, + type: 'integer', + }, + capture_personal_data: { + value: true, + type: 'bool', + }, + max_header_bytes: { + value: 1048576, + type: 'integer', + }, + idle_timeout: { + value: '45s', + type: 'text', + }, + read_timeout: { + value: '3600s', + type: 'text', + }, + shutdown_timeout: { + value: '30s', + type: 'text', + }, + write_timeout: { + value: '30s', + type: 'text', + }, + max_connections: { + value: 0, + type: 'integer', + }, + response_headers: { + type: 'yaml', + }, + expvar_enabled: { + value: false, + type: 'bool', + }, + tls_enabled: { + value: false, + type: 'bool', + }, + tls_certificate: { + type: 'text', + }, + tls_key: { + type: 'text', + }, + tls_supported_protocols: { + value: ['TLSv1.0', 'TLSv1.1', 'TLSv1.2'], + type: 'text', + }, + tls_cipher_suites: { + value: [], + type: 'text', + }, + tls_curve_types: { + value: [], + type: 'text', + }, + tail_sampling_policies: { + type: 'yaml', + }, + tail_sampling_interval: { + type: 'text', + }, + }, + config: { + 'apm-server': { + value: { + rum: { + source_mapping: { + metadata: [], + }, + }, + agent_config: [], + }, + }, + }, + compiled_input: { + 'apm-server': { + auth: { + anonymous: { + allow_agent: ['rum-js', 'js-base', 'iOS/swift'], + allow_service: null, + enabled: true, + rate_limit: { + event_limit: 10, + ip_limit: 10000, + }, + }, + api_key: { + enabled: false, + limit: 100, + }, + secret_token: null, + }, + capture_personal_data: true, + idle_timeout: '45s', + default_service_environment: null, + 'expvar.enabled': false, + host: 'localhost:8200', + max_connections: 0, + max_event_size: 307200, + max_header_size: 1048576, + read_timeout: '3600s', + response_headers: null, + rum: { + allow_headers: null, + allow_origins: ['*'], + enabled: true, + exclude_from_grouping: '^/webpack', + library_pattern: 'node_modules|bower_components|~', + response_headers: null, + }, + shutdown_timeout: '30s', + write_timeout: '30s', + }, + }, + }, + ], + package: { + name: 'apm', + title: 'Elastic APM', + version: '7.16.0', + }, + elasticsearch: { + privileges: { + cluster: ['cluster:monitor/main'], + }, + }, + revision: 1, + created_at: '2021-11-18T02:14:55.758Z', + created_by: 'admin', + updated_at: '2021-11-18T02:14:55.758Z', + updated_by: 'admin', +}; + +const newPolicy = { + version: 'WzM0MzA2LDJd', + name: 'apm-1', + description: '', + namespace: 'default', + policy_id: 'policy-elastic-agent-on-cloud', + enabled: true, + output_id: '', + package: { + name: 'apm', + title: 'Elastic APM', + version: '8.0.0-dev2', + }, + elasticsearch: { + privileges: { + cluster: ['cluster:monitor/main'], + }, + }, + inputs: [ + { + type: 'apm', + policy_template: 'apmserver', + enabled: true, + vars: { + host: { + value: 'localhost:8200', + type: 'text', + }, + url: { + value: 'http://localhost:8200', + type: 'text', + }, + secret_token: { + type: 'text', + }, + api_key_enabled: { + value: false, + type: 'bool', + }, + enable_rum: { + value: true, + type: 'bool', + }, + anonymous_enabled: { + value: true, + type: 'bool', + }, + anonymous_allow_agent: { + value: ['rum-js', 'js-base', 'iOS/swift'], + type: 'text', + }, + anonymous_allow_service: { + value: [], + type: 'text', + }, + anonymous_rate_limit_event_limit: { + value: 10, + type: 'integer', + }, + anonymous_rate_limit_ip_limit: { + value: 10000, + type: 'integer', + }, + default_service_environment: { + type: 'text', + }, + rum_allow_origins: { + value: ['"*"'], + type: 'text', + }, + rum_allow_headers: { + value: [], + type: 'text', + }, + rum_response_headers: { + type: 'yaml', + }, + rum_library_pattern: { + value: '"node_modules|bower_components|~"', + type: 'text', + }, + rum_exclude_from_grouping: { + value: '"^/webpack"', + type: 'text', + }, + api_key_limit: { + value: 100, + type: 'integer', + }, + max_event_bytes: { + value: 307200, + type: 'integer', + }, + capture_personal_data: { + value: true, + type: 'bool', + }, + max_header_bytes: { + value: 1048576, + type: 'integer', + }, + idle_timeout: { + value: '45s', + type: 'text', + }, + read_timeout: { + value: '3600s', + type: 'text', + }, + shutdown_timeout: { + value: '30s', + type: 'text', + }, + write_timeout: { + value: '30s', + type: 'text', + }, + max_connections: { + value: 0, + type: 'integer', + }, + response_headers: { + type: 'yaml', + }, + expvar_enabled: { + value: false, + type: 'bool', + }, + tls_enabled: { + value: false, + type: 'bool', + }, + tls_certificate: { + type: 'text', + }, + tls_key: { + type: 'text', + }, + tls_supported_protocols: { + value: ['TLSv1.0', 'TLSv1.1', 'TLSv1.2'], + type: 'text', + }, + tls_cipher_suites: { + value: [], + type: 'text', + }, + tls_curve_types: { + value: [], + type: 'text', + }, + tail_sampling_policies: { + type: 'yaml', + }, + tail_sampling_interval: { + type: 'text', + }, + }, + config: { + 'apm-server': { + value: { + rum: { + source_mapping: { + metadata: [], + }, + }, + agent_config: [], + }, + }, + }, + streams: [], + }, + ], +}; diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx new file mode 100644 index 0000000000000..3592eb4f04745 --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/runtime_attachment.tsx @@ -0,0 +1,235 @@ +/* + * 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 { + EuiCallOut, + EuiSpacer, + EuiSwitch, + EuiText, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiDragDropContext, + EuiDroppable, + EuiDraggable, + EuiIcon, + DropResult, + EuiComboBox, + EuiComboBoxProps, + EuiFormRow, +} from '@elastic/eui'; +import React, { ReactNode } from 'react'; +import { i18n } from '@kbn/i18n'; +import { DiscoveryRule } from './discovery_rule'; +import { DefaultDiscoveryRule } from './default_discovery_rule'; +import { EditDiscoveryRule } from './edit_discovery_rule'; +import { IDiscoveryRuleList, Operation } from '.'; + +interface Props { + isEnabled: boolean; + onToggleEnable: () => void; + discoveryRuleList: IDiscoveryRuleList; + setDiscoveryRuleList: (discoveryRuleItems: IDiscoveryRuleList) => void; + onDelete: (discoveryItemId: string) => void; + editDiscoveryRuleId: null | string; + onEdit: (discoveryItemId: string) => void; + onChangeOperation: (operationText: string) => void; + stagedOperationText: string; + onChangeType: (typeText: string) => void; + stagedTypeText: string; + onChangeProbe: (probeText: string) => void; + stagedProbeText: string; + onCancel: () => void; + onSubmit: () => void; + onAddRule: () => void; + operationTypes: Operation[]; + toggleDescription: ReactNode; + discoveryRulesDescription: ReactNode; + showUnsavedWarning?: boolean; + onDragEnd: (dropResult: DropResult) => void; + selectedVersion: string; + versions: string[]; + onChangeVersion: EuiComboBoxProps['onChange']; + onCreateNewVersion: EuiComboBoxProps['onCreateOption']; + isValidVersion: boolean; +} + +export function RuntimeAttachment({ + isEnabled, + onToggleEnable, + discoveryRuleList, + setDiscoveryRuleList, + onDelete, + editDiscoveryRuleId, + onEdit, + onChangeOperation, + stagedOperationText, + onChangeType, + stagedTypeText, + onChangeProbe, + stagedProbeText, + onCancel, + onSubmit, + onAddRule, + operationTypes, + toggleDescription, + discoveryRulesDescription, + showUnsavedWarning, + onDragEnd, + selectedVersion, + versions, + onChangeVersion, + onCreateNewVersion, + isValidVersion, +}: Props) { + return ( +
+ {showUnsavedWarning && ( + <> + + + + )} + + + + + +

{toggleDescription}

+
+
+ {isEnabled && versions && ( + + + ({ label: _version }))} + onChange={onChangeVersion} + onCreateOption={onCreateNewVersion} + singleSelection + isClearable={false} + /> + + + )} +
+ {isEnabled && ( + <> + + +

+ {i18n.translate( + 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.discoveryRules', + { defaultMessage: 'Discovery rules' } + )} +

+
+ + + + + + + +

{discoveryRulesDescription}

+
+
+ + + {i18n.translate( + 'xpack.apm.fleetIntegration.apmAgent.runtimeAttachment.addRule', + { defaultMessage: 'Add rule' } + )} + + +
+ + + + {discoveryRuleList.map(({ discoveryRule, id }, idx) => ( + + {(provided) => + id === editDiscoveryRuleId ? ( + + ) : ( + + ) + } + + ))} + + + + + )} + +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/supported_agents/java_runtime_attachment.tsx b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/supported_agents/java_runtime_attachment.tsx new file mode 100644 index 0000000000000..2284315d4a6ba --- /dev/null +++ b/x-pack/plugins/apm/public/components/fleet_integration/apm_agents/runtime_attachment/supported_agents/java_runtime_attachment.tsx @@ -0,0 +1,276 @@ +/* + * 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 yaml from 'js-yaml'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useCallback, useState, useMemo } from 'react'; +import { + RuntimeAttachment, + RuntimeAttachmentSettings, + IDiscoveryRule, +} from '..'; +import type { + NewPackagePolicy, + PackagePolicy, + PackagePolicyEditExtensionComponentProps, +} from '../../../apm_policy_form/typings'; + +interface Props { + policy: PackagePolicy; + newPolicy: NewPackagePolicy; + onChange: PackagePolicyEditExtensionComponentProps['onChange']; +} + +const excludeOptions = [ + { + value: 'main', + label: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.exclude.options.main', + { defaultMessage: 'main' } + ), + description: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.exclude.options.mainDescription', + { + defaultMessage: + 'A regular expression of fully qualified main class names or paths to JARs of applications the java agent should be attached to. Performs a partial match so that foo matches /bin/foo.jar.', + } + ), + }, + { + value: 'vmarg', + label: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.exclude.options.vmarg', + { defaultMessage: 'vmarg' } + ), + description: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.exclude.options.vmargDescription', + { + defaultMessage: + 'A regular expression matched against the arguments passed to the JVM, such as system properties. Performs a partial match so that attach=true matches the system property -Dattach=true.', + } + ), + }, + { + value: 'user', + label: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.exclude.options.user', + { defaultMessage: 'user' } + ), + description: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.exclude.options.userDescription', + { + defaultMessage: + 'A username that is matched against the operating system user that runs the JVM.', + } + ), + }, +]; +const includeOptions = [ + { + value: 'all', + label: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.include.options.all', + { defaultMessage: 'All' } + ), + description: i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.operationType.include.options.allDescription', + { defaultMessage: 'Includes all JVMs for attachment.' } + ), + }, + ...excludeOptions, +]; + +const versions = [ + '1.27.1', + '1.27.0', + '1.26.0', + '1.25.0', + '1.24.0', + '1.23.0', + '1.22.0', + '1.21.0', + '1.20.0', + '1.19.0', + '1.18.1', + '1.18.0', + '1.18.0.RC1', + '1.17.0', + '1.16.0', + '1.15.0', + '1.14.0', + '1.13.0', + '1.12.0', + '1.11.0', + '1.10.0', + '1.9.0', + '1.8.0', + '1.7.0', + '1.6.1', + '1.6.0', + '1.5.0', + '1.4.0', + '1.3.0', + '1.2.0', +]; + +function getApmVars(newPolicy: NewPackagePolicy) { + return newPolicy.inputs.find(({ type }) => type === 'apm')?.vars; +} + +export function JavaRuntimeAttachment({ newPolicy, onChange }: Props) { + const [isDirty, setIsDirty] = useState(false); + const onChangePolicy = useCallback( + (runtimeAttachmentSettings: RuntimeAttachmentSettings) => { + const apmInputIdx = newPolicy.inputs.findIndex( + ({ type }) => type === 'apm' + ); + onChange({ + isValid: true, + updatedPolicy: { + ...newPolicy, + inputs: [ + ...newPolicy.inputs.slice(0, apmInputIdx), + { + ...newPolicy.inputs[apmInputIdx], + vars: { + ...newPolicy.inputs[apmInputIdx].vars, + java_attacher_enabled: { + value: runtimeAttachmentSettings.enabled, + type: 'bool', + }, + java_attacher_discovery_rules: { + type: 'yaml', + value: encodeDiscoveryRulesYaml( + runtimeAttachmentSettings.discoveryRules + ), + }, + java_attacher_agent_version: { + type: 'text', + value: runtimeAttachmentSettings.version, + }, + }, + }, + ...newPolicy.inputs.slice(apmInputIdx + 1), + ], + }, + }); + setIsDirty(true); + }, + [newPolicy, onChange] + ); + + const apmVars = useMemo(() => getApmVars(newPolicy), [newPolicy]); + + return ( + + {i18n.translate( + 'xpack.apm.fleetIntegration.javaRuntime.discoveryRulesDescription.docLink', + { defaultMessage: 'docs' } + )} + + ), + }} + /> + } + showUnsavedWarning={isDirty} + initialIsEnabled={apmVars?.java_attacher_enabled?.value} + initialDiscoveryRules={decodeDiscoveryRulesYaml( + apmVars?.java_attacher_discovery_rules?.value ?? '[]\n', + [initialDiscoveryRule] + )} + selectedVersion={ + apmVars?.java_attacher_agent_version?.value || versions[0] + } + versions={versions} + /> + ); +} + +const initialDiscoveryRule = { + operation: 'include', + type: 'vmarg', + probe: 'elastic.apm.attach=true', +}; + +type DiscoveryRulesParsedYaml = Array<{ [operationType: string]: string }>; + +function decodeDiscoveryRulesYaml( + discoveryRulesYaml: string, + defaultDiscoveryRules: IDiscoveryRule[] = [] +): IDiscoveryRule[] { + try { + const parsedYaml: DiscoveryRulesParsedYaml = + yaml.load(discoveryRulesYaml) ?? []; + + if (parsedYaml.length === 0) { + return defaultDiscoveryRules; + } + + // transform into array of discovery rules + return parsedYaml.map((discoveryRuleMap) => { + const [operationType, probe] = Object.entries(discoveryRuleMap)[0]; + return { + operation: operationType.split('-')[0], + type: operationType.split('-')[1], + probe, + }; + }); + } catch (error) { + return defaultDiscoveryRules; + } +} + +function encodeDiscoveryRulesYaml(discoveryRules: IDiscoveryRule[]): string { + // transform into list of key,value objects for expected yaml result + const mappedDiscoveryRules: DiscoveryRulesParsedYaml = discoveryRules.map( + ({ operation, type, probe }) => ({ + [`${operation}-${type}`]: probe, + }) + ); + return yaml.dump(mappedDiscoveryRules); +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index c0914e41872b1..a7dd682384748 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -348,7 +348,8 @@ export const EditPackagePolicyForm = memo<{ const [formState, setFormState] = useState('INVALID'); const savePackagePolicy = async () => { setFormState('LOADING'); - const result = await sendUpdatePackagePolicy(packagePolicyId, packagePolicy); + const { elasticsearch, ...restPackagePolicy } = packagePolicy; // ignore 'elasticsearch' property since it fails route validation + const result = await sendUpdatePackagePolicy(packagePolicyId, restPackagePolicy); setFormState('SUBMITTED'); return result; }; From 905a4472ca9a5e9395708055530507a90ca9545d Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 7 Dec 2021 22:03:50 +0000 Subject: [PATCH 009/145] chore(NA): use internal pkg_npm on @kbn/es (#120637) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-es/BUILD.bazel | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kbn-es/BUILD.bazel b/packages/kbn-es/BUILD.bazel index 91dc48cec7d0c..2ea9c32858dd3 100644 --- a/packages/kbn-es/BUILD.bazel +++ b/packages/kbn-es/BUILD.bazel @@ -1,5 +1,5 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") -load("//src/dev/bazel:index.bzl", "jsts_transpiler") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm") PKG_BASE_NAME = "kbn-es" PKG_REQUIRE_NAME = "@kbn/es" From 8a8fe7ad14630d193da911f385a84a90cdcce512 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Tue, 7 Dec 2021 17:18:53 -0500 Subject: [PATCH 010/145] [Fleet] Use a fixed id for settings saved object (#120681) --- .../plugins/fleet/common/constants/settings.ts | 2 ++ x-pack/plugins/fleet/server/services/settings.ts | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/fleet/common/constants/settings.ts b/x-pack/plugins/fleet/common/constants/settings.ts index 772d938086938..423e71edf10e6 100644 --- a/x-pack/plugins/fleet/common/constants/settings.ts +++ b/x-pack/plugins/fleet/common/constants/settings.ts @@ -6,3 +6,5 @@ */ export const GLOBAL_SETTINGS_SAVED_OBJECT_TYPE = 'ingest_manager_settings'; + +export const GLOBAL_SETTINGS_ID = 'fleet-default-settings'; diff --git a/x-pack/plugins/fleet/server/services/settings.ts b/x-pack/plugins/fleet/server/services/settings.ts index 26d581f32d9a2..0e7b7c5e7a093 100644 --- a/x-pack/plugins/fleet/server/services/settings.ts +++ b/x-pack/plugins/fleet/server/services/settings.ts @@ -11,6 +11,7 @@ import type { SavedObjectsClientContract } from 'kibana/server'; import { decodeCloudId, GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + GLOBAL_SETTINGS_ID, normalizeHostsForAgents, } from '../../common'; import type { SettingsSOAttributes, Settings, BaseSettings } from '../../common'; @@ -80,10 +81,17 @@ export async function saveSettings( } catch (e) { if (e.isBoom && e.output.statusCode === 404) { const defaultSettings = createDefaultSettings(); - const res = await soClient.create(GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, { - ...defaultSettings, - ...data, - }); + const res = await soClient.create( + GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, + { + ...defaultSettings, + ...data, + }, + { + id: GLOBAL_SETTINGS_ID, + overwrite: true, + } + ); return { id: res.id, From e62dc02b3929abff3b05d973131bd495c342fd04 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 7 Dec 2021 15:23:47 -0700 Subject: [PATCH 011/145] [Reporting] fix unsupported platform crash (#120659) * [Reporting] fix unsupported platform crash * revert test code * improve types and add test * Apply suggestions from code review Co-authored-by: Michael Dokolin * updates per feedback - remove the error class member * add stack trace of error * revert change to guard a type Co-authored-by: Michael Dokolin --- .../screenshotting/server/plugin.test.ts | 76 +++++++++++++++++++ .../plugins/screenshotting/server/plugin.ts | 25 +++--- 2 files changed, 88 insertions(+), 13 deletions(-) create mode 100644 x-pack/plugins/screenshotting/server/plugin.test.ts diff --git a/x-pack/plugins/screenshotting/server/plugin.test.ts b/x-pack/plugins/screenshotting/server/plugin.test.ts new file mode 100644 index 0000000000000..22cd2c2f75ac5 --- /dev/null +++ b/x-pack/plugins/screenshotting/server/plugin.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +jest.mock('./browsers/install'); + +import type { CoreSetup, CoreStart, PluginInitializerContext } from 'kibana/server'; +import { coreMock } from 'src/core/server/mocks'; +import type { ScreenshotModePluginSetup } from 'src/plugins/screenshot_mode/server'; +import { install } from './browsers/install'; +import { ScreenshottingPlugin } from './plugin'; + +describe('ScreenshottingPlugin', () => { + let initContext: PluginInitializerContext; + let coreSetup: CoreSetup; + let coreStart: CoreStart; + let setupDeps: Parameters[1]; + let plugin: ScreenshottingPlugin; + + beforeEach(() => { + const configSchema = { + browser: { chromium: { disableSandbox: false } }, + }; + initContext = coreMock.createPluginInitializerContext(configSchema); + coreSetup = coreMock.createSetup({}); + coreStart = coreMock.createStart(); + setupDeps = { + screenshotMode: {} as ScreenshotModePluginSetup, + }; + plugin = new ScreenshottingPlugin(initContext); + }); + + describe('setup', () => { + test('returns a setup contract', async () => { + const setupContract = plugin.setup(coreSetup, setupDeps); + expect(setupContract).toEqual({}); + }); + + test('handles setup issues', async () => { + (install as jest.Mock).mockRejectedValue(`Unsupported platform!!!`); + + const setupContract = plugin.setup(coreSetup, setupDeps); + expect(setupContract).toEqual({}); + + await coreSetup.getStartServices(); + + const startContract = plugin.start(coreStart); + expect(startContract).toEqual( + expect.objectContaining({ + diagnose: expect.any(Function), + getScreenshots: expect.any(Function), + }) + ); + }); + }); + + describe('start', () => { + beforeEach(async () => { + plugin.setup(coreSetup, setupDeps); + await coreSetup.getStartServices(); + }); + + test('returns a start contract', async () => { + const startContract = plugin.start(coreStart); + expect(startContract).toEqual( + expect.objectContaining({ + diagnose: expect.any(Function), + getScreenshots: expect.any(Function), + }) + ); + }); + }); +}); diff --git a/x-pack/plugins/screenshotting/server/plugin.ts b/x-pack/plugins/screenshotting/server/plugin.ts index 53f855e1f544d..a301dd6764367 100755 --- a/x-pack/plugins/screenshotting/server/plugin.ts +++ b/x-pack/plugins/screenshotting/server/plugin.ts @@ -55,22 +55,21 @@ export class ScreenshottingPlugin implements Plugin { - try { - const paths = new ChromiumArchivePaths(); - const logger = this.logger.get('chromium'); - const [config, binaryPath] = await Promise.all([ - createConfig(this.logger, this.config), - install(paths, logger), - ]); + const paths = new ChromiumArchivePaths(); + const logger = this.logger.get('chromium'); + const [config, binaryPath] = await Promise.all([ + createConfig(this.logger, this.config), + install(paths, logger), + ]); - return new HeadlessChromiumDriverFactory(this.screenshotMode, config, logger, binaryPath); - } catch (error) { - this.logger.error('Error in screenshotting setup, it may not function properly.'); - - throw error; - } + return new HeadlessChromiumDriverFactory(this.screenshotMode, config, logger, binaryPath); })(); + this.browserDriverFactory.catch((error) => { + this.logger.error('Error in screenshotting setup, it may not function properly.'); + this.logger.error(error); + }); + return {}; } From 482538f7b05b41b9222e0e1f946678618e9ee7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 7 Dec 2021 17:33:07 -0500 Subject: [PATCH 012/145] [APM] Fixing service inventory unit test (#120665) --- .../service_inventory/service_list/index.tsx | 50 ++++++---- .../service_list/service_list.test.tsx | 91 +++++++++++-------- 2 files changed, 85 insertions(+), 56 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx index ea65c837a4177..fe91b14e64e8a 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_list/index.tsx @@ -66,9 +66,11 @@ export function getServiceColumns({ showTransactionTypeColumn, comparisonData, breakpoints, + showHealthStatusColumn, }: { query: TypeOf['query']; showTransactionTypeColumn: boolean; + showHealthStatusColumn: boolean; breakpoints: Breakpoints; comparisonData?: ServicesDetailedStatisticsAPIResponse; }): Array> { @@ -76,21 +78,25 @@ export function getServiceColumns({ const showWhenSmallOrGreaterThanLarge = isSmall || !isLarge; const showWhenSmallOrGreaterThanXL = isSmall || !isXl; return [ - { - field: 'healthStatus', - name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { - defaultMessage: 'Health', - }), - width: `${unit * 6}px`, - sortable: true, - render: (_, { healthStatus }) => { - return ( - - ); - }, - }, + ...(showHealthStatusColumn + ? [ + { + field: 'healthStatus', + name: i18n.translate('xpack.apm.servicesTable.healthColumnLabel', { + defaultMessage: 'Health', + }), + width: `${unit * 6}px`, + sortable: true, + render: (_, { healthStatus }) => { + return ( + + ); + }, + } as ITableColumn, + ] + : []), { field: 'serviceName', name: i18n.translate('xpack.apm.servicesTable.nameColumnLabel', { @@ -248,13 +254,17 @@ export function ServiceList({ showTransactionTypeColumn, comparisonData, breakpoints, + showHealthStatusColumn: displayHealthStatus, }), - [query, showTransactionTypeColumn, comparisonData, breakpoints] + [ + query, + showTransactionTypeColumn, + comparisonData, + breakpoints, + displayHealthStatus, + ] ); - const columns = displayHealthStatus - ? serviceColumns - : serviceColumns.filter((column) => column.field !== 'healthStatus'); const initialSortField = displayHealthStatus ? 'healthStatus' : 'transactionsPerMinute'; @@ -300,7 +310,7 @@ export function ServiceList({ { it('renders empty state', async () => { @@ -29,34 +55,10 @@ describe('ServiceList', () => { }); describe('responsive columns', () => { - const query = { - rangeFrom: 'now-15m', - rangeTo: 'now', - environment: ENVIRONMENT_ALL.value, - kuery: '', - }; - - const service: any = { - serviceName: 'opbeans-python', - agentName: 'python', - transactionsPerMinute: { - value: 86.93333333333334, - timeseries: [], - }, - errorsPerMinute: { - value: 12.6, - timeseries: [], - }, - avgResponseTime: { - value: 91535.42944785276, - timeseries: [], - }, - environments: ['test'], - transactionType: 'request', - }; describe('when small', () => { it('shows environment, transaction type and sparklines', () => { const renderedColumns = getServiceColumns({ + showHealthStatusColumn: true, query, showTransactionTypeColumn: true, breakpoints: { @@ -91,6 +93,7 @@ describe('ServiceList', () => { describe('when Large', () => { it('hides environment, transaction type and sparklines', () => { const renderedColumns = getServiceColumns({ + showHealthStatusColumn: true, query, showTransactionTypeColumn: true, breakpoints: { @@ -114,6 +117,7 @@ describe('ServiceList', () => { describe('when XL', () => { it('hides transaction type', () => { const renderedColumns = getServiceColumns({ + showHealthStatusColumn: true, query, showTransactionTypeColumn: true, breakpoints: { @@ -147,6 +151,7 @@ describe('ServiceList', () => { describe('when XXL', () => { it('hides transaction type', () => { const renderedColumns = getServiceColumns({ + showHealthStatusColumn: true, query, showTransactionTypeColumn: true, breakpoints: { @@ -181,20 +186,34 @@ describe('ServiceList', () => { }); describe('without ML data', () => { - it('sorts by throughput', async () => { - render(); - - expect(await screen.findByTitle('Throughput')).toBeInTheDocument(); + it('hides healthStatus column', () => { + const renderedColumns = getServiceColumns({ + showHealthStatusColumn: false, + query, + showTransactionTypeColumn: true, + breakpoints: { + isSmall: false, + isLarge: false, + isXl: false, + } as Breakpoints, + }).map((c) => c.field); + expect(renderedColumns.includes('healthStatus')).toBeFalsy(); }); }); describe('with ML data', () => { - it('renders the health column', async () => { - render(); - - expect( - await screen.findByRole('button', { name: /Health/ }) - ).toBeInTheDocument(); + it('shows healthStatus column', () => { + const renderedColumns = getServiceColumns({ + showHealthStatusColumn: true, + query, + showTransactionTypeColumn: true, + breakpoints: { + isSmall: false, + isLarge: false, + isXl: false, + } as Breakpoints, + }).map((c) => c.field); + expect(renderedColumns.includes('healthStatus')).toBeTruthy(); }); }); }); From 894f89d8ce9c074341ab8487ecfa651fb4de020c Mon Sep 17 00:00:00 2001 From: Orhan Toy Date: Wed, 8 Dec 2021 00:16:05 +0100 Subject: [PATCH 013/145] [App Search, Crawler] Crawler API GA in 8.0 (v0 -> v1) (#120643) The Crawler API will GA in 8.0 so we're changing v0 to v1 in the API paths. (The old v0 routes are still available but will trigger a deprecation warning in Enterprise Search) --- .../server/routes/app_search/crawler.test.ts | 30 +++++++++---------- .../server/routes/app_search/crawler.ts | 30 +++++++++---------- .../app_search/crawler_crawl_rules.test.ts | 6 ++-- .../routes/app_search/crawler_crawl_rules.ts | 6 ++-- .../app_search/crawler_entry_points.test.ts | 6 ++-- .../routes/app_search/crawler_entry_points.ts | 6 ++-- .../app_search/crawler_sitemaps.test.ts | 6 ++-- .../routes/app_search/crawler_sitemaps.ts | 6 ++-- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts index 3070be1e56b5b..c9212bca322d7 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.test.ts @@ -28,7 +28,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler', + path: '/api/as/v1/engines/:name/crawler', }); }); @@ -61,7 +61,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests', + path: '/api/as/v1/engines/:name/crawler/crawl_requests', }); }); @@ -94,7 +94,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests/:id', + path: '/api/as/v1/engines/:name/crawler/crawl_requests/:id', }); }); @@ -132,7 +132,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests', + path: '/api/as/v1/engines/:name/crawler/crawl_requests', }); }); @@ -165,7 +165,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/domains', + path: '/api/as/v1/engines/:name/crawler/domains', }); }); @@ -204,7 +204,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests/active/cancel', + path: '/api/as/v1/engines/:name/crawler/crawl_requests/active/cancel', }); }); @@ -237,7 +237,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/domains', + path: '/api/as/v1/engines/:name/crawler/domains', }); }); @@ -293,7 +293,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/domains/:id', + path: '/api/as/v1/engines/:name/crawler/domains/:id', }); }); @@ -339,7 +339,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/domains/:id', + path: '/api/as/v1/engines/:name/crawler/domains/:id', }); }); @@ -397,7 +397,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/domains/:id', + path: '/api/as/v1/engines/:name/crawler/domains/:id', }); }); @@ -435,7 +435,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/crawler/validate_url', + path: '/api/as/v1/crawler/validate_url', }); }); @@ -472,7 +472,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/process_crawls', + path: '/api/as/v1/engines/:name/crawler/process_crawls', }); }); @@ -519,7 +519,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_schedule', + path: '/api/as/v1/engines/:name/crawler/crawl_schedule', }); }); @@ -556,7 +556,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_schedule', + path: '/api/as/v1/engines/:name/crawler/crawl_schedule', }); }); @@ -611,7 +611,7 @@ describe('crawler routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:name/crawler/crawl_schedule', + path: '/api/as/v1/engines/:name/crawler/crawl_schedule', }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts index f53b15dadd061..f0fdc5c16098b 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler.ts @@ -23,7 +23,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler', + path: '/api/as/v1/engines/:name/crawler', }) ); @@ -37,7 +37,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests', + path: '/api/as/v1/engines/:name/crawler/crawl_requests', }) ); @@ -52,7 +52,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests/:id', + path: '/api/as/v1/engines/:name/crawler/crawl_requests/:id', }) ); @@ -66,7 +66,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests', + path: '/api/as/v1/engines/:name/crawler/crawl_requests', }) ); @@ -80,7 +80,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_requests/active/cancel', + path: '/api/as/v1/engines/:name/crawler/crawl_requests/active/cancel', }) ); @@ -98,7 +98,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/domains', + path: '/api/as/v1/engines/:name/crawler/domains', }) ); @@ -123,7 +123,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/domains', + path: '/api/as/v1/engines/:name/crawler/domains', }) ); @@ -138,7 +138,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/domains/:id', + path: '/api/as/v1/engines/:name/crawler/domains/:id', }) ); @@ -156,7 +156,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/domains/:id', + path: '/api/as/v1/engines/:name/crawler/domains/:id', }) ); @@ -183,7 +183,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/domains/:id', + path: '/api/as/v1/engines/:name/crawler/domains/:id', }) ); @@ -198,7 +198,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/crawler/validate_url', + path: '/api/as/v1/crawler/validate_url', }) ); @@ -215,7 +215,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/process_crawls', + path: '/api/as/v1/engines/:name/crawler/process_crawls', }) ); @@ -229,7 +229,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_schedule', + path: '/api/as/v1/engines/:name/crawler/crawl_schedule', }) ); @@ -247,7 +247,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_schedule', + path: '/api/as/v1/engines/:name/crawler/crawl_schedule', }) ); @@ -261,7 +261,7 @@ export function registerCrawlerRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:name/crawler/crawl_schedule', + path: '/api/as/v1/engines/:name/crawler/crawl_schedule', }) ); } diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts index 018ab433536b2..c3d1468687ec4 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.test.ts @@ -28,7 +28,7 @@ describe('crawler crawl rules routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/crawl_rules', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/crawl_rules', params: { respond_with: 'index', }, @@ -71,7 +71,7 @@ describe('crawler crawl rules routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', params: { respond_with: 'index', }, @@ -115,7 +115,7 @@ describe('crawler crawl rules routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', params: { respond_with: 'index', }, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts index 7c82c73db7263..26637623f0885 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_crawl_rules.ts @@ -29,7 +29,7 @@ export function registerCrawlerCrawlRulesRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/crawl_rules', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/crawl_rules', params: { respond_with: 'index', }, @@ -54,7 +54,7 @@ export function registerCrawlerCrawlRulesRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', params: { respond_with: 'index', }, @@ -73,7 +73,7 @@ export function registerCrawlerCrawlRulesRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/crawl_rules/:crawlRuleId', params: { respond_with: 'index', }, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts index 6fb7e99400877..dc7ad493a5149 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.test.ts @@ -28,7 +28,7 @@ describe('crawler entry point routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/entry_points', params: { respond_with: 'index', }, @@ -69,7 +69,7 @@ describe('crawler entry point routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', params: { respond_with: 'index', }, @@ -110,7 +110,7 @@ describe('crawler entry point routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', params: { respond_with: 'index', }, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts index a6d6fdb24b41f..fd81475c860ad 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_entry_points.ts @@ -27,7 +27,7 @@ export function registerCrawlerEntryPointRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/entry_points', params: { respond_with: 'index', }, @@ -49,7 +49,7 @@ export function registerCrawlerEntryPointRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', params: { respond_with: 'index', }, @@ -68,7 +68,7 @@ export function registerCrawlerEntryPointRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/entry_points/:entryPointId', params: { respond_with: 'index', }, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts index a37a8311093c7..3d6eb86bcba26 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.test.ts @@ -28,7 +28,7 @@ describe('crawler sitemap routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/sitemaps', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/sitemaps', params: { respond_with: 'index', }, @@ -69,7 +69,7 @@ describe('crawler sitemap routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', params: { respond_with: 'index', }, @@ -110,7 +110,7 @@ describe('crawler sitemap routes', () => { it('creates a request to enterprise search', () => { expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', params: { respond_with: 'index', }, diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts index b63473888eecc..0965acd967306 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/crawler_sitemaps.ts @@ -27,7 +27,7 @@ export function registerCrawlerSitemapRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/sitemaps', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/sitemaps', params: { respond_with: 'index', }, @@ -49,7 +49,7 @@ export function registerCrawlerSitemapRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', params: { respond_with: 'index', }, @@ -68,7 +68,7 @@ export function registerCrawlerSitemapRoutes({ }, }, enterpriseSearchRequestHandler.createRequest({ - path: '/api/as/v0/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', + path: '/api/as/v1/engines/:engineName/crawler/domains/:domainId/sitemaps/:sitemapId', params: { respond_with: 'index', }, From 5d44d79c2ba6bd0e15279b1cdf3fc5046f8e8c9d Mon Sep 17 00:00:00 2001 From: Davis Plumlee <56367316+dplumlee@users.noreply.github.com> Date: Tue, 7 Dec 2021 18:26:47 -0500 Subject: [PATCH 014/145] [Security Solution] Switches remaining rule types to use new Rule Preview API (#116374) --- x-pack/plugins/rule_registry/server/config.ts | 1 - .../rule_data_client/rule_data_client.mock.ts | 1 + .../rule_data_client/rule_data_client.ts | 4 + .../server/rule_data_client/types.ts | 1 + .../rule_data_plugin_service/index_info.ts | 12 +- .../rule_data_plugin_service/index_options.ts | 11 + .../security_solution/common/constants.ts | 4 +- .../detection_engine/constants.ts} | 14 +- .../schemas/request/rule_schemas.ts | 1 + .../detection_rules/threshold_rule.spec.ts | 4 +- .../cypress/screens/create_new_rule.ts | 6 +- .../cypress/tasks/create_new_rule.ts | 6 +- .../components/matrix_histogram/types.ts | 4 +- .../query_preview/custom_histogram.test.tsx | 128 ----- .../rules/query_preview/custom_histogram.tsx | 76 --- .../query_preview/eql_histogram.test.tsx | 152 ------ .../rules/query_preview/eql_histogram.tsx | 73 --- .../rules/query_preview/histogram.test.tsx | 74 --- .../rules/query_preview/histogram.tsx | 75 --- .../rules/query_preview/index.test.tsx | 502 ------------------ .../components/rules/query_preview/index.tsx | 362 ------------- .../rules/query_preview/reducer.test.ts | 502 ------------------ .../components/rules/query_preview/reducer.ts | 167 ------ .../threshold_histogram.test.tsx | 104 ---- .../query_preview/threshold_histogram.tsx | 82 --- .../rules/rule_preview/helpers.test.ts | 51 +- .../components/rules/rule_preview/helpers.ts | 11 +- .../rules/rule_preview/index.test.tsx | 30 +- .../components/rules/rule_preview/index.tsx | 27 +- .../rule_preview/preview_histogram.test.tsx | 7 + .../rules/rule_preview/preview_histogram.tsx | 61 ++- .../rule_preview/use_preview_histogram.tsx | 45 +- .../rules/rule_preview/use_preview_route.tsx | 18 +- .../rules/step_define_rule/index.test.tsx | 1 - .../rules/step_define_rule/index.tsx | 33 +- .../detection_engine/alerts/api.test.ts | 20 - .../containers/detection_engine/alerts/api.ts | 10 - .../rules/use_preview_rule.ts | 21 +- .../detection_engine/rules/create/helpers.ts | 15 +- .../rules/create/index.test.tsx | 1 - .../rules/details/index.test.tsx | 1 - .../index/create_preview_index_route.ts | 92 ---- .../routes/rules/preview_rules_route.ts | 140 +++-- .../create_security_rule_type_wrapper.ts | 16 +- .../rule_types/eql/create_eql_alert_type.ts | 4 +- .../create_indicator_match_alert_type.ts | 4 +- .../rule_types/ml/create_ml_alert_type.ts | 8 +- .../query/create_query_alert_type.ts | 4 +- .../threshold/create_threshold_alert_type.ts | 4 +- .../lib/detection_engine/rule_types/types.ts | 12 +- .../security_solution/server/plugin.ts | 97 ++-- .../security_solution/server/routes/index.ts | 23 +- .../security_and_spaces/tests/index.ts | 1 + .../tests/preview_rules.ts | 94 ++++ .../detection_engine_api_integration/utils.ts | 40 ++ 55 files changed, 614 insertions(+), 2643 deletions(-) rename x-pack/plugins/security_solution/{public/detections/containers/detection_engine/alerts/use_preview_index.tsx => common/detection_engine/constants.ts} (58%) delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.test.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/reducer.ts delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/threshold_histogram.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/detections/components/rules/query_preview/threshold_histogram.tsx delete mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/routes/index/create_preview_index_route.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/preview_rules.ts diff --git a/x-pack/plugins/rule_registry/server/config.ts b/x-pack/plugins/rule_registry/server/config.ts index 4b691c15d1b3c..7f3a3db42556e 100644 --- a/x-pack/plugins/rule_registry/server/config.ts +++ b/x-pack/plugins/rule_registry/server/config.ts @@ -32,4 +32,3 @@ export const config: PluginConfigDescriptor = { export type RuleRegistryPluginConfig = TypeOf; export const INDEX_PREFIX = '.alerts' as const; -export const INDEX_PREFIX_FOR_BACKING_INDICES = '.internal.alerts' as const; diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.mock.ts b/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.mock.ts index 63a159121e009..3daf5cd722ce9 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.mock.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.mock.ts @@ -29,6 +29,7 @@ export const createRuleDataClientMock = ( indexName, kibanaVersion: '7.16.0', isWriteEnabled: jest.fn(() => true), + indexNameWithNamespace: jest.fn((namespace: string) => indexName + namespace), // @ts-ignore 4.3.5 upgrade getReader: jest.fn((_options?: { namespace?: string }) => ({ diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts b/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts index 8ffe4e4db753f..4aa0126cdabf8 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/rule_data_client.ts @@ -54,6 +54,10 @@ export class RuleDataClient implements IRuleDataClient { return this.options.indexInfo.kibanaVersion; } + public indexNameWithNamespace(namespace: string): string { + return this.options.indexInfo.getPrimaryAlias(namespace); + } + private get writeEnabled(): boolean { return this._isWriteEnabled; } diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts index 5ddbd0035526d..5fab32eb38868 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts @@ -14,6 +14,7 @@ import { TechnicalRuleDataFieldName } from '../../common/technical_rule_data_fie export interface IRuleDataClient { indexName: string; + indexNameWithNamespace(namespace: string): string; kibanaVersion: string; isWriteEnabled(): boolean; getReader(options?: { namespace?: string }): IRuleDataReader; diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_info.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_info.ts index 52fef63a732f0..eca44c550411f 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_info.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_info.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { INDEX_PREFIX, INDEX_PREFIX_FOR_BACKING_INDICES } from '../config'; +import { INDEX_PREFIX } from '../config'; import { IndexOptions } from './index_options'; import { joinWithDash } from './utils'; @@ -23,16 +23,16 @@ interface ConstructorOptions { export class IndexInfo { constructor(options: ConstructorOptions) { const { indexOptions, kibanaVersion } = options; - const { registrationContext, dataset } = indexOptions; + const { registrationContext, dataset, additionalPrefix } = indexOptions; this.indexOptions = indexOptions; this.kibanaVersion = kibanaVersion; - this.baseName = joinWithDash(INDEX_PREFIX, `${registrationContext}.${dataset}`); - this.basePattern = joinWithDash(this.baseName, '*'); - this.baseNameForBackingIndices = joinWithDash( - INDEX_PREFIX_FOR_BACKING_INDICES, + this.baseName = joinWithDash( + `${additionalPrefix ?? ''}${INDEX_PREFIX}`, `${registrationContext}.${dataset}` ); + this.basePattern = joinWithDash(this.baseName, '*'); + this.baseNameForBackingIndices = `.internal${this.baseName}`; } /** diff --git a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_options.ts b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_options.ts index ba0961c7926a1..cdec7c609699d 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_options.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_plugin_service/index_options.ts @@ -95,6 +95,17 @@ export interface IndexOptions { * @example '.siem-signals', undefined */ secondaryAlias?: string; + + /** + * Optional prefix name that will be prepended to indices in addition to + * primary dataset and context naming convention. + * + * Currently used only for creating a preview index for the purpose of + * previewing alerts from a rule. The documents are identical to alerts, but + * shouldn't exist on an alert index and shouldn't be queried together with + * real alerts in any way, because the rule that created them doesn't exist + */ + additionalPrefix?: string; } /** diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 14e3c8cc95fe6..7fa387207e3ff 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -39,7 +39,7 @@ export const DEFAULT_APP_TIME_RANGE = 'securitySolution:timeDefaults' as const; export const DEFAULT_APP_REFRESH_INTERVAL = 'securitySolution:refreshIntervalDefaults' as const; export const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts' as const; export const DEFAULT_SIGNALS_INDEX = '.siem-signals' as const; -export const DEFAULT_PREVIEW_INDEX = '.siem-preview-signals' as const; +export const DEFAULT_PREVIEW_INDEX = '.preview.alerts-security.alerts' as const; export const DEFAULT_LISTS_INDEX = '.lists' as const; export const DEFAULT_ITEMS_INDEX = '.items' as const; // The DEFAULT_MAX_SIGNALS value exists also in `x-pack/plugins/cases/common/constants.ts` @@ -256,8 +256,6 @@ export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = export const DETECTION_ENGINE_RULES_BULK_ACTION = `${DETECTION_ENGINE_RULES_URL}/_bulk_action` as const; export const DETECTION_ENGINE_RULES_PREVIEW = `${DETECTION_ENGINE_RULES_URL}/preview` as const; -export const DETECTION_ENGINE_RULES_PREVIEW_INDEX_URL = - `${DETECTION_ENGINE_RULES_PREVIEW}/index` as const; /** * Internal detection engine routes diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_preview_index.tsx b/x-pack/plugins/security_solution/common/detection_engine/constants.ts similarity index 58% rename from x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_preview_index.tsx rename to x-pack/plugins/security_solution/common/detection_engine/constants.ts index 7a35e35acefe8..7f3c822800673 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_preview_index.tsx +++ b/x-pack/plugins/security_solution/common/detection_engine/constants.ts @@ -5,11 +5,9 @@ * 2.0. */ -import { useEffect } from 'react'; -import { createPreviewIndex } from './api'; - -export const usePreviewIndex = () => { - useEffect(() => { - createPreviewIndex(); - }, []); -}; +export enum RULE_PREVIEW_INVOCATION_COUNT { + HOUR = 20, + DAY = 24, + WEEK = 168, + MONTH = 30, +} diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts index c5f4e5631e5c8..97079253606f1 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts @@ -370,6 +370,7 @@ export const previewRulesSchema = t.intersection([ createTypeSpecific, t.type({ invocationCount: t.number }), ]); +export type PreviewRulesSchema = t.TypeOf; type UpdateSchema = SharedUpdateSchema & T; export type EqlUpdateSchema = UpdateSchema>; diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts index 02d8837261f2f..81022a43ff683 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_rules/threshold_rule.spec.ts @@ -174,7 +174,7 @@ describe('Detection rules, threshold', () => { cy.get(ALERT_GRID_CELL).contains(rule.name); }); - it('Preview results of keyword using "host.name"', () => { + it.skip('Preview results of keyword using "host.name"', () => { rule.index = [...rule.index, '.siem-signals*']; createCustomRuleActivated(getNewRule()); @@ -188,7 +188,7 @@ describe('Detection rules, threshold', () => { cy.get(PREVIEW_HEADER_SUBTITLE).should('have.text', '3 unique hits'); }); - it('Preview results of "ip" using "source.ip"', () => { + it.skip('Preview results of "ip" using "source.ip"', () => { const previewRule: ThresholdRule = { ...rule, thresholdField: 'source.ip', diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index aadaa5dfa0d88..a3e5e8af3f598 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -104,9 +104,9 @@ export const DEFINE_INDEX_INPUT = export const EQL_TYPE = '[data-test-subj="eqlRuleType"]'; -export const EQL_QUERY_INPUT = '[data-test-subj="eqlQueryBarTextInput"]'; +export const PREVIEW_HISTOGRAM = '[data-test-subj="preview-histogram-panel"]'; -export const EQL_QUERY_PREVIEW_HISTOGRAM = '[data-test-subj="queryPreviewEqlHistogram"]'; +export const EQL_QUERY_INPUT = '[data-test-subj="eqlQueryBarTextInput"]'; export const EQL_QUERY_VALIDATION_SPINNER = '[data-test-subj="eql-validation-loading"]'; @@ -170,7 +170,7 @@ export const RISK_OVERRIDE = export const RULES_CREATION_FORM = '[data-test-subj="stepDefineRule"]'; -export const RULES_CREATION_PREVIEW = '[data-test-subj="ruleCreationQueryPreview"]'; +export const RULES_CREATION_PREVIEW = '[data-test-subj="rule-preview"]'; export const RULE_DESCRIPTION_INPUT = '[data-test-subj="detectionEngineStepAboutRuleDescription"] [data-test-subj="input"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 68449363b8643..538f95c3c0a80 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -33,7 +33,6 @@ import { DEFAULT_RISK_SCORE_INPUT, DEFINE_CONTINUE_BUTTON, EQL_QUERY_INPUT, - EQL_QUERY_PREVIEW_HISTOGRAM, EQL_QUERY_VALIDATION_SPINNER, EQL_TYPE, FALSE_POSITIVES_INPUT, @@ -92,6 +91,7 @@ import { EMAIL_CONNECTOR_USER_INPUT, EMAIL_CONNECTOR_PASSWORD_INPUT, EMAIL_CONNECTOR_SERVICE_SELECTOR, + PREVIEW_HISTOGRAM, } from '../screens/create_new_rule'; import { TOAST_ERROR } from '../screens/shared'; import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline'; @@ -324,12 +324,12 @@ export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => { .find(QUERY_PREVIEW_BUTTON) .should('not.be.disabled') .click({ force: true }); - cy.get(EQL_QUERY_PREVIEW_HISTOGRAM) + cy.get(PREVIEW_HISTOGRAM) .invoke('text') .then((text) => { if (text !== 'Hits') { cy.get(RULES_CREATION_PREVIEW).find(QUERY_PREVIEW_BUTTON).click({ force: true }); - cy.get(EQL_QUERY_PREVIEW_HISTOGRAM).should('contain.text', 'Hits'); + cy.get(PREVIEW_HISTOGRAM).should('contain.text', 'Hits'); } }); cy.get(TOAST_ERROR).should('not.exist'); diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts index b67505a66be44..e5da55f740033 100644 --- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts @@ -15,7 +15,7 @@ import { MatrixHistogramType } from '../../../../common/search_strategy/security import { UpdateDateRange } from '../charts/common'; import { GlobalTimeArgs } from '../../containers/use_global_time'; import { DocValueFields } from '../../../../common/search_strategy'; -import { Threshold } from '../../../detections/components/rules/query_preview'; +import { FieldValueThreshold } from '../../../detections/components/rules/threshold_input'; export type MatrixHistogramMappingTypes = Record< string, @@ -77,7 +77,7 @@ export interface MatrixHistogramQueryProps { stackByField: string; startDate: string; histogramType: MatrixHistogramType; - threshold?: Threshold; + threshold?: FieldValueThreshold; skip?: boolean; isPtrIncluded?: boolean; includeMissingData?: boolean; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.test.tsx deleted file mode 100644 index 2e6991f87ec5a..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.test.tsx +++ /dev/null @@ -1,128 +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 React from 'react'; -import { mount } from 'enzyme'; - -import * as i18n from '../rule_preview/translations'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { TestProviders } from '../../../../common/mock'; -import { PreviewCustomQueryHistogram } from './custom_histogram'; - -jest.mock('../../../../common/containers/use_global_time'); - -describe('PreviewCustomQueryHistogram', () => { - const mockSetQuery = jest.fn(); - - beforeEach(() => { - (useGlobalTime as jest.Mock).mockReturnValue({ - from: '2020-07-07T08:20:18.966Z', - isInitializing: false, - to: '2020-07-08T08:20:18.966Z', - setQuery: mockSetQuery, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('it renders loader when isLoading is true', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeTruthy(); - expect( - wrapper.find('[dataTestSubj="queryPreviewCustomHistogram"]').at(0).prop('subtitle') - ).toEqual(i18n.QUERY_PREVIEW_SUBTITLE_LOADING); - }); - - test('it configures data and subtitle', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeFalsy(); - expect( - wrapper.find('[dataTestSubj="queryPreviewCustomHistogram"]').at(0).prop('subtitle') - ).toEqual(i18n.QUERY_PREVIEW_TITLE(9154)); - expect(wrapper.find('[dataTestSubj="queryPreviewCustomHistogram"]').at(0).props().data).toEqual( - [ - { - key: 'hits', - value: [ - { - g: 'All others', - x: 1602247050000, - y: 2314, - }, - { - g: 'All others', - x: 1602247162500, - y: 3471, - }, - { - g: 'All others', - x: 1602247275000, - y: 3369, - }, - ], - }, - ] - ); - }); - - test('it invokes setQuery with id, inspect, isLoading and refetch', async () => { - const mockRefetch = jest.fn(); - - mount( - - - - ); - - expect(mockSetQuery).toHaveBeenCalledWith({ - id: 'queryPreviewCustomHistogramQuery', - inspect: { dsl: ['some dsl'], response: ['query response'] }, - loading: false, - refetch: mockRefetch, - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx deleted file mode 100644 index 5392b08889128..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/custom_histogram.tsx +++ /dev/null @@ -1,76 +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 React, { useEffect, useMemo } from 'react'; - -import * as i18n from '../rule_preview/translations'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { getHistogramConfig } from '../rule_preview/helpers'; -import { - ChartSeriesConfigs, - ChartSeriesData, - ChartData, -} from '../../../../common/components/charts/common'; -import { InspectResponse } from '../../../../../public/types'; -import { inputsModel } from '../../../../common/store'; -import { PreviewHistogram } from './histogram'; - -export const ID = 'queryPreviewCustomHistogramQuery'; - -interface PreviewCustomQueryHistogramProps { - to: string; - from: string; - isLoading: boolean; - data: ChartData[]; - totalCount: number; - inspect: InspectResponse; - refetch: inputsModel.Refetch; -} - -export const PreviewCustomQueryHistogram = ({ - to, - from, - data, - totalCount, - inspect, - refetch, - isLoading, -}: PreviewCustomQueryHistogramProps) => { - const { setQuery, isInitializing } = useGlobalTime(); - - useEffect((): void => { - if (!isLoading && !isInitializing) { - setQuery({ id: ID, inspect, loading: isLoading, refetch }); - } - }, [setQuery, inspect, isLoading, isInitializing, refetch]); - - const barConfig = useMemo( - (): ChartSeriesConfigs => getHistogramConfig(to, from, true), - [from, to] - ); - - const subtitle = useMemo( - (): string => - isLoading ? i18n.QUERY_PREVIEW_SUBTITLE_LOADING : i18n.QUERY_PREVIEW_TITLE(totalCount), - [isLoading, totalCount] - ); - - const chartData = useMemo((): ChartSeriesData[] => [{ key: 'hits', value: data }], [data]); - - return ( - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.test.tsx deleted file mode 100644 index df32223fc7ec3..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.test.tsx +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { mount } from 'enzyme'; - -import * as i18n from '../rule_preview/translations'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { TestProviders } from '../../../../common/mock'; -import { PreviewEqlQueryHistogram } from './eql_histogram'; - -jest.mock('../../../../common/containers/use_global_time'); - -describe('PreviewEqlQueryHistogram', () => { - const mockSetQuery = jest.fn(); - - beforeEach(() => { - (useGlobalTime as jest.Mock).mockReturnValue({ - from: '2020-07-07T08:20:18.966Z', - isInitializing: false, - to: '2020-07-08T08:20:18.966Z', - setQuery: mockSetQuery, - }); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('it renders loader when isLoading is true', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeTruthy(); - expect( - wrapper.find('[dataTestSubj="queryPreviewEqlHistogram"]').at(0).prop('subtitle') - ).toEqual(i18n.QUERY_PREVIEW_SUBTITLE_LOADING); - }); - - test('it configures data and subtitle', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeFalsy(); - expect( - wrapper.find('[dataTestSubj="queryPreviewEqlHistogram"]').at(0).prop('subtitle') - ).toEqual(i18n.QUERY_PREVIEW_TITLE(9154)); - expect(wrapper.find('[dataTestSubj="queryPreviewEqlHistogram"]').at(0).props().data).toEqual([ - { - key: 'hits', - value: [ - { - g: 'All others', - x: 1602247050000, - y: 2314, - }, - { - g: 'All others', - x: 1602247162500, - y: 3471, - }, - { - g: 'All others', - x: 1602247275000, - y: 3369, - }, - ], - }, - ]); - }); - - test('it invokes setQuery with id, inspect, isLoading and refetch', async () => { - const mockRefetch = jest.fn(); - - mount( - - - - ); - - expect(mockSetQuery).toHaveBeenCalledWith({ - id: 'queryEqlPreviewHistogramQuery', - inspect: { dsl: ['some dsl'], response: ['query response'] }, - loading: false, - refetch: mockRefetch, - }); - }); - - test('it displays histogram', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeFalsy(); - expect( - wrapper.find('[data-test-subj="sharedPreviewQueryNoHistogramAvailable"]').exists() - ).toBeFalsy(); - expect(wrapper.find('[data-test-subj="sharedPreviewQueryHistogram"]').exists()).toBeTruthy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.tsx deleted file mode 100644 index eae2a593d5f25..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/eql_histogram.tsx +++ /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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useEffect, useMemo } from 'react'; - -import * as i18n from '../rule_preview/translations'; -import { getHistogramConfig } from '../rule_preview/helpers'; -import { - ChartSeriesData, - ChartSeriesConfigs, - ChartData, -} from '../../../../common/components/charts/common'; -import { InspectQuery } from '../../../../common/store/inputs/model'; -import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { inputsModel } from '../../../../common/store'; -import { PreviewHistogram } from './histogram'; - -export const ID = 'queryEqlPreviewHistogramQuery'; - -interface PreviewEqlQueryHistogramProps { - to: string; - from: string; - totalCount: number; - isLoading: boolean; - data: ChartData[]; - inspect: InspectQuery; - refetch: inputsModel.Refetch; -} - -export const PreviewEqlQueryHistogram = ({ - from, - to, - totalCount, - data, - inspect, - refetch, - isLoading, -}: PreviewEqlQueryHistogramProps) => { - const { setQuery, isInitializing } = useGlobalTime(); - - useEffect((): void => { - if (!isInitializing) { - setQuery({ id: ID, inspect, loading: false, refetch }); - } - }, [setQuery, inspect, isInitializing, refetch]); - - const barConfig = useMemo((): ChartSeriesConfigs => getHistogramConfig(to, from), [from, to]); - - const subtitle = useMemo( - (): string => - isLoading ? i18n.QUERY_PREVIEW_SUBTITLE_LOADING : i18n.QUERY_PREVIEW_TITLE(totalCount), - [isLoading, totalCount] - ); - - const chartData = useMemo((): ChartSeriesData[] => [{ key: 'hits', value: data }], [data]); - - return ( - - ); -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.test.tsx deleted file mode 100644 index 500a7f3d0e3db..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.test.tsx +++ /dev/null @@ -1,74 +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 React from 'react'; -import { mount } from 'enzyme'; - -import { TestProviders } from '../../../../common/mock'; -import { PreviewHistogram } from './histogram'; -import { getHistogramConfig } from '../rule_preview/helpers'; - -describe('PreviewHistogram', () => { - test('it renders loading icon if "isLoading" is true', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="sharedPreviewQueryHistogram"]').exists()).toBeFalsy(); - }); - - test('it renders chart if "isLoading" is true', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewLoading"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="sharedPreviewQueryHistogram"]').exists()).toBeTruthy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.tsx deleted file mode 100644 index 3391ed1c5560a..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/histogram.tsx +++ /dev/null @@ -1,75 +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 React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiLoadingChart } from '@elastic/eui'; -import styled from 'styled-components'; - -import { BarChart } from '../../../../common/components/charts/barchart'; -import { Panel } from '../../../../common/components/panel'; -import { HeaderSection } from '../../../../common/components/header_section'; -import { ChartSeriesData, ChartSeriesConfigs } from '../../../../common/components/charts/common'; - -const LoadingChart = styled(EuiLoadingChart)` - display: block; - margin: 0 auto; -`; - -interface PreviewHistogramProps { - id: string; - data: ChartSeriesData[]; - dataTestSubj?: string; - barConfig: ChartSeriesConfigs; - title: string; - subtitle: string; - disclaimer: string; - isLoading: boolean; -} - -export const PreviewHistogram = ({ - id, - data, - dataTestSubj, - barConfig, - title, - subtitle, - disclaimer, - isLoading, -}: PreviewHistogramProps) => { - return ( - <> - - - - - - - {isLoading ? ( - - ) : ( - - )} - - - <> - - -

{disclaimer}

-
- -
-
-
- - ); -}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx deleted file mode 100644 index f14bd5f7354d9..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.test.tsx +++ /dev/null @@ -1,502 +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 React from 'react'; -import { of } from 'rxjs'; -import { ThemeProvider } from 'styled-components'; -import { mount } from 'enzyme'; - -import { TestProviders } from '../../../../common/mock'; -import { useKibana } from '../../../../common/lib/kibana'; -import { PreviewQuery } from './'; -import { getMockEqlResponse } from '../../../../common/hooks/eql/eql_search_response.mock'; -import { useMatrixHistogram } from '../../../../common/containers/matrix_histogram'; -import { useEqlPreview } from '../../../../common/hooks/eql/'; -import { getMockTheme } from '../../../../common/lib/kibana/kibana_react.mock'; -import type { FilterMeta } from '@kbn/es-query'; - -const mockTheme = getMockTheme({ - eui: { - euiSuperDatePickerWidth: '180px', - }, -}); - -jest.mock('../../../../common/lib/kibana'); -jest.mock('../../../../common/containers/matrix_histogram'); -jest.mock('../../../../common/hooks/eql/'); - -describe('PreviewQuery', () => { - beforeEach(() => { - useKibana().services.notifications.toasts.addError = jest.fn(); - - useKibana().services.notifications.toasts.addWarning = jest.fn(); - - (useMatrixHistogram as jest.Mock).mockReturnValue([ - false, - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - (useKibana().services.data.search.search as jest.Mock).mockReturnValue( - of(getMockEqlResponse()) - ), - ]); - - (useEqlPreview as jest.Mock).mockReturnValue([ - false, - (useKibana().services.data.search.search as jest.Mock).mockReturnValue( - of(getMockEqlResponse()) - ), - { - inspect: { dsl: [], response: [] }, - totalCount: 1, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('it renders timeframe select and preview button on render', () => { - const wrapper = mount( - - - - ); - - expect(wrapper.find('[data-test-subj="queryPreviewSelect"]').exists()).toBeTruthy(); - expect( - wrapper.find('[data-test-subj="queryPreviewButton"] button').props().disabled - ).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeFalsy(); - }); - - test('it renders preview button disabled if "isDisabled" is true', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find('[data-test-subj="queryPreviewButton"] button').props().disabled - ).toBeTruthy(); - }); - - test('it renders preview button disabled if "query" is undefined', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find('[data-test-subj="queryPreviewButton"] button').props().disabled - ).toBeTruthy(); - }); - - test('it renders preview button enabled if query exists', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find('[data-test-subj="queryPreviewButton"] button').props().disabled - ).toBeFalsy(); - }); - - test('it renders preview button enabled if no query exists but filters do exist', () => { - const wrapper = mount( - - - - ); - - expect( - wrapper.find('[data-test-subj="queryPreviewButton"] button').props().disabled - ).toBeFalsy(); - }); - - test('it renders query histogram when rule type is query and preview button clicked', () => { - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - const mockCalls = (useKibana().services.data.search.search as jest.Mock).mock.calls; - - expect(mockCalls.length).toEqual(1); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeFalsy(); - }); - - test('it renders noise warning when rule type is query, timeframe is last hour and hit average is greater than 1/hour', async () => { - const wrapper = mount( - - - - ); - - (useMatrixHistogram as jest.Mock).mockReturnValue([ - false, - { - inspect: { dsl: [], response: [] }, - totalCount: 2, - refetch: jest.fn(), - data: [], - buckets: [], - }, - (useKibana().services.data.search.search as jest.Mock).mockReturnValue( - of(getMockEqlResponse()) - ), - ]); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - expect(wrapper.find('[data-test-subj="previewQueryWarning"]').exists()).toBeTruthy(); - }); - - test('it renders query histogram when rule type is saved_query and preview button clicked', () => { - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - const mockCalls = (useKibana().services.data.search.search as jest.Mock).mock.calls; - - expect(mockCalls.length).toEqual(1); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeFalsy(); - }); - - test('it renders eql histogram when preview button clicked and rule type is eql', () => { - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - const mockCalls = (useKibana().services.data.search.search as jest.Mock).mock.calls; - - expect(mockCalls.length).toEqual(1); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeTruthy(); - }); - - test('it renders noise warning when rule type is eql, timeframe is last hour and hit average is greater than 1/hour', async () => { - const wrapper = mount( - - - - ); - - (useEqlPreview as jest.Mock).mockReturnValue([ - false, - (useKibana().services.data.search.search as jest.Mock).mockReturnValue( - of(getMockEqlResponse()) - ), - { - inspect: { dsl: [], response: [] }, - totalCount: 2, - refetch: jest.fn(), - data: [], - buckets: [], - }, - ]); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - expect(wrapper.find('[data-test-subj="previewQueryWarning"]').exists()).toBeTruthy(); - }); - - test('it renders threshold histogram when preview button clicked, rule type is threshold, and threshold field is defined', () => { - const wrapper = mount( - - - - ); - - (useMatrixHistogram as jest.Mock).mockReturnValue([ - false, - { - inspect: { dsl: [], response: [] }, - totalCount: 500, - refetch: jest.fn(), - data: [], - buckets: [{ key: 'siem-kibana', doc_count: 500 }], - }, - (useKibana().services.data.search.search as jest.Mock).mockReturnValue( - of(getMockEqlResponse()) - ), - ]); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - const mockCalls = (useKibana().services.data.search.search as jest.Mock).mock.calls; - - expect(mockCalls.length).toEqual(1); - expect(wrapper.find('[data-test-subj="previewQueryWarning"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeFalsy(); - }); - - test('it renders noise warning when rule type is threshold, and threshold field is defined, timeframe is last hour and hit average is greater than 1/hour', async () => { - const wrapper = mount( - - - - ); - - (useMatrixHistogram as jest.Mock).mockReturnValue([ - false, - { - inspect: { dsl: [], response: [] }, - totalCount: 500, - refetch: jest.fn(), - data: [], - buckets: [ - { key: 'siem-kibana', doc_count: 200 }, - { key: 'siem-windows', doc_count: 300 }, - ], - }, - (useKibana().services.data.search.search as jest.Mock).mockReturnValue( - of(getMockEqlResponse()) - ), - ]); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - expect(wrapper.find('[data-test-subj="previewQueryWarning"]').exists()).toBeTruthy(); - }); - - test('it renders query histogram when preview button clicked, rule type is threshold, and threshold field is empty array', () => { - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - const mockCalls = (useKibana().services.data.search.search as jest.Mock).mock.calls; - - expect(mockCalls.length).toEqual(1); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeFalsy(); - }); - - test('it renders query histogram when preview button clicked, rule type is threshold, and threshold field is empty string', () => { - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - const mockCalls = (useKibana().services.data.search.search as jest.Mock).mock.calls; - - expect(mockCalls.length).toEqual(1); - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeTruthy(); - expect(wrapper.find('[data-test-subj="thresholdQueryPreviewHistogram"]').exists()).toBeFalsy(); - expect(wrapper.find('[data-test-subj="queryPreviewEqlHistogram"]').exists()).toBeFalsy(); - }); - - test('it hides histogram when timeframe changes', () => { - const wrapper = mount( - - - - ); - - wrapper.find('[data-test-subj="queryPreviewButton"] button').at(0).simulate('click'); - - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeTruthy(); - - wrapper - .find('[data-test-subj="queryPreviewTimeframeSelect"] select') - .at(0) - .simulate('change', { target: { value: 'd' } }); - - expect(wrapper.find('[data-test-subj="queryPreviewCustomHistogram"]').exists()).toBeFalsy(); - }); -}); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx deleted file mode 100644 index e7cc34ef49bef..0000000000000 --- a/x-pack/plugins/security_solution/public/detections/components/rules/query_preview/index.tsx +++ /dev/null @@ -1,362 +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 React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef } from 'react'; -import { Unit } from '@elastic/datemath'; -import styled from 'styled-components'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiSelect, - EuiFormRow, - EuiButton, - EuiCallOut, - EuiText, - EuiSpacer, -} from '@elastic/eui'; -import { debounce } from 'lodash/fp'; - -import { Type } from '@kbn/securitysolution-io-ts-alerting-types'; -import * as i18n from '../rule_preview/translations'; -import { useMatrixHistogram } from '../../../../common/containers/matrix_histogram'; -import { MatrixHistogramType } from '../../../../../common/search_strategy'; -import { FieldValueQueryBar } from '../query_bar'; -import { PreviewEqlQueryHistogram } from './eql_histogram'; -import { useEqlPreview } from '../../../../common/hooks/eql/'; -import { PreviewThresholdQueryHistogram } from './threshold_histogram'; -import { formatDate } from '../../../../common/components/super_date_picker'; -import { State, queryPreviewReducer } from './reducer'; -import { isNoisy } from '../rule_preview/helpers'; -import { PreviewCustomQueryHistogram } from './custom_histogram'; -import { FieldValueThreshold } from '../threshold_input'; - -const Select = styled(EuiSelect)` - width: ${({ theme }) => theme.eui.euiSuperDatePickerWidth}; -`; - -const PreviewButton = styled(EuiButton)` - margin-left: 0; -`; - -export const initialState: State = { - timeframeOptions: [], - showHistogram: false, - timeframe: 'h', - warnings: [], - queryFilter: undefined, - toTime: '', - fromTime: '', - queryString: '', - language: 'kuery', - filters: [], - thresholdFieldExists: false, - showNonEqlHistogram: false, -}; - -export type Threshold = FieldValueThreshold | undefined; - -interface PreviewQueryProps { - dataTestSubj: string; - idAria: string; - query: FieldValueQueryBar | undefined; - index: string[]; - ruleType: Type; - threshold: Threshold; - isDisabled: boolean; -} - -export const PreviewQuery = ({ - ruleType, - dataTestSubj, - idAria, - query, - index, - threshold, - isDisabled, -}: PreviewQueryProps) => { - const [ - eqlQueryLoading, - startEql, - { - totalCount: eqlQueryTotal, - data: eqlQueryData, - refetch: eqlQueryRefetch, - inspect: eqlQueryInspect, - }, - ] = useEqlPreview(); - - const [ - { - thresholdFieldExists, - showNonEqlHistogram, - timeframeOptions, - showHistogram, - timeframe, - warnings, - queryFilter, - toTime, - fromTime, - queryString, - }, - dispatch, - ] = useReducer(queryPreviewReducer(), { - ...initialState, - toTime: formatDate('now-1h'), - fromTime: formatDate('now'), - }); - const [ - isMatrixHistogramLoading, - { inspect, totalCount: matrixHistTotal, refetch, data: matrixHistoData, buckets }, - startNonEql, - ] = useMatrixHistogram({ - errorMessage: i18n.QUERY_PREVIEW_ERROR, - endDate: fromTime, - startDate: toTime, - filterQuery: queryFilter, - indexNames: index, - includeMissingData: false, - histogramType: MatrixHistogramType.events, - stackByField: 'event.category', - threshold: ruleType === 'threshold' ? threshold : undefined, - skip: true, - }); - - const setQueryInfo = useCallback( - (queryBar: FieldValueQueryBar | undefined, indices: string[], type: Type): void => { - dispatch({ - type: 'setQueryInfo', - queryBar, - index: indices, - ruleType: type, - }); - }, - [dispatch] - ); - - const debouncedSetQueryInfo = useRef(debounce(500, setQueryInfo)); - - const setTimeframeSelect = useCallback( - (selection: Unit): void => { - dispatch({ - type: 'setTimeframeSelect', - timeframe: selection, - }); - }, - [dispatch] - ); - - const setRuleTypeChange = useCallback( - (type: Type): void => { - dispatch({ - type: 'setResetRuleTypeChange', - ruleType: type, - }); - }, - [dispatch] - ); - - const setWarnings = useCallback( - (yikes: string[]): void => { - dispatch({ - type: 'setWarnings', - warnings: yikes, - }); - }, - [dispatch] - ); - - const setNoiseWarning = useCallback((): void => { - dispatch({ - type: 'setNoiseWarning', - }); - }, [dispatch]); - - const setShowHistogram = useCallback( - (show: boolean): void => { - dispatch({ - type: 'setShowHistogram', - show, - }); - }, - [dispatch] - ); - - const setThresholdValues = useCallback( - (thresh: Threshold, type: Type): void => { - dispatch({ - type: 'setThresholdQueryVals', - threshold: thresh, - ruleType: type, - }); - }, - [dispatch] - ); - - useEffect(() => { - debouncedSetQueryInfo.current(query, index, ruleType); - }, [index, query, ruleType]); - - useEffect((): void => { - setThresholdValues(threshold, ruleType); - }, [setThresholdValues, threshold, ruleType]); - - useEffect((): void => { - setRuleTypeChange(ruleType); - }, [ruleType, setRuleTypeChange]); - - useEffect((): void => { - switch (ruleType) { - case 'eql': - if (isNoisy(eqlQueryTotal, timeframe)) { - setNoiseWarning(); - } - break; - case 'threshold': - const totalHits = thresholdFieldExists ? buckets.length : matrixHistTotal; - if (isNoisy(totalHits, timeframe)) { - setNoiseWarning(); - } - break; - default: - if (isNoisy(matrixHistTotal, timeframe)) { - setNoiseWarning(); - } - } - }, [ - timeframe, - matrixHistTotal, - eqlQueryTotal, - ruleType, - setNoiseWarning, - thresholdFieldExists, - buckets.length, - ]); - - const handlePreviewEqlQuery = useCallback( - (to: string, from: string): void => { - startEql({ - index, - query: queryString, - from, - to, - interval: timeframe, - }); - }, - [startEql, index, queryString, timeframe] - ); - - const handleSelectPreviewTimeframe = useCallback( - ({ target: { value } }: React.ChangeEvent): void => { - setTimeframeSelect(value as Unit); - }, - [setTimeframeSelect] - ); - - const handlePreviewClicked = useCallback((): void => { - const to = formatDate('now'); - const from = formatDate(`now-1${timeframe}`); - - setWarnings([]); - setShowHistogram(true); - - if (ruleType === 'eql') { - handlePreviewEqlQuery(to, from); - } else { - startNonEql(to, from); - } - }, [setWarnings, setShowHistogram, ruleType, handlePreviewEqlQuery, startNonEql, timeframe]); - - const previewButtonDisabled = useMemo(() => { - return ( - isMatrixHistogramLoading || - eqlQueryLoading || - isDisabled || - query == null || - (query != null && query.query.query === '' && query.filters.length === 0) - ); - }, [eqlQueryLoading, isDisabled, isMatrixHistogramLoading, query]); - - return ( - <> - - - - - + /> diff --git a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx index 8fb4ede96a819..3305f05dd90f9 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/agent_keys/index.tsx @@ -75,7 +75,7 @@ export function AgentKeys() { {i18n.translate('xpack.apm.settings.agentKeys.descriptionText', { defaultMessage: - 'View and delete agent keys. An agent key sends requests on behalf of a user.', + 'View and delete APM agent keys. An APM agent key sends requests on behalf of a user.', })} @@ -84,7 +84,7 @@ export function AgentKeys() {

{i18n.translate('xpack.apm.settings.agentKeys.title', { - defaultMessage: 'Agent keys', + defaultMessage: 'APM agent keys', })}

@@ -99,7 +99,7 @@ export function AgentKeys() { {i18n.translate( 'xpack.apm.settings.agentKeys.createAgentKeyButton', { - defaultMessage: 'Create agent key', + defaultMessage: 'Create APM agent key', } )} @@ -123,11 +123,12 @@ export function AgentKeys() { setIsFlyoutVisible(false); refetchAgentKeys(); }} - onError={(keyName: string) => { + onError={(keyName: string, message: string) => { toasts.addDanger( i18n.translate('xpack.apm.settings.agentKeys.crate.failed', { - defaultMessage: 'Error creating agent key "{keyName}"', - values: { keyName }, + defaultMessage: + 'Error creating APM agent key "{keyName}". Error: "{message}"', + values: { keyName, message }, }) ); setIsFlyoutVisible(false); @@ -184,7 +185,7 @@ function AgentKeysContent({ {i18n.translate( 'xpack.apm.settings.agentKeys.agentKeysLoadingPromptTitle', { - defaultMessage: 'Loading Agent keys...', + defaultMessage: 'Loading APM agent keys...', } )} @@ -202,7 +203,7 @@ function AgentKeysContent({ {i18n.translate( 'xpack.apm.settings.agentKeys.agentKeysErrorPromptTitle', { - defaultMessage: 'Could not load agent keys.', + defaultMessage: 'Could not load APM agent keys.', } )} @@ -235,7 +236,7 @@ function AgentKeysContent({

{i18n.translate('xpack.apm.settings.agentKeys.emptyPromptBody', { defaultMessage: - 'Create keys to authorize agent requests to the APM Server.', + 'Create APM agent keys to authorize APM agent requests to the APM Server.', })}

} @@ -248,7 +249,7 @@ function AgentKeysContent({ {i18n.translate( 'xpack.apm.settings.agentKeys.createAgentKeyButton', { - defaultMessage: 'Create agent key', + defaultMessage: 'Create APM agent key', } )} diff --git a/x-pack/plugins/apm/public/hooks/use_current_user.ts b/x-pack/plugins/apm/public/hooks/use_current_user.ts new file mode 100644 index 0000000000000..6f7c999c01e86 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_current_user.ts @@ -0,0 +1,33 @@ +/* + * 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, useEffect } from 'react'; +import { useKibana } from '../../../../../src/plugins/kibana_react/public'; +import { ApmPluginStartDeps } from '../plugin'; +import { AuthenticatedUser } from '../../../security/common/model'; + +export function useCurrentUser() { + const { + services: { security }, + } = useKibana(); + + const [user, setUser] = useState(); + + useEffect(() => { + const getCurrentUser = async () => { + try { + const authenticatedUser = await security?.authc.getCurrentUser(); + setUser(authenticatedUser); + } catch { + setUser(undefined); + } + }; + getCurrentUser(); + }, [security?.authc]); + + return user; +} diff --git a/x-pack/plugins/apm/server/routes/agent_keys/create_agent_key.ts b/x-pack/plugins/apm/server/routes/agent_keys/create_agent_key.ts index 02207dad32efb..d83a7af2737cd 100644 --- a/x-pack/plugins/apm/server/routes/agent_keys/create_agent_key.ts +++ b/x-pack/plugins/apm/server/routes/agent_keys/create_agent_key.ts @@ -8,17 +8,14 @@ import Boom from '@hapi/boom'; import { ApmPluginRequestHandlerContext } from '../typings'; import { CreateApiKeyResponse } from '../../../common/agent_key_types'; +import { PrivilegeType } from '../../../common/privilege_type'; -const enum PrivilegeType { - SOURCEMAP = 'sourcemap:write', - EVENT = 'event:write', - AGENT_CONFIG = 'config_agent:read', -} +const resource = '*'; interface SecurityHasPrivilegesResponse { application: { apm: { - '-': { + [resource]: { [PrivilegeType.SOURCEMAP]: boolean; [PrivilegeType.EVENT]: boolean; [PrivilegeType.AGENT_CONFIG]: boolean; @@ -36,75 +33,56 @@ export async function createAgentKey({ context: ApmPluginRequestHandlerContext; requestBody: { name: string; - sourcemap?: boolean; - event?: boolean; - agentConfig?: boolean; + privileges: string[]; }; }) { + const { name, privileges } = requestBody; + const application = { + application: 'apm', + privileges, + resources: [resource], + }; + // Elasticsearch will allow a user without the right apm privileges to create API keys, but the keys won't validate // check first whether the user has the right privileges, and bail out early if not const { - body: { application, username, has_all_requested: hasRequiredPrivileges }, + body: { + application: userApplicationPrivileges, + username, + has_all_requested: hasRequiredPrivileges, + }, } = await context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges( { body: { - application: [ - { - application: 'apm', - privileges: [ - PrivilegeType.SOURCEMAP, - PrivilegeType.EVENT, - PrivilegeType.AGENT_CONFIG, - ], - resources: ['-'], - }, - ], + application: [application], }, } ); if (!hasRequiredPrivileges) { - const missingPrivileges = Object.entries(application.apm['-']) + const missingPrivileges = Object.entries( + userApplicationPrivileges.apm[resource] + ) .filter((x) => !x[1]) - .map((x) => x[0]) - .join(', '); - const error = `${username} is missing the following requested privilege(s): ${missingPrivileges}.\ - You might try with the superuser, or add the APM application privileges to the role of the authenticated user, eg.: - PUT /_security/role/my_role { + .map((x) => x[0]); + + const error = `${username} is missing the following requested privilege(s): ${missingPrivileges.join( + ', ' + )}.\ + You might try with the superuser, or add the missing APM application privileges to the role of the authenticated user, eg.: + PUT /_security/role/my_role + { ... "applications": [{ "application": "apm", - "privileges": ["sourcemap:write", "event:write", "config_agent:read"], - "resources": ["*"] + "privileges": ${JSON.stringify(missingPrivileges)}, + "resources": [${resource}] }], ... }`; throw Boom.internal(error); } - const { name = 'apm-key', sourcemap, event, agentConfig } = requestBody; - - const privileges: PrivilegeType[] = []; - if (!sourcemap && !event && !agentConfig) { - privileges.push( - PrivilegeType.SOURCEMAP, - PrivilegeType.EVENT, - PrivilegeType.AGENT_CONFIG - ); - } - - if (sourcemap) { - privileges.push(PrivilegeType.SOURCEMAP); - } - - if (event) { - privileges.push(PrivilegeType.EVENT); - } - - if (agentConfig) { - privileges.push(PrivilegeType.AGENT_CONFIG); - } - const body = { name, metadata: { @@ -114,13 +92,7 @@ export async function createAgentKey({ apm: { cluster: [], index: [], - applications: [ - { - application: 'apm', - privileges, - resources: ['*'], - }, - ], + applications: [application], }, }, }; diff --git a/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts b/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts index e2f86298efdca..1ccb63382de4e 100644 --- a/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts +++ b/x-pack/plugins/apm/server/routes/agent_keys/invalidate_agent_key.ts @@ -19,6 +19,7 @@ export async function invalidateAgentKey({ { body: { ids: [id], + owner: true, }, } ); diff --git a/x-pack/plugins/apm/server/routes/agent_keys/route.ts b/x-pack/plugins/apm/server/routes/agent_keys/route.ts index 44bbb22e703b5..5878ce75680ac 100644 --- a/x-pack/plugins/apm/server/routes/agent_keys/route.ts +++ b/x-pack/plugins/apm/server/routes/agent_keys/route.ts @@ -8,13 +8,13 @@ import Boom from '@hapi/boom'; import { i18n } from '@kbn/i18n'; import * as t from 'io-ts'; -import { toBooleanRt } from '@kbn/io-ts-utils/to_boolean_rt'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; import { getAgentKeys } from './get_agent_keys'; import { getAgentKeysPrivileges } from './get_agent_keys_privileges'; import { invalidateAgentKey } from './invalidate_agent_key'; import { createAgentKey } from './create_agent_key'; +import { privilegesTypeRt } from '../../../common/privilege_type'; const agentKeysRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/agent_keys', @@ -77,19 +77,13 @@ const invalidateAgentKeyRoute = createApmServerRoute({ }); const createAgentKeyRoute = createApmServerRoute({ - endpoint: 'POST /apm/agent_keys', + endpoint: 'POST /api/apm/agent_keys', options: { tags: ['access:apm', 'access:apm_write'] }, params: t.type({ - body: t.intersection([ - t.partial({ - sourcemap: toBooleanRt, - event: toBooleanRt, - agentConfig: toBooleanRt, - }), - t.type({ - name: t.string, - }), - ]), + body: t.type({ + name: t.string, + privileges: privilegesTypeRt, + }), }), handler: async (resources) => { const { context, params } = resources; From 2660e147bc3bba5c2dd818aaf911590391944a55 Mon Sep 17 00:00:00 2001 From: Adam Locke Date: Thu, 9 Dec 2021 11:57:25 -0500 Subject: [PATCH 089/145] [DOCS] Add kibana-verification-tool and CLI main page (#120836) * [DOCS] Add kibana-verification-tool and CLI main page * Change wording from generating to retrieving --- docs/user/commands/cli-commands.asciidoc | 8 ++++ .../kibana-verification-code.asciidoc | 44 +++++++++++++++++++ docs/user/setup.asciidoc | 2 + 3 files changed, 54 insertions(+) create mode 100644 docs/user/commands/cli-commands.asciidoc create mode 100644 docs/user/commands/kibana-verification-code.asciidoc diff --git a/docs/user/commands/cli-commands.asciidoc b/docs/user/commands/cli-commands.asciidoc new file mode 100644 index 0000000000000..35a25235bc238 --- /dev/null +++ b/docs/user/commands/cli-commands.asciidoc @@ -0,0 +1,8 @@ +[[cli-commands]] +== Command line tools + +{kib} provides the following tools for configuring security and performing other tasks from the command line: + +* <> + +include::kibana-verification-code.asciidoc[] \ No newline at end of file diff --git a/docs/user/commands/kibana-verification-code.asciidoc b/docs/user/commands/kibana-verification-code.asciidoc new file mode 100644 index 0000000000000..3ad1b0da51e2b --- /dev/null +++ b/docs/user/commands/kibana-verification-code.asciidoc @@ -0,0 +1,44 @@ +[[kibana-verification-code]] +=== kibana-verification-code + +The `kibana-verification-code` tool retrieves a verification code for enrolling +a {kib} instance with a secured {es} cluster. + +[discrete] +==== Synopsis + +[source,shell] +---- +bin/kibana-verification-code +[-V, --version] [-h, --help] +---- + +[discrete] +==== Description + +Use this command to retrieve a verification code for {kib}. You enter this code +in {kib} when manually configuring a secure connection with an {es} cluster. +This tool is useful if you don’t have access to the {kib} terminal output, such +as on a hosted environment. You can connect to a machine where {kib} is +running (such as using SSH) and retrieve a verification code that you enter in +{kib}. + +IMPORTANT: You must run this tool on the same machine where {kib} is running. + +[discrete] +[[kibana-verification-code-parameters]] +==== Parameters + +`-h, --help`:: Returns all of the command parameters. + +`-V, --version`:: Displays the {kib} version number. + +[discrete] +==== Examples + +The following command retrieves a verification code for {kib}. + +[source,shell] +---- +bin/kibana-verification-code +---- \ No newline at end of file diff --git a/docs/user/setup.asciidoc b/docs/user/setup.asciidoc index 546cc8f974865..87213249e0d97 100644 --- a/docs/user/setup.asciidoc +++ b/docs/user/setup.asciidoc @@ -70,3 +70,5 @@ include::monitoring/configuring-monitoring.asciidoc[leveloffset=+1] include::monitoring/monitoring-metricbeat.asciidoc[leveloffset=+2] include::monitoring/viewing-metrics.asciidoc[leveloffset=+2] include::monitoring/monitoring-kibana.asciidoc[leveloffset=+2] + +include::commands/cli-commands.asciidoc[] From 01f7f71551001fc963e2747541c78164ecffdcd1 Mon Sep 17 00:00:00 2001 From: Michael Dokolin Date: Thu, 9 Dec 2021 17:58:32 +0100 Subject: [PATCH 090/145] [Data] Fix public API exports (#120793) --- dev_docs/tutorials/expressions.mdx | 10 +++++----- src/plugins/data/common/search/aggs/agg_types.ts | 1 - .../data/common/search/aggs/agg_types_registry.ts | 2 -- .../data/common/search/aggs/aggs_service.ts | 2 -- .../common/search/aggs/metrics/percentile_ranks.ts | 3 ++- .../common/search/aggs/metrics/percentiles.test.ts | 2 +- .../data/common/search/aggs/metrics/percentiles.ts | 3 ++- .../search/aggs/metrics/percentiles_get_value.ts | 2 +- .../common/search/aggs/metrics/std_deviation.ts | 3 ++- src/plugins/data/common/search/aggs/types.ts | 4 ---- .../expressions/esaggs/request_handler.test.ts | 4 ++-- .../search/expressions/esaggs/request_handler.ts | 3 +-- .../data/common/search/expressions/esdsl.ts | 3 +-- .../common/search/expressions/kibana_context.ts | 1 - .../search_source/fetch/get_search_params.ts | 2 +- .../common/search/search_source/search_source.ts | 3 ++- .../data/common/search/search_source/types.ts | 2 +- .../data/common/search/tabify/get_columns.test.ts | 2 +- .../data/common/search/tabify/get_columns.ts | 2 +- .../common/search/tabify/response_writer.test.ts | 2 +- src/plugins/data/common/search/tabify/tabify.ts | 2 +- .../data/common/search/tabify/tabify_docs.ts | 2 +- src/plugins/data/common/search/tabify/types.ts | 1 - .../filters/create_filters_from_range_select.ts | 3 +-- .../create_filters_from_value_click.test.ts | 7 ++----- .../filters/create_filters_from_value_click.ts | 3 +-- .../public/autocomplete/autocomplete_service.ts | 4 ++-- .../providers/value_suggestion_provider.test.ts | 5 +++-- .../providers/value_suggestion_provider.ts | 2 +- src/plugins/data/public/plugin.ts | 2 +- .../public/query/filter_manager/filter_manager.ts | 6 +++++- .../data/public/query/filter_manager/types.ts | 14 -------------- .../data/public/query/lib/get_default_query.ts | 2 +- src/plugins/data/public/query/query_service.ts | 6 ++++-- .../query_string/query_string_manager.mock.ts | 2 +- .../query/saved_query/saved_query_service.test.ts | 2 +- .../query/saved_query/saved_query_service.ts | 2 +- .../state_sync/create_global_query_observable.ts | 4 ++-- .../query/timefilter/lib/diff_time_picker_vals.ts | 2 +- .../data/public/query/timefilter/timefilter.ts | 2 +- src/plugins/data/public/search/collectors/types.ts | 3 --- src/plugins/data/public/search/errors/types.ts | 2 +- .../data/public/search/fetch/handle_response.tsx | 2 +- src/plugins/data/public/search/mocks.ts | 2 +- src/plugins/data/public/search/search_service.ts | 2 +- .../data/public/search/search_source/mocks.ts | 2 +- src/plugins/data/public/search/session/mocks.ts | 3 ++- .../search/session/search_session_state.test.ts | 2 +- .../public/search/session/search_session_state.ts | 2 +- .../public/search/session/session_service.test.ts | 2 +- .../data/public/search/session/session_service.ts | 8 ++++---- src/plugins/data/public/types.ts | 2 +- .../filter_bar/filter_editor/lib/filter_label.tsx | 1 - src/plugins/data/public/ui/filter_bar/index.tsx | 7 ++----- .../ui/query_string_input/query_bar_top_row.tsx | 3 ++- .../ui/query_string_input/query_string_input.tsx | 5 +++-- .../public/ui/search_bar/create_search_bar.tsx | 3 ++- .../ui/search_bar/lib/use_query_string_manager.ts | 2 +- .../data/public/ui/search_bar/search_bar.tsx | 2 +- src/plugins/data/public/ui/typeahead/index.tsx | 5 +++-- .../public/ui/typeahead/suggestions_component.tsx | 3 +-- src/plugins/data/server/plugin.ts | 4 ++-- .../server/query/route_handler_context.test.ts | 8 ++------ src/plugins/data/server/search/mocks.ts | 2 +- src/plugins/data/server/search/routes/bsearch.ts | 2 +- .../data/server/search/search_service.test.ts | 4 ++-- .../data/server/search/search_source/mocks.ts | 2 +- src/plugins/data/server/search/session/mocks.ts | 2 +- .../search/strategies/ese_search/response_utils.ts | 2 +- src/plugins/data/server/search/types.ts | 5 +---- 70 files changed, 96 insertions(+), 127 deletions(-) delete mode 100644 src/plugins/data/public/query/filter_manager/types.ts diff --git a/dev_docs/tutorials/expressions.mdx b/dev_docs/tutorials/expressions.mdx index c4b37a125838e..d9abf3dd57eb8 100644 --- a/dev_docs/tutorials/expressions.mdx +++ b/dev_docs/tutorials/expressions.mdx @@ -57,7 +57,7 @@ const result = await executionContract.getData(); ``` - Check the full spec of execute function [here](https://github.com/elastic/kibana/blob/main/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.execution.md) + Check the full spec of execute function In addition, on the browser side, there are two additional ways to run expressions and render the results. @@ -71,7 +71,7 @@ This is the easiest way to get expressions rendered inside your application. ``` - Check the full spec of ReactExpressionRenderer component props [here](https://github.com/elastic/kibana/blob/main/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.reactexpressionrendererprops.md) + Check the full spec of ReactExpressionRenderer component props #### Expression loader @@ -83,7 +83,7 @@ const handler = loader(domElement, expression, params); ``` - Check the full spec of expression loader params [here](https://github.com/elastic/kibana/blob/main/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) + Check the full spec of expression loader params ### Creating new expression functions @@ -106,7 +106,7 @@ expressions.registerFunction(functionDefinition); ``` - Check the full interface of ExpressionFuntionDefinition [here](https://github.com/elastic/kibana/blob/main/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionfunctiondefinition.md) + Check the full interface of ExpressionFuntionDefinition ### Creating new expression renderers @@ -128,5 +128,5 @@ expressions.registerRenderer(rendererDefinition); ``` - Check the full interface of ExpressionRendererDefinition [here](https://github.com/elastic/kibana/blob/main/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.expressionrenderdefinition.md) + Check the full interface of ExpressionRendererDefinition diff --git a/src/plugins/data/common/search/aggs/agg_types.ts b/src/plugins/data/common/search/aggs/agg_types.ts index a84fddb19c5fa..87496767a33b2 100644 --- a/src/plugins/data/common/search/aggs/agg_types.ts +++ b/src/plugins/data/common/search/aggs/agg_types.ts @@ -14,7 +14,6 @@ import * as metrics from './metrics'; import { BUCKET_TYPES, CalculateBoundsFn } from './buckets'; import { METRIC_TYPES } from './metrics'; -/** @internal */ export interface AggTypesDependencies { calculateBounds: CalculateBoundsFn; getConfig: (key: string) => T; diff --git a/src/plugins/data/common/search/aggs/agg_types_registry.ts b/src/plugins/data/common/search/aggs/agg_types_registry.ts index 108b1eb379ddd..4e57b4db3fb50 100644 --- a/src/plugins/data/common/search/aggs/agg_types_registry.ts +++ b/src/plugins/data/common/search/aggs/agg_types_registry.ts @@ -16,8 +16,6 @@ export type AggTypesRegistrySetup = ReturnType; * real start contract we will need to return the initialized versions. * So we need to provide the correct typings so they can be overwritten * on client/server. - * - * @internal */ export interface AggTypesRegistryStart { get: (id: string) => BucketAggType | MetricAggType; diff --git a/src/plugins/data/common/search/aggs/aggs_service.ts b/src/plugins/data/common/search/aggs/aggs_service.ts index 86bda5019a496..58f65bb0cab44 100644 --- a/src/plugins/data/common/search/aggs/aggs_service.ts +++ b/src/plugins/data/common/search/aggs/aggs_service.ts @@ -32,12 +32,10 @@ export const aggsRequiredUiSettings = [ UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX, ]; -/** @internal */ export interface AggsCommonSetupDependencies { registerFunction: ExpressionsServiceSetup['registerFunction']; } -/** @internal */ export interface AggsCommonStartDependencies { getConfig: GetConfigFn; getIndexPattern(id: string): Promise; diff --git a/src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts index fb142ee1f77c8..8f976ba979b95 100644 --- a/src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts +++ b/src/plugins/data/common/search/aggs/metrics/percentile_ranks.ts @@ -13,7 +13,8 @@ import { AggTypesDependencies } from '../agg_types'; import { BaseAggParams } from '../types'; import { MetricAggType } from './metric_agg_type'; -import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; +import { getResponseAggConfigClass } from './lib/get_response_agg_config_class'; +import type { IResponseAggConfig } from './lib/get_response_agg_config_class'; import { aggPercentileRanksFnName } from './percentile_ranks_fn'; import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; diff --git a/src/plugins/data/common/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/common/search/aggs/metrics/percentiles.test.ts index 26189e022e7c6..17c49e2484a80 100644 --- a/src/plugins/data/common/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/common/search/aggs/metrics/percentiles.test.ts @@ -10,7 +10,7 @@ import { IPercentileAggConfig, getPercentilesMetricAgg } from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; -import { IResponseAggConfig } from './lib/get_response_agg_config_class'; +import type { IResponseAggConfig } from './lib/get_response_agg_config_class'; describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; diff --git a/src/plugins/data/common/search/aggs/metrics/percentiles.ts b/src/plugins/data/common/search/aggs/metrics/percentiles.ts index 07c4ac2bf2646..d0e1c6df77696 100644 --- a/src/plugins/data/common/search/aggs/metrics/percentiles.ts +++ b/src/plugins/data/common/search/aggs/metrics/percentiles.ts @@ -10,7 +10,8 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; +import { getResponseAggConfigClass } from './lib/get_response_agg_config_class'; +import type { IResponseAggConfig } from './lib/get_response_agg_config_class'; import { aggPercentilesFnName } from './percentiles_fn'; import { getPercentileValue } from './percentiles_get_value'; import { ordinalSuffix } from './lib/ordinal_suffix'; diff --git a/src/plugins/data/common/search/aggs/metrics/percentiles_get_value.ts b/src/plugins/data/common/search/aggs/metrics/percentiles_get_value.ts index 90585909db42a..242a12da35128 100644 --- a/src/plugins/data/common/search/aggs/metrics/percentiles_get_value.ts +++ b/src/plugins/data/common/search/aggs/metrics/percentiles_get_value.ts @@ -7,7 +7,7 @@ */ import { find } from 'lodash'; -import { IResponseAggConfig } from './lib/get_response_agg_config_class'; +import type { IResponseAggConfig } from './lib/get_response_agg_config_class'; export const getPercentileValue = ( agg: TAggConfig, diff --git a/src/plugins/data/common/search/aggs/metrics/std_deviation.ts b/src/plugins/data/common/search/aggs/metrics/std_deviation.ts index fa160e5e9d161..9a4c38e296635 100644 --- a/src/plugins/data/common/search/aggs/metrics/std_deviation.ts +++ b/src/plugins/data/common/search/aggs/metrics/std_deviation.ts @@ -11,7 +11,8 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { aggStdDeviationFnName } from './std_deviation_fn'; import { METRIC_TYPES } from './metric_agg_types'; -import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; +import { getResponseAggConfigClass } from './lib/get_response_agg_config_class'; +import type { IResponseAggConfig } from './lib/get_response_agg_config_class'; import { KBN_FIELD_TYPES } from '../../../../common'; import { BaseAggParams } from '../types'; diff --git a/src/plugins/data/common/search/aggs/types.ts b/src/plugins/data/common/search/aggs/types.ts index 9c4866c19714d..74356263845d1 100644 --- a/src/plugins/data/common/search/aggs/types.ts +++ b/src/plugins/data/common/search/aggs/types.ts @@ -102,12 +102,10 @@ export type { IMetricAggType } from './metrics/metric_agg_type'; export type { IpRangeKey } from './buckets/lib/ip_range'; export type { OptionedValueProp } from './param_types/optioned'; -/** @internal */ export interface AggsCommonSetup { types: AggTypesRegistrySetup; } -/** @internal */ export interface AggsCommonStart { calculateAutoTimeExpression: ReturnType; datatableUtilities: { @@ -131,14 +129,12 @@ export interface AggsCommonStart { */ export type AggsStart = Assign; -/** @internal */ export interface BaseAggParams { json?: string; customLabel?: string; timeShift?: string; } -/** @internal */ export interface AggExpressionType { type: 'agg_type'; value: AggConfigSerialized; diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index a44613cb98b50..eefaf8a9dcd54 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -14,7 +14,7 @@ import type { IAggConfigs } from '../../aggs'; import type { ISearchSource } from '../../search_source'; import { searchSourceCommonMock, searchSourceInstanceMock } from '../../search_source/mocks'; -import { handleRequest, RequestHandlerParams } from './request_handler'; +import { handleRequest } from './request_handler'; jest.mock('../../tabify', () => ({ tabifyAggResponse: jest.fn(), @@ -25,7 +25,7 @@ import { of } from 'rxjs'; import { toArray } from 'rxjs/operators'; describe('esaggs expression function - public', () => { - let mockParams: MockedKeys; + let mockParams: MockedKeys[0]>; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 87c1685c9730d..d395baed2f08e 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -17,8 +17,7 @@ import { IAggConfigs } from '../../aggs'; import { ISearchStartSearchSource } from '../../search_source'; import { tabifyAggResponse } from '../../tabify'; -/** @internal */ -export interface RequestHandlerParams { +interface RequestHandlerParams { abortSignal?: AbortSignal; aggs: IAggConfigs; filters?: Filter[]; diff --git a/src/plugins/data/common/search/expressions/esdsl.ts b/src/plugins/data/common/search/expressions/esdsl.ts index faa43dab65657..69e3c54e43806 100644 --- a/src/plugins/data/common/search/expressions/esdsl.ts +++ b/src/plugins/data/common/search/expressions/esdsl.ts @@ -34,8 +34,7 @@ export type EsdslExpressionFunctionDefinition = ExpressionFunctionDefinition< Output >; -/** @internal */ -export interface EsdslStartDependencies { +interface EsdslStartDependencies { search: ISearchGeneric; uiSettingsClient: UiSettingsCommon; } diff --git a/src/plugins/data/common/search/expressions/kibana_context.ts b/src/plugins/data/common/search/expressions/kibana_context.ts index 47ca24b5be42b..6e38e2a3949d5 100644 --- a/src/plugins/data/common/search/expressions/kibana_context.ts +++ b/src/plugins/data/common/search/expressions/kibana_context.ts @@ -19,7 +19,6 @@ import { KibanaTimerangeOutput } from './timerange'; import { SavedObjectReference } from '../../../../../core/types'; import { SavedObjectsClientCommon } from '../..'; -/** @internal */ export interface KibanaContextStartDependencies { savedObjectsClient: SavedObjectsClientCommon; } diff --git a/src/plugins/data/common/search/search_source/fetch/get_search_params.ts b/src/plugins/data/common/search/search_source/fetch/get_search_params.ts index 28ee7993c175c..ae01dcf4ea051 100644 --- a/src/plugins/data/common/search/search_source/fetch/get_search_params.ts +++ b/src/plugins/data/common/search/search_source/fetch/get_search_params.ts @@ -9,7 +9,7 @@ import { UI_SETTINGS } from '../../../constants'; import { GetConfigFn } from '../../../types'; import { ISearchRequestParams } from '../../index'; -import { SearchRequest } from './types'; +import type { SearchRequest } from './types'; const sessionId = Date.now(); 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 3ac6b623fbc80..8acdb0514cccb 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -95,7 +95,8 @@ import type { SearchSourceFields, SearchSourceOptions, } from './types'; -import { FetchHandlers, getSearchParamsFromRequest, RequestFailure, SearchRequest } from './fetch'; +import { getSearchParamsFromRequest, RequestFailure } from './fetch'; +import type { FetchHandlers, SearchRequest } from './fetch'; import { getRequestInspectorStats, getResponseInspectorStats } from './inspect'; import { diff --git a/src/plugins/data/common/search/search_source/types.ts b/src/plugins/data/common/search/search_source/types.ts index d496c109f68c8..94697ba9521e9 100644 --- a/src/plugins/data/common/search/search_source/types.ts +++ b/src/plugins/data/common/search/search_source/types.ts @@ -12,7 +12,7 @@ import { SerializableRecord } from '@kbn/utility-types'; import { Query } from '../..'; import { Filter } from '../../es_query'; import { IndexPattern } from '../..'; -import { SearchSource } from './search_source'; +import type { SearchSource } from './search_source'; import { PersistableStateService } from '../../../../kibana_utils/common'; /** diff --git a/src/plugins/data/common/search/tabify/get_columns.test.ts b/src/plugins/data/common/search/tabify/get_columns.test.ts index d679b3fb36311..1741abfe729d7 100644 --- a/src/plugins/data/common/search/tabify/get_columns.test.ts +++ b/src/plugins/data/common/search/tabify/get_columns.test.ts @@ -7,7 +7,7 @@ */ import { tabifyGetColumns } from './get_columns'; -import { TabbedAggColumn } from './types'; +import type { TabbedAggColumn } from './types'; import { AggConfigs } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; diff --git a/src/plugins/data/common/search/tabify/get_columns.ts b/src/plugins/data/common/search/tabify/get_columns.ts index 62798ba8bf680..8957c96a69881 100644 --- a/src/plugins/data/common/search/tabify/get_columns.ts +++ b/src/plugins/data/common/search/tabify/get_columns.ts @@ -8,7 +8,7 @@ import { groupBy } from 'lodash'; import { IAggConfig } from '../aggs'; -import { TabbedAggColumn } from './types'; +import type { TabbedAggColumn } from './types'; const getColumn = (agg: IAggConfig, i: number): TabbedAggColumn => { let name = ''; diff --git a/src/plugins/data/common/search/tabify/response_writer.test.ts b/src/plugins/data/common/search/tabify/response_writer.test.ts index cee297d255db3..ec131458b8510 100644 --- a/src/plugins/data/common/search/tabify/response_writer.test.ts +++ b/src/plugins/data/common/search/tabify/response_writer.test.ts @@ -9,7 +9,7 @@ import { TabbedAggResponseWriter } from './response_writer'; import { AggConfigs, BUCKET_TYPES, METRIC_TYPES } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; -import { TabbedResponseWriterOptions } from './types'; +import type { TabbedResponseWriterOptions } from './types'; describe('TabbedAggResponseWriter class', () => { let responseWriter: TabbedAggResponseWriter; diff --git a/src/plugins/data/common/search/tabify/tabify.ts b/src/plugins/data/common/search/tabify/tabify.ts index d3273accff974..5b1247a8f1719 100644 --- a/src/plugins/data/common/search/tabify/tabify.ts +++ b/src/plugins/data/common/search/tabify/tabify.ts @@ -9,7 +9,7 @@ import { get } from 'lodash'; import { TabbedAggResponseWriter } from './response_writer'; import { TabifyBuckets } from './buckets'; -import { TabbedResponseWriterOptions } from './types'; +import type { TabbedResponseWriterOptions } from './types'; import { AggResponseBucket } from './types'; import { AggGroupNames, IAggConfigs } from '../aggs'; diff --git a/src/plugins/data/common/search/tabify/tabify_docs.ts b/src/plugins/data/common/search/tabify/tabify_docs.ts index 43b6155f6662f..08172a918c042 100644 --- a/src/plugins/data/common/search/tabify/tabify_docs.ts +++ b/src/plugins/data/common/search/tabify/tabify_docs.ts @@ -48,7 +48,7 @@ function isValidMetaFieldName(field: string): field is ValidMetaFieldNames { return (VALID_META_FIELD_NAMES as string[]).includes(field); } -export interface TabifyDocsOptions { +interface TabifyDocsOptions { shallow?: boolean; /** * If set to `false` the _source of the document, if requested, won't be diff --git a/src/plugins/data/common/search/tabify/types.ts b/src/plugins/data/common/search/tabify/types.ts index 9fadb0ef860e3..bf0a99725e2ab 100644 --- a/src/plugins/data/common/search/tabify/types.ts +++ b/src/plugins/data/common/search/tabify/types.ts @@ -22,7 +22,6 @@ export interface TimeRangeInformation { timeFields: string[]; } -/** @internal **/ export interface TabbedResponseWriterOptions { metricsAtAllLevels: boolean; partialRows: boolean; diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts index ea17e91d085e7..2ae1805c8aa28 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts @@ -13,8 +13,7 @@ import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; import { AggConfigSerialized } from '../../../common/search/aggs'; -/** @internal */ -export interface RangeSelectDataContext { +interface RangeSelectDataContext { table: Datatable; column: number; range: number[]; diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index e4854dac9408b..5163f979d3ff5 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -9,10 +9,7 @@ import { IndexPatternsContract } from '../../../public'; import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns, setSearchService } from '../../../public/services'; -import { - createFiltersFromValueClickAction, - ValueClickDataContext, -} from './create_filters_from_value_click'; +import { createFiltersFromValueClickAction } from './create_filters_from_value_click'; import { FieldFormatsGetConfigFn, BytesFormat } from '../../../../field_formats/common'; import { RangeFilter } from '@kbn/es-query'; @@ -22,7 +19,7 @@ const mockField = { }; describe('createFiltersFromValueClick', () => { - let dataPoints: ValueClickDataContext['data']; + let dataPoints: Parameters[0]['data']; beforeEach(() => { dataPoints = [ diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index e1088b42e37b6..23ab718e512bd 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -12,8 +12,7 @@ import { esFilters, Filter } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; import { AggConfigSerialized } from '../../../common/search/aggs'; -/** @internal */ -export interface ValueClickDataContext { +interface ValueClickDataContext { data: Array<{ table: Pick; column: number; diff --git a/src/plugins/data/public/autocomplete/autocomplete_service.ts b/src/plugins/data/public/autocomplete/autocomplete_service.ts index 67efbe2af29ce..0d21c7e765501 100644 --- a/src/plugins/data/public/autocomplete/autocomplete_service.ts +++ b/src/plugins/data/public/autocomplete/autocomplete_service.ts @@ -8,13 +8,13 @@ import { CoreSetup, PluginInitializerContext } from 'src/core/public'; import moment from 'moment'; -import { TimefilterSetup } from '../query'; +import type { TimefilterSetup } from '../query'; import { QuerySuggestionGetFn } from './providers/query_suggestion_provider'; import { getEmptyValueSuggestions, setupValueSuggestionProvider, - ValueSuggestionsGetFn, } from './providers/value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from './providers/value_suggestion_provider'; import { ConfigSchema } from '../../config'; import { UsageCollectionSetup } from '../../../usage_collection/public'; diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts index 7ecd371e39db7..4a68c7232ea7e 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.test.ts @@ -7,8 +7,9 @@ */ import { stubIndexPattern, stubFields } from '../../stubs'; -import { TimefilterSetup } from '../../query'; -import { setupValueSuggestionProvider, ValueSuggestionsGetFn } from './value_suggestion_provider'; +import type { TimefilterSetup } from '../../query'; +import { setupValueSuggestionProvider } from './value_suggestion_provider'; +import type { ValueSuggestionsGetFn } from './value_suggestion_provider'; import { IUiSettingsClient, CoreSetup } from 'kibana/public'; import { UI_SETTINGS } from '../../../common'; diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index 588bac4739c53..31f886daeb4cc 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -11,7 +11,7 @@ import { buildQueryFromFilters } from '@kbn/es-query'; import { memoize } from 'lodash'; import { CoreSetup } from 'src/core/public'; import { IIndexPattern, IFieldType, UI_SETTINGS, ValueSuggestionsMethod } from '../../../common'; -import { TimefilterSetup } from '../../query'; +import type { TimefilterSetup } from '../../query'; import { AutocompleteUsageCollector } from '../collectors'; export type ValueSuggestionsGetFn = (args: ValueSuggestionsGetFnArgs) => Promise; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 25f649f69a052..7d6983725b179 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -11,7 +11,7 @@ import './index.scss'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { ConfigSchema } from '../config'; import { Storage, IStorageWrapper, createStartServicesGetter } from '../../kibana_utils/public'; -import { +import type { DataPublicPluginSetup, DataPublicPluginStart, DataSetupDependencies, diff --git a/src/plugins/data/public/query/filter_manager/filter_manager.ts b/src/plugins/data/public/query/filter_manager/filter_manager.ts index f076a2c591fb1..bfedf444cf23e 100644 --- a/src/plugins/data/public/query/filter_manager/filter_manager.ts +++ b/src/plugins/data/public/query/filter_manager/filter_manager.ts @@ -14,7 +14,6 @@ import { IUiSettingsClient } from 'src/core/public'; import { isFilterPinned, onlyDisabledFiltersChanged, Filter } from '@kbn/es-query'; import { sortFilters } from './lib/sort_filters'; import { mapAndFlattenFilters } from './lib/map_and_flatten_filters'; -import { PartitionedFilters } from './types'; import { FilterStateStore, @@ -31,6 +30,11 @@ import { telemetry, } from '../../../common/query/persistable_state'; +interface PartitionedFilters { + globalFilters: Filter[]; + appFilters: Filter[]; +} + export class FilterManager implements PersistableStateService { private filters: Filter[] = []; private updated$: Subject = new Subject(); diff --git a/src/plugins/data/public/query/filter_manager/types.ts b/src/plugins/data/public/query/filter_manager/types.ts deleted file mode 100644 index 5c2667fbf1d2a..0000000000000 --- a/src/plugins/data/public/query/filter_manager/types.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 { Filter } from '../../../common'; - -export interface PartitionedFilters { - globalFilters: Filter[]; - appFilters: Filter[]; -} diff --git a/src/plugins/data/public/query/lib/get_default_query.ts b/src/plugins/data/public/query/lib/get_default_query.ts index 015c128171a8e..fd571e46083f5 100644 --- a/src/plugins/data/public/query/lib/get_default_query.ts +++ b/src/plugins/data/public/query/lib/get_default_query.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export type QueryLanguage = 'kuery' | 'lucene'; +type QueryLanguage = 'kuery' | 'lucene'; export function getDefaultQuery(language: QueryLanguage = 'kuery') { return { diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index 314f13e3524db..dc6b9586b0b4b 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -12,10 +12,12 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { buildEsQuery } from '@kbn/es-query'; import { FilterManager } from './filter_manager'; import { createAddToQueryLog } from './lib'; -import { TimefilterService, TimefilterSetup } from './timefilter'; +import { TimefilterService } from './timefilter'; +import type { TimefilterSetup } from './timefilter'; import { createSavedQueryService } from './saved_query/saved_query_service'; import { createQueryStateObservable } from './state_sync/create_global_query_observable'; -import { QueryStringContract, QueryStringManager } from './query_string'; +import type { QueryStringContract } from './query_string'; +import { QueryStringManager } from './query_string'; import { getEsQueryConfig, TimeRange } from '../../common'; import { getUiSettings } from '../services'; import { NowProviderInternalContract } from '../now_provider'; diff --git a/src/plugins/data/public/query/query_string/query_string_manager.mock.ts b/src/plugins/data/public/query/query_string/query_string_manager.mock.ts index 976d3ce13e7de..6d20f2a4bea34 100644 --- a/src/plugins/data/public/query/query_string/query_string_manager.mock.ts +++ b/src/plugins/data/public/query/query_string/query_string_manager.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { QueryStringContract } from '.'; +import type { QueryStringContract } from '.'; import { Observable } from 'rxjs'; const createSetupContractMock = () => { diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts index 047051c302083..57af09a0ea824 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -8,7 +8,7 @@ import { createSavedQueryService } from './saved_query_service'; import { httpServiceMock } from '../../../../../core/public/mocks'; -import { SavedQueryAttributes } from '../../../common'; +import type { SavedQueryAttributes } from '../../../common'; const http = httpServiceMock.createStartContract(); diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts index 17b47c78c7000..b5a21e2ac2095 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -8,7 +8,7 @@ import { HttpStart } from 'src/core/public'; import { SavedQuery } from './types'; -import { SavedQueryAttributes } from '../../../common'; +import type { SavedQueryAttributes } from '../../../common'; export const createSavedQueryService = (http: HttpStart) => { const createQuery = async (attributes: SavedQueryAttributes, { overwrite = false } = {}) => { diff --git a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts index 3c94d6eb3c056..3577478154c31 100644 --- a/src/plugins/data/public/query/state_sync/create_global_query_observable.ts +++ b/src/plugins/data/public/query/state_sync/create_global_query_observable.ts @@ -9,12 +9,12 @@ import { Observable, Subscription } from 'rxjs'; import { map, tap } from 'rxjs/operators'; import { isFilterPinned } from '@kbn/es-query'; -import { TimefilterSetup } from '../timefilter'; +import type { TimefilterSetup } from '../timefilter'; import { FilterManager } from '../filter_manager'; import { QueryState, QueryStateChange } from './index'; import { createStateContainer } from '../../../../kibana_utils/public'; import { compareFilters, COMPARE_ALL_OPTIONS } from '../../../common'; -import { QueryStringContract } from '../query_string'; +import type { QueryStringContract } from '../query_string'; export function createQueryStateObservable({ timefilter: { timefilter }, diff --git a/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts index 2d815ea168f6b..9b50c8d93d496 100644 --- a/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts +++ b/src/plugins/data/public/query/timefilter/lib/diff_time_picker_vals.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import { RefreshInterval } from '../../../../common'; -import { InputTimeRange } from '../types'; +import type { InputTimeRange } from '../types'; const valueOf = function (o: any) { if (o) return o.valueOf(); diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index f3520abb2f46e..e13e8b17a7f43 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -11,7 +11,7 @@ import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; import { PublicMethodsOf } from '@kbn/utility-types'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; -import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; +import type { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; import { NowProviderInternalContract } from '../../now_provider'; import { calculateBounds, diff --git a/src/plugins/data/public/search/collectors/types.ts b/src/plugins/data/public/search/collectors/types.ts index 49c240d1ccb16..d0a2e61f45109 100644 --- a/src/plugins/data/public/search/collectors/types.ts +++ b/src/plugins/data/public/search/collectors/types.ts @@ -68,9 +68,6 @@ export enum SEARCH_EVENT_TYPE { SESSIONS_LIST_LOADED = 'sessionsListLoaded', } -/** - * @internal - */ export interface SearchUsageCollector { trackQueryTimedOut: () => Promise; trackSessionIndicatorTourLoading: () => Promise; diff --git a/src/plugins/data/public/search/errors/types.ts b/src/plugins/data/public/search/errors/types.ts index d541e53be78f9..8f18ab06fcd94 100644 --- a/src/plugins/data/public/search/errors/types.ts +++ b/src/plugins/data/public/search/errors/types.ts @@ -32,7 +32,7 @@ export interface Reason { }; } -export interface IEsErrorAttributes { +interface IEsErrorAttributes { type: string; reason: string; root_cause?: Reason[]; diff --git a/src/plugins/data/public/search/fetch/handle_response.tsx b/src/plugins/data/public/search/fetch/handle_response.tsx index 9e68209af2b92..10b2f69a2a320 100644 --- a/src/plugins/data/public/search/fetch/handle_response.tsx +++ b/src/plugins/data/public/search/fetch/handle_response.tsx @@ -13,7 +13,7 @@ import { IKibanaSearchResponse } from 'src/plugins/data/common'; import { ShardFailureOpenModalButton } from '../../ui/shard_failure_modal'; import { toMountPoint } from '../../../../kibana_react/public'; import { getNotifications } from '../../services'; -import { SearchRequest } from '..'; +import type { SearchRequest } from '..'; export function handleResponse(request: SearchRequest, response: IKibanaSearchResponse) { const { rawResponse } = response; diff --git a/src/plugins/data/public/search/mocks.ts b/src/plugins/data/public/search/mocks.ts index 562b367b92c92..b82e0776777c5 100644 --- a/src/plugins/data/public/search/mocks.ts +++ b/src/plugins/data/public/search/mocks.ts @@ -8,7 +8,7 @@ import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; -import { ISearchSetup, ISearchStart } from './types'; +import type { ISearchSetup, ISearchStart } from './types'; import { getSessionsClientMock, getSessionServiceMock } from './session/mocks'; import { createSearchUsageCollectorMock } from './collectors/mocks'; diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index ecc0e84917251..76aae8582287d 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -15,7 +15,7 @@ import { } from 'src/core/public'; import { BehaviorSubject } from 'rxjs'; import { BfetchPublicSetup } from 'src/plugins/bfetch/public'; -import { ISearchSetup, ISearchStart } from './types'; +import type { ISearchSetup, ISearchStart } from './types'; import { handleResponse } from './fetch'; import { diff --git a/src/plugins/data/public/search/search_source/mocks.ts b/src/plugins/data/public/search/search_source/mocks.ts index 75ab8dbac7d2d..169ac4b84a505 100644 --- a/src/plugins/data/public/search/search_source/mocks.ts +++ b/src/plugins/data/public/search/search_source/mocks.ts @@ -7,7 +7,7 @@ */ import { searchSourceCommonMock } from '../../../common/search/search_source/mocks'; -import { ISearchStart } from '../types'; +import type { ISearchStart } from '../types'; function createStartContract(): jest.Mocked { return searchSourceCommonMock; diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index dee0216530205..c6706ff8cf72d 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -9,7 +9,8 @@ import { BehaviorSubject } from 'rxjs'; import { ISessionsClient } from './sessions_client'; import { ISessionService } from './session_service'; -import { SearchSessionState, SessionMeta } from './search_session_state'; +import { SearchSessionState } from './search_session_state'; +import type { SessionMeta } from './search_session_state'; export function getSessionsClientMock(): jest.Mocked { return { diff --git a/src/plugins/data/public/search/session/search_session_state.test.ts b/src/plugins/data/public/search/session/search_session_state.test.ts index ef18275da12fa..1137ceddb0da6 100644 --- a/src/plugins/data/public/search/session/search_session_state.test.ts +++ b/src/plugins/data/public/search/session/search_session_state.test.ts @@ -7,7 +7,7 @@ */ import { createSessionStateContainer, SearchSessionState } from './search_session_state'; -import { SearchSessionSavedObject } from './sessions_client'; +import type { SearchSessionSavedObject } from './sessions_client'; import { SearchSessionStatus } from '../../../common'; const mockSavedObject: SearchSessionSavedObject = { diff --git a/src/plugins/data/public/search/session/search_session_state.ts b/src/plugins/data/public/search/session/search_session_state.ts index 73c75d046da96..c714a3e387641 100644 --- a/src/plugins/data/public/search/session/search_session_state.ts +++ b/src/plugins/data/public/search/session/search_session_state.ts @@ -11,7 +11,7 @@ import deepEqual from 'fast-deep-equal'; import { Observable } from 'rxjs'; import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'; import { createStateContainer, StateContainer } from '../../../../kibana_utils/public'; -import { SearchSessionSavedObject } from './sessions_client'; +import type { SearchSessionSavedObject } from './sessions_client'; /** * Possible state that current session can be in diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 4a11cdb38bb7d..ad131fbea60b2 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -15,7 +15,7 @@ import { SearchSessionState } from './search_session_state'; import { createNowProviderMock } from '../../now_provider/mocks'; import { NowProviderInternalContract } from '../../now_provider'; import { SEARCH_SESSIONS_MANAGEMENT_ID } from './constants'; -import { SearchSessionSavedObject, ISessionsClient } from './sessions_client'; +import type { SearchSessionSavedObject, ISessionsClient } from './sessions_client'; import { SearchSessionStatus } from '../../../common'; import { CoreStart } from 'kibana/public'; diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 360e8808c186d..9a02e336ecf86 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -16,8 +16,8 @@ import { } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { ConfigSchema } from '../../../config'; -import { - createSessionStateContainer, +import { createSessionStateContainer } from './search_session_state'; +import type { SearchSessionState, SessionMeta, SessionStateContainer, @@ -31,7 +31,7 @@ import { formatSessionName } from './lib/session_name_formatter'; export type ISessionService = PublicContract; -export interface TrackSearchDescriptor { +interface TrackSearchDescriptor { abort: () => void; } @@ -66,7 +66,7 @@ export interface SearchSessionInfoProvider

; const LazyFilterLabel = React.lazy(() => import('./filter_editor/lib/filter_label')); -export const FilterLabel = (props: FilterLabelProps) => ( +export const FilterLabel = (props: React.ComponentProps) => ( }> ); -import type { FilterItemProps } from './filter_item'; - const LazyFilterItem = React.lazy(() => import('./filter_item')); -export const FilterItem = (props: FilterItemProps) => ( +export const FilterItem = (props: React.ComponentProps) => ( }> diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index a78055f0d61a1..a5f59b976d3ba 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -24,7 +24,8 @@ import { IDataPluginServices, IIndexPattern, TimeRange, TimeHistoryContract, Que import { useKibana, withKibana } from '../../../../kibana_react/public'; import QueryStringInputUI from './query_string_input'; import { UI_SETTINGS } from '../../../common'; -import { PersistedLog, getQueryLog } from '../../query'; +import { getQueryLog } from '../../query'; +import type { PersistedLog } from '../../query'; import { NoDataPopover } from './no_data_popover'; const QueryStringInput = withKibana(QueryStringInputUI); diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 5d3e359ca5fc5..2e150b2c1e1bc 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -34,8 +34,9 @@ import { QuerySuggestion, QuerySuggestionTypes } from '../../autocomplete'; import { KibanaReactContextValue, toMountPoint } from '../../../../kibana_react/public'; import { fetchIndexPatterns } from './fetch_index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; -import { PersistedLog, getQueryLog, matchPairs, toUser, fromUser } from '../../query'; -import { SuggestionsListSize } from '../typeahead/suggestions_component'; +import { getQueryLog, matchPairs, toUser, fromUser } from '../../query'; +import type { PersistedLog } from '../../query'; +import type { SuggestionsListSize } from '../typeahead/suggestions_component'; import { SuggestionsComponent } from '..'; import { KIBANA_USER_QUERY_LANGUAGE_KEY, getFieldSubtypeNested } from '../../../common'; diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 7b7538441c38f..fda6a74e4b500 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -12,7 +12,8 @@ import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { KibanaContextProvider } from '../../../../kibana_react/public'; import { QueryStart, SavedQuery } from '../../query'; -import { SearchBar, SearchBarOwnProps } from './'; +import { SearchBar } from '.'; +import type { SearchBarOwnProps } from '.'; import { useFilterManager } from './lib/use_filter_manager'; import { useTimefilter } from './lib/use_timefilter'; import { useSavedQuery } from './lib/use_saved_query'; diff --git a/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts b/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts index 508ae711f52a9..713020f249ae3 100644 --- a/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts +++ b/src/plugins/data/public/ui/search_bar/lib/use_query_string_manager.ts @@ -9,7 +9,7 @@ import { useState, useEffect } from 'react'; import { Subscription } from 'rxjs'; import { Query } from '../../..'; -import { QueryStringContract } from '../../../query/query_string'; +import type { QueryStringContract } from '../../../query/query_string'; interface UseQueryStringProps { query?: Query; diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index 385f052adece6..3fef455be41c3 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -18,7 +18,7 @@ import { Query, Filter } from '@kbn/es-query'; import { withKibana, KibanaReactContextValue } from '../../../../kibana_react/public'; import QueryBarTopRow from '../query_string_input/query_bar_top_row'; -import { SavedQueryAttributes, TimeHistoryContract, SavedQuery } from '../../query'; +import type { SavedQueryAttributes, TimeHistoryContract, SavedQuery } from '../../query'; import { IDataPluginServices } from '../../types'; import { TimeRange, IIndexPattern } from '../../../common'; import { FilterBar } from '../filter_bar/filter_bar'; diff --git a/src/plugins/data/public/ui/typeahead/index.tsx b/src/plugins/data/public/ui/typeahead/index.tsx index 103580875151b..fb565d2711f64 100644 --- a/src/plugins/data/public/ui/typeahead/index.tsx +++ b/src/plugins/data/public/ui/typeahead/index.tsx @@ -7,12 +7,13 @@ */ import React from 'react'; -import type { SuggestionsComponentProps } from './suggestions_component'; const Fallback = () =>
; const LazySuggestionsComponent = React.lazy(() => import('./suggestions_component')); -export const SuggestionsComponent = (props: SuggestionsComponentProps) => ( +export const SuggestionsComponent = ( + props: React.ComponentProps +) => ( }> diff --git a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx index 6bc91619fe868..f7d6e2c3d6403 100644 --- a/src/plugins/data/public/ui/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,8 +19,7 @@ import { } from './constants'; import { SuggestionOnClick } from './types'; -// @internal -export interface SuggestionsComponentProps { +interface SuggestionsComponentProps { index: number | null; onClick: SuggestionOnClick; onMouseEnter: (index: number) => void; diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index cb52500e78f94..74b4edde21ae0 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -11,7 +11,7 @@ import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { PluginStart as DataViewsServerPluginStart } from 'src/plugins/data_views/server'; import { ConfigSchema } from '../config'; -import { ISearchSetup, ISearchStart, SearchEnhancements } from './search'; +import type { ISearchSetup, ISearchStart, SearchEnhancements } from './search'; import { SearchService } from './search/search_service'; import { QueryService } from './query/query_service'; import { ScriptsService } from './scripts'; @@ -21,7 +21,7 @@ import { AutocompleteService } from './autocomplete'; import { FieldFormatsSetup, FieldFormatsStart } from '../../field_formats/server'; import { getUiSettings } from './ui_settings'; -export interface DataEnhancements { +interface DataEnhancements { search: SearchEnhancements; } diff --git a/src/plugins/data/server/query/route_handler_context.test.ts b/src/plugins/data/server/query/route_handler_context.test.ts index cc7686a06cb67..f8c14d59e0f85 100644 --- a/src/plugins/data/server/query/route_handler_context.test.ts +++ b/src/plugins/data/server/query/route_handler_context.test.ts @@ -7,12 +7,8 @@ */ import { coreMock } from '../../../../core/server/mocks'; -import { - DATA_VIEW_SAVED_OBJECT_TYPE, - FilterStateStore, - SavedObject, - SavedQueryAttributes, -} from '../../common'; +import { DATA_VIEW_SAVED_OBJECT_TYPE, FilterStateStore } from '../../common'; +import type { SavedObject, SavedQueryAttributes } from '../../common'; import { registerSavedQueryRouteHandlerContext } from './route_handler_context'; import { SavedObjectsFindResponse, SavedObjectsUpdateResponse } from 'kibana/server'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index f358cd78d8f90..bca01c6a15d55 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISearchSetup, ISearchStart } from './types'; +import type { ISearchSetup, ISearchStart } from './types'; import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts index 9a15f84687f43..314de4254851f 100644 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ b/src/plugins/data/server/search/routes/bsearch.ts @@ -14,7 +14,7 @@ import { IKibanaSearchResponse, ISearchOptionsSerializable, } from '../../../common/search'; -import { ISearchStart } from '../types'; +import type { ISearchStart } from '../types'; export function registerBsearchRoute( bfetch: BfetchServerSetup, diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index d8fc180ea1781..f449018612cef 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -17,7 +17,7 @@ import { createIndexPatternsStartMock } from '../data_views/mocks'; import { SearchService, SearchServiceSetupDependencies } from './search_service'; import { bfetchPluginMock } from '../../../bfetch/server/mocks'; import { of } from 'rxjs'; -import { +import type { IEsSearchRequest, IEsSearchResponse, IScopedSearchClient, @@ -25,8 +25,8 @@ import { ISearchSessionService, ISearchStart, ISearchStrategy, - NoSearchIdInSessionError, } from '.'; +import { NoSearchIdInSessionError } from '.'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { expressionsPluginMock } from '../../../expressions/public/mocks'; import { createSearchSessionsClientMock } from './mocks'; diff --git a/src/plugins/data/server/search/search_source/mocks.ts b/src/plugins/data/server/search/search_source/mocks.ts index c990597d9a217..6ae30e0391000 100644 --- a/src/plugins/data/server/search/search_source/mocks.ts +++ b/src/plugins/data/server/search/search_source/mocks.ts @@ -10,7 +10,7 @@ import type { MockedKeys } from '@kbn/utility-types/jest'; import { KibanaRequest } from 'src/core/server'; import { searchSourceCommonMock } from '../../../common/search/search_source/mocks'; -import { ISearchStart } from '../types'; +import type { ISearchStart } from '../types'; function createStartContract(): MockedKeys { return { diff --git a/src/plugins/data/server/search/session/mocks.ts b/src/plugins/data/server/search/session/mocks.ts index b55292e4ac469..047f12df822c4 100644 --- a/src/plugins/data/server/search/session/mocks.ts +++ b/src/plugins/data/server/search/session/mocks.ts @@ -7,7 +7,7 @@ */ import moment from 'moment'; -import { IScopedSearchSessionsClient } from './types'; +import type { IScopedSearchSessionsClient } from './types'; import { SearchSessionsConfigSchema } from '../../../config'; export function createSearchSessionsClientMock(): jest.Mocked< diff --git a/src/plugins/data/server/search/strategies/ese_search/response_utils.ts b/src/plugins/data/server/search/strategies/ese_search/response_utils.ts index 0a92c95dac615..c9390a1b381d5 100644 --- a/src/plugins/data/server/search/strategies/ese_search/response_utils.ts +++ b/src/plugins/data/server/search/strategies/ese_search/response_utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { AsyncSearchResponse } from './types'; +import type { AsyncSearchResponse } from './types'; import { getTotalLoaded } from '../es_search'; /** diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 026ff9139d932..b2e28eec40c09 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -26,7 +26,7 @@ import { } from '../../common/search'; import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; -import { IScopedSearchSessionsClient, ISearchSessionService } from './session'; +import type { IScopedSearchSessionsClient, ISearchSessionService } from './session'; export interface SearchEnhancements { sessionService: ISearchSessionService; @@ -123,9 +123,6 @@ export interface ISearchStart< export type SearchRequestHandlerContext = IScopedSearchClient; -/** - * @internal - */ export interface DataRequestHandlerContext extends RequestHandlerContext { search: SearchRequestHandlerContext; } From b40a7d0008a8b30876d58e71d21edc8b2875ea53 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Thu, 9 Dec 2021 12:09:54 -0500 Subject: [PATCH 091/145] remove firefox tag from dashboard_filtering test suite. Will research why running with firefox causes failures (#120673) --- test/functional/apps/dashboard/dashboard_filtering.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/functional/apps/dashboard/dashboard_filtering.ts b/test/functional/apps/dashboard/dashboard_filtering.ts index 796e8e35f0d49..6c8a378831340 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.ts +++ b/test/functional/apps/dashboard/dashboard_filtering.ts @@ -29,8 +29,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'timePicker']); describe('dashboard filtering', function () { - this.tags('includeFirefox'); - const populateDashboard = async () => { await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultDataRange(); @@ -67,8 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await security.testUser.restoreDefaults(); }); - // FLAKY: https://github.com/elastic/kibana/issues/120195 - describe.skip('adding a filter that excludes all data', () => { + describe('adding a filter that excludes all data', () => { before(async () => { await populateDashboard(); await addFilterAndRefresh(); From cb4ef5537d56075d86bda0d5e01a3ca4ad5a0613 Mon Sep 17 00:00:00 2001 From: Hannah Mudge Date: Thu, 9 Dec 2021 10:28:55 -0700 Subject: [PATCH 092/145] [Dashboard] Fix full screen error when pressing back arrow on browser. (#118113) * Fixed fullscreen error when pressing backspace. * Fixed prettier issues. * Added functional test. * Fixed prettier issues once again. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../embeddable/dashboard_container.tsx | 6 ++++++ .../application/lib/diff_dashboard_state.ts | 10 +++++++++- .../apps/dashboard/full_screen_mode.ts | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index 3e259d4e26179..36261fbe130a3 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -101,6 +101,7 @@ export class DashboardContainer extends Container void; public controlGroup?: ControlGroupContainer; + private domNode?: HTMLElement; public getPanelCount = () => { return Object.keys(this.getInput().panels).length; @@ -258,6 +259,10 @@ export class DashboardContainer extends Container @@ -275,6 +280,7 @@ export class DashboardContainer extends Container( original as unknown as DashboardDiffCommonFilters, newState as unknown as DashboardDiffCommonFilters, - ['viewMode', 'panels', 'options', 'savedQuery', 'expandedPanelId', 'controlGroupInput'], + [ + 'viewMode', + 'panels', + 'options', + 'fullScreenMode', + 'savedQuery', + 'expandedPanelId', + 'controlGroupInput', + ], true ); diff --git a/test/functional/apps/dashboard/full_screen_mode.ts b/test/functional/apps/dashboard/full_screen_mode.ts index 02669759f68ea..fcfd0fc49dd2b 100644 --- a/test/functional/apps/dashboard/full_screen_mode.ts +++ b/test/functional/apps/dashboard/full_screen_mode.ts @@ -12,6 +12,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); + const browser = getService('browser'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const dashboardPanelActions = getService('dashboardPanelActions'); @@ -93,5 +94,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); await filterBar.removeFilter('bytes'); }); + + it('exits full screen mode when back button pressed', async () => { + await PageObjects.dashboard.clickFullScreenMode(); + await browser.goBack(); + await retry.try(async () => { + const isChromeVisible = await PageObjects.common.isChromeVisible(); + expect(isChromeVisible).to.be(true); + }); + + await browser.goForward(); + await retry.try(async () => { + const isChromeVisible = await PageObjects.common.isChromeVisible(); + expect(isChromeVisible).to.be(true); + }); + }); }); } From e4c20a42b69ebd6e4c55b79687ec68ce31b50ac1 Mon Sep 17 00:00:00 2001 From: Kate Patticha Date: Thu, 9 Dec 2021 18:31:11 +0100 Subject: [PATCH 093/145] [APM] Fix loading message for correlations table (#120921) --- .../public/components/app/correlations/correlations_table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx b/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx index a2026b0a8abea..a530b950cf061 100644 --- a/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx +++ b/x-pack/plugins/apm/public/components/app/correlations/correlations_table.tsx @@ -122,7 +122,7 @@ export function CorrelationsTable({ const loadingText = i18n.translate( 'xpack.apm.correlations.correlationsTable.loadingText', - { defaultMessage: 'Loading' } + { defaultMessage: 'Loading...' } ); const noDataText = i18n.translate( From 3a4c6030b901241eb8e9fbef337c5d36f6fd2fb5 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 9 Dec 2021 11:35:01 -0600 Subject: [PATCH 094/145] [build] Include x-pack example plugins when using example-plugins flag (#120697) * [build] Include x-pack example plugins when using example-plugins flag * revert test --- .../tasks/build_kibana_example_plugins.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/dev/build/tasks/build_kibana_example_plugins.ts b/src/dev/build/tasks/build_kibana_example_plugins.ts index 7eb696ffdd3b2..93ebf41d259e7 100644 --- a/src/dev/build/tasks/build_kibana_example_plugins.ts +++ b/src/dev/build/tasks/build_kibana_example_plugins.ts @@ -13,17 +13,23 @@ import { exec, mkdirp, copyAll, Task } from '../lib'; export const BuildKibanaExamplePlugins: Task = { description: 'Building distributable versions of Kibana example plugins', - async run(config, log, build) { - const examplesDir = Path.resolve(REPO_ROOT, 'examples'); + async run(config, log) { const args = [ - '../../scripts/plugin_helpers', + Path.resolve(REPO_ROOT, 'scripts/plugin_helpers'), 'build', `--kibana-version=${config.getBuildVersion()}`, ]; - const folders = Fs.readdirSync(examplesDir, { withFileTypes: true }) - .filter((f) => f.isDirectory()) - .map((f) => Path.resolve(REPO_ROOT, 'examples', f.name)); + const getExampleFolders = (dir: string) => { + return Fs.readdirSync(dir, { withFileTypes: true }) + .filter((f) => f.isDirectory()) + .map((f) => Path.resolve(dir, f.name)); + }; + + const folders = [ + ...getExampleFolders(Path.resolve(REPO_ROOT, 'examples')), + ...getExampleFolders(Path.resolve(REPO_ROOT, 'x-pack/examples')), + ]; for (const examplePlugin of folders) { try { @@ -40,8 +46,8 @@ export const BuildKibanaExamplePlugins: Task = { const pluginsDir = config.resolveFromTarget('example_plugins'); await mkdirp(pluginsDir); - await copyAll(examplesDir, pluginsDir, { - select: ['*/build/*.zip'], + await copyAll(REPO_ROOT, pluginsDir, { + select: ['examples/*/build/*.zip', 'x-pack/examples/*/build/*.zip'], }); }, }; From 3672215e5fc21e117e9733eb52bc07e436b461dc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Dec 2021 13:05:54 -0500 Subject: [PATCH 095/145] Update APM (main) (#120860) * Update APM * fix types * fix types in tests too Co-authored-by: Renovate Bot Co-authored-by: pgayvallet --- package.json | 6 ++--- src/core/public/apm_system.test.ts | 1 + src/core/public/apm_system.ts | 2 +- yarn.lock | 36 +++++++++++++++--------------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index a4f8ae69eda39..cae25e40ccf07 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,8 @@ "@dnd-kit/core": "^3.1.1", "@dnd-kit/sortable": "^4.0.0", "@dnd-kit/utilities": "^2.0.0", - "@elastic/apm-rum": "^5.9.1", - "@elastic/apm-rum-react": "^1.3.1", + "@elastic/apm-rum": "^5.10.0", + "@elastic/apm-rum-react": "^1.3.2", "@elastic/apm-synthtrace": "link:bazel-bin/packages/elastic-apm-synthtrace", "@elastic/charts": "40.1.0", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", @@ -226,7 +226,7 @@ "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", - "elastic-apm-node": "^3.25.0", + "elastic-apm-node": "^3.26.0", "execa": "^4.0.2", "exit-hook": "^2.2.0", "expiry-js": "0.1.7", diff --git a/src/core/public/apm_system.test.ts b/src/core/public/apm_system.test.ts index f62421cb55abc..842d5de7e5afc 100644 --- a/src/core/public/apm_system.test.ts +++ b/src/core/public/apm_system.test.ts @@ -9,6 +9,7 @@ jest.mock('@elastic/apm-rum'); import type { DeeplyMockedKeys, MockedKeys } from '@kbn/utility-types/jest'; import { init, apm } from '@elastic/apm-rum'; +import type { Transaction } from '@elastic/apm-rum'; import { ApmSystem } from './apm_system'; import { Subject } from 'rxjs'; import { InternalApplicationStart } from './application/types'; diff --git a/src/core/public/apm_system.ts b/src/core/public/apm_system.ts index f15a317f9f934..2231f394381f0 100644 --- a/src/core/public/apm_system.ts +++ b/src/core/public/apm_system.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ApmBase, AgentConfigOptions } from '@elastic/apm-rum'; +import type { ApmBase, AgentConfigOptions, Transaction } from '@elastic/apm-rum'; import { modifyUrl } from '@kbn/std'; import { CachedResourceObserver } from './apm_resource_counter'; import type { InternalApplicationStart } from './application'; diff --git a/yarn.lock b/yarn.lock index c68a855a3f340..fbeab26149b3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1492,29 +1492,29 @@ dependencies: tslib "^2.0.0" -"@elastic/apm-rum-core@^5.12.1": - version "5.12.1" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.12.1.tgz#ad78787876c68b9ce718d1c42b8e7b12b12eaa69" - integrity sha512-b9CyqLdu2rSdjqi5Pc2bNfQCRQT26GjQzCTpJq1WoewDaoivsPoUDrY7tCJV+j3rmRSxG7vX91pM5SygjFr7aA== +"@elastic/apm-rum-core@^5.13.0": + version "5.13.0" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-core/-/apm-rum-core-5.13.0.tgz#dbada016fc73c0be4d7df1ba835a1556dc48d21e" + integrity sha512-5kSTbdmyLfpCdLgoy387y+WMCVl4YuYHdkFgDWAGfOBR+aaOCQAcQoLc8KK6+FZoN/vqvVSFCN+GxxyBc9Kmdw== dependencies: error-stack-parser "^1.3.5" opentracing "^0.14.3" promise-polyfill "^8.1.3" -"@elastic/apm-rum-react@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.3.1.tgz#2b1a5cfe3763538d7e655816a333162696f906c9" - integrity sha512-nJebgxMUWsWWz93v39ok0DwFGUvv9qZkA+oElUzCKyVvWpgHsWE2pvgjthrvay64qzfEg5QeM56ywaef9V13rw== +"@elastic/apm-rum-react@^1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum-react/-/apm-rum-react-1.3.2.tgz#682dbf040aad4a6d7c423d0da81cc9e65a910693" + integrity sha512-ohSgd8wXPziJQpaixvbNAHL6/sMLBW+iOrxCFvCKF9gRllowUTqYi+etery96Hq0X8yXC+/fJg18ZXx9wlJLEQ== dependencies: - "@elastic/apm-rum" "^5.9.1" + "@elastic/apm-rum" "^5.10.0" hoist-non-react-statics "^3.3.0" -"@elastic/apm-rum@^5.9.1": - version "5.9.1" - resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.9.1.tgz#23b12650d443ec0a5f7c6b7b4039334e42d2fe8a" - integrity sha512-NJAdzxXxf+LeCI0Dz3P+RMVY66C8sAztIg4tvnrhvBqxf8d7se+FpYw3oYjw3BZ8UDycmXEaIqEGcynUUndgqA== +"@elastic/apm-rum@^5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@elastic/apm-rum/-/apm-rum-5.10.0.tgz#ffadc50e53d7b7bc9f1c7851f7d70631c3e14eba" + integrity sha512-Aw3UwiduxNfJ0/S3Uq0cO8O+60RmEMa9AJGq6v8fFQ8UdnTV1IgT73NpPyMLtn3qBcKyJNKpvx0jW28w5IVLcQ== dependencies: - "@elastic/apm-rum-core" "^5.12.1" + "@elastic/apm-rum-core" "^5.13.0" "@elastic/apm-synthtrace@link:bazel-bin/packages/elastic-apm-synthtrace": version "0.0.0" @@ -12420,10 +12420,10 @@ elastic-apm-http-client@^10.3.0: readable-stream "^3.4.0" stream-chopper "^3.0.1" -elastic-apm-node@^3.25.0: - version "3.25.0" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.25.0.tgz#3207c936429739cd07f64cbf76d7b5b4b8e0da3e" - integrity sha512-3K+uUQkKeaJarjPb/pDY3fldP7QeppgPPx8nJOkOrW+BvQK5YBMiWbf4S9fdx0yUUkWsVX6K+CAc401+Y1COkg== +elastic-apm-node@^3.26.0: + version "3.26.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.26.0.tgz#551eb425eb85bf16c6ce9567fe92013b038018ab" + integrity sha512-MwYFlBTlcHI8GGQXLnnEm70JJ4RRFkHCY1D3Wt2027l8T/Ye5tgssMSiKyRbjb9bVdibbte73Xn8HF8i35UaxA== dependencies: "@elastic/ecs-pino-format" "^1.2.0" after-all-results "^2.0.0" From 3fa1750e6a02de62f48be01aacd31f22345a211c Mon Sep 17 00:00:00 2001 From: Vadim Yakhin Date: Thu, 9 Dec 2021 11:25:08 -0800 Subject: [PATCH 096/145] Improve github apps frontend validation (#120983) by disabling the submit button if private key has not been uploaded Note: the validation will not work if a user removes the file from the file picker after uploading it, as file picker doesn't call the onChange callback on that action. --- .../components/add_source/github_via_app.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx index a08f49b8bbe78..b62648348ed80 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/github_via_app.tsx @@ -44,8 +44,13 @@ interface GithubViaAppProps { export const GitHubViaApp: React.FC = ({ isGithubEnterpriseServer }) => { const { isOrganization } = useValues(AppLogic); - const { githubAppId, githubEnterpriseServerUrl, isSubmitButtonLoading, indexPermissionsValue } = - useValues(GithubViaAppLogic); + const { + githubAppId, + githubEnterpriseServerUrl, + stagedPrivateKey, + isSubmitButtonLoading, + indexPermissionsValue, + } = useValues(GithubViaAppLogic); const { setGithubAppId, setGithubEnterpriseServerUrl, @@ -118,7 +123,12 @@ export const GitHubViaApp: React.FC = ({ isGithubEnterpriseSe fill type="submit" isLoading={isSubmitButtonLoading} - isDisabled={!githubAppId || (isGithubEnterpriseServer && !githubEnterpriseServerUrl)} + isDisabled={ + // disable submit button if any required fields are empty + !githubAppId || + (isGithubEnterpriseServer && !githubEnterpriseServerUrl) || + !stagedPrivateKey + } > {isSubmitButtonLoading ? 'Connecting…' : `Connect ${name}`} From bd77e4f681b87aee580bec36028958b0176cceef Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 9 Dec 2021 15:00:19 -0500 Subject: [PATCH 097/145] [Fleet] Add CA fingerprint field to the output form (#120980) --- .../components/edit_output_flyout/index.tsx | 21 ++++++++++++++ .../output_form_validators.tsx | 10 +++++++ .../edit_output_flyout/use_output_form.tsx | 28 ++++++++++++++++--- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx index 824eec081e28b..62b22d0bdffc6 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/settings/components/edit_output_flyout/index.tsx @@ -135,6 +135,27 @@ export const EditOutputFlyout: React.FunctionComponent = })} {...inputs.elasticsearchUrlInput.props} /> + + } + {...inputs.caTrustedFingerprintInput.formRowProps} + > + + ( void, output?: Output) { isPreconfigured ); + const caTrustedFingerprintInput = useInput( + output?.ca_trusted_fingerprint ?? '', + validateCATrustedFingerPrint, + isPreconfigured + ); + const defaultOutputInput = useSwitchInput( output?.is_default ?? false, isPreconfigured || output?.is_default @@ -127,6 +138,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { additionalYamlConfigInput, defaultOutputInput, defaultMonitoringOutputInput, + caTrustedFingerprintInput, }; const hasChanged = Object.values(inputs).some((input) => input.hasChanged); @@ -135,13 +147,19 @@ export function useOutputForm(onSucess: () => void, output?: Output) { const nameInputValid = nameInput.validate(); const elasticsearchUrlsValid = elasticsearchUrlInput.validate(); const additionalYamlConfigValid = additionalYamlConfigInput.validate(); - - if (!elasticsearchUrlsValid || !additionalYamlConfigValid || !nameInputValid) { + const caTrustedFingerprintValid = caTrustedFingerprintInput.validate(); + + if ( + !elasticsearchUrlsValid || + !additionalYamlConfigValid || + !nameInputValid || + !caTrustedFingerprintValid + ) { return false; } return true; - }, [nameInput, elasticsearchUrlInput, additionalYamlConfigInput]); + }, [nameInput, elasticsearchUrlInput, additionalYamlConfigInput, caTrustedFingerprintInput]); const submit = useCallback(async () => { try { @@ -157,6 +175,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { is_default: defaultOutputInput.value, is_default_monitoring: defaultMonitoringOutputInput.value, config_yaml: additionalYamlConfigInput.value, + ca_trusted_fingerprint: caTrustedFingerprintInput.value, }; if (output) { @@ -195,6 +214,7 @@ export function useOutputForm(onSucess: () => void, output?: Output) { defaultMonitoringOutputInput.value, defaultOutputInput.value, elasticsearchUrlInput.value, + caTrustedFingerprintInput.value, nameInput.value, notifications.toasts, onSucess, From c72d6b46c3dd904626a562e32cbef8c05dd5d25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Thu, 9 Dec 2021 15:11:02 -0500 Subject: [PATCH 098/145] [APM] disable fleet depreaction warning (#120988) --- x-pack/plugins/apm/server/plugin.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index b603d9e72a2b0..2f8e10d68ae51 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -47,7 +47,6 @@ import { TRANSACTION_TYPE, } from '../common/elasticsearch_fieldnames'; import { tutorialProvider } from './tutorial'; -import { getDeprecations } from './deprecations'; export class APMPlugin implements @@ -197,14 +196,6 @@ export class APMPlugin kibanaVersion: this.initContext.env.packageInfo.version, }); - core.deprecations.registerDeprecations({ - getDeprecations: getDeprecations({ - cloudSetup: plugins.cloud, - fleet: resourcePlugins.fleet, - branch: this.initContext.env.packageInfo.branch, - }), - }); - return { config$, getApmIndices: boundGetApmIndices, From 744849f491b201c5bd4a0715894ff17b0086f3c2 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Thu, 9 Dec 2021 13:23:27 -0800 Subject: [PATCH 099/145] Support system indices being hidden (#120985) Co-authored-by: Josh Dover --- packages/kbn-es-archiver/src/actions/load.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index c5bea5e29a687..0a318f895deb3 100644 --- a/packages/kbn-es-archiver/src/actions/load.ts +++ b/packages/kbn-es-archiver/src/actions/load.ts @@ -86,15 +86,17 @@ export async function loadAction({ progress.deactivate(); const result = stats.toJSON(); + const indicesWithDocs: string[] = []; for (const [index, { docs }] of Object.entries(result)) { if (docs && docs.indexed > 0) { log.info('[%s] Indexed %d docs into %j', name, docs.indexed, index); + indicesWithDocs.push(index); } } await client.indices.refresh( { - index: '_all', + index: indicesWithDocs.join(','), allow_no_indices: true, }, { From d8d48fe6568682d899fd4744815e2bab5f82fb3d Mon Sep 17 00:00:00 2001 From: vladpro25 <91911546+vladpro25@users.noreply.github.com> Date: Thu, 9 Dec 2021 23:47:04 +0200 Subject: [PATCH 100/145] Auto complete for script suggests deprecated query type (#120283) * Change suggestions for Sampler and Diversified sampler aggregations * Auto complete for script suggests deprecated query type * Auto complete for script suggests deprecated query type * Auto complete for script suggests deprecated query type * Auto complete for script suggests deprecated query type Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/spec_definitions/js/query/dsl.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts b/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts index 50da221c7e6bb..895d2e4ed72fe 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/query/dsl.ts @@ -745,6 +745,16 @@ export const query = (specService: SpecDefinitionsService) => { // populated by a global rule }, }, + script_score: { + __template: { + script: {}, + query: {}, + }, + script: {}, + query: {}, + min_score: '', + boost: 1.0, + }, wrapper: { __template: { query: 'QUERY_BASE64_ENCODED', From 21af67080d1bbecbf8175e397b61f63eb7080223 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Thu, 9 Dec 2021 14:48:57 -0700 Subject: [PATCH 101/145] [Rule Registry] Switch to _source for updating documents instead of Fields API (#118245) * [Rule Registry] Switch to _source for updating documents instead of Fields API * updating test with _source instead of fields * removing mapValues dep * Refactor types and clean up names Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/rule_data_client/types.ts | 6 ++---- .../utils/create_lifecycle_executor.test.ts | 8 ++++---- .../server/utils/create_lifecycle_executor.ts | 18 ++++++++---------- .../utils/create_lifecycle_rule_type.test.ts | 14 +++----------- 4 files changed, 17 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts index 5fab32eb38868..e970a13c78aaa 100644 --- a/x-pack/plugins/rule_registry/server/rule_data_client/types.ts +++ b/x-pack/plugins/rule_registry/server/rule_data_client/types.ts @@ -10,7 +10,7 @@ import { BulkRequest, BulkResponse } from '@elastic/elasticsearch/lib/api/typesW import { ESSearchRequest, ESSearchResponse } from 'src/core/types/elasticsearch'; import { FieldDescriptor } from 'src/plugins/data/server'; -import { TechnicalRuleDataFieldName } from '../../common/technical_rule_data_field_names'; +import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; export interface IRuleDataClient { indexName: string; @@ -24,9 +24,7 @@ export interface IRuleDataClient { export interface IRuleDataReader { search( request: TSearchRequest - ): Promise< - ESSearchResponse>, TSearchRequest> - >; + ): Promise, TSearchRequest>>; getDynamicIndexPattern(target?: string): Promise<{ title: string; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index 2c5fe09d80563..d1c20e0667e24 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -127,7 +127,7 @@ describe('createLifecycleExecutor', () => { hits: { hits: [ { - fields: { + _source: { '@timestamp': '', [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', [ALERT_UUID]: 'ALERT_0_UUID', @@ -144,7 +144,7 @@ describe('createLifecycleExecutor', () => { }, }, { - fields: { + _source: { '@timestamp': '', [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', [ALERT_UUID]: 'ALERT_1_UUID', @@ -247,7 +247,7 @@ describe('createLifecycleExecutor', () => { hits: { hits: [ { - fields: { + _source: { '@timestamp': '', [ALERT_INSTANCE_ID]: 'TEST_ALERT_0', [ALERT_UUID]: 'ALERT_0_UUID', @@ -263,7 +263,7 @@ describe('createLifecycleExecutor', () => { }, }, { - fields: { + _source: { '@timestamp': '', [ALERT_INSTANCE_ID]: 'TEST_ALERT_1', [ALERT_UUID]: 'ALERT_1_UUID', diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index c30b1654a3587..0ca0002470af0 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -18,7 +18,7 @@ import { AlertTypeParams, AlertTypeState, } from '../../../alerting/server'; -import { ParsedTechnicalFields, parseTechnicalFields } from '../../common/parse_technical_fields'; +import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; import { ALERT_DURATION, ALERT_END, @@ -216,8 +216,6 @@ export const createLifecycleExecutor = collapse: { field: ALERT_UUID, }, - _source: false, - fields: [{ field: '*', include_unmapped: true }], sort: { [TIMESTAMP]: 'desc' as const, }, @@ -226,13 +224,13 @@ export const createLifecycleExecutor = }); hits.hits.forEach((hit) => { - const fields = parseTechnicalFields(hit.fields); - const indexName = hit._index; - const alertId = fields[ALERT_INSTANCE_ID]; - trackedAlertsDataMap[alertId] = { - indexName, - fields, - }; + const alertId = hit._source[ALERT_INSTANCE_ID]; + if (alertId) { + trackedAlertsDataMap[alertId] = { + indexName: hit._index, + fields: hit._source, + }; + } }); } diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index f0e2412629bb1..7aa7dcd9620fe 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -14,7 +14,7 @@ import { ALERT_UUID, } from '@kbn/rule-data-utils'; import { loggerMock } from '@kbn/logging/mocks'; -import { castArray, omit, mapValues } from 'lodash'; +import { castArray, omit } from 'lodash'; import { RuleDataClient } from '../rule_data_client'; import { createRuleDataClientMock } from '../rule_data_client/rule_data_client.mock'; import { createLifecycleRuleTypeFactory } from './create_lifecycle_rule_type_factory'; @@ -293,14 +293,10 @@ describe('createLifecycleRuleTypeFactory', () => { (doc: any) => !('index' in doc) && doc['service.name'] === 'opbeans-node' ) as Record; - const stored = mapValues(lastOpbeansNodeDoc, (val) => { - return castArray(val); - }); - // @ts-ignore 4.3.5 upgrade helpers.ruleDataClientMock.getReader().search.mockResolvedValueOnce({ hits: { - hits: [{ fields: stored } as any], + hits: [{ _source: lastOpbeansNodeDoc } as any], total: { value: 1, relation: 'eq', @@ -378,13 +374,9 @@ describe('createLifecycleRuleTypeFactory', () => { (doc: any) => !('index' in doc) && doc['service.name'] === 'opbeans-node' ) as Record; - const stored = mapValues(lastOpbeansNodeDoc, (val) => { - return castArray(val); - }); - helpers.ruleDataClientMock.getReader().search.mockResolvedValueOnce({ hits: { - hits: [{ fields: stored } as any], + hits: [{ _source: lastOpbeansNodeDoc } as any], total: { value: 1, relation: 'eq', From 63f58dad17129b6b95c720e55596839b63f614ff Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 9 Dec 2021 16:07:26 -0600 Subject: [PATCH 102/145] [build/docker] Use /tmp to store kibana archive (#120991) --- .../os_packages/docker_generator/templates/base/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile index b1d9fafffab57..90a622e64efe4 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/base/Dockerfile @@ -16,7 +16,7 @@ RUN {{packageManager}} install -y findutils tar gzip {{/ubi}} {{#usePublicArtifact}} -RUN cd /opt && \ +RUN cd /tmp && \ curl --retry 8 -s -L \ --output kibana.tar.gz \ https://artifacts.elastic.co/downloads/kibana/{{artifactPrefix}}-$(arch).tar.gz && \ From 34711539fa61c5add55032e97a379961cc9d4706 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Thu, 9 Dec 2021 15:57:30 -0700 Subject: [PATCH 103/145] Fixes alerts and cases to work with telemetry (#121002) ## Summary One line fix to where we have to expose cases to the saved object client as hidden to work with telemetry. This one liner was broken out from: https://github.com/elastic/kibana/pull/120809 So we could back-port easier to earlier versions. Manual testing: To see telemetry go to advanced settings -> Usage Data (and click cluster data): Screen Shot 2021-12-08 at 4 14 43 PM And you will see it like so: Screen Shot 2021-12-08 at 4 43 10 PM --- x-pack/plugins/security_solution/server/usage/collector.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/usage/collector.ts b/x-pack/plugins/security_solution/server/usage/collector.ts index 5402dd4c375a8..3f64a6e5e227b 100644 --- a/x-pack/plugins/security_solution/server/usage/collector.ts +++ b/x-pack/plugins/security_solution/server/usage/collector.ts @@ -9,6 +9,7 @@ import { CoreSetup, SavedObjectsClientContract } from '../../../../../src/core/s import { CollectorFetchContext } from '../../../../../src/plugins/usage_collection/server'; import { CollectorDependencies } from './types'; import { fetchDetectionsMetrics } from './detections'; +import { SAVED_OBJECT_TYPES } from '../../../cases/common/constants'; export type RegisterCollector = (deps: CollectorDependencies) => void; export interface UsageData { @@ -17,7 +18,8 @@ export interface UsageData { export async function getInternalSavedObjectsClient(core: CoreSetup) { return core.getStartServices().then(async ([coreStart]) => { - return coreStart.savedObjects.createInternalRepository(); + // note: we include the cases hidden types here otherwise we would not be able to query them. If at some point cases is not considered a hidden type this can be removed + return coreStart.savedObjects.createInternalRepository(SAVED_OBJECT_TYPES); }); } From 5e34758d43884106cb2411433a5324c92ed3b741 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Thu, 9 Dec 2021 18:26:59 -0600 Subject: [PATCH 104/145] [Workplace Search] Add a download diagnostics button when error is shown (#121017) * Factor out DownloadDiagnosticsButton for reuse * Add logic for rendering button * Add button to layout * Fix test name * Better method naming --- .../download_diagnostics_button.test.tsx | 51 +++++++++++++++++++ .../download_diagnostics_button.tsx | 48 +++++++++++++++++ .../components/source_layout.test.tsx | 12 +++++ .../components/source_layout.tsx | 12 ++++- .../components/source_settings.test.tsx | 34 +------------ .../components/source_settings.tsx | 21 ++------ .../views/content_sources/constants.ts | 7 +++ .../content_sources/source_logic.test.ts | 28 ++++++++++ .../views/content_sources/source_logic.ts | 18 +++++++ 9 files changed, 180 insertions(+), 51 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.test.tsx new file mode 100644 index 0000000000000..ca2af637c1d6e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { setMockValues } from '../../../../__mocks__/kea_logic'; +import { fullContentSources } from '../../../__mocks__/content_sources.mock'; + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiButton } from '@elastic/eui'; + +import { DownloadDiagnosticsButton } from './download_diagnostics_button'; + +describe('DownloadDiagnosticsButton', () => { + const label = 'foo123'; + const contentSource = fullContentSources[0]; + const buttonLoading = false; + const isOrganization = true; + + const mockValues = { + contentSource, + buttonLoading, + isOrganization, + }; + + beforeEach(() => { + setMockValues(mockValues); + }); + + it('renders the Download diagnostics button with org href', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiButton).prop('href')).toEqual( + '/internal/workplace_search/org/sources/123/download_diagnostics' + ); + }); + + it('renders the Download diagnostics button with account href', () => { + setMockValues({ ...mockValues, isOrganization: false }); + const wrapper = shallow(); + + expect(wrapper.find(EuiButton).prop('href')).toEqual( + '/internal/workplace_search/account/sources/123/download_diagnostics' + ); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.tsx new file mode 100644 index 0000000000000..866746f43d653 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/download_diagnostics_button.tsx @@ -0,0 +1,48 @@ +/* + * 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 { useValues } from 'kea'; + +import { EuiButton } from '@elastic/eui'; + +import { HttpLogic } from '../../../../shared/http'; +import { AppLogic } from '../../../app_logic'; + +import { SourceLogic } from '../source_logic'; + +interface Props { + label: string; +} + +export const DownloadDiagnosticsButton: React.FC = ({ label }) => { + const { http } = useValues(HttpLogic); + const { isOrganization } = useValues(AppLogic); + const { + contentSource: { id, serviceType }, + buttonLoading, + } = useValues(SourceLogic); + + const diagnosticsPath = isOrganization + ? http.basePath.prepend(`/internal/workplace_search/org/sources/${id}/download_diagnostics`) + : http.basePath.prepend( + `/internal/workplace_search/account/sources/${id}/download_diagnostics` + ); + + return ( + + {label} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx index 944a54169f0b8..62d1bff27dd78 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.test.tsx @@ -18,6 +18,7 @@ import { EuiCallOut } from '@elastic/eui'; import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../../components/layout'; +import { DownloadDiagnosticsButton } from './download_diagnostics_button'; import { SourceInfoCard } from './source_info_card'; import { SourceLayout } from './source_layout'; @@ -26,6 +27,7 @@ describe('SourceLayout', () => { const mockValues = { contentSource, dataLoading: false, + diagnosticDownloadButtonVisible: false, isOrganization: true, }; @@ -87,4 +89,14 @@ describe('SourceLayout', () => { expect(wrapper.find(EuiCallOut)).toHaveLength(1); }); + + it('renders DownloadDiagnosticsButton', () => { + setMockValues({ + ...mockValues, + diagnosticDownloadButtonVisible: true, + }); + const wrapper = shallow(); + + expect(wrapper.find(DownloadDiagnosticsButton)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx index f741cfdc538fc..727e171d1073c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_layout.tsx @@ -19,12 +19,14 @@ import { WorkplaceSearchPageTemplate, PersonalDashboardLayout } from '../../../c import { NAV } from '../../../constants'; import { + DOWNLOAD_DIAGNOSTIC_BUTTON, SOURCE_DISABLED_CALLOUT_TITLE, SOURCE_DISABLED_CALLOUT_DESCRIPTION, SOURCE_DISABLED_CALLOUT_BUTTON, } from '../constants'; import { SourceLogic } from '../source_logic'; +import { DownloadDiagnosticsButton } from './download_diagnostics_button'; import { SourceInfoCard } from './source_info_card'; export const SourceLayout: React.FC = ({ @@ -32,7 +34,7 @@ export const SourceLayout: React.FC = ({ pageChrome = [], ...props }) => { - const { contentSource, dataLoading } = useValues(SourceLogic); + const { contentSource, dataLoading, diagnosticDownloadButtonVisible } = useValues(SourceLogic); const { isOrganization } = useValues(AppLogic); const { name, createdAt, serviceType, isFederatedSource, supportedByLicense } = contentSource; @@ -61,6 +63,13 @@ export const SourceLayout: React.FC = ({ ); + const downloadDiagnosticButton = ( + <> + + + + ); + const Layout = isOrganization ? WorkplaceSearchPageTemplate : PersonalDashboardLayout; return ( @@ -69,6 +78,7 @@ export const SourceLayout: React.FC = ({ {...props} pageChrome={[NAV.SOURCES, name || '...', ...pageChrome]} > + {diagnosticDownloadButtonVisible && downloadDiagnosticButton} {!supportedByLicense && callout} {pageHeader} {children} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx index 83cf21ce86233..ec499293f2fd1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.test.tsx @@ -18,6 +18,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import { SourceConfigFields } from '../../../components/shared/source_config_fields'; +import { DownloadDiagnosticsButton } from './download_diagnostics_button'; import { SourceSettings } from './source_settings'; describe('SourceSettings', () => { @@ -48,6 +49,7 @@ describe('SourceSettings', () => { const wrapper = shallow(); expect(wrapper.find('form')).toHaveLength(1); + expect(wrapper.find(DownloadDiagnosticsButton)).toHaveLength(1); }); it('handles form submission', () => { @@ -104,36 +106,4 @@ describe('SourceSettings', () => { sourceConfigData.configuredFields.publicKey ); }); - - describe('DownloadDiagnosticsButton', () => { - it('renders for org with correct href', () => { - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('href')).toEqual( - '/internal/workplace_search/org/sources/123/download_diagnostics' - ); - }); - - it('renders for account with correct href', () => { - setMockValues({ - ...mockValues, - isOrganization: false, - }); - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('href')).toEqual( - '/internal/workplace_search/account/sources/123/download_diagnostics' - ); - }); - - it('renders with the correct download file name', () => { - jest.spyOn(global.Date, 'now').mockImplementationOnce(() => new Date('1970-01-01').valueOf()); - - const wrapper = shallow(); - - expect(wrapper.find('[data-test-subj="DownloadDiagnosticsButton"]').prop('download')).toEqual( - '123_custom_0_diagnostics.json' - ); - }); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx index e5924b672c771..484a9ca14b4e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx @@ -23,7 +23,6 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { HttpLogic } from '../../../../shared/http'; import { EuiButtonEmptyTo } from '../../../../shared/react_router_helpers'; import { AppLogic } from '../../../app_logic'; import { ContentSection } from '../../../components/shared/content_section'; @@ -61,11 +60,11 @@ import { import { staticSourceData } from '../source_data'; import { SourceLogic } from '../source_logic'; +import { DownloadDiagnosticsButton } from './download_diagnostics_button'; + import { SourceLayout } from './source_layout'; export const SourceSettings: React.FC = () => { - const { http } = useValues(HttpLogic); - const { updateContentSource, removeContentSource, @@ -110,12 +109,6 @@ export const SourceSettings: React.FC = () => { const { clientId, clientSecret, publicKey, consumerKey, baseUrl } = configuredFields || {}; - const diagnosticsPath = isOrganization - ? http.basePath.prepend(`/internal/workplace_search/org/sources/${id}/download_diagnostics`) - : http.basePath.prepend( - `/internal/workplace_search/account/sources/${id}/download_diagnostics` - ); - const handleNameChange = (e: ChangeEvent) => setValue(e.target.value); const submitNameChange = (e: FormEvent) => { @@ -241,15 +234,7 @@ export const SourceSettings: React.FC = () => { )} - - {SYNC_DIAGNOSTICS_BUTTON} - + { dataLoading: true, sectionLoading: true, buttonLoading: false, + diagnosticDownloadButtonVisible: false, contentMeta: DEFAULT_META, contentFilterValue: '', isConfigurationUpdateButtonLoading: false, @@ -125,6 +126,12 @@ describe('SourceLogic', () => { expect(SourceLogic.values.buttonLoading).toEqual(false); }); + + it('showDiagnosticDownloadButton', () => { + SourceLogic.actions.showDiagnosticDownloadButton(); + + expect(SourceLogic.values.diagnosticDownloadButtonVisible).toEqual(true); + }); }); describe('listeners', () => { @@ -183,6 +190,27 @@ describe('SourceLogic', () => { expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); + it('handles error message with diagnostic bundle error message', async () => { + const showDiagnosticDownloadButtonSpy = jest.spyOn( + SourceLogic.actions, + 'showDiagnosticDownloadButton' + ); + + // For contenst source errors, the API returns the source errors in an error property in the success + // response. We don't reject here because we still render the content source with the error. + const promise = Promise.resolve({ + ...contentSource, + errors: [ + 'The database is on fire. [Check diagnostic bundle for details - Message id: 123]', + ], + }); + http.get.mockReturnValue(promise); + SourceLogic.actions.initializeSource(contentSource.id); + await promise; + + expect(showDiagnosticDownloadButtonSpy).toHaveBeenCalled(); + }); + describe('404s', () => { const mock404 = Promise.reject({ response: { status: 404 } }); 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 b76627f57b3a3..8f0cfa8cfa280 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 @@ -52,6 +52,7 @@ export interface SourceActions { setButtonNotLoading(): void; setStagedPrivateKey(stagedPrivateKey: string | null): string | null; setConfigurationUpdateButtonNotLoading(): void; + showDiagnosticDownloadButton(): void; } interface SourceValues { @@ -59,6 +60,7 @@ interface SourceValues { dataLoading: boolean; sectionLoading: boolean; buttonLoading: boolean; + diagnosticDownloadButtonVisible: boolean; contentItems: SourceContentItem[]; contentMeta: Meta; contentFilterValue: string; @@ -108,6 +110,7 @@ export const SourceLogic = kea>({ setButtonNotLoading: () => false, setStagedPrivateKey: (stagedPrivateKey: string) => stagedPrivateKey, setConfigurationUpdateButtonNotLoading: () => false, + showDiagnosticDownloadButton: true, }, reducers: { contentSource: [ @@ -147,6 +150,13 @@ export const SourceLogic = kea>({ setSearchResults: () => false, }, ], + diagnosticDownloadButtonVisible: [ + false, + { + showDiagnosticDownloadButton: () => true, + initializeSource: () => false, + }, + ], contentItems: [ [], { @@ -200,6 +210,9 @@ export const SourceLogic = kea>({ } if (response.errors) { setErrorMessage(response.errors); + if (errorsHaveDiagnosticBundleString(response.errors as unknown as string[])) { + actions.showDiagnosticDownloadButton(); + } } else { clearFlashMessages(); } @@ -343,3 +356,8 @@ const setPage = (state: Meta, page: number) => ({ current: page, }, }); + +const errorsHaveDiagnosticBundleString = (errors: string[]) => { + const ERROR_SUBSTRING = 'Check diagnostic bundle for details'; + return errors.find((e) => e.includes(ERROR_SUBSTRING)); +}; From 181b9e0271966d13e2f2b1097e8fac351135c74b Mon Sep 17 00:00:00 2001 From: Frank Hassanabad Date: Thu, 9 Dec 2021 17:56:42 -0700 Subject: [PATCH 105/145] [Security Solutions] Fixes telemetry to work with rule types (#120809) ## Summary What this does: * Fixes telemetry to work with the newer rule types * Updates the queries to the new rule types and rule query names * Uses constants where I can from cases and the new rule types * Changes the index to the new index type alias * Adds e2e backend tests we didn't have before What this does not do: * Doesn't add e2e backend tests for alerts added to cases * Doesn't add e2e backend tests for ML jobs for security_solution Those two test scenarios have to be manually tested still. Manual testing: To see telemetry go to advanced settings -> Usage Data (and click cluster data): Screen Shot 2021-12-08 at 4 14 43 PM Create alerts of different types and add them to cases: Screen Shot 2021-12-08 at 4 48 21 PM Activate ML_jobs and any alerts that have ML jobs associated: Screen Shot 2021-12-08 at 5 08 42 PM When clicking advanced settings -> Usage Data -> Click cluster data Search for `security_solution` and then ensure that the data looks as expected underneath the different values such as: `ml_jobs` Screen Shot 2021-12-08 at 3 08 25 PM `detection_rules` and `cases` working again: Screen Shot 2021-12-08 at 4 43 10 PM Note, `detection_rule_detail` will only be filled in if have prepackaged rules installed: Screen Shot 2021-12-08 at 5 14 50 PM Also note that the `detection_rule_detail`'s `rule_id` is its UUID and not its `rule_id`. That's the way it's been in the codebase for a while it looks like so I have not changed that behavior. ### 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 --- .../security_solution/server/plugin.ts | 9 +- .../detections/detection_rule_helpers.ts | 42 +- .../usage/detections/detections.test.ts | 4 +- .../server/usage/detections/types.ts | 2 +- .../security_and_spaces/tests/index.ts | 4 + .../tests/telemetry/README.md | 3 + .../tests/telemetry/detection_rules.ts | 426 ++++++++++++++++++ .../tests/telemetry/index.ts | 18 + .../detection_engine_api_integration/utils.ts | 150 ++++++ .../es_archives/security_solution/README.md | 2 + .../security_solution/telemetry/data.json | 51 +++ .../security_solution/telemetry/mappings.json | 20 + 12 files changed, 718 insertions(+), 13 deletions(-) create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/README.md create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/detection_rules.ts create mode 100644 x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/index.ts create mode 100644 x-pack/test/functional/es_archives/security_solution/telemetry/data.json create mode 100644 x-pack/test/functional/es_archives/security_solution/telemetry/mappings.json diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 75933afca9686..a676ca8779f6a 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -45,7 +45,12 @@ import { initSavedObjects } from './saved_objects'; import { AppClientFactory } from './client'; import { createConfig, ConfigType } from './config'; import { initUiSettings } from './ui_settings'; -import { APP_ID, SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID } from '../common/constants'; +import { + APP_ID, + SERVER_APP_ID, + LEGACY_NOTIFICATIONS_ID, + DEFAULT_ALERTS_INDEX, +} from '../common/constants'; import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerLimitedConcurrencyRoutes } from './endpoint/routes/limited_concurrency'; import { registerResolverRoutes } from './endpoint/routes/resolver'; @@ -161,7 +166,7 @@ export class Plugin implements ISecuritySolutionPlugin { initUsageCollectors({ core, kibanaIndex: core.savedObjects.getKibanaIndex(), - signalsIndex: config.signalsIndex, + signalsIndex: DEFAULT_ALERTS_INDEX, ml: plugins.ml, usageCollection: plugins.usageCollection, }); diff --git a/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts b/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts index a85f70d5a328d..0eb2a03e0c3a2 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/detection_rule_helpers.ts @@ -5,7 +5,17 @@ * 2.0. */ -import { SIGNALS_ID } from '@kbn/securitysolution-rules'; +import { + SIGNALS_ID, + EQL_RULE_TYPE_ID, + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, + THRESHOLD_RULE_TYPE_ID, + SAVED_QUERY_RULE_TYPE_ID, +} from '@kbn/securitysolution-rules'; +import { ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../../cases/common/constants'; import { ElasticsearchClient, SavedObjectsClientContract } from '../../../../../../src/core/server'; import { isElasticRule } from './index'; @@ -188,7 +198,25 @@ export const getDetectionRuleMetrics = async ( ): Promise => { let rulesUsage: DetectionRulesTypeUsage = initialDetectionRulesUsage; const ruleSearchOptions: RuleSearchParams = { - body: { query: { bool: { filter: { term: { 'alert.alertTypeId': SIGNALS_ID } } } } }, + body: { + query: { + bool: { + filter: { + terms: { + 'alert.alertTypeId': [ + SIGNALS_ID, + EQL_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, + SAVED_QUERY_RULE_TYPE_ID, + INDICATOR_RULE_TYPE_ID, + THRESHOLD_RULE_TYPE_ID, + ], + }, + }, + }, + }, + }, filter_path: [], ignore_unavailable: true, index: kibanaIndex, @@ -203,7 +231,7 @@ export const getDetectionRuleMetrics = async ( body: { aggs: { detectionAlerts: { - terms: { field: 'signal.rule.id.keyword' }, + terms: { field: ALERT_RULE_UUID }, }, }, query: { @@ -224,11 +252,10 @@ export const getDetectionRuleMetrics = async ( })) as { body: AlertsAggregationResponse }; const cases = await savedObjectClient.find({ - type: 'cases-comments', - fields: [], + type: CASE_COMMENT_SAVED_OBJECT, page: 1, perPage: MAX_RESULTS_WINDOW, - filter: 'cases-comments.attributes.type: alert', + filter: `${CASE_COMMENT_SAVED_OBJECT}.attributes.type: alert`, }); const casesCache = cases.saved_objects.reduce((cache, { attributes: casesObject }) => { @@ -247,14 +274,13 @@ export const getDetectionRuleMetrics = async ( 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_id: hit._source?.alert.params.ruleId, rule_type: hit._source?.alert.params.type, rule_version: hit._source?.alert.params.version, enabled: hit._source?.alert.enabled, 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 86c77f8febaf6..3e60691011fe9 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 @@ -111,7 +111,7 @@ describe('Detections Usage and Metrics', () => { created_on: '2021-03-23T17:15:59.634Z', elastic_rule: true, enabled: false, - rule_id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + rule_id: '5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de', rule_name: 'Azure Diagnostic Settings Deletion', rule_type: 'query', rule_version: 4, @@ -248,7 +248,7 @@ describe('Detections Usage and Metrics', () => { created_on: '2021-03-23T17:15:59.634Z', elastic_rule: true, enabled: false, - rule_id: '6eecd8c2-8bfb-11eb-afbe-1b7a66309c6d', + rule_id: '5370d4cd-2bb3-4d71-abf5-1e1d0ff5a2de', rule_name: 'Azure Diagnostic Settings Deletion', rule_type: 'query', rule_version: 4, diff --git a/x-pack/plugins/security_solution/server/usage/detections/types.ts b/x-pack/plugins/security_solution/server/usage/detections/types.ts index 430a524f3f03a..635ff47d0067a 100644 --- a/x-pack/plugins/security_solution/server/usage/detections/types.ts +++ b/x-pack/plugins/security_solution/server/usage/detections/types.ts @@ -9,7 +9,7 @@ interface RuleSearchBody { query: { bool: { filter: { - term: { [key: string]: string }; + terms: { [key: string]: string[] }; }; }; }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 23cb9b7d9c42c..55b7327670631 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -68,5 +68,9 @@ export default ({ loadTestFile }: FtrProviderContext): void => { describe('', function () { loadTestFile(require.resolve('./alerts/index')); }); + + describe('', function () { + loadTestFile(require.resolve('./telemetry/index')); + }); }); }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/README.md b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/README.md new file mode 100644 index 0000000000000..6035675db4349 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/README.md @@ -0,0 +1,3 @@ +These are tests for the telemetry rules within "security_solution/server/usage" +* detection_rules + diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/detection_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/detection_rules.ts new file mode 100644 index 0000000000000..a3dfaea64d2d4 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/detection_rules.ts @@ -0,0 +1,426 @@ +/* + * 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 { DetectionMetrics } from '../../../../../plugins/security_solution/server/usage/detections/types'; +import { + EqlCreateSchema, + QueryCreateSchema, + ThreatMatchCreateSchema, + ThresholdCreateSchema, +} from '../../../../../plugins/security_solution/common/detection_engine/schemas/request'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + createRule, + createSignalsIndex, + deleteAllAlerts, + deleteSignalsIndex, + getEqlRuleForSignalTesting, + getInitialDetectionMetrics, + getRuleForSignalTesting, + getSimpleMlRule, + getSimpleThreatMatch, + getStats, + getThresholdRuleForSignalTesting, + installPrePackagedRules, + waitForRuleSuccessOrStatus, + waitForSignalsToBePresent, +} from '../../../utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + const log = getService('log'); + const retry = getService('retry'); + + describe('Detection rule telemetry', async () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/security_solution/telemetry'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/security_solution/telemetry'); + }); + + beforeEach(async () => { + await createSignalsIndex(supertest, log); + }); + + afterEach(async () => { + await deleteSignalsIndex(supertest, log); + await deleteAllAlerts(supertest, log); + }); + + it('should have initialized empty/zero values when no rules are running', async () => { + await retry.try(async () => { + const stats = await getStats(supertest, log); + expect(stats).to.eql(getInitialDetectionMetrics()); + }); + }); + + describe('"kql" rule type', () => { + it('should show stats for active rule', async () => { + const rule: QueryCreateSchema = getRuleForSignalTesting(['telemetry']); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + query: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + enabled: 1, + alerts: 4, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + enabled: 1, + alerts: 4, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + + it('should show stats for in-active rule', async () => { + const rule: QueryCreateSchema = getRuleForSignalTesting(['telemetry'], 'rule-1', false); + await createRule(supertest, log, rule); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + query: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + disabled: 1, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + disabled: 1, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + }); + + describe('"eql" rule type', () => { + it('should show stats for active rule', async () => { + const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['telemetry']); + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + eql: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + enabled: 1, + alerts: 4, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + enabled: 1, + alerts: 4, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + + it('should show stats for in-active rule', async () => { + const rule: EqlCreateSchema = getEqlRuleForSignalTesting(['telemetry'], 'rule-1', false); + await createRule(supertest, log, rule); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + eql: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + disabled: 1, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + disabled: 1, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + }); + + describe('"threshold" rule type', () => { + it('should show stats for active rule', async () => { + const rule: ThresholdCreateSchema = { + ...getThresholdRuleForSignalTesting(['telemetry']), + threshold: { + field: 'keyword', + value: 1, + }, + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + threshold: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + enabled: 1, + alerts: 4, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + enabled: 1, + alerts: 4, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + + it('should show stats for in-active rule', async () => { + const rule: ThresholdCreateSchema = { + ...getThresholdRuleForSignalTesting(['telemetry'], 'rule-1', false), + threshold: { + field: 'keyword', + value: 1, + }, + }; + await createRule(supertest, log, rule); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + threshold: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + disabled: 1, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + disabled: 1, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + }); + + describe('"ml" rule type', () => { + // Note: We don't actually find signals with this test as we don't have a good way of signal finding with ML rules. + it('should show stats for active rule', async () => { + const rule = getSimpleMlRule('rule-1', true); + await createRule(supertest, log, rule); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + machine_learning: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + enabled: 1, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + enabled: 1, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + + it('should show stats for in-active rule', async () => { + const rule = getSimpleMlRule(); + await createRule(supertest, log, rule); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + machine_learning: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + disabled: 1, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + disabled: 1, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + }); + + describe('"indicator_match/threat_match" rule type', () => { + it('should show stats for active rule', async () => { + const rule: ThreatMatchCreateSchema = { + ...getSimpleThreatMatch('rule-1', true), + index: ['telemetry'], + threat_index: ['telemetry'], + threat_mapping: [ + { + entries: [ + { + field: 'keyword', + value: 'keyword', + type: 'mapping', + }, + ], + }, + ], + }; + const { id } = await createRule(supertest, log, rule); + await waitForRuleSuccessOrStatus(supertest, log, id); + await waitForSignalsToBePresent(supertest, log, 4, [id]); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + threat_match: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + enabled: 1, + alerts: 4, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + enabled: 1, + alerts: 4, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + + it('should show stats for in-active rule', async () => { + const rule = getSimpleThreatMatch(); + await createRule(supertest, log, rule); + await retry.try(async () => { + const stats = await getStats(supertest, log); + const expected: DetectionMetrics = { + ...getInitialDetectionMetrics(), + detection_rules: { + ...getInitialDetectionMetrics().detection_rules, + detection_rule_usage: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage, + threat_match: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.query, + disabled: 1, + }, + custom_total: { + ...getInitialDetectionMetrics().detection_rules.detection_rule_usage.custom_total, + disabled: 1, + }, + }, + }, + }; + expect(stats).to.eql(expected); + }); + }); + }); + + describe('"pre-packaged" rules', async () => { + it('should show stats for totals for in-active pre-packaged rules', async () => { + await installPrePackagedRules(supertest, log); + await retry.try(async () => { + const stats = await getStats(supertest, log); + expect(stats.detection_rules.detection_rule_usage.elastic_total.enabled).above(0); + expect(stats.detection_rules.detection_rule_usage.elastic_total.disabled).above(0); + expect(stats.detection_rules.detection_rule_usage.elastic_total.enabled).above(0); + expect(stats.detection_rules.detection_rule_usage.custom_total.enabled).equal(0); + expect(stats.detection_rules.detection_rule_detail.length).above(0); + }); + }); + + it('should show stats for the detection_rule_details for pre-packaged rules', async () => { + await installPrePackagedRules(supertest, log); + await retry.try(async () => { + const stats = await getStats(supertest, log); + + // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: + // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json + // We have to search by "rule_name" since the "rule_id" it is storing is the Saved Object ID and not the rule_id + const foundRule = stats.detection_rules.detection_rule_detail.find( + (rule) => rule.rule_id === '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + ); + if (foundRule == null) { + throw new Error('Found rule should not be null'); + } + const { + created_on: createdOn, + updated_on: updatedOn, + rule_id: ruleId, + ...omittedFields + } = foundRule; + expect(omittedFields).to.eql({ + rule_name: 'Endpoint Security', + rule_type: 'query', + rule_version: 3, + enabled: true, + elastic_rule: true, + alert_count_daily: 0, + cases_count_total: 0, + }); + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/index.ts new file mode 100644 index 0000000000000..cf9db6373033a --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/telemetry/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 { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default ({ loadTestFile }: FtrProviderContext): void => { + describe('Detection rule type telemetry', function () { + describe('', function () { + this.tags('ciGroup11'); + loadTestFile(require.resolve('./detection_rules')); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/utils.ts b/x-pack/test/detection_engine_api_integration/utils.ts index 01e4e85bb7d03..858c4901b2dd3 100644 --- a/x-pack/test/detection_engine_api_integration/utils.ts +++ b/x-pack/test/detection_engine_api_integration/utils.ts @@ -34,6 +34,7 @@ import { EqlCreateSchema, ThresholdCreateSchema, PreviewRulesSchema, + ThreatMatchCreateSchema, } from '../../plugins/security_solution/common/detection_engine/schemas/request'; import { signalsMigrationType } from '../../plugins/security_solution/server/lib/detection_engine/migrations/saved_objects'; import { @@ -53,6 +54,7 @@ import { UPDATE_OR_CREATE_LEGACY_ACTIONS, } from '../../plugins/security_solution/common/constants'; import { RACAlert } from '../../plugins/security_solution/server/lib/detection_engine/rule_types/types'; +import { DetectionMetrics } from '../../plugins/security_solution/server/usage/detections/types'; /** * This will remove server generated properties such as date times, etc... @@ -1827,3 +1829,151 @@ export const getOpenSignals = async ( await refreshIndex(es, '.alerts-security.alerts-default*'); return getSignalsByIds(supertest, log, [rule.id]); }; + +/** + * Cluster stats URL. Replace this with any from kibana core if there is ever a constant there for this. + */ +export const getStatsUrl = (): string => '/api/telemetry/v2/clusters/_stats'; + +/** + * Initial detection metrics initialized. + */ +export const getInitialDetectionMetrics = (): DetectionMetrics => ({ + ml_jobs: { + ml_job_usage: { + custom: { + enabled: 0, + disabled: 0, + }, + elastic: { + enabled: 0, + disabled: 0, + }, + }, + ml_job_metrics: [], + }, + 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, + }, + }, + }, +}); + +/** + * Given a body this will return the detection metrics from it. + * @param body The Stats body + * @returns Detection metrics + */ +export const getDetectionMetricsFromBody = ( + body: Array<{ + stats: { + stack_stats: { + kibana: { plugins: { security_solution: { detectionMetrics: DetectionMetrics } } }; + }; + }; + }> +): DetectionMetrics => { + return body[0].stats.stack_stats.kibana.plugins.security_solution.detectionMetrics; +}; + +/** + * Gets the stats from the stats endpoint + * @param supertest The supertest agent. + * @returns The detection metrics + */ +export const getStats = async ( + supertest: SuperTest.SuperTest, + log: ToolingLog +): Promise => { + const response = await supertest + .post(getStatsUrl()) + .set('kbn-xsrf', 'true') + .send({ unencrypted: true }); + if (response.status !== 200) { + log.error( + `Did not get an expected 200 "ok" when getting the stats for detections. CI issues could happen. Suspect this line if you are seeing CI issues. body: ${JSON.stringify( + response.body + )}, status: ${JSON.stringify(response.status)}` + ); + } + return getDetectionMetricsFromBody(response.body); +}; + +/** + * This is a typical simple indicator match/threat match for testing that is easy for most basic testing + * @param ruleId + * @param enabled Enables the rule on creation or not. Defaulted to false. + */ +export const getSimpleThreatMatch = ( + ruleId = 'rule-1', + enabled = false +): ThreatMatchCreateSchema => ({ + description: 'Detecting root and admin users', + name: 'Query with a rule id', + severity: 'high', + enabled, + index: ['auditbeat-*'], + type: 'threat_match', + risk_score: 55, + language: 'kuery', + rule_id: ruleId, + from: '1900-01-01T00:00:00.000Z', + query: '*:*', + threat_query: '*:*', + threat_index: ['auditbeat-*'], + threat_mapping: [ + // We match host.name against host.name + { + entries: [ + { + field: 'host.name', + value: 'host.name', + type: 'mapping', + }, + ], + }, + ], + threat_filters: [], +}); diff --git a/x-pack/test/functional/es_archives/security_solution/README.md b/x-pack/test/functional/es_archives/security_solution/README.md index c832e0835bbbc..897da48316155 100644 --- a/x-pack/test/functional/es_archives/security_solution/README.md +++ b/x-pack/test/functional/es_archives/security_solution/README.md @@ -9,3 +9,5 @@ or ``` x-pack/test/api_integration/apis/security_solution ``` + +* Folder `telemetry` is for the tests underneath `detection_engine_api_integration/security_and_spaces/tests/telemetry`. diff --git a/x-pack/test/functional/es_archives/security_solution/telemetry/data.json b/x-pack/test/functional/es_archives/security_solution/telemetry/data.json new file mode 100644 index 0000000000000..587b1a496c737 --- /dev/null +++ b/x-pack/test/functional/es_archives/security_solution/telemetry/data.json @@ -0,0 +1,51 @@ +{ + "type": "doc", + "value": { + "id": "1", + "index": "telemetry", + "source": { + "@timestamp": "2020-10-28T05:00:53.000Z", + "keyword": "word one" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "telemetry", + "source": { + "@timestamp": "2020-10-28T05:01:53.000Z", + "keyword": "word two" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "telemetry", + "source": { + "@timestamp": "2020-10-28T05:02:53.000Z", + "keyword": "word three" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "telemetry", + "source": { + "@timestamp": "2020-10-28T05:03:53.000Z", + "keyword": "word four" + }, + "type": "_doc" + } +} diff --git a/x-pack/test/functional/es_archives/security_solution/telemetry/mappings.json b/x-pack/test/functional/es_archives/security_solution/telemetry/mappings.json new file mode 100644 index 0000000000000..ec48b7b750763 --- /dev/null +++ b/x-pack/test/functional/es_archives/security_solution/telemetry/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "telemetry", + "mappings": { + "properties": { + "@timestamp": { + "type": "date" + }, + "keyword": { "type": "keyword" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} From 6c0f7be1641adad587440c042c29082737ab9b3d Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 10 Dec 2021 02:45:33 +0000 Subject: [PATCH 106/145] chore(NA): use internal pkg_npm on @kbn/eslint-plugin-eslint (#120940) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-eslint-plugin-eslint/BUILD.bazel | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kbn-eslint-plugin-eslint/BUILD.bazel b/packages/kbn-eslint-plugin-eslint/BUILD.bazel index 5baab89d6f03d..c02a468456f77 100644 --- a/packages/kbn-eslint-plugin-eslint/BUILD.bazel +++ b/packages/kbn-eslint-plugin-eslint/BUILD.bazel @@ -1,4 +1,5 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "pkg_npm") PKG_BASE_NAME = "kbn-eslint-plugin-eslint" PKG_REQUIRE_NAME = "@kbn/eslint-plugin-eslint" @@ -28,7 +29,7 @@ NPM_MODULE_EXTRA_FILES = [ "README.md", ] -DEPS = [ +RUNTIME_DEPS = [ "@npm//@babel/eslint-parser", "@npm//dedent", "@npm//eslint", @@ -41,7 +42,7 @@ js_library( srcs = NPM_MODULE_EXTRA_FILES + [ ":srcs", ], - deps = DEPS, + deps = RUNTIME_DEPS, package_name = PKG_REQUIRE_NAME, visibility = ["//visibility:public"], ) From 9b6995c30d73c4e437b44d901848f7beb4dfa548 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 10 Dec 2021 02:45:49 +0000 Subject: [PATCH 107/145] chore(NA): use internal pkg_npm on @kbn/expect (#120941) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-expect/BUILD.bazel | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kbn-expect/BUILD.bazel b/packages/kbn-expect/BUILD.bazel index b7eb91a451b9a..9f74cfe6a093d 100644 --- a/packages/kbn-expect/BUILD.bazel +++ b/packages/kbn-expect/BUILD.bazel @@ -1,4 +1,5 @@ -load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "pkg_npm") PKG_BASE_NAME = "kbn-expect" PKG_REQUIRE_NAME = "@kbn/expect" From b5b4ae73d29d27f30ece9c560741b91c2dd95a9a Mon Sep 17 00:00:00 2001 From: Julia Bardi <90178898+juliaElastic@users.noreply.github.com> Date: Fri, 10 Dec 2021 08:57:21 +0100 Subject: [PATCH 108/145] fix setup error during test (#120917) --- .../integration_tests/7_13_0_failed_action_tasks.test.ts | 3 +-- .../server/services/epm/elasticsearch/template/install.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/server/saved_objects/migrations/integration_tests/7_13_0_failed_action_tasks.test.ts b/src/core/server/saved_objects/migrations/integration_tests/7_13_0_failed_action_tasks.test.ts index 2def8e375c81f..479b1e78e1b72 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/7_13_0_failed_action_tasks.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/7_13_0_failed_action_tasks.test.ts @@ -19,8 +19,7 @@ async function removeLogFile() { await fs.unlink(logFilePath).catch(() => void 0); } -// FLAKY: https://github.com/elastic/kibana/issues/118626 -describe.skip('migration from 7.13 to 7.14+ with many failed action_tasks', () => { +describe('migration from 7.13 to 7.14+ with many failed action_tasks', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; let startES: () => Promise; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 7031ef1e6a33f..4224ff6b01a19 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -341,7 +341,7 @@ export async function ensureDefaultComponentTemplate( await putComponentTemplate(esClient, logger, { name: FLEET_GLOBAL_COMPONENT_TEMPLATE_NAME, body: FLEET_GLOBAL_COMPONENT_TEMPLATE_CONTENT, - }); + }).clusterPromise; } return { isCreated: !existingTemplate }; From 7ceb878989a09b0c364a79d5ad834e944bf3aab6 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Fri, 10 Dec 2021 14:36:04 +0500 Subject: [PATCH 109/145] [Console] Highlight the tutorial example text with console syntax (#120474) * Highlight tutorial example text Co-authored-by: Muhammad Ibragimov --- .../application/components/editor_example.tsx | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/plugins/console/public/application/components/editor_example.tsx b/src/plugins/console/public/application/components/editor_example.tsx index 577f32fa912fb..21e3ab0c7d274 100644 --- a/src/plugins/console/public/application/components/editor_example.tsx +++ b/src/plugins/console/public/application/components/editor_example.tsx @@ -8,8 +8,10 @@ import { EuiScreenReaderOnly } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useEffect } from 'react'; -import { createReadOnlyAceEditor } from '../models/legacy_core_editor'; +import React, { useEffect, useRef } from 'react'; +import { createReadOnlyAceEditor, CustomAceEditor } from '../models/sense_editor'; +// @ts-ignore +import { Mode } from '../models/legacy_core_editor/mode/input'; interface EditorExampleProps { panel: string; @@ -27,21 +29,33 @@ GET index/_doc/1 `; export function EditorExample(props: EditorExampleProps) { - const elemId = `help-example-${props.panel}`; const inputId = `help-example-${props.panel}-input`; + const wrapperDivRef = useRef(null); + const editorRef = useRef(); useEffect(() => { - const el = document.getElementById(elemId)!; - el.textContent = exampleText.trim(); - const editor = createReadOnlyAceEditor(el); - const textarea = el.querySelector('textarea')!; - textarea.setAttribute('id', inputId); - textarea.setAttribute('readonly', 'true'); + if (wrapperDivRef.current) { + editorRef.current = createReadOnlyAceEditor(wrapperDivRef.current); + + const editor = editorRef.current; + editor.update(exampleText.trim()); + editor.session.setMode(new Mode()); + editor.session.setUseWorker(false); + editor.setHighlightActiveLine(false); + + const textareaElement = wrapperDivRef.current.querySelector('textarea'); + if (textareaElement) { + textareaElement.setAttribute('id', inputId); + textareaElement.setAttribute('readonly', 'true'); + } + } return () => { - editor.destroy(); + if (editorRef.current) { + editorRef.current.destroy(); + } }; - }, [elemId, inputId]); + }, [inputId]); return ( <> @@ -52,7 +66,7 @@ export function EditorExample(props: EditorExampleProps) { })} -
+
); } From e60c6e1873e25b2f80433d7d4b134212af5b8646 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Fri, 10 Dec 2021 10:52:15 +0100 Subject: [PATCH 110/145] [home] use correct cloud reset link for tutorials (#120884) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../home/server/tutorials/instructions/cloud_instructions.ts | 3 ++- x-pack/plugins/cloud/public/plugin.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts b/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts index ed9e588a999b4..6d547b2a1d40d 100644 --- a/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts +++ b/src/plugins/home/server/tutorials/instructions/cloud_instructions.ts @@ -7,13 +7,14 @@ */ import { i18n } from '@kbn/i18n'; + export const cloudPasswordAndResetLink = i18n.translate( 'home.tutorials.common.cloudInstructions.passwordAndResetLink', { defaultMessage: 'Where {passwordTemplate} is the password of the `elastic` user.' + `\\{#config.cloud.profileUrl\\} - Forgot the password? [Reset in Elastic Cloud](\\{config.cloud.baseUrl\\}\\{config.cloud.profileUrl\\}). + Forgot the password? [Reset in Elastic Cloud](\\{config.cloud.baseUrl\\}\\{config.cloud.deploymentUrl\\}/security). \\{/config.cloud.profileUrl\\}`, values: { passwordTemplate: '``' }, } diff --git a/x-pack/plugins/cloud/public/plugin.ts b/x-pack/plugins/cloud/public/plugin.ts index e71b145c438ed..81aad8bf79ccc 100644 --- a/x-pack/plugins/cloud/public/plugin.ts +++ b/x-pack/plugins/cloud/public/plugin.ts @@ -98,7 +98,7 @@ export class CloudPlugin implements Plugin { if (home) { home.environment.update({ cloud: this.isCloudEnabled }); if (this.isCloudEnabled) { - home.tutorials.setVariable('cloud', { id, baseUrl, profileUrl }); + home.tutorials.setVariable('cloud', { id, baseUrl, profileUrl, deploymentUrl }); } } From 0b200103aa0e950b27af3336bc40ecc250d61613 Mon Sep 17 00:00:00 2001 From: Jan Monschke Date: Fri, 10 Dec 2021 11:30:01 +0100 Subject: [PATCH 111/145] [Security Solution][Investigations] Alert flyout UX updates (pt. 1) (#120347) * feat: Move timestamp from summary to below the title * refactor: creat reusable getEnrichedFieldInfo This method can be used in different places to enrich the field data. * feat: make unpadded/unsized version of ActionCell available Ideally, ActionCell and HoverActions would not have padding and width declaration. This could be part of a future refactor. For now, a version with padding and size information is all that is needed. * feat: add OverviewCards w/ severity, risk score and rule name * feat: add status to overview cards * refactor: use FormattedFieldValue instead of RuleStatus directly * fix: limit height of the overview cards * fix: clamp content to 2 lines * chore: add displayName * feat: Add interactive popover to status badge * chore: remove signal status from document summary * feat: Remove rule link and headline from reason component * feat: Add table-tab pivot link * feat: close alert flyout after status change * test: fix snapshots * chore: remove unused imports * chore: use correct padding in context menu * chore: split over cards into multiple files * chore: use shared severity badge * chore: revert back to plain risk score text * chore: rename and move overview * fix: fix alignment between actions and content * fix: fix types in test * chore: remove unused import * chore: useMemo & useCallback * chore: import type * feat: add iconType, iconSide and onClickArialabel to rule status * feat: add hover actions to the alert status overview card * fix: use correct data * fix: action cell did not look good on small screens Now the action cell slides in similar to how the action buttons slide in in a data grid. * fix: use different card layout based on container width * fix: use new Severity type * fix: align children centered * test: add popover button tests * test: add overview card test * test: test overview cards * fix: prevent rendering of two cards in two ingle rows * fix: change i18n key to prevent a duplicate key * chore: remove unused translations * nit: use less vertical screen estate Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../event_details/alert_summary_view.test.tsx | 2 + .../event_details/alert_summary_view.tsx | 17 +- .../event_details/event_details.test.tsx | 2 + .../event_details/event_details.tsx | 28 +- .../event_details/get_alert_summary_rows.tsx | 83 ++--- .../components/event_details/helpers.tsx | 55 +++- .../__snapshots__/index.test.tsx.snap | 311 ++++++++++++++++++ .../event_details/overview/index.test.tsx | 198 +++++++++++ .../event_details/overview/index.tsx | 210 ++++++++++++ .../overview/overview_card.test.tsx | 71 ++++ .../event_details/overview/overview_card.tsx | 99 ++++++ .../overview/status_popover_button.test.tsx | 82 +++++ .../overview/status_popover_button.tsx | 81 +++++ .../components/event_details/reason.tsx | 57 +--- .../components/event_details/summary_view.tsx | 41 ++- .../event_details/table/action_cell.tsx | 14 +- .../components/event_details/translations.ts | 7 + .../common/components/event_details/types.ts | 19 ++ .../common/components/hover_actions/index.tsx | 17 +- .../components/alerts_table/translations.ts | 21 +- .../__snapshots__/index.test.tsx.snap | 5 + .../event_details/expandable_event.tsx | 25 +- .../side_panel/event_details/index.tsx | 14 +- .../body/renderers/formatted_field.tsx | 6 + .../timeline/body/renderers/rule_status.tsx | 27 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 27 files changed, 1321 insertions(+), 173 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/index.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index 7e1e71a01642f..c397ac313c48c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -30,6 +30,8 @@ const props = { browserFields: mockBrowserFields, eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', timelineId: 'detections-page', + title: '', + goToTable: jest.fn(), }; describe('AlertSummaryView', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index b42a0425355cc..c30837dc6eca8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiBasicTableColumn, EuiSpacer } from '@elastic/eui'; +import { EuiBasicTableColumn } from '@elastic/eui'; import React, { useMemo } from 'react'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; @@ -60,18 +60,21 @@ const AlertSummaryViewComponent: React.FC<{ eventId: string; isDraggable?: boolean; timelineId: string; - title?: string; -}> = ({ browserFields, data, eventId, isDraggable, timelineId, title }) => { + title: string; + goToTable: () => void; +}> = ({ browserFields, data, eventId, isDraggable, timelineId, title, goToTable }) => { const summaryRows = useMemo( () => getSummaryRows({ browserFields, data, eventId, isDraggable, timelineId }), [browserFields, data, eventId, isDraggable, timelineId] ); return ( - <> - - - + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 37ca3b0b897a6..14910c77d198c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -48,6 +48,8 @@ describe('EventDetails', () => { timelineId: 'test', eventView: EventsViewType.summaryView, hostRisk: { fields: [], loading: true }, + indexName: 'test', + handleOnEventClosed: jest.fn(), rawEventData, }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index 0fe48d5a998ea..08f97ab7d1bc7 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -6,6 +6,7 @@ */ import { + EuiHorizontalRule, EuiTabbedContent, EuiTabbedContentTab, EuiSpacer, @@ -39,7 +40,9 @@ import { EnrichmentRangePicker } from './cti_details/enrichment_range_picker'; import { Reason } from './reason'; import { InvestigationGuideView } from './investigation_guide_view'; + import { HostRisk } from '../../containers/hosts_risk/use_hosts_risk_score'; +import { Overview } from './overview'; type EventViewTab = EuiTabbedContentTab; @@ -59,12 +62,14 @@ interface Props { browserFields: BrowserFields; data: TimelineEventsDetailsItem[]; id: string; + indexName: string; isAlert: boolean; isDraggable?: boolean; rawEventData: object | undefined; timelineTabType: TimelineTabs | 'flyout'; timelineId: string; hostRisk: HostRisk | null; + handleOnEventClosed: () => void; } export const Indent = styled.div` @@ -105,18 +110,21 @@ const EventDetailsComponent: React.FC = ({ browserFields, data, id, + indexName, isAlert, isDraggable, rawEventData, timelineId, timelineTabType, hostRisk, + handleOnEventClosed, }) => { const [selectedTabId, setSelectedTabId] = useState(EventsViewType.summaryView); const handleTabClick = useCallback( (tab: EuiTabbedContentTab) => setSelectedTabId(tab.id as EventViewId), - [setSelectedTabId] + [] ); + const goToTableTab = useCallback(() => setSelectedTabId(EventsViewType.tableView), []); const eventFields = useMemo(() => getEnrichmentFields(data), [data]); const existingEnrichments = useMemo( @@ -152,7 +160,19 @@ const EventDetailsComponent: React.FC = ({ name: i18n.OVERVIEW, content: ( <> + + + + = ({ timelineId, title: i18n.DUCOMENT_SUMMARY, }} + goToTable={goToTableTab} /> {(enrichmentCount > 0 || hostRisk) && ( @@ -188,8 +209,9 @@ const EventDetailsComponent: React.FC = ({ } : undefined, [ - isAlert, id, + indexName, + isAlert, data, browserFields, isDraggable, @@ -198,6 +220,8 @@ const EventDetailsComponent: React.FC = ({ allEnrichments, isEnrichmentsLoading, hostRisk, + goToTableTab, + handleOnEventClosed, ] ); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx index 4af444c2ab8ad..0bf404fe51e39 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/get_alert_summary_rows.tsx @@ -5,53 +5,31 @@ * 2.0. */ -import { get, getOr, find, isEmpty } from 'lodash/fp'; +import { getOr, find, isEmpty } from 'lodash/fp'; import * as i18n from './translations'; import { BrowserFields } from '../../../../common/search_strategy/index_fields'; import { - ALERTS_HEADERS_RISK_SCORE, - ALERTS_HEADERS_RULE, - ALERTS_HEADERS_SEVERITY, ALERTS_HEADERS_THRESHOLD_CARDINALITY, ALERTS_HEADERS_THRESHOLD_COUNT, ALERTS_HEADERS_THRESHOLD_TERMS, ALERTS_HEADERS_RULE_NAME, - SIGNAL_STATUS, ALERTS_HEADERS_TARGET_IMPORT_HASH, - TIMESTAMP, ALERTS_HEADERS_RULE_DESCRIPTION, } from '../../../detections/components/alerts_table/translations'; import { AGENT_STATUS_FIELD_NAME, IP_FIELD_TYPE, - SIGNAL_RULE_NAME_FIELD_NAME, } from '../../../timelines/components/timeline/body/renderers/constants'; import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../network/components/ip'; -import { SummaryRow } from './helpers'; +import { getEnrichedFieldInfo, SummaryRow } from './helpers'; +import { EventSummaryField } from './types'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; import { isAlertFromEndpointEvent } from '../../utils/endpoint_alert_check'; import { EventCode } from '../../../../common/ecs/event'; -interface EventSummaryField { - id: string; - label?: string; - linkField?: string; - fieldType?: string; - overrideField?: string; -} - const defaultDisplayFields: EventSummaryField[] = [ - { id: 'kibana.alert.workflow_status', label: SIGNAL_STATUS }, - { id: '@timestamp', label: TIMESTAMP }, - { - id: SIGNAL_RULE_NAME_FIELD_NAME, - linkField: 'kibana.alert.rule.uuid', - label: ALERTS_HEADERS_RULE, - }, - { id: 'kibana.alert.rule.severity', label: ALERTS_HEADERS_SEVERITY }, - { id: 'kibana.alert.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE }, { id: 'host.name' }, { id: 'agent.id', overrideField: AGENT_STATUS_FIELD_NAME, label: i18n.AGENT_STATUS }, { id: 'user.name' }, @@ -151,50 +129,34 @@ export const getSummaryRows = ({ const tableFields = getEventFieldsToDisplay({ eventCategory, eventCode }); return data != null - ? tableFields.reduce((acc, item) => { - const initialDescription = { - contextId: timelineId, - eventId, - isDraggable, - value: null, - fieldType: 'string', - linkValue: undefined, - timelineId, - }; - const field = data.find((d) => d.field === item.id); - if (!field || isEmpty(field?.values)) { + ? tableFields.reduce((acc, field) => { + const item = data.find((d) => d.field === field.id); + if (!item || isEmpty(item?.values)) { return acc; } const linkValueField = - item.linkField != null && data.find((d) => d.field === item.linkField); - const linkValue = getOr(null, 'originalValue.0', linkValueField); - const value = getOr(null, 'originalValue.0', field); - const category = field.category ?? ''; - const fieldName = field.field ?? ''; - - const browserField = get([category, 'fields', fieldName], browserFields); + field.linkField != null && data.find((d) => d.field === field.linkField); const description = { - ...initialDescription, - data: { - field: field.field, - format: browserField?.format ?? '', - type: browserField?.type ?? '', - isObjectArray: field.isObjectArray, - ...(item.overrideField ? { field: item.overrideField } : {}), - }, - values: field.values, - linkValue: linkValue ?? undefined, - fieldFromBrowserField: browserField, + ...getEnrichedFieldInfo({ + item, + linkValueField: linkValueField || undefined, + contextId: timelineId, + timelineId, + browserFields, + eventId, + field, + }), + isDraggable, }; - if (item.id === 'agent.id' && !isAlertFromEndpointEvent({ data })) { + if (field.id === 'agent.id' && !isAlertFromEndpointEvent({ data })) { return acc; } - if (item.id === 'kibana.alert.threshold_result.terms') { + if (field.id === 'kibana.alert.threshold_result.terms') { try { - const terms = getOr(null, 'originalValue', field); + const terms = getOr(null, 'originalValue', item); const parsedValue = terms.map((term: string) => JSON.parse(term)); const thresholdTerms = (parsedValue ?? []).map( (entry: { field: string; value: string }) => { @@ -213,8 +175,9 @@ export const getSummaryRows = ({ } } - if (item.id === 'kibana.alert.threshold_result.cardinality') { + if (field.id === 'kibana.alert.threshold_result.cardinality') { try { + const value = getOr(null, 'originalValue.0', field); const parsedValue = JSON.parse(value); return [ ...acc, @@ -234,7 +197,7 @@ export const getSummaryRows = ({ return [ ...acc, { - title: item.label ?? item.id, + title: field.label ?? field.id, description, }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index 648bc96b5c9e7..dcca42f2a1df7 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -22,7 +22,8 @@ import { DEFAULT_DATE_COLUMN_MIN_WIDTH, DEFAULT_COLUMN_MIN_WIDTH, } from '../../../timelines/components/timeline/body/constants'; -import { FieldsData } from './types'; +import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline'; +import type { EnrichedFieldInfo, EventSummaryField } from './types'; import * as i18n from './translations'; import { ColumnHeaderOptions } from '../../../../common/types'; @@ -56,14 +57,8 @@ export interface Item { export interface AlertSummaryRow { title: string; - description: { - data: FieldsData; - eventId: string; + description: EnrichedFieldInfo & { isDraggable?: boolean; - fieldFromBrowserField?: BrowserField; - linkValue: string | undefined; - timelineId: string; - values: string[] | null | undefined; }; } @@ -232,3 +227,47 @@ export const getSummaryColumns = ( }, ]; }; + +export function getEnrichedFieldInfo({ + browserFields, + contextId, + eventId, + field, + item, + linkValueField, + timelineId, +}: { + browserFields: BrowserFields; + contextId: string; + item: TimelineEventsDetailsItem; + eventId: string; + field?: EventSummaryField; + timelineId: string; + linkValueField?: TimelineEventsDetailsItem; +}): EnrichedFieldInfo { + const fieldInfo = { + contextId, + eventId, + fieldType: 'string', + linkValue: undefined, + timelineId, + }; + const linkValue = getOr(null, 'originalValue.0', linkValueField); + const category = item.category ?? ''; + const fieldName = item.field ?? ''; + + const browserField = get([category, 'fields', fieldName], browserFields); + const overrideField = field?.overrideField; + return { + ...fieldInfo, + data: { + field: overrideField ?? fieldName, + format: browserField?.format ?? '', + type: browserField?.type ?? '', + isObjectArray: item.isObjectArray, + }, + values: item.values, + linkValue: linkValue ?? undefined, + fieldFromBrowserField: browserField, + }; +} diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000000..4e62766fc1477 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/__snapshots__/index.test.tsx.snap @@ -0,0 +1,311 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Event Details Overview Cards renders rows and spacers correctly 1`] = ` + + .c6 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.c6:focus-within .timelines__hoverActionButton, +.c6:focus-within .securitySolution__hoverActionButton { + opacity: 1; +} + +.c6:hover .timelines__hoverActionButton, +.c6:hover .securitySolution__hoverActionButton { + opacity: 1; +} + +.c6 .timelines__hoverActionButton, +.c6 .securitySolution__hoverActionButton { + opacity: 0; +} + +.c6 .timelines__hoverActionButton:focus, +.c6 .securitySolution__hoverActionButton:focus { + opacity: 1; +} + +.c3 { + text-transform: capitalize; +} + +.c5 { + width: 0; + -webkit-transform: translate(6px); + -ms-transform: translate(6px); + transform: translate(6px); + -webkit-transition: -webkit-transform 50ms ease-in-out; + -webkit-transition: transform 50ms ease-in-out; + transition: transform 50ms ease-in-out; + margin-left: 8px; +} + +.c1.c1.c1 { + background-color: #25262e; + padding: 8px; + height: 78px; +} + +.c1 .hoverActions-active .timelines__hoverActionButton, +.c1 .hoverActions-active .securitySolution__hoverActionButton { + opacity: 1; +} + +.c1:hover .timelines__hoverActionButton, +.c1:hover .securitySolution__hoverActionButton { + opacity: 1; +} + +.c1:hover .c4 { + width: auto; + -webkit-transform: translate(0); + -ms-transform: translate(0); + transform: translate(0); +} + +.c2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.c0 { + -webkit-box-flex: 0; + -webkit-flex-grow: 0; + -ms-flex-positive: 0; + flex-grow: 0; +} + +
+
+
+
+
+ Status +
+
+
+
+
+
+ +
+
+
+
+
+
+

+ You are in a dialog, containing options for field kibana.alert.workflow_status. Press tab to navigate options. Press escape to exit. +

+
+ Filter button +
+
+ Filter out button +
+
+ Overflow button +
+
+
+
+
+
+
+
+
+
+ Risk Score +
+
+
+
+ 47 +
+
+
+
+

+ You are in a dialog, containing options for field kibana.alert.rule.risk_score. Press tab to navigate options. Press escape to exit. +

+
+ Filter button +
+
+ Filter out button +
+
+ Overflow button +
+
+
+
+
+
+
+
+
+
+
+
+
+ Rule +
+
+
+
+ +
+
+
+
+

+ You are in a dialog, containing options for field kibana.alert.rule.name. Press tab to navigate options. Press escape to exit. +

+
+ Filter button +
+
+ Filter out button +
+
+ Overflow button +
+
+
+
+
+
+
+
+
+ +`; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.test.tsx new file mode 100644 index 0000000000000..50da80f7b1304 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.test.tsx @@ -0,0 +1,198 @@ +/* + * 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 { render } from '@testing-library/react'; +import { Overview } from './'; +import { TestProviders } from '../../../../common/mock'; + +jest.mock('../../../lib/kibana'); +jest.mock('../../utils', () => ({ + useThrottledResizeObserver: () => ({ width: 400 }), // force row-chunking +})); + +describe('Event Details Overview Cards', () => { + it('renders all cards', () => { + const { getByText } = render( + + + + ); + + getByText('Status'); + getByText('Severity'); + getByText('Risk Score'); + getByText('Rule'); + }); + + it('renders all cards it has data for', () => { + const { getByText, queryByText } = render( + + + + ); + + getByText('Status'); + getByText('Risk Score'); + getByText('Rule'); + + expect(queryByText('Severity')).not.toBeInTheDocument(); + }); + + it('renders rows and spacers correctly', () => { + const { asFragment } = render( + + + + ); + + expect(asFragment()).toMatchSnapshot(); + }); +}); + +const props = { + handleOnEventClosed: jest.fn(), + contextId: 'detections-page', + eventId: 'testId', + indexName: 'testIndex', + timelineId: 'page', + data: [ + { + category: 'kibana', + field: 'kibana.alert.rule.risk_score', + values: ['47'], + originalValue: ['47'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.uuid', + values: ['d9f537c0-47b2-11ec-9517-c1c68c44dec0'], + originalValue: ['d9f537c0-47b2-11ec-9517-c1c68c44dec0'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.workflow_status', + values: ['open'], + originalValue: ['open'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.name', + values: ['More than one event with user name'], + originalValue: ['More than one event with user name'], + isObjectArray: false, + }, + { + category: 'kibana', + field: 'kibana.alert.rule.severity', + values: ['medium'], + originalValue: ['medium'], + isObjectArray: false, + }, + ], + browserFields: { + kibana: { + fields: { + 'kibana.alert.rule.severity': { + category: 'kibana', + count: 0, + name: 'kibana.alert.rule.severity', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'string', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + }, + 'kibana.alert.rule.risk_score': { + category: 'kibana', + count: 0, + name: 'kibana.alert.rule.risk_score', + type: 'number', + esTypes: ['float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'number', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + }, + 'kibana.alert.rule.uuid': { + category: 'kibana', + count: 0, + name: 'kibana.alert.rule.uuid', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'string', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + }, + 'kibana.alert.workflow_status': { + category: 'kibana', + count: 0, + name: 'kibana.alert.workflow_status', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'string', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + }, + 'kibana.alert.rule.name': { + category: 'kibana', + count: 0, + name: 'kibana.alert.rule.name', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'string', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + }, + }, + }, + }, +}; + +const dataWithoutSeverity = props.data.filter( + (data) => data.field !== 'kibana.alert.rule.severity' +); + +const fieldsWithoutSeverity = { + 'kibana.alert.rule.risk_score': props.browserFields.kibana.fields['kibana.alert.rule.risk_score'], + 'kibana.alert.rule.uuid': props.browserFields.kibana.fields['kibana.alert.rule.uuid'], + 'kibana.alert.workflow_status': props.browserFields.kibana.fields['kibana.alert.workflow_status'], + 'kibana.alert.rule.name': props.browserFields.kibana.fields['kibana.alert.rule.name'], +}; + +const propsWithoutSeverity = { + ...props, + browserFields: { kibana: { fields: fieldsWithoutSeverity } }, + data: dataWithoutSeverity, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx new file mode 100644 index 0000000000000..70a8ec7ad0d22 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/index.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { chunk, find } from 'lodash/fp'; +import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types'; + +import type { BrowserFields } from '../../../containers/source'; +import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; +import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline'; +import type { EnrichedFieldInfo, EnrichedFieldInfoWithValues } from '../types'; +import { getEnrichedFieldInfo } from '../helpers'; +import { + ALERTS_HEADERS_RISK_SCORE, + ALERTS_HEADERS_RULE, + ALERTS_HEADERS_SEVERITY, + SIGNAL_STATUS, +} from '../../../../detections/components/alerts_table/translations'; +import { + SIGNAL_RULE_NAME_FIELD_NAME, + SIGNAL_STATUS_FIELD_NAME, +} from '../../../../timelines/components/timeline/body/renderers/constants'; +import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; +import { OverviewCardWithActions } from '../overview/overview_card'; +import { StatusPopoverButton } from '../overview/status_popover_button'; +import { SeverityBadge } from '../../../../../public/detections/components/rules/severity_badge'; +import { useThrottledResizeObserver } from '../../utils'; +import { isNotNull } from '../../../../../public/timelines/store/timeline/helpers'; + +export const NotGrowingFlexGroup = euiStyled(EuiFlexGroup)` + flex-grow: 0; +`; + +interface Props { + browserFields: BrowserFields; + contextId: string; + data: TimelineEventsDetailsItem[]; + eventId: string; + handleOnEventClosed: () => void; + indexName: string; + timelineId: string; +} + +export const Overview = React.memo( + ({ browserFields, contextId, data, eventId, handleOnEventClosed, indexName, timelineId }) => { + const statusData = useMemo(() => { + const item = find({ field: SIGNAL_STATUS_FIELD_NAME, category: 'kibana' }, data); + return ( + item && + getEnrichedFieldInfo({ + eventId, + contextId, + timelineId, + browserFields, + item, + }) + ); + }, [browserFields, contextId, data, eventId, timelineId]); + + const severityData = useMemo(() => { + const item = find({ field: 'kibana.alert.rule.severity', category: 'kibana' }, data); + return ( + item && + getEnrichedFieldInfo({ + eventId, + contextId, + timelineId, + browserFields, + item, + }) + ); + }, [browserFields, contextId, data, eventId, timelineId]); + + const riskScoreData = useMemo(() => { + const item = find({ field: 'kibana.alert.rule.risk_score', category: 'kibana' }, data); + return ( + item && + getEnrichedFieldInfo({ + eventId, + contextId, + timelineId, + browserFields, + item, + }) + ); + }, [browserFields, contextId, data, eventId, timelineId]); + + const ruleNameData = useMemo(() => { + const item = find({ field: SIGNAL_RULE_NAME_FIELD_NAME, category: 'kibana' }, data); + const linkValueField = find({ field: 'kibana.alert.rule.uuid', category: 'kibana' }, data); + return ( + item && + getEnrichedFieldInfo({ + eventId, + contextId, + timelineId, + browserFields, + item, + linkValueField, + }) + ); + }, [browserFields, contextId, data, eventId, timelineId]); + + const signalCard = hasData(statusData) ? ( + + + + + + ) : null; + + const severityCard = hasData(severityData) ? ( + + + + + + ) : null; + + const riskScoreCard = hasData(riskScoreData) ? ( + + + {riskScoreData.values[0]} + + + ) : null; + + const ruleNameCard = hasData(ruleNameData) ? ( + + + + + + ) : null; + + const { width, ref } = useThrottledResizeObserver(); + + // 675px is the container width at which none of the cards, when hovered, + // creates a visual overflow in a single row setup + const showAsSingleRow = width === 0 || width >= 675; + + // Only render cards with content + const cards = [signalCard, severityCard, riskScoreCard, ruleNameCard].filter(isNotNull); + + // If there is enough space, render a single row. + // Otherwise, render two rows with each two cards. + const content = showAsSingleRow ? ( + {cards} + ) : ( + <> + {chunk(2, cards).map((elements, index, { length }) => { + // Add a spacer between rows but not after the last row + const addSpacer = index < length - 1; + return ( + <> + {elements} + {addSpacer && } + + ); + })} + + ); + + return
{content}
; + } +); + +function hasData(fieldInfo?: EnrichedFieldInfo): fieldInfo is EnrichedFieldInfoWithValues { + return !!fieldInfo && Array.isArray(fieldInfo.values); +} + +Overview.displayName = 'Overview'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.test.tsx new file mode 100644 index 0000000000000..8ed3dc7e36165 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.test.tsx @@ -0,0 +1,71 @@ +/* + * 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 { render } from '@testing-library/react'; +import { OverviewCardWithActions } from './overview_card'; +import { TestProviders } from '../../../../common/mock'; +import { SeverityBadge } from '../../../../../public/detections/components/rules/severity_badge'; + +const props = { + title: 'Severity', + contextId: 'timeline-case', + enrichedFieldInfo: { + contextId: 'timeline-case', + eventId: 'testid', + fieldType: 'string', + timelineId: 'timeline-case', + data: { + field: 'kibana.alert.rule.severity', + format: 'string', + type: 'string', + isObjectArray: false, + }, + values: ['medium'], + fieldFromBrowserField: { + category: 'kibana', + count: 0, + name: 'kibana.alert.rule.severity', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'string', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + description: '', + example: '', + fields: {}, + }, + }, +}; + +jest.mock('../../../lib/kibana'); + +describe('OverviewCardWithActions', () => { + test('it renders correctly', () => { + const { getByText } = render( + + + + + + ); + + // Headline + getByText('Severity'); + + // Content + getByText('Medium'); + + // Hover actions + getByText('Add To Timeline'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.tsx new file mode 100644 index 0000000000000..4d3dae271f5c9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/overview_card.tsx @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiFlexGroup, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; + +import { ActionCell } from '../table/action_cell'; +import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; +import { EnrichedFieldInfo } from '../types'; + +const ActionWrapper = euiStyled.div` + width: 0; + transform: translate(6px); + transition: transform 50ms ease-in-out; + margin-left: ${({ theme }) => theme.eui.paddingSizes.s}; +`; + +const OverviewPanel = euiStyled(EuiPanel)` + &&& { + background-color: ${({ theme }) => theme.eui.euiColorLightestShade}; + padding: ${({ theme }) => theme.eui.paddingSizes.s}; + height: 78px; + } + + & { + .hoverActions-active { + .timelines__hoverActionButton, + .securitySolution__hoverActionButton { + opacity: 1; + } + } + + &:hover { + .timelines__hoverActionButton, + .securitySolution__hoverActionButton { + opacity: 1; + } + + ${ActionWrapper} { + width: auto; + transform: translate(0); + } + } + } +`; + +interface OverviewCardProps { + title: string; +} + +export const OverviewCard: React.FC = ({ title, children }) => ( + + {title} + + {children} + +); + +OverviewCard.displayName = 'OverviewCard'; + +const ClampedContent = euiStyled.div` + /* Clamp text content to 2 lines */ + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +`; + +ClampedContent.displayName = 'ClampedContent'; + +type OverviewCardWithActionsProps = OverviewCardProps & { + contextId: string; + enrichedFieldInfo: EnrichedFieldInfo; +}; + +export const OverviewCardWithActions: React.FC = ({ + title, + children, + contextId, + enrichedFieldInfo, +}) => { + return ( + + + {children} + + + + + + + ); +}; + +OverviewCardWithActions.displayName = 'OverviewCardWithActions'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx new file mode 100644 index 0000000000000..3c3316618a72c --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.test.tsx @@ -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 React from 'react'; +import { render } from '@testing-library/react'; +import { StatusPopoverButton } from './status_popover_button'; +import { TestProviders } from '../../../../common/mock'; + +const props = { + eventId: 'testid', + contextId: 'detections-page', + enrichedFieldInfo: { + contextId: 'detections-page', + eventId: 'testid', + fieldType: 'string', + timelineId: 'detections-page', + data: { + field: 'kibana.alert.workflow_status', + format: 'string', + type: 'string', + isObjectArray: false, + }, + values: ['open'], + fieldFromBrowserField: { + category: 'kibana', + count: 0, + name: 'kibana.alert.workflow_status', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + format: 'string', + shortDotsEnable: false, + isMapped: true, + indexes: ['apm-*-transaction*'], + description: '', + example: '', + fields: {}, + }, + }, + indexName: '.internal.alerts-security.alerts-default-000001', + timelineId: 'detections-page', + handleOnEventClosed: jest.fn(), +}; + +jest.mock( + '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges', + () => ({ + useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }), + }) +); + +describe('StatusPopoverButton', () => { + test('it renders the correct status', () => { + const { getByText } = render( + + + + ); + + getByText('open'); + }); + + test('it shows the correct options when clicked', () => { + const { getByText } = render( + + + + ); + + getByText('open').click(); + + getByText('Mark as acknowledged'); + getByText('Mark as closed'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx new file mode 100644 index 0000000000000..0ffa1570e7c29 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/overview/status_popover_button.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { EuiContextMenuPanel, EuiPopover, EuiPopoverTitle } from '@elastic/eui'; +import React, { useCallback, useMemo, useState } from 'react'; + +import { useAlertsActions } from '../../../../detections/components/alerts_table/timeline_actions/use_alerts_actions'; +import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; +import { + CHANGE_ALERT_STATUS, + CLICK_TO_CHANGE_ALERT_STATUS, +} from '../../../../detections/components/alerts_table/translations'; +import { FormattedFieldValue } from '../../../../timelines/components/timeline/body/renderers/formatted_field'; +import type { EnrichedFieldInfoWithValues } from '../types'; + +interface StatusPopoverButtonProps { + eventId: string; + contextId: string; + enrichedFieldInfo: EnrichedFieldInfoWithValues; + indexName: string; + timelineId: string; + handleOnEventClosed: () => void; +} + +export const StatusPopoverButton = React.memo( + ({ eventId, contextId, enrichedFieldInfo, indexName, timelineId, handleOnEventClosed }) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); + const closeAfterAction = useCallback(() => { + closePopover(); + handleOnEventClosed(); + }, [closePopover, handleOnEventClosed]); + + const { actionItems } = useAlertsActions({ + closePopover: closeAfterAction, + eventId, + timelineId, + indexName, + alertStatus: enrichedFieldInfo.values[0] as Status, + }); + + const button = useMemo( + () => ( + + ), + [contextId, eventId, enrichedFieldInfo, togglePopover] + ); + + return ( + + {CHANGE_ALERT_STATUS} + + + ); + } +); + +StatusPopoverButton.displayName = 'StatusPopoverButton'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx index d06f4d3ea105b..88208dd1b9780 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx @@ -5,18 +5,12 @@ * 2.0. */ -import { EuiTextColor, EuiFlexItem, EuiSpacer, EuiHorizontalRule, EuiTitle } from '@elastic/eui'; -import { ALERT_REASON, ALERT_RULE_UUID } from '@kbn/rule-data-utils'; +import { EuiTextColor, EuiFlexItem } from '@elastic/eui'; +import { ALERT_REASON } from '@kbn/rule-data-utils'; import React, { useMemo } from 'react'; -import styled from 'styled-components'; -import { getRuleDetailsUrl, useFormatUrl } from '../link_to'; -import * as i18n from './translations'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { LinkAnchor } from '../links'; -import { useKibana } from '../../lib/kibana'; -import { APP_UI_ID, SecurityPageName } from '../../../../common/constants'; import { EVENT_DETAILS_PLACEHOLDER } from '../../../timelines/components/side_panel/event_details/translations'; import { getFieldValue } from '../../../detections/components/host_isolation/helpers'; @@ -25,16 +19,7 @@ interface Props { eventId: string; } -export const Indent = styled.div` - padding: 0 8px; - word-break: break-word; - line-height: 1.7em; -`; - export const ReasonComponent: React.FC = ({ eventId, data }) => { - const { navigateToApp } = useKibana().services.application; - const { formatUrl } = useFormatUrl(SecurityPageName.rules); - const reason = useMemo(() => { const siemSignalsReason = getFieldValue( { category: 'signal', field: 'signal.alert.reason' }, @@ -44,47 +29,11 @@ export const ReasonComponent: React.FC = ({ eventId, data }) => { return aadReason.length > 0 ? aadReason : siemSignalsReason; }, [data]); - const ruleId = useMemo(() => { - const siemSignalsRuleId = getFieldValue({ category: 'signal', field: 'signal.rule.id' }, data); - const aadRuleId = getFieldValue({ category: 'kibana', field: ALERT_RULE_UUID }, data); - return aadRuleId.length > 0 ? aadRuleId : siemSignalsRuleId; - }, [data]); - if (!eventId) { return {EVENT_DETAILS_PLACEHOLDER}; } - return reason ? ( - - - -
{i18n.REASON}
-
- - - {reason} - - - - - void }) => { - ev.preventDefault(); - navigateToApp(APP_UI_ID, { - deepLinkId: SecurityPageName.rules, - path: getRuleDetailsUrl(ruleId), - }); - }} - href={formatUrl(getRuleDetailsUrl(ruleId))} - > - {i18n.VIEW_RULE_DETAIL_PAGE} - - - - -
- ) : null; + return reason ? {reason} : null; }; ReasonComponent.displayName = 'ReasonComponent'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index cf8bf3ddb7474..a84d831524983 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -5,14 +5,24 @@ * 2.0. */ -import { EuiInMemoryTable, EuiBasicTableColumn, EuiTitle } from '@elastic/eui'; +import { + EuiInMemoryTable, + EuiBasicTableColumn, + EuiLink, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, +} from '@elastic/eui'; import React from 'react'; import styled from 'styled-components'; import { SummaryRow } from './helpers'; +import { VIEW_ALL_DOCUMENT_FIELDS } from './translations'; export const Indent = styled.div` - padding: 0 4px; + padding: 0 12px; `; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -43,18 +53,27 @@ export const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` `; export const SummaryViewComponent: React.FC<{ - title?: string; + goToTable: () => void; + title: string; summaryColumns: Array>; summaryRows: SummaryRow[]; dataTestSubj?: string; -}> = ({ summaryColumns, summaryRows, dataTestSubj = 'summary-view', title }) => { +}> = ({ goToTable, summaryColumns, summaryRows, dataTestSubj = 'summary-view', title }) => { return ( - <> - {title && ( - -
{title}
-
- )} +
+ + + +
{title}
+
+
+ + + {VIEW_ALL_DOCUMENT_FIELDS} + + +
+ - +
); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx index 74d46cf3431dc..b49aafea92245 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/action_cell.tsx @@ -8,27 +8,22 @@ import React, { useCallback, useState, useContext } from 'react'; import { HoverActions } from '../../hover_actions'; import { useActionCellDataProvider } from './use_action_cell_data_provider'; -import { EventFieldsData, FieldsData } from '../types'; +import { EnrichedFieldInfo } from '../types'; import { ColumnHeaderOptions } from '../../../../../common/types/timeline'; -import { BrowserField } from '../../../containers/source'; import { TimelineContext } from '../../../../../../timelines/public'; -interface Props { +interface Props extends EnrichedFieldInfo { contextId: string; - data: FieldsData | EventFieldsData; + applyWidthAndPadding?: boolean; disabled?: boolean; - eventId: string; - fieldFromBrowserField?: BrowserField; getLinkValue?: (field: string) => string | null; - linkValue?: string | null | undefined; onFilterAdded?: () => void; - timelineId?: string; toggleColumn?: (column: ColumnHeaderOptions) => void; - values: string[] | null | undefined; } export const ActionCell: React.FC = React.memo( ({ + applyWidthAndPadding = true, contextId, data, eventId, @@ -68,6 +63,7 @@ export const ActionCell: React.FC = React.memo( return ( ` - min-width: ${({ $hideTopN }) => `${$hideTopN ? '112px' : '138px'}`}; - padding: ${(props) => `0 ${props.theme.eui.paddingSizes.s}`}; display: flex; ${(props) => @@ -82,8 +80,14 @@ const StyledHoverActionsContainer = styled.div<{ : ''} `; +const StyledHoverActionsContainerWithPaddingsAndMinWidth = styled(StyledHoverActionsContainer)` + min-width: ${({ $hideTopN }) => `${$hideTopN ? '112px' : '138px'}`}; + padding: ${(props) => `0 ${props.theme.eui.paddingSizes.s}`}; +`; + interface Props { additionalContent?: React.ReactNode; + applyWidthAndPadding?: boolean; closeTopN?: () => void; closePopOver?: () => void; dataProvider?: DataProvider | DataProvider[]; @@ -128,6 +132,7 @@ export const HoverActions: React.FC = React.memo( dataType, draggableId, enableOverflowButton = false, + applyWidthAndPadding = true, field, goGetTimelineId, isObjectArray, @@ -227,6 +232,10 @@ export const HoverActions: React.FC = React.memo( values, }); + const Container = applyWidthAndPadding + ? StyledHoverActionsContainerWithPaddingsAndMinWidth + : StyledHoverActionsContainer; + return ( = React.memo( showTopN, })} > - = React.memo( {additionalContent != null && {additionalContent}} {enableOverflowButton && !isCaseView ? overflowActionItems : allActionItems} - + ); } 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 309c6c7f9761c..1897ad45fe7ff 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 @@ -265,6 +265,20 @@ export const STATUS = i18n.translate( } ); +export const CHANGE_ALERT_STATUS = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.overview.changeAlertStatus', + { + defaultMessage: 'Change alert status', + } +); + +export const CLICK_TO_CHANGE_ALERT_STATUS = i18n.translate( + 'xpack.securitySolution.eventsViewer.alerts.overview.clickToChangeAlertStatus', + { + defaultMessage: 'Click to change alert status', + } +); + export const SIGNAL_STATUS = i18n.translate( 'xpack.securitySolution.eventsViewer.alerts.overviewTable.signalStatusTitle', { @@ -278,10 +292,3 @@ export const TRIGGERED = i18n.translate( defaultMessage: 'Triggered', } ); - -export const TIMESTAMP = i18n.translate( - 'xpack.securitySolution.eventsViewer.alerts.overviewTable.timestampTitle', - { - defaultMessage: 'Timestamp', - } -); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 06dc3ea3ed967..80d8e8f9b9e26 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -439,6 +439,7 @@ exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should "indexName": "my-index", } } + handleOnEventClosed={[Function]} hostRisk={null} isAlert={false} isDraggable={false} @@ -760,6 +761,7 @@ Array [ isAlert={false} loading={true} ruleName="" + timestamp="" > void; interface Props { @@ -37,12 +40,14 @@ interface Props { timelineTabType: TimelineTabs | 'flyout'; timelineId: string; hostRisk: HostRisk | null; + handleOnEventClosed: HandleOnEventClosed; } interface ExpandableEventTitleProps { isAlert: boolean; loading: boolean; ruleName?: string; + timestamp?: string; handleOnEventClosed?: HandleOnEventClosed; } @@ -63,13 +68,22 @@ const StyledEuiFlexItem = styled(EuiFlexItem)` `; export const ExpandableEventTitle = React.memo( - ({ isAlert, loading, handleOnEventClosed, ruleName }) => ( + ({ isAlert, loading, handleOnEventClosed, ruleName, timestamp }) => ( {!loading && ( - -

{isAlert && !isEmpty(ruleName) ? ruleName : i18n.EVENT_DETAILS}

-
+ <> + +

{isAlert && !isEmpty(ruleName) ? ruleName : i18n.EVENT_DETAILS}

+
+ {timestamp && ( + <> + + + + )} + + )}
{handleOnEventClosed && ( @@ -95,6 +109,7 @@ export const ExpandableEvent = React.memo( detailsData, hostRisk, rawEventData, + handleOnEventClosed, }) => { if (!event.eventId) { return {i18n.EVENT_DETAILS_PLACEHOLDER}; @@ -112,11 +127,13 @@ export const ExpandableEvent = React.memo( data={detailsData ?? []} id={event.eventId} isAlert={isAlert} + indexName={event.indexName} isDraggable={isDraggable} rawEventData={rawEventData} timelineId={timelineId} timelineTabType={timelineTabType} hostRisk={hostRisk} + handleOnEventClosed={handleOnEventClosed} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 1d68356fc0bb7..4325e8ed64542 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -133,6 +133,11 @@ const EventDetailsPanelComponent: React.FC = ({ hostName, }); + const timestamp = useMemo( + () => getFieldValue({ category: 'base', field: '@timestamp' }, detailsData), + [detailsData] + ); + const backToAlertDetailsLink = useMemo(() => { return ( <> @@ -173,7 +178,12 @@ const EventDetailsPanelComponent: React.FC = ({ {isHostIsolationPanelOpen ? ( backToAlertDetailsLink ) : ( - + )} {isIsolateActionSuccessBannerVisible && ( @@ -203,6 +213,7 @@ const EventDetailsPanelComponent: React.FC = ({ timelineId={timelineId} timelineTabType="flyout" hostRisk={hostRisk} + handleOnEventClosed={handleOnEventClosed} /> )} @@ -237,6 +248,7 @@ const EventDetailsPanelComponent: React.FC = ({ timelineId={timelineId} timelineTabType={tabType} hostRisk={hostRisk} + handleOnEventClosed={handleOnEventClosed} /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 63e7e164854df..ffd8da99bb607 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -57,6 +57,7 @@ const FormattedFieldValueComponent: React.FC<{ isButton?: boolean; isDraggable?: boolean; onClick?: () => void; + onClickAriaLabel?: string; title?: string; truncate?: boolean; value: string | number | undefined | null; @@ -73,6 +74,7 @@ const FormattedFieldValueComponent: React.FC<{ isObjectArray = false, isDraggable = true, onClick, + onClickAriaLabel, title, truncate = true, value, @@ -190,6 +192,10 @@ const FormattedFieldValueComponent: React.FC<{ fieldName={fieldName} isDraggable={isDraggable} value={value} + onClick={onClick} + onClickAriaLabel={onClickAriaLabel} + iconType={isButton ? 'arrowDown' : undefined} + iconSide={isButton ? 'right' : undefined} /> ); } else if (fieldName === AGENT_STATUS_FIELD_NAME) { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx index d75bf436028f5..7f0d036812869 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/rule_status.tsx @@ -6,7 +6,7 @@ */ import React, { useMemo } from 'react'; -import { EuiBadge } from '@elastic/eui'; +import { EuiBadge, EuiBadgeProps } from '@elastic/eui'; import { getOr } from 'lodash/fp'; import styled from 'styled-components'; @@ -22,7 +22,7 @@ const StyledEuiBadge = styled(EuiBadge)` text-transform: capitalize; `; -interface Props { +interface BaseProps { contextId: string; eventId: string; fieldName: string; @@ -30,14 +30,33 @@ interface Props { value: string | number | undefined | null; } +type Props = BaseProps & + Pick; + const RuleStatusComponent: React.FC = ({ contextId, eventId, fieldName, isDraggable, value, + onClick, + onClickAriaLabel, + iconSide, + iconType, }) => { const color = useMemo(() => getOr('default', `${value}`, mapping), [value]); + const badge = ( + + {value} + + ); + return isDraggable ? ( = ({ value={`${value}`} tooltipContent={fieldName} > - {value} + {badge} ) : ( - {value} + badge ); }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 921794f6b6af0..b821888f0e397 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -22890,7 +22890,6 @@ "xpack.securitySolution.eventsViewer.alerts.defaultHeaders.versionTitle": "バージョン", "xpack.securitySolution.eventsViewer.alerts.overviewTable.signalStatusTitle": "ステータス", "xpack.securitySolution.eventsViewer.alerts.overviewTable.targetImportHash": "ハッシュのインポート", - "xpack.securitySolution.eventsViewer.alerts.overviewTable.timestampTitle": "タイムスタンプ", "xpack.securitySolution.eventsViewer.errorFetchingEventsData": "イベントデータをクエリできませんでした", "xpack.securitySolution.eventsViewer.eventsLabel": "イベント", "xpack.securitySolution.eventsViewer.showingLabel": "表示中", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 66c2f192a6a3b..d3b1cc495da47 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -23254,7 +23254,6 @@ "xpack.securitySolution.eventsViewer.alerts.defaultHeaders.versionTitle": "版本", "xpack.securitySolution.eventsViewer.alerts.overviewTable.signalStatusTitle": "状态", "xpack.securitySolution.eventsViewer.alerts.overviewTable.targetImportHash": "导入哈希", - "xpack.securitySolution.eventsViewer.alerts.overviewTable.timestampTitle": "时间戳", "xpack.securitySolution.eventsViewer.errorFetchingEventsData": "无法查询事件数据", "xpack.securitySolution.eventsViewer.eventsLabel": "事件", "xpack.securitySolution.eventsViewer.showingLabel": "正在显示", From ab2fdb17b1c38add71745ad3b62555c4d89d91dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Fri, 10 Dec 2021 13:25:58 +0000 Subject: [PATCH 112/145] @kbn/config: Clear empty-object properties after their props are unset (#120889) --- ...kibana-plugin-core-public.doclinksstart.md | 2 +- .../deprecation/apply_deprecations.test.ts | 30 ++++++++ .../src/deprecation/apply_deprecations.ts | 5 +- packages/kbn-config/src/deprecation/types.ts | 76 +++++++++++++++++++ .../unset_and_clean_empty_parent.test.ts | 41 ++++++++++ .../unset_and_clean_empty_parent.ts | 42 ++++++++++ .../data/server/config_deprecations.test.ts | 4 +- .../server/config_deprecations.test.ts | 2 +- 8 files changed, 196 insertions(+), 6 deletions(-) create mode 100644 packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.test.ts create mode 100644 packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.ts diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index a9828f04672e9..b60f9ad17e9c4 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | { readonly settings: string; readonly elasticStackGetStarted: string; readonly upgrade: { readonly upgradingElasticStack: string; }; readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; readonly canvas: { readonly guide: string; }; readonly cloud: { readonly indexManagement: string; }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record<string, string>; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; readonly suricataModule: string; readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; readonly auditdModule: string; readonly systemModule: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly appSearch: { readonly apiRef: string; readonly apiClients: string; readonly apiKeys: string; readonly authentication: string; readonly crawlRules: string; readonly curations: string; readonly duplicateDocuments: string; readonly entryPoints: string; readonly guide: string; readonly indexingDocuments: string; readonly indexingDocumentsSchema: string; readonly logSettings: string; readonly metaEngines: string; readonly recisionTuning: string; readonly relevanceTuning: string; readonly resultSettings: string; readonly searchUI: string; readonly security: string; readonly synonyms: string; readonly webCrawler: string; readonly webCrawlerEventLogs: string; }; readonly enterpriseSearch: { readonly configuration: string; readonly licenseManagement: string; readonly mailService: string; readonly usersAccess: string; }; readonly workplaceSearch: { readonly apiKeys: string; readonly box: string; readonly confluenceCloud: string; readonly confluenceServer: string; readonly customSources: string; readonly customSourcePermissions: string; readonly documentPermissions: string; readonly dropbox: string; readonly externalIdentities: string; readonly gitHub: string; readonly gettingStarted: string; readonly gmail: string; readonly googleDrive: string; readonly indexingSchedule: string; readonly jiraCloud: string; readonly jiraServer: string; readonly oneDrive: string; readonly permissions: string; readonly salesforce: string; readonly security: string; readonly serviceNow: string; readonly sharePoint: string; readonly slack: string; readonly synch: string; readonly zendesk: string; }; readonly heartbeat: { readonly base: string; }; readonly libbeat: { readonly getStarted: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite\_missing\_bucket: string; readonly date\_histogram: string; readonly date\_range: string; readonly date\_format\_pattern: string; readonly filter: string; readonly filters: string; readonly geohash\_grid: string; readonly histogram: string; readonly ip\_range: string; readonly range: string; readonly significant\_terms: string; readonly terms: string; readonly terms\_doc\_count\_error: string; readonly avg: string; readonly avg\_bucket: string; readonly max\_bucket: string; readonly min\_bucket: string; readonly sum\_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative\_sum: string; readonly derivative: string; readonly geo\_bounds: string; readonly geo\_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving\_avg: string; readonly percentile\_ranks: string; readonly serial\_diff: string; readonly std\_dev: string; readonly sum: string; readonly top\_hits: string; }; readonly runtimeFields: { readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly search: { readonly sessions: string; readonly sessionLimits: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; readonly runtimeFields: string; }; readonly addData: string; readonly kibana: string; readonly upgradeAssistant: { readonly overview: string; readonly batchReindex: string; readonly remoteReindex: string; }; readonly rollupJobs: string; readonly elasticsearch: Record<string, string>; readonly siem: { readonly privileges: string; readonly guide: string; readonly gettingStarted: string; readonly ml: string; readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; readonly troubleshootGaps: string; }; readonly securitySolution: { readonly trustedApps: string; }; readonly query: { readonly eql: string; readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; readonly percolate: string; readonly queryDsl: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record<string, string>; readonly ml: Record<string, string>; readonly transforms: Record<string, string>; readonly visualize: Record<string, string>; readonly apis: Readonly<{ bulkIndexAlias: string; byteSizeUnits: string; createAutoFollowPattern: string; createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; timeUnits: string; updateTransform: string; }>; readonly observability: Readonly<{ guide: string; infrastructureThreshold: string; logsThreshold: string; metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; uptimeDurationAnomaly: string; }>; readonly alerting: Record<string, string>; readonly maps: Readonly<{ guide: string; importGeospatialPrivileges: string; gdalTutorial: string; }>; readonly monitoring: Record<string, string>; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly spaces: Readonly<{ kibanaLegacyUrlAliases: string; kibanaDisableLegacyUrlAliasesApi: string; }>; readonly watcher: Record<string, string>; readonly ccs: Record<string, string>; readonly plugins: Record<string, string>; readonly snapshotRestore: Record<string, string>; readonly ingest: Record<string, string>; readonly fleet: Readonly<{ beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; settings: string; settingsFleetServerHostSettings: string; settingsFleetServerProxySettings: string; troubleshooting: string; elasticAgent: string; datastreams: string; datastreamsNamingScheme: string; installElasticAgent: string; installElasticAgentStandalone: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; learnMoreBlog: string; apiKeysLearnMore: string; onPremRegistry: string; }>; readonly ecs: { readonly guide: string; }; readonly clients: { readonly guide: string; readonly goOverview: string; readonly javaIndex: string; readonly jsIntro: string; readonly netGuide: string; readonly perlGuide: string; readonly phpGuide: string; readonly pythonGuide: string; readonly rubyOverview: string; readonly rustGuide: string; }; readonly endpoints: { readonly troubleshooting: string; }; } | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | { readonly settings: string; readonly elasticStackGetStarted: string; readonly upgrade: { readonly upgradingElasticStack: string; }; readonly apm: { readonly kibanaSettings: string; readonly supportedServiceMaps: string; readonly customLinks: string; readonly droppedTransactionSpans: string; readonly upgrading: string; readonly metaData: string; }; readonly canvas: { readonly guide: string; }; readonly cloud: { readonly indexManagement: string; }; readonly dashboard: { readonly guide: string; readonly drilldowns: string; readonly drilldownsTriggerPicker: string; readonly urlDrilldownTemplateSyntax: string; readonly urlDrilldownVariables: string; }; readonly discover: Record<string, string>; readonly filebeat: { readonly base: string; readonly installation: string; readonly configuration: string; readonly elasticsearchOutput: string; readonly elasticsearchModule: string; readonly startup: string; readonly exportedFields: string; readonly suricataModule: string; readonly zeekModule: string; }; readonly auditbeat: { readonly base: string; readonly auditdModule: string; readonly systemModule: string; }; readonly metricbeat: { readonly base: string; readonly configure: string; readonly httpEndpoint: string; readonly install: string; readonly start: string; }; readonly appSearch: { readonly apiRef: string; readonly apiClients: string; readonly apiKeys: string; readonly authentication: string; readonly crawlRules: string; readonly curations: string; readonly duplicateDocuments: string; readonly entryPoints: string; readonly guide: string; readonly indexingDocuments: string; readonly indexingDocumentsSchema: string; readonly logSettings: string; readonly metaEngines: string; readonly precisionTuning: string; readonly relevanceTuning: string; readonly resultSettings: string; readonly searchUI: string; readonly security: string; readonly synonyms: string; readonly webCrawler: string; readonly webCrawlerEventLogs: string; }; readonly enterpriseSearch: { readonly configuration: string; readonly licenseManagement: string; readonly mailService: string; readonly usersAccess: string; }; readonly workplaceSearch: { readonly apiKeys: string; readonly box: string; readonly confluenceCloud: string; readonly confluenceServer: string; readonly customSources: string; readonly customSourcePermissions: string; readonly documentPermissions: string; readonly dropbox: string; readonly externalIdentities: string; readonly gitHub: string; readonly gettingStarted: string; readonly gmail: string; readonly googleDrive: string; readonly indexingSchedule: string; readonly jiraCloud: string; readonly jiraServer: string; readonly oneDrive: string; readonly permissions: string; readonly salesforce: string; readonly security: string; readonly serviceNow: string; readonly sharePoint: string; readonly slack: string; readonly synch: string; readonly zendesk: string; }; readonly heartbeat: { readonly base: string; }; readonly libbeat: { readonly getStarted: string; }; readonly logstash: { readonly base: string; }; readonly functionbeat: { readonly base: string; }; readonly winlogbeat: { readonly base: string; }; readonly aggs: { readonly composite: string; readonly composite\_missing\_bucket: string; readonly date\_histogram: string; readonly date\_range: string; readonly date\_format\_pattern: string; readonly filter: string; readonly filters: string; readonly geohash\_grid: string; readonly histogram: string; readonly ip\_range: string; readonly range: string; readonly significant\_terms: string; readonly terms: string; readonly terms\_doc\_count\_error: string; readonly avg: string; readonly avg\_bucket: string; readonly max\_bucket: string; readonly min\_bucket: string; readonly sum\_bucket: string; readonly cardinality: string; readonly count: string; readonly cumulative\_sum: string; readonly derivative: string; readonly geo\_bounds: string; readonly geo\_centroid: string; readonly max: string; readonly median: string; readonly min: string; readonly moving\_avg: string; readonly percentile\_ranks: string; readonly serial\_diff: string; readonly std\_dev: string; readonly sum: string; readonly top\_hits: string; }; readonly runtimeFields: { readonly overview: string; readonly mapping: string; }; readonly scriptedFields: { readonly scriptFields: string; readonly scriptAggs: string; readonly painless: string; readonly painlessApi: string; readonly painlessLangSpec: string; readonly painlessSyntax: string; readonly painlessWalkthrough: string; readonly luceneExpressions: string; }; readonly search: { readonly sessions: string; readonly sessionLimits: string; }; readonly indexPatterns: { readonly introduction: string; readonly fieldFormattersNumber: string; readonly fieldFormattersString: string; readonly runtimeFields: string; }; readonly addData: string; readonly kibana: string; readonly upgradeAssistant: { readonly overview: string; readonly batchReindex: string; readonly remoteReindex: string; }; readonly rollupJobs: string; readonly elasticsearch: Record<string, string>; readonly siem: { readonly privileges: string; readonly guide: string; readonly gettingStarted: string; readonly ml: string; readonly ruleChangeLog: string; readonly detectionsReq: string; readonly networkMap: string; readonly troubleshootGaps: string; }; readonly securitySolution: { readonly trustedApps: string; }; readonly query: { readonly eql: string; readonly kueryQuerySyntax: string; readonly luceneQuerySyntax: string; readonly percolate: string; readonly queryDsl: string; }; readonly date: { readonly dateMath: string; readonly dateMathIndexNames: string; }; readonly management: Record<string, string>; readonly ml: Record<string, string>; readonly transforms: Record<string, string>; readonly visualize: Record<string, string>; readonly apis: Readonly<{ bulkIndexAlias: string; byteSizeUnits: string; createAutoFollowPattern: string; createFollower: string; createIndex: string; createSnapshotLifecyclePolicy: string; createRoleMapping: string; createRoleMappingTemplates: string; createRollupJobsRequest: string; createApiKey: string; createPipeline: string; createTransformRequest: string; cronExpressions: string; executeWatchActionModes: string; indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; painlessExecuteAPIContexts: string; putComponentTemplateMetadata: string; putSnapshotLifecyclePolicy: string; putIndexTemplateV1: string; putWatch: string; simulatePipeline: string; timeUnits: string; updateTransform: string; }>; readonly observability: Readonly<{ guide: string; infrastructureThreshold: string; logsThreshold: string; metricsThreshold: string; monitorStatus: string; monitorUptime: string; tlsCertificate: string; uptimeDurationAnomaly: string; }>; readonly alerting: Record<string, string>; readonly maps: Readonly<{ guide: string; importGeospatialPrivileges: string; gdalTutorial: string; }>; readonly monitoring: Record<string, string>; readonly security: Readonly<{ apiKeyServiceSettings: string; clusterPrivileges: string; elasticsearchSettings: string; elasticsearchEnableSecurity: string; elasticsearchEnableApiKeys: string; indicesPrivileges: string; kibanaTLS: string; kibanaPrivileges: string; mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; }>; readonly spaces: Readonly<{ kibanaLegacyUrlAliases: string; kibanaDisableLegacyUrlAliasesApi: string; }>; readonly watcher: Record<string, string>; readonly ccs: Record<string, string>; readonly plugins: { azureRepo: string; gcsRepo: string; hdfsRepo: string; s3Repo: string; snapshotRestoreRepos: string; mapperSize: string; }; readonly snapshotRestore: Record<string, string>; readonly ingest: Record<string, string>; readonly fleet: Readonly<{ beatsAgentComparison: string; guide: string; fleetServer: string; fleetServerAddFleetServer: string; settings: string; settingsFleetServerHostSettings: string; settingsFleetServerProxySettings: string; troubleshooting: string; elasticAgent: string; datastreams: string; datastreamsNamingScheme: string; installElasticAgent: string; installElasticAgentStandalone: string; upgradeElasticAgent: string; upgradeElasticAgent712lower: string; learnMoreBlog: string; apiKeysLearnMore: string; onPremRegistry: string; }>; readonly ecs: { readonly guide: string; }; readonly clients: { readonly guide: string; readonly goOverview: string; readonly javaIndex: string; readonly jsIntro: string; readonly netGuide: string; readonly perlGuide: string; readonly phpGuide: string; readonly pythonGuide: string; readonly rubyOverview: string; readonly rustGuide: string; }; readonly endpoints: { readonly troubleshooting: string; }; } | | diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts index 70945b2d96b32..3f84eed867655 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.test.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.test.ts @@ -116,6 +116,36 @@ describe('applyDeprecations', () => { expect(migrated).toEqual({ foo: 'bar', newname: 'renamed' }); }); + it('nested properties take into account if their parents are empty objects, and remove them if so', () => { + const initialConfig = { + foo: 'bar', + deprecated: { nested: 'deprecated' }, + nested: { + from: { + rename: 'renamed', + }, + to: { + keep: 'keep', + }, + }, + }; + + const { config: migrated } = applyDeprecations(initialConfig, [ + wrapHandler(deprecations.unused('deprecated.nested')), + wrapHandler(deprecations.rename('nested.from.rename', 'nested.to.renamed')), + ]); + + expect(migrated).toStrictEqual({ + foo: 'bar', + nested: { + to: { + keep: 'keep', + renamed: 'renamed', + }, + }, + }); + }); + it('does not alter the initial config', () => { const initialConfig = { foo: 'bar', deprecated: 'deprecated' }; diff --git a/packages/kbn-config/src/deprecation/apply_deprecations.ts b/packages/kbn-config/src/deprecation/apply_deprecations.ts index 11b35840969d0..9b0c409204414 100644 --- a/packages/kbn-config/src/deprecation/apply_deprecations.ts +++ b/packages/kbn-config/src/deprecation/apply_deprecations.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ -import { cloneDeep, unset } from 'lodash'; +import { cloneDeep } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; import type { AddConfigDeprecation, ChangedDeprecatedPaths, ConfigDeprecationWithContext, } from './types'; +import { unsetAndCleanEmptyParent } from './unset_and_clean_empty_parent'; const noopAddDeprecationFactory: () => AddConfigDeprecation = () => () => undefined; @@ -45,7 +46,7 @@ export const applyDeprecations = ( if (commands.unset) { changedPaths.unset.push(...commands.unset.map((c) => c.path)); commands.unset.forEach(function ({ path: commandPath }) { - unset(result, commandPath); + unsetAndCleanEmptyParent(result, commandPath); }); } } diff --git a/packages/kbn-config/src/deprecation/types.ts b/packages/kbn-config/src/deprecation/types.ts index 7b1eb4a0ea6c1..6abe4cd94a6fb 100644 --- a/packages/kbn-config/src/deprecation/types.ts +++ b/packages/kbn-config/src/deprecation/types.ts @@ -186,6 +186,25 @@ export interface ConfigDeprecationFactory { * rename('oldKey', 'newKey'), * ] * ``` + * + * @remarks + * If the oldKey is a nested property and it's the last property in an object, it may remove any empty-object parent keys. + * ``` + * // Original object + * { + * a: { + * b: { c: 1 }, + * d: { e: 1 } + * } + * } + * + * // If rename('a.b.c', 'a.d.c'), the resulting object removes the entire "a.b" tree because "c" was the last property in that branch + * { + * a: { + * d: { c: 1, e: 1 } + * } + * } + * ``` */ rename( oldKey: string, @@ -207,6 +226,25 @@ export interface ConfigDeprecationFactory { * renameFromRoot('oldplugin.key', 'newplugin.key'), * ] * ``` + * + * @remarks + * If the oldKey is a nested property and it's the last property in an object, it may remove any empty-object parent keys. + * ``` + * // Original object + * { + * a: { + * b: { c: 1 }, + * d: { e: 1 } + * } + * } + * + * // If renameFromRoot('a.b.c', 'a.d.c'), the resulting object removes the entire "a.b" tree because "c" was the last property in that branch + * { + * a: { + * d: { c: 1, e: 1 } + * } + * } + * ``` */ renameFromRoot( oldKey: string, @@ -225,6 +263,25 @@ export interface ConfigDeprecationFactory { * unused('deprecatedKey'), * ] * ``` + * + * @remarks + * If the path is a nested property and it's the last property in an object, it may remove any empty-object parent keys. + * ``` + * // Original object + * { + * a: { + * b: { c: 1 }, + * d: { e: 1 } + * } + * } + * + * // If unused('a.b.c'), the resulting object removes the entire "a.b" tree because "c" was the last property in that branch + * { + * a: { + * d: { e: 1 } + * } + * } + * ``` */ unused(unusedKey: string, details?: Partial): ConfigDeprecation; @@ -242,6 +299,25 @@ export interface ConfigDeprecationFactory { * unusedFromRoot('somepath.deprecatedProperty'), * ] * ``` + * + * @remarks + * If the path is a nested property and it's the last property in an object, it may remove any empty-object parent keys. + * ``` + * // Original object + * { + * a: { + * b: { c: 1 }, + * d: { e: 1 } + * } + * } + * + * // If unused('a.b.c'), the resulting object removes the entire "a.b" tree because "c" was the last property in that branch + * { + * a: { + * d: { e: 1 } + * } + * } + * ``` */ unusedFromRoot(unusedKey: string, details?: Partial): ConfigDeprecation; } diff --git a/packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.test.ts b/packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.test.ts new file mode 100644 index 0000000000000..115730c106137 --- /dev/null +++ b/packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.test.ts @@ -0,0 +1,41 @@ +/* + * 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 { unsetAndCleanEmptyParent } from './unset_and_clean_empty_parent'; + +describe('unsetAndcleanEmptyParent', () => { + test('unsets the property of the root object, and returns an empty root object', () => { + const config = { toRemove: 'toRemove' }; + unsetAndCleanEmptyParent(config, 'toRemove'); + expect(config).toStrictEqual({}); + }); + + test('unsets a nested property of the root object, and removes the empty parent property', () => { + const config = { nestedToRemove: { toRemove: 'toRemove' } }; + unsetAndCleanEmptyParent(config, 'nestedToRemove.toRemove'); + expect(config).toStrictEqual({}); + }); + + describe('Navigating to parent known issue: Array paths', () => { + // We navigate to the parent property by splitting the "." and dropping the last item in the path. + // This means that paths that are declared as prop1[idx] cannot apply the parent's cleanup logic. + // The use cases for this are quite limited, so we'll accept it as a documented limitation. + + test('does not remove a parent array when the index is specified with square brackets', () => { + const config = { nestedToRemove: [{ toRemove: 'toRemove' }] }; + unsetAndCleanEmptyParent(config, 'nestedToRemove[0].toRemove'); + expect(config).toStrictEqual({ nestedToRemove: [{}] }); + }); + + test('removes a parent array when the index is specified with dots', () => { + const config = { nestedToRemove: [{ toRemove: 'toRemove' }] }; + unsetAndCleanEmptyParent(config, 'nestedToRemove.0.toRemove'); + expect(config).toStrictEqual({}); + }); + }); +}); diff --git a/packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.ts b/packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.ts new file mode 100644 index 0000000000000..c5f5e5951adc4 --- /dev/null +++ b/packages/kbn-config/src/deprecation/unset_and_clean_empty_parent.ts @@ -0,0 +1,42 @@ +/* + * 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 { get, unset } from 'lodash'; + +/** + * Unsets the path and checks if the parent property is an empty object. + * If so, it removes the property from the config object (mutation is applied). + * + * @internal + */ +export const unsetAndCleanEmptyParent = ( + config: Record, + path: string | string[] +): void => { + // 1. Unset the provided path + const didUnset = unset(config, path); + + // Check if the unset actually removed anything. + // This way we avoid some CPU cycles when the previous action didn't apply any changes. + if (didUnset) { + // 2. Check if the parent property in the resulting object is an empty object + const pathArray = Array.isArray(path) ? path : path.split('.'); + const parentPath = pathArray.slice(0, -1); + if (parentPath.length === 0) { + return; + } + const parentObj = get(config, parentPath); + if ( + typeof parentObj === 'object' && + parentObj !== null && + Object.keys(parentObj).length === 0 + ) { + unsetAndCleanEmptyParent(config, parentPath); + } + } +}; diff --git a/src/plugins/data/server/config_deprecations.test.ts b/src/plugins/data/server/config_deprecations.test.ts index 6c09b060aa763..3df1ea9119292 100644 --- a/src/plugins/data/server/config_deprecations.test.ts +++ b/src/plugins/data/server/config_deprecations.test.ts @@ -50,7 +50,7 @@ describe('Config Deprecations', () => { }, }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.kibana.autocompleteTerminateAfter).not.toBeDefined(); + expect(migrated.kibana?.autocompleteTerminateAfter).not.toBeDefined(); expect(migrated.data.autocomplete.valueSuggestions.terminateAfter).toEqual(123); expect(messages).toMatchInlineSnapshot(` Array [ @@ -66,7 +66,7 @@ describe('Config Deprecations', () => { }, }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.kibana.autocompleteTimeout).not.toBeDefined(); + expect(migrated.kibana?.autocompleteTimeout).not.toBeDefined(); expect(migrated.data.autocomplete.valueSuggestions.timeout).toEqual(123); expect(messages).toMatchInlineSnapshot(` Array [ diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index 7a85e614e4b62..150e878f4297f 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -182,7 +182,7 @@ describe('Config Deprecations', () => { }, }; const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); - expect(migrated.security.showInsecureClusterWarning).not.toBeDefined(); + expect(migrated.security?.showInsecureClusterWarning).not.toBeDefined(); expect(migrated.xpack.security.showInsecureClusterWarning).toEqual(false); expect(messages).toMatchInlineSnapshot(` Array [ From 3129b98313e55e0e1b9508a37a0d67b20067d7a6 Mon Sep 17 00:00:00 2001 From: John Dorlus Date: Fri, 10 Dec 2021 16:51:12 -0500 Subject: [PATCH 113/145] Added Close Index Component Integration Test For Index Management (#114020) * Added close index test. * Fixed linting issues. * Fixed linting issues. * Abstracted out the index action option selection method and cleaned up test. * Merged Yulia's changes into this PR and updated the test to consume the new data test subjects. * Adjusted assertion to check for second to last request since there is a refresh done after the close index call. * Fixed linting issue. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../client_integration/helpers/test_subjects.ts | 1 - .../client_integration/home/indices_tab.helpers.ts | 1 - .../client_integration/home/indices_tab.test.ts | 13 ++++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index ac4b4c46ad4d1..5da1fc61742e6 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -25,7 +25,6 @@ export type TestSubjects = | 'ilmPolicyLink' | 'includeStatsSwitch' | 'includeManagedSwitch' - | 'indexActionsContextMenuButton' | 'indexContextMenu' | 'indexManagementHeaderContent' | 'indexTable' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index 0e4564163c553..c1b8dfcc0034f 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -45,7 +45,6 @@ export const setup = async (overridingDependencies: any = {}): Promise { const { find, component } = testBed; diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts index ec80bf5d712c0..689c48b24a9c3 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts @@ -196,11 +196,22 @@ describe('', () => { httpRequestsMockHelpers.setReloadIndicesResponse({ indexNames: [indexName] }); testBed = await setup(); - const { find, component } = testBed; + const { component, find } = testBed; + component.update(); find('indexTableIndexNameLink').at(0).simulate('click'); }); + test('should be able to close an open index', async () => { + const { actions } = testBed; + + await actions.clickManageContextMenuButton(); + await actions.clickContextMenuOption('closeIndexMenuButton'); + + // A refresh call was added after closing an index so we need to check the second to last request. + const latestRequest = server.requests[server.requests.length - 2]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/indices/close`); + }); test('should be able to flush index', async () => { const { actions } = testBed; From c4e8cb3594b0b5dd81e0cda735831508c26afa81 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Mon, 13 Dec 2021 09:24:38 +0100 Subject: [PATCH 114/145] [Discover] Rename IndexPatternFieldEditor > DataViewFieldEditor (#120344) * [Discover] Rename IndexPatternFieldEditor > DataViewFieldEditor * Update remaining test * Rename editIndexPatternField > editDataViewField * Fix wrong variable name Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../discover/public/__mocks__/services.ts | 2 +- ...ver_index_pattern_management.test.tsx.snap | 4 +-- ...discover_index_pattern_management.test.tsx | 2 +- .../discover_index_pattern_management.tsx | 9 +++---- .../components/sidebar/discover_sidebar.tsx | 27 +++++++++---------- .../sidebar/discover_sidebar_responsive.tsx | 8 +++--- src/plugins/discover/public/build_services.ts | 4 +-- 7 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/plugins/discover/public/__mocks__/services.ts b/src/plugins/discover/public/__mocks__/services.ts index ec7657827d95b..337d44227139e 100644 --- a/src/plugins/discover/public/__mocks__/services.ts +++ b/src/plugins/discover/public/__mocks__/services.ts @@ -78,7 +78,7 @@ export const discoverServiceMock = { http: { basePath: '/', }, - indexPatternFieldEditor: { + dataViewFieldEditor: { openEditor: jest.fn(), userPermissions: { editIndexPattern: jest.fn(), diff --git a/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap b/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap index 6cb6a15aa0f66..17d414215af55 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap +++ b/src/plugins/discover/public/application/main/components/sidebar/__snapshots__/discover_index_pattern_management.test.tsx.snap @@ -653,13 +653,13 @@ exports[`Discover IndexPattern Management renders correctly 1`] = ` "navigateToApp": [MockFunction], }, }, - "history": [Function], - "indexPatternFieldEditor": Object { + "dataViewFieldEditor": Object { "openEditor": [MockFunction], "userPermissions": Object { "editIndexPattern": [Function], }, }, + "history": [Function], "uiSettings": Object { "get": [Function], }, diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx index c5b1f4d2612d6..4132e4fb1b9b8 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.test.tsx @@ -39,7 +39,7 @@ const mockServices = { } }, }, - indexPatternFieldEditor: { + dataViewFieldEditor: { openEditor: jest.fn(), userPermissions: { editIndexPattern: () => { diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx index 9353073e7fad6..7fbb518ca3034 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_index_pattern_management.tsx @@ -33,14 +33,13 @@ export interface DiscoverIndexPatternManagementProps { } export function DiscoverIndexPatternManagement(props: DiscoverIndexPatternManagementProps) { - const { indexPatternFieldEditor, core } = props.services; + const { dataViewFieldEditor, core } = props.services; const { useNewFieldsApi, selectedIndexPattern, editField } = props; - const indexPatternFieldEditPermission = - indexPatternFieldEditor?.userPermissions.editIndexPattern(); - const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi; + const dataViewEditPermission = dataViewFieldEditor?.userPermissions.editIndexPattern(); + const canEditDataViewField = !!dataViewEditPermission && useNewFieldsApi; const [isAddIndexPatternFieldPopoverOpen, setIsAddIndexPatternFieldPopoverOpen] = useState(false); - if (!useNewFieldsApi || !selectedIndexPattern || !canEditIndexPatternField) { + if (!useNewFieldsApi || !selectedIndexPattern || !canEditDataViewField) { return null; } diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx index 78aee49d1b288..ea7b6fd31923e 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar.tsx @@ -109,10 +109,9 @@ export function DiscoverSidebarComponent({ }: DiscoverSidebarProps) { const [fields, setFields] = useState(null); - const { indexPatternFieldEditor } = services; - const indexPatternFieldEditPermission = - indexPatternFieldEditor?.userPermissions.editIndexPattern(); - const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi; + const { dataViewFieldEditor } = services; + const dataViewFieldEditPermission = dataViewFieldEditor?.userPermissions.editIndexPattern(); + const canEditDataViewField = !!dataViewFieldEditPermission && useNewFieldsApi; const [scrollContainer, setScrollContainer] = useState(null); const [fieldsToRender, setFieldsToRender] = useState(FIELDS_PER_PAGE); const [fieldsPerPage, setFieldsPerPage] = useState(FIELDS_PER_PAGE); @@ -243,9 +242,9 @@ export function DiscoverSidebarComponent({ const deleteField = useMemo( () => - canEditIndexPatternField && selectedIndexPattern + canEditDataViewField && selectedIndexPattern ? async (fieldName: string) => { - const ref = indexPatternFieldEditor.openDeleteModal({ + const ref = dataViewFieldEditor.openDeleteModal({ ctx: { dataView: selectedIndexPattern, }, @@ -264,11 +263,11 @@ export function DiscoverSidebarComponent({ : undefined, [ selectedIndexPattern, - canEditIndexPatternField, + canEditDataViewField, setFieldEditorRef, closeFlyout, onEditRuntimeField, - indexPatternFieldEditor, + dataViewFieldEditor, ] ); @@ -413,8 +412,8 @@ export function DiscoverSidebarComponent({ selected={true} trackUiMetric={trackUiMetric} multiFields={multiFields?.get(field.name)} - onEditField={canEditIndexPatternField ? editField : undefined} - onDeleteField={canEditIndexPatternField ? deleteField : undefined} + onEditField={canEditDataViewField ? editField : undefined} + onDeleteField={canEditDataViewField ? deleteField : undefined} showFieldStats={showFieldStats} /> @@ -473,8 +472,8 @@ export function DiscoverSidebarComponent({ getDetails={getDetailsByField} trackUiMetric={trackUiMetric} multiFields={multiFields?.get(field.name)} - onEditField={canEditIndexPatternField ? editField : undefined} - onDeleteField={canEditIndexPatternField ? deleteField : undefined} + onEditField={canEditDataViewField ? editField : undefined} + onDeleteField={canEditDataViewField ? deleteField : undefined} showFieldStats={showFieldStats} /> @@ -502,8 +501,8 @@ export function DiscoverSidebarComponent({ getDetails={getDetailsByField} trackUiMetric={trackUiMetric} multiFields={multiFields?.get(field.name)} - onEditField={canEditIndexPatternField ? editField : undefined} - onDeleteField={canEditIndexPatternField ? deleteField : undefined} + onEditField={canEditDataViewField ? editField : undefined} + onDeleteField={canEditDataViewField ? deleteField : undefined} showFieldStats={showFieldStats} /> diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index a4e84bd831619..6316369ff4c6f 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -180,17 +180,17 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) setIsFlyoutVisible(false); }, []); - const { indexPatternFieldEditor } = props.services; + const { dataViewFieldEditor } = props.services; const editField = useCallback( (fieldName?: string) => { const indexPatternFieldEditPermission = - indexPatternFieldEditor?.userPermissions.editIndexPattern(); + dataViewFieldEditor?.userPermissions.editIndexPattern(); const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi; if (!canEditIndexPatternField || !selectedIndexPattern) { return; } - const ref = indexPatternFieldEditor.openEditor({ + const ref = dataViewFieldEditor.openEditor({ ctx: { dataView: selectedIndexPattern, }, @@ -208,7 +208,7 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) }, [ closeFlyout, - indexPatternFieldEditor, + dataViewFieldEditor, selectedIndexPattern, setFieldEditorRef, onEditRuntimeField, diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 9cc2eb78aafbe..9f21294efdfc1 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -66,7 +66,7 @@ export interface DiscoverServices { toastNotifications: ToastsStart; uiSettings: IUiSettingsClient; trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; - indexPatternFieldEditor: IndexPatternFieldEditorStart; + dataViewFieldEditor: IndexPatternFieldEditorStart; http: HttpStart; storage: Storage; spaces?: SpacesApi; @@ -105,7 +105,7 @@ export function buildServices( uiSettings: core.uiSettings, storage, trackUiMetric: usageCollection?.reportUiCounter.bind(usageCollection, 'discover'), - indexPatternFieldEditor: plugins.dataViewFieldEditor, + dataViewFieldEditor: plugins.dataViewFieldEditor, http: core.http, spaces: plugins.spaces, }; From 1e159f2aee82028e1406488c5c68a017b6fc569d Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Mon, 13 Dec 2021 10:44:47 +0000 Subject: [PATCH 115/145] [ML] Transforms: Use KibanaThemeProvider in transform plugin (#120933) * [ML] Use KibanaThemeProvider in transform plugin * [ML] Add missing mock Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/app/__mocks__/app_dependencies.tsx | 3 +- x-pack/plugins/transform/public/app/app.tsx | 21 ++++++++----- .../transform/public/app/app_dependencies.tsx | 1 + .../toast_notification_text.test.tsx | 3 ++ .../components/toast_notification_text.tsx | 5 +++- .../public/app/hooks/use_delete_transform.tsx | 30 +++++++++++++++---- .../public/app/hooks/use_start_transform.tsx | 9 ++++-- .../public/app/hooks/use_stop_transform.tsx | 9 ++++-- .../public/app/mount_management_section.ts | 3 +- .../step_create/step_create_form.tsx | 24 +++++++++++---- .../step_details/step_details_form.tsx | 24 +++++++++++---- 11 files changed, 102 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx index ab38d05ec9f8f..6aab7b558cf4d 100644 --- a/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/__mocks__/app_dependencies.tsx @@ -9,7 +9,7 @@ import { useContext } from 'react'; import type { ScopedHistory } from 'kibana/public'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { coreMock, themeServiceMock } from '../../../../../../src/core/public/mocks'; import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; import { savedObjectsPluginMock } from '../../../../../../src/plugins/saved_objects/public/mocks'; import { SharePluginStart } from '../../../../../../src/plugins/share/public'; @@ -39,6 +39,7 @@ const appDependencies: AppDependencies = { savedObjects: coreStart.savedObjects, storage: { get: jest.fn() } as unknown as Storage, overlays: coreStart.overlays, + theme: themeServiceMock.createStartContract(), http: coreSetup.http, history: {} as ScopedHistory, savedObjectsPlugin: savedObjectsPluginMock.createStartContract(), diff --git a/x-pack/plugins/transform/public/app/app.tsx b/x-pack/plugins/transform/public/app/app.tsx index fd14ab8440202..ec88882d16425 100644 --- a/x-pack/plugins/transform/public/app/app.tsx +++ b/x-pack/plugins/transform/public/app/app.tsx @@ -14,7 +14,10 @@ import { EuiErrorBoundary } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; +import { + KibanaContextProvider, + KibanaThemeProvider, +} from '../../../../../src/plugins/kibana_react/public'; import { API_BASE_PATH } from '../../common/constants'; @@ -65,13 +68,15 @@ export const renderApp = (element: HTMLElement, appDependencies: AppDependencies render( - - - - - - - + + + + + + + + + , element ); diff --git a/x-pack/plugins/transform/public/app/app_dependencies.tsx b/x-pack/plugins/transform/public/app/app_dependencies.tsx index da1178e395720..89807fb2e1247 100644 --- a/x-pack/plugins/transform/public/app/app_dependencies.tsx +++ b/x-pack/plugins/transform/public/app/app_dependencies.tsx @@ -30,6 +30,7 @@ export interface AppDependencies { savedObjects: CoreStart['savedObjects']; storage: Storage; overlays: CoreStart['overlays']; + theme: CoreStart['theme']; history: ScopedHistory; savedObjectsPlugin: SavedObjectsStart; share: SharePluginStart; diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx index b085492f07e75..2539f1cf9bc06 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { CoreStart } from 'src/core/public'; +import { themeServiceMock } from 'src/core/public/mocks'; import { ToastNotificationText } from './toast_notification_text'; @@ -20,6 +21,7 @@ describe('ToastNotificationText', () => { const props = { overlays: {} as CoreStart['overlays'], text: 'a short text message', + theme: themeServiceMock.createStartContract(), }; const { container } = render(); expect(container.textContent).toBe('a short text message'); @@ -29,6 +31,7 @@ describe('ToastNotificationText', () => { const props = { overlays: {} as CoreStart['overlays'], text: 'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ', + theme: themeServiceMock.createStartContract(), }; const { container } = render(); expect(container.textContent).toBe( diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx index 2555105fe2530..aebe74a539a1c 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx @@ -29,6 +29,7 @@ const MAX_SIMPLE_MESSAGE_LENGTH = 140; // That's why we need to pass in `overlays` as a prop cannot get it via context. interface ToastNotificationTextProps { overlays: CoreStart['overlays']; + theme: CoreStart['theme']; text: any; previewTextLength?: number; } @@ -36,6 +37,7 @@ interface ToastNotificationTextProps { export const ToastNotificationText: FC = ({ overlays, text, + theme, previewTextLength, }) => { if (typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { @@ -80,7 +82,8 @@ export const ToastNotificationText: FC = ({ })} - + , + { theme$: theme.theme$ } ) ); }; diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 9ea5856434c52..ff93f027fc3a4 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -120,7 +120,7 @@ export const useDeleteIndexAndTargetIndex = (items: TransformListRow[]) => { type SuccessCountField = keyof Omit; export const useDeleteTransforms = () => { - const { overlays } = useAppDependencies(); + const { overlays, theme } = useAppDependencies(); const toastNotifications = useToastNotifications(); const api = useApi(); @@ -136,8 +136,10 @@ export const useDeleteTransforms = () => { + />, + { theme$: theme.theme$ } ), }); return; @@ -203,7 +205,13 @@ export const useDeleteTransforms = () => { values: { transformId }, }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); } @@ -219,7 +227,13 @@ export const useDeleteTransforms = () => { } ), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); } @@ -235,7 +249,13 @@ export const useDeleteTransforms = () => { } ), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); } diff --git a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx index d808d4ab509fd..c8abd13db394d 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_start_transform.tsx @@ -23,7 +23,7 @@ import { ToastNotificationText } from '../components'; import { useApi } from './use_api'; export const useStartTransforms = () => { - const deps = useAppDependencies(); + const { overlays, theme } = useAppDependencies(); const toastNotifications = useToastNotifications(); const api = useApi(); @@ -39,7 +39,12 @@ export const useStartTransforms = () => { } ), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); return; diff --git a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx index 61e0c6dfbeebc..894ac757b396a 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_stop_transform.tsx @@ -23,7 +23,7 @@ import { ToastNotificationText } from '../components'; import { useApi } from './use_api'; export const useStopTransforms = () => { - const deps = useAppDependencies(); + const { overlays, theme } = useAppDependencies(); const toastNotifications = useToastNotifications(); const api = useApi(); @@ -39,7 +39,12 @@ export const useStopTransforms = () => { } ), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); return; diff --git a/x-pack/plugins/transform/public/app/mount_management_section.ts b/x-pack/plugins/transform/public/app/mount_management_section.ts index 6e63094064584..2f47b6d70bc79 100644 --- a/x-pack/plugins/transform/public/app/mount_management_section.ts +++ b/x-pack/plugins/transform/public/app/mount_management_section.ts @@ -28,7 +28,7 @@ export async function mountManagementSection( const { http, notifications, getStartServices } = coreSetup; const startServices = await getStartServices(); const [core, plugins] = startServices; - const { application, chrome, docLinks, i18n, overlays, savedObjects, uiSettings } = core; + const { application, chrome, docLinks, i18n, overlays, theme, savedObjects, uiSettings } = core; const { data, share, spaces, triggersActionsUi } = plugins; const { docTitle } = chrome; @@ -47,6 +47,7 @@ export async function mountManagementSection( i18n, notifications, overlays, + theme, savedObjects, storage: localStorage, uiSettings, diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 874dd149da3e0..728fcef23f8a0 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -136,6 +136,7 @@ export const StepCreateForm: FC = React.memo( // eslint-disable-next-line react-hooks/exhaustive-deps }, [created, started, indexPatternId]); + const { overlays, theme } = useAppDependencies(); const api = useApi(); async function createTransform() { @@ -160,9 +161,11 @@ export const StepCreateForm: FC = React.memo( }), text: toMountPoint( + />, + { theme$: theme.theme$ } ), }); setCreated(false); @@ -214,7 +217,12 @@ export const StepCreateForm: FC = React.memo( values: { transformId }, }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); setStarted(false); @@ -275,7 +283,8 @@ export const StepCreateForm: FC = React.memo( values: { dataViewName }, }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); setLoading(false); @@ -321,7 +330,12 @@ export const StepCreateForm: FC = React.memo( defaultMessage: 'An error occurred getting the progress percentage:', }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); clearInterval(interval); diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index 3e9f80de9152e..828db2d37d913 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -118,6 +118,7 @@ export const StepDetailsForm: FC = React.memo( [setIndexPatternTimeField, indexPatternAvailableTimeFields] ); + const { overlays, theme } = useAppDependencies(); const api = useApi(); // fetch existing transform IDs and indices once for form validation @@ -150,9 +151,11 @@ export const StepDetailsForm: FC = React.memo( }), text: toMountPoint( + />, + { theme$: theme.theme$ } ), }); } @@ -165,7 +168,12 @@ export const StepDetailsForm: FC = React.memo( defaultMessage: 'An error occurred getting the existing transform IDs:', }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); } else { @@ -182,7 +190,12 @@ export const StepDetailsForm: FC = React.memo( defaultMessage: 'An error occurred getting the existing index names:', }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); } @@ -195,7 +208,8 @@ export const StepDetailsForm: FC = React.memo( defaultMessage: 'An error occurred getting the existing data view titles:', }), text: toMountPoint( - + , + { theme$: theme.theme$ } ), }); } From 32d0e877249121364f7adef97bd2cee530feb152 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Mon, 13 Dec 2021 10:45:02 +0000 Subject: [PATCH 116/145] [ML] Use KibanaThemeProvider in ML plugin (#120892) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/ml/public/application/app.tsx | 20 +-- .../application/license/expired_warning.tsx | 13 +- .../jobs_list_page/jobs_list_page.tsx | 126 +++++++++--------- .../models_management/force_stop_dialog.tsx | 31 +++-- .../models_management/models_list.tsx | 3 +- .../application/util/dependency_cache.ts | 11 ++ .../anomaly_charts_embeddable.tsx | 34 +++-- .../anomaly_charts_setup_flyout.tsx | 38 +++--- .../anomaly_swimlane_embeddable.tsx | 34 +++-- .../anomaly_swimlane_setup_flyout.tsx | 42 +++--- .../common/resolve_job_selection.tsx | 31 +++-- 11 files changed, 220 insertions(+), 163 deletions(-) diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 5334f420698ab..9b9ed3a93322b 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -16,6 +16,7 @@ import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { KibanaContextProvider, + KibanaThemeProvider, RedirectAppLinks, } from '../../../../../src/plugins/kibana_react/public'; import { setDependencyCache, clearCache } from './util/dependency_cache'; @@ -99,14 +100,16 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { - - - + + + + + @@ -128,6 +131,7 @@ export const renderApp = ( docLinks: coreStart.docLinks!, toastNotifications: coreStart.notifications.toasts, overlays: coreStart.overlays, + theme: coreStart.theme, recentlyAccessed: coreStart.chrome!.recentlyAccessed, basePath: coreStart.http.basePath, savedObjectsClient: coreStart.savedObjects.client, diff --git a/x-pack/plugins/ml/public/application/license/expired_warning.tsx b/x-pack/plugins/ml/public/application/license/expired_warning.tsx index c8028b641cf9a..87b69fc54fc28 100644 --- a/x-pack/plugins/ml/public/application/license/expired_warning.tsx +++ b/x-pack/plugins/ml/public/application/license/expired_warning.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiCallOut } from '@elastic/eui'; -import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; -import { getOverlays } from '../util/dependency_cache'; +import { toMountPoint, wrapWithTheme } from '../../../../../../src/plugins/kibana_react/public'; +import { getOverlays, getTheme } from '../util/dependency_cache'; let expiredLicenseBannerId: string; @@ -20,8 +20,15 @@ export function showExpiredLicenseWarning() { }); // Only show the banner once with no way to dismiss it const overlays = getOverlays(); + const theme = getTheme(); + expiredLicenseBannerId = overlays.banners.add( - toMountPoint() + toMountPoint( + wrapWithTheme( + , + theme.theme$ + ) + ) ); } } diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 3a969823088f1..083982e8fccd4 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -30,6 +30,7 @@ import type { ManagementAppMountParams } from '../../../../../../../../../src/pl import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities'; import { KibanaContextProvider, + KibanaThemeProvider, RedirectAppLinks, } from '../../../../../../../../../src/plugins/kibana_react/public'; @@ -139,6 +140,7 @@ export const JobsListPage: FC<{ const tabs = useTabs(isMlEnabledInSpace, spacesApi); const [currentTabId, setCurrentTabId] = useState('anomaly-detector'); const I18nContext = coreStart.i18n.Context; + const theme$ = coreStart.theme.theme$; const check = async () => { try { @@ -219,69 +221,71 @@ export const JobsListPage: FC<{ return ( - - - - - } - description={ - - } - rightSideItems={[docsLink]} - bottomBorder - /> + + + + + + } + description={ + + } + rightSideItems={[docsLink]} + bottomBorder + /> - + - - - - {spacesEnabled && ( - <> - setShowSyncFlyout(true)} - data-test-subj="mlStackMgmtSyncButton" - > - {i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', { - defaultMessage: 'Synchronize saved objects', - })} - - {showSyncFlyout && } - - - )} - - - - - - - - - {renderTabs()} - - - - + + + + {spacesEnabled && ( + <> + setShowSyncFlyout(true)} + data-test-subj="mlStackMgmtSyncButton" + > + {i18n.translate('xpack.ml.management.jobsList.syncFlyoutButton', { + defaultMessage: 'Synchronize saved objects', + })} + + {showSyncFlyout && } + + + )} + + + + + + + + + {renderTabs()} + + + + + ); diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/force_stop_dialog.tsx index 86120a4003e23..30e110317148b 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/force_stop_dialog.tsx @@ -8,9 +8,9 @@ import React, { FC } from 'react'; import { EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { OverlayStart } from 'kibana/public'; +import type { OverlayStart, ThemeServiceStart } from 'kibana/public'; import type { ModelItem } from './models_list'; -import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; +import { toMountPoint, wrapWithTheme } from '../../../../../../../src/plugins/kibana_react/public'; interface ForceStopModelConfirmDialogProps { model: ModelItem; @@ -64,22 +64,25 @@ export const ForceStopModelConfirmDialog: FC = }; export const getUserConfirmationProvider = - (overlays: OverlayStart) => async (forceStopModel: ModelItem) => { + (overlays: OverlayStart, theme: ThemeServiceStart) => async (forceStopModel: ModelItem) => { return new Promise(async (resolve, reject) => { try { const modalSession = overlays.openModal( toMountPoint( - { - modalSession.close(); - resolve(false); - }} - onConfirm={() => { - modalSession.close(); - resolve(true); - }} - /> + wrapWithTheme( + { + modalSession.close(); + resolve(false); + }} + onConfirm={() => { + modalSession.close(); + resolve(true); + }} + />, + theme.theme$ + ) ) ); } catch (e) { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx index c80ff808aa539..75659a1e3567d 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx @@ -82,6 +82,7 @@ export const ModelsList: FC = () => { services: { application: { navigateToUrl, capabilities }, overlays, + theme, }, } = useMlKibana(); const urlLocator = useMlLocator()!; @@ -112,7 +113,7 @@ export const ModelsList: FC = () => { {} ); - const getUserConfirmation = useMemo(() => getUserConfirmationProvider(overlays), []); + const getUserConfirmation = useMemo(() => getUserConfirmationProvider(overlays, theme), []); const navigateToPath = useNavigateToPath(); diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index 7b6b75677dddd..93d7c069d873d 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -16,6 +16,7 @@ import type { DocLinksStart, ToastsStart, OverlayStart, + ThemeServiceStart, ChromeRecentlyAccessed, IBasePath, } from 'kibana/public'; @@ -34,6 +35,7 @@ export interface DependencyCache { docLinks: DocLinksStart | null; toastNotifications: ToastsStart | null; overlays: OverlayStart | null; + theme: ThemeServiceStart | null; recentlyAccessed: ChromeRecentlyAccessed | null; fieldFormats: DataPublicPluginStart['fieldFormats'] | null; autocomplete: DataPublicPluginStart['autocomplete'] | null; @@ -57,6 +59,7 @@ const cache: DependencyCache = { docLinks: null, toastNotifications: null, overlays: null, + theme: null, recentlyAccessed: null, fieldFormats: null, autocomplete: null, @@ -80,6 +83,7 @@ export function setDependencyCache(deps: Partial) { cache.docLinks = deps.docLinks || null; cache.toastNotifications = deps.toastNotifications || null; cache.overlays = deps.overlays || null; + cache.theme = deps.theme || null; cache.recentlyAccessed = deps.recentlyAccessed || null; cache.fieldFormats = deps.fieldFormats || null; cache.autocomplete = deps.autocomplete || null; @@ -128,6 +132,13 @@ export function getOverlays() { return cache.overlays; } +export function getTheme() { + if (cache.theme === null) { + throw new Error("theme hasn't been initialized"); + } + return cache.theme; +} + export function getUiSettings() { if (cache.config === null) { throw new Error("uiSettings hasn't been initialized"); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx index 60b7c628229b9..ce0a270c35306 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_embeddable.tsx @@ -10,7 +10,10 @@ import ReactDOM from 'react-dom'; import { CoreStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { Subject } from 'rxjs'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { + KibanaContextProvider, + KibanaThemeProvider, +} from '../../../../../../src/plugins/kibana_react/public'; import { Embeddable, IContainer } from '../../../../../../src/plugins/embeddable/public'; import { EmbeddableAnomalyChartsContainer } from './embeddable_anomaly_charts_container_lazy'; import type { JobId } from '../../../common/types/anomaly_detection_jobs'; @@ -96,22 +99,25 @@ export class AnomalyChartsEmbeddable extends Embeddable< this.node = node; const I18nContext = this.services[0].i18n.Context; + const theme$ = this.services[0].theme.theme$; ReactDOM.render( - - }> - - - + + + }> + + + + , node ); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx index 5090274ca7383..c4ac15ffdbe76 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_charts/anomaly_charts_setup_flyout.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { CoreStart } from 'kibana/public'; import { VIEW_BY_JOB_LABEL } from '../../application/explorer/explorer_constants'; -import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; +import { toMountPoint, wrapWithTheme } from '../../../../../../src/plugins/kibana_react/public'; import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service'; import { getDefaultExplorerChartsPanelTitle } from './anomaly_charts_embeddable'; import { HttpService } from '../../application/services/http_service'; @@ -31,24 +31,28 @@ export async function resolveEmbeddableAnomalyChartsUserInput( const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); const influencers = anomalyDetectorService.extractInfluencers(jobs); influencers.push(VIEW_BY_JOB_LABEL); + const { theme$ } = coreStart.theme; const modalSession = overlays.openModal( toMountPoint( - { - modalSession.close(); - resolve({ - jobIds, - title: panelTitle, - maxSeriesToPlot, - }); - }} - onCancel={() => { - modalSession.close(); - reject(); - }} - /> + wrapWithTheme( + { + modalSession.close(); + resolve({ + jobIds, + title: panelTitle, + maxSeriesToPlot, + }); + }} + onCancel={() => { + modalSession.close(); + reject(); + }} + />, + theme$ + ) ) ); } catch (error) { diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx index 7f9e99f3a0c8e..e168029148006 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_embeddable.tsx @@ -10,7 +10,10 @@ import ReactDOM from 'react-dom'; import { CoreStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { Subject } from 'rxjs'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import { + KibanaContextProvider, + KibanaThemeProvider, +} from '../../../../../../src/plugins/kibana_react/public'; import { Embeddable, IContainer } from '../../../../../../src/plugins/embeddable/public'; import { EmbeddableSwimLaneContainer } from './embeddable_swim_lane_container_lazy'; import type { JobId } from '../../../common/types/anomaly_detection_jobs'; @@ -58,22 +61,25 @@ export class AnomalySwimlaneEmbeddable extends Embeddable< this.node = node; const I18nContext = this.services[0].i18n.Context; + const theme$ = this.services[0].theme.theme$; ReactDOM.render( - - }> - - - + + + }> + + + + , node ); diff --git a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx index 5027eb6783a64..28cf197de5dfe 100644 --- a/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx +++ b/x-pack/plugins/ml/public/embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { CoreStart } from 'kibana/public'; import { VIEW_BY_JOB_LABEL } from '../../application/explorer/explorer_constants'; -import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; +import { toMountPoint, wrapWithTheme } from '../../../../../../src/plugins/kibana_react/public'; import { AnomalySwimlaneInitializer } from './anomaly_swimlane_initializer'; import { AnomalyDetectorService } from '../../application/services/anomaly_detector_service'; import { getDefaultSwimlanePanelTitle } from './anomaly_swimlane_embeddable'; @@ -31,26 +31,30 @@ export async function resolveAnomalySwimlaneUserInput( const jobs = await anomalyDetectorService.getJobs$(jobIds).toPromise(); const influencers = anomalyDetectorService.extractInfluencers(jobs); influencers.push(VIEW_BY_JOB_LABEL); + const { theme$ } = coreStart.theme; const modalSession = overlays.openModal( toMountPoint( - { - modalSession.close(); - resolve({ - jobIds, - title: panelTitle, - swimlaneType, - viewBy, - }); - }} - onCancel={() => { - modalSession.close(); - reject(); - }} - /> + wrapWithTheme( + { + modalSession.close(); + resolve({ + jobIds, + title: panelTitle, + swimlaneType, + viewBy, + }); + }} + onCancel={() => { + modalSession.close(); + reject(); + }} + />, + theme$ + ) ) ); } catch (error) { diff --git a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx index fbceeb7f7cf79..bf7ea8eac3f50 100644 --- a/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx +++ b/x-pack/plugins/ml/public/embeddables/common/resolve_job_selection.tsx @@ -13,6 +13,7 @@ import { getInitialGroupsMap } from '../../application/components/job_selector/j import { KibanaContextProvider, toMountPoint, + wrapWithTheme, } from '../../../../../../src/plugins/kibana_react/public'; import { getMlGlobalServices } from '../../application/app'; import { DashboardConstants } from '../../../../../../src/plugins/dashboard/public'; @@ -34,6 +35,7 @@ export async function resolveJobSelection( const { http, uiSettings, + theme, application: { currentAppId$ }, } = coreStart; @@ -70,18 +72,23 @@ export async function resolveJobSelection( const flyoutSession = coreStart.overlays.openFlyout( toMountPoint( - - - + wrapWithTheme( + + + , + theme.theme$ + ) ), { 'data-test-subj': 'mlFlyoutJobSelector', From da96f61330c0888180f5fa3cddc88dd16cda1afa Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 13 Dec 2021 12:16:20 +0100 Subject: [PATCH 117/145] Hosts Risk Step 2 - Hosts Page - Risk Column #119734 (#120487) * Add Host risk classification column to All hosts table * Add cypress test to risk column on all hosts table * Fix unit test * Add unit test * Add tooltip to host risk column Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security_solution/hosts/common/index.ts | 1 + .../hosts/risk_score/index.test.ts | 14 ++ .../hosts/risk_score/index.ts | 7 +- .../integration/hosts/hosts_risk_column.ts | 33 +++ .../integration/hosts/risky_hosts_kpi.spec.ts | 5 - .../hosts_risk/use_hosts_risk_score.ts | 6 +- .../use_hosts_risk_score_complete.ts | 4 +- .../security_solution/public/helpers.test.tsx | 7 - .../security_solution/public/helpers.tsx | 5 - .../common/host_risk_score.test.tsx | 102 +++++++++ .../components/common/host_risk_score.tsx | 44 ++++ .../hosts/components/hosts_table/columns.tsx | 214 ++++++++++-------- .../components/hosts_table/index.test.tsx | 48 ++++ .../hosts/components/hosts_table/index.tsx | 11 +- .../components/hosts_table/translations.ts | 12 + .../kpi_hosts/risky_hosts/index.tsx | 38 +--- .../kpi_hosts/risky_hosts/index.tsx | 7 +- .../security_solution/server/plugin.ts | 4 +- .../factory/hosts/all/__mocks__/index.ts | 24 ++ .../factory/hosts/all/index.test.ts | 96 +++++++- .../factory/hosts/all/index.ts | 73 +++++- .../hosts/risk_score/query.hosts_risk.dsl.ts | 6 +- .../security_solution/factory/types.ts | 1 + .../security_solution/index.ts | 5 +- .../es_archives/risky_hosts/data.json | 2 +- .../es_archives/risky_hosts/mappings.json | 8 +- 26 files changed, 606 insertions(+), 171 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.test.ts create mode 100644 x-pack/plugins/security_solution/cypress/integration/hosts/hosts_risk_column.ts create mode 100644 x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.test.tsx create mode 100644 x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.tsx diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts index f6f5ad4cd23f1..8a9a047aab3fd 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts @@ -45,6 +45,7 @@ export interface HostItem { endpoint?: Maybe; host?: Maybe; lastSeen?: Maybe; + risk?: string; } export interface HostValue { diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.test.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.test.ts new file mode 100644 index 0000000000000..8c58ccaabe8df --- /dev/null +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.test.ts @@ -0,0 +1,14 @@ +/* + * 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 { getHostRiskIndex } from '.'; + +describe('hosts risk search_strategy getHostRiskIndex', () => { + it('should properly return index if space is specified', () => { + expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName'); + }); +}); diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts index 23cda0b68f038..4273c08c638f3 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/risk_score/index.ts @@ -10,12 +10,13 @@ import type { IEsSearchRequest, IEsSearchResponse, } from '../../../../../../../../src/plugins/data/common'; +import { RISKY_HOSTS_INDEX_PREFIX } from '../../../../constants'; import { Inspect, Maybe, TimerangeInput } from '../../../common'; export interface HostsRiskScoreRequestOptions extends IEsSearchRequest { defaultIndex: string[]; factoryQueryType?: FactoryQueryTypes; - hostName?: string; + hostNames?: string[]; timerange?: TimerangeInput; } @@ -38,3 +39,7 @@ export interface RuleRisk { rule_name: string; rule_risk: string; } + +export const getHostRiskIndex = (spaceId: string): string => { + return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`; +}; diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/hosts_risk_column.ts b/x-pack/plugins/security_solution/cypress/integration/hosts/hosts_risk_column.ts new file mode 100644 index 0000000000000..bb57a8973c8e6 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/integration/hosts/hosts_risk_column.ts @@ -0,0 +1,33 @@ +/* + * 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 { loginAndWaitForPage } from '../../tasks/login'; + +import { HOSTS_URL } from '../../urls/navigation'; +import { cleanKibana } from '../../tasks/common'; +import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver'; +import { TABLE_CELL } from '../../screens/alerts_details'; +import { kqlSearch } from '../../tasks/security_header'; + +describe('All hosts table', () => { + before(() => { + cleanKibana(); + esArchiverLoad('risky_hosts'); + }); + + after(() => { + esArchiverUnload('risky_hosts'); + }); + + it('it renders risk column', () => { + loginAndWaitForPage(HOSTS_URL); + kqlSearch('host.name: "siem-kibana" {enter}'); + + cy.get('[data-test-subj="tableHeaderCell_node.risk_4"]').should('exist'); + cy.get(`${TABLE_CELL} .euiTableCellContent`).eq(4).should('have.text', 'Low'); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts b/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts index 4f282e1e69d5c..602a9118128b5 100644 --- a/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/hosts/risky_hosts_kpi.spec.ts @@ -8,13 +8,8 @@ import { loginAndWaitForPage } from '../../tasks/login'; import { HOSTS_URL } from '../../urls/navigation'; -import { cleanKibana } from '../../tasks/common'; describe('RiskyHosts KPI', () => { - before(() => { - cleanKibana(); - }); - it('it renders', () => { loginAndWaitForPage(HOSTS_URL); diff --git a/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts index 41fcd29191da2..debdacb570ad0 100644 --- a/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts +++ b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score.ts @@ -13,10 +13,10 @@ import { useAppToasts } from '../../hooks/use_app_toasts'; import { useKibana } from '../../lib/kibana'; import { inputsActions } from '../../store/actions'; import { isIndexNotFoundError } from '../../utils/exceptions'; -import { HostsRiskScore } from '../../../../common/search_strategy'; +import { getHostRiskIndex, HostsRiskScore } from '../../../../common/search_strategy'; + import { useHostsRiskScoreComplete } from './use_hosts_risk_score_complete'; import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; -import { getHostRiskIndex } from '../../../helpers'; export const QUERY_ID = 'host_risk_score'; const noop = () => {}; @@ -104,7 +104,7 @@ export const useHostsRiskScore = ({ timerange: timerange ? { to: timerange.to, from: timerange.from, interval: '' } : undefined, - hostName, + hostNames: hostName ? [hostName] : undefined, defaultIndex: [getHostRiskIndex(space.id)], }); }); diff --git a/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts index 934cb88ee0d86..6faaa3c8f08db 100644 --- a/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts +++ b/x-pack/plugins/security_solution/public/common/containers/hosts_risk/use_hosts_risk_score_complete.ts @@ -28,7 +28,7 @@ export const getHostsRiskScore = ({ data, defaultIndex, timerange, - hostName, + hostNames, signal, }: GetHostsRiskScoreProps): Observable => data.search.search( @@ -36,7 +36,7 @@ export const getHostsRiskScore = ({ defaultIndex, factoryQueryType: HostsQueries.hostsRiskScore, timerange, - hostName, + hostNames, }, { strategy: 'securitySolutionSearchStrategy', diff --git a/x-pack/plugins/security_solution/public/helpers.test.tsx b/x-pack/plugins/security_solution/public/helpers.test.tsx index 3475ac7c28f7a..5ba5d882c16d0 100644 --- a/x-pack/plugins/security_solution/public/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/helpers.test.tsx @@ -10,7 +10,6 @@ import { Capabilities } from '../../../../src/core/public'; import { CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants'; import { parseRoute, - getHostRiskIndex, isSubPluginAvailable, getSubPluginRoutesByCapabilities, RedirectRoute, @@ -65,12 +64,6 @@ describe('public helpers parseRoute', () => { }); }); -describe('public helpers export getHostRiskIndex', () => { - it('should properly return index if space is specified', () => { - expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName'); - }); -}); - describe('#getSubPluginRoutesByCapabilities', () => { const mockRender = () => null; const mockSubPlugins = { diff --git a/x-pack/plugins/security_solution/public/helpers.tsx b/x-pack/plugins/security_solution/public/helpers.tsx index d330da94e779c..09f955a53cd0a 100644 --- a/x-pack/plugins/security_solution/public/helpers.tsx +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -17,7 +17,6 @@ import { EXCEPTIONS_PATH, RULES_PATH, UEBA_PATH, - RISKY_HOSTS_INDEX_PREFIX, SERVER_APP_ID, CASES_FEATURE_ID, OVERVIEW_PATH, @@ -164,10 +163,6 @@ export const isDetectionsPath = (pathname: string): boolean => { }); }; -export const getHostRiskIndex = (spaceId: string): string => { - return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`; -}; - export const getSubPluginRoutesByCapabilities = ( subPlugins: StartedSubPlugins, capabilities: Capabilities diff --git a/x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.test.tsx new file mode 100644 index 0000000000000..4f70dce3c1160 --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 { render } from '@testing-library/react'; +import React from 'react'; +import { HostRiskSeverity } from '../../../../common/search_strategy'; +import { TestProviders } from '../../../common/mock'; +import { HostRiskScore } from './host_risk_score'; + +import { EuiHealth, EuiHealthProps } from '@elastic/eui'; + +import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...jest.requireActual('@elastic/eui'), + EuiHealth: jest.fn((props: EuiHealthProps) => ), + }; +}); + +describe('HostRiskScore', () => { + const context = {}; + it('renders critical severity risk score', () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent(HostRiskSeverity.critical); + + expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith( + expect.objectContaining({ color: euiThemeVars.euiColorDanger }), + context + ); + }); + + it('renders hight severity risk score', () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent(HostRiskSeverity.high); + + expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith( + expect.objectContaining({ color: euiThemeVars.euiColorVis9_behindText }), + context + ); + }); + + it('renders moderate severity risk score', () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent(HostRiskSeverity.moderate); + + expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith( + expect.objectContaining({ color: euiThemeVars.euiColorWarning }), + context + ); + }); + + it('renders low severity risk score', () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent(HostRiskSeverity.low); + + expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith( + expect.objectContaining({ color: euiThemeVars.euiColorVis0 }), + context + ); + }); + + it('renders unknown severity risk score', () => { + const { container } = render( + + + + ); + + expect(container).toHaveTextContent(HostRiskSeverity.unknown); + + expect(EuiHealth as jest.Mock).toHaveBeenLastCalledWith( + expect.objectContaining({ color: euiThemeVars.euiColorMediumShade }), + context + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.tsx b/x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.tsx new file mode 100644 index 0000000000000..94f344b54036f --- /dev/null +++ b/x-pack/plugins/security_solution/public/hosts/components/common/host_risk_score.tsx @@ -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 React from 'react'; + +import { EuiHealth, transparentize } from '@elastic/eui'; + +import styled, { css } from 'styled-components'; +import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; +import { HostRiskSeverity } from '../../../../common/search_strategy'; + +const HOST_RISK_SEVERITY_COLOUR = { + Unknown: euiLightVars.euiColorMediumShade, + Low: euiLightVars.euiColorVis0, + Moderate: euiLightVars.euiColorWarning, + High: euiLightVars.euiColorVis9_behindText, + Critical: euiLightVars.euiColorDanger, +}; + +const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity }>` + ${({ theme, $severity }) => css` + width: fit-content; + padding-right: ${theme.eui.paddingSizes.s}; + padding-left: ${theme.eui.paddingSizes.xs}; + + ${($severity === 'Critical' || $severity === 'High') && + css` + background-color: ${transparentize(theme.eui.euiColorDanger, 0.2)}; + border-radius: 999px; // pill shaped + `}; + `} +`; + +export const HostRiskScore: React.FC<{ severity: HostRiskSeverity }> = ({ severity }) => ( + + + {severity} + + +); diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx index 95f88da0a24ac..2aff7124990a6 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/columns.tsx @@ -24,102 +24,128 @@ import { import { HostsTableColumns } from './'; import * as i18n from './translations'; -import { Maybe } from '../../../../common/search_strategy'; +import { HostRiskSeverity, Maybe } from '../../../../common/search_strategy'; +import { HostRiskScore } from '../common/host_risk_score'; -export const getHostsColumns = (): HostsTableColumns => [ - { - field: 'node.host.name', - name: i18n.NAME, - truncateText: false, - mobileOptions: { show: true }, - sortable: true, - render: (hostName) => { - if (hostName != null && hostName.length > 0) { - const id = escapeDataProviderId(`hosts-table-hostName-${hostName[0]}`); - return ( - - snapshot.isDragging ? ( - - - - ) : ( - - ) - } - /> - ); - } - return getEmptyTagValue(); +export const getHostsColumns = (showRiskColumn: boolean): HostsTableColumns => { + const columns: HostsTableColumns = [ + { + field: 'node.host.name', + name: i18n.NAME, + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + render: (hostName) => { + if (hostName != null && hostName.length > 0) { + const id = escapeDataProviderId(`hosts-table-hostName-${hostName[0]}`); + return ( + + snapshot.isDragging ? ( + + + + ) : ( + + ) + } + /> + ); + } + return getEmptyTagValue(); + }, + width: '35%', }, - width: '35%', - }, - { - field: 'node.lastSeen', - name: ( - - <> - {i18n.LAST_SEEN}{' '} - - - - ), - truncateText: false, - mobileOptions: { show: true }, - sortable: true, - render: (lastSeen: Maybe | undefined) => { - if (lastSeen != null && lastSeen.length > 0) { - return ( - - ); - } - return getEmptyTagValue(); + { + field: 'node.lastSeen', + name: ( + + <> + {i18n.LAST_SEEN} + + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: true, + render: (lastSeen: Maybe | undefined) => { + if (lastSeen != null && lastSeen.length > 0) { + return ( + + ); + } + return getEmptyTagValue(); + }, }, - }, - { - field: 'node.host.os.name', - name: i18n.OS, - truncateText: false, - mobileOptions: { show: true }, - sortable: false, - render: (hostOsName) => { - if (hostOsName != null) { - return ( - - <>{hostOsName} - - ); - } - return getEmptyTagValue(); + { + field: 'node.host.os.name', + name: i18n.OS, + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (hostOsName) => { + if (hostOsName != null) { + return ( + + <>{hostOsName} + + ); + } + return getEmptyTagValue(); + }, }, - }, - { - field: 'node.host.os.version', - name: i18n.VERSION, - truncateText: false, - mobileOptions: { show: true }, - sortable: false, - render: (hostOsVersion) => { - if (hostOsVersion != null) { - return ( - - <>{hostOsVersion} - - ); - } - return getEmptyTagValue(); + { + field: 'node.host.os.version', + name: i18n.VERSION, + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (hostOsVersion) => { + if (hostOsVersion != null) { + return ( + + <>{hostOsVersion} + + ); + } + return getEmptyTagValue(); + }, }, - }, -]; + ]; + + if (showRiskColumn) { + columns.push({ + field: 'node.risk', + name: ( + + <> + {i18n.HOST_RISK} + + + ), + truncateText: false, + mobileOptions: { show: true }, + sortable: false, + render: (riskScore: HostRiskSeverity) => { + if (riskScore != null) { + return ; + } + return getEmptyTagValue(); + }, + }); + } + + return columns; +}; diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx index 413b8cda9b6ab..e30e87ffcb8fb 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx @@ -22,6 +22,8 @@ import { hostsModel } from '../../../hosts/store'; import { HostsTableType } from '../../../hosts/store/model'; import { HostsTable } from './index'; import { mockData } from './mock'; +import { render } from '@testing-library/react'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; jest.mock('../../../common/lib/kibana'); @@ -36,6 +38,8 @@ jest.mock('../../../common/components/query_bar', () => ({ jest.mock('../../../common/components/link_to'); +jest.mock('../../../common/hooks/use_experimental_features'); + describe('Hosts Table', () => { const loadPage = jest.fn(); const state: State = mockGlobalState; @@ -69,6 +73,50 @@ describe('Hosts Table', () => { expect(wrapper.find('HostsTable')).toMatchSnapshot(); }); + test('it renders "Host Risk classfication" column when "riskyHostsEnabled" feature flag is enabled', () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('tableHeaderCell_node.risk_4')).toBeInTheDocument(); + }); + + test("it doesn't renders 'Host Risk classfication' column when 'riskyHostsEnabled' feature flag is disabled", () => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); + + const { queryByTestId } = render( + + + + ); + + expect(queryByTestId('tableHeaderCell_node.riskScore_4')).not.toBeInTheDocument(); + }); + describe('Sorting on Table', () => { let wrapper: ReturnType; diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx index d20333d210559..dc9312b1ad4c4 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.tsx @@ -25,9 +25,11 @@ import { HostItem, HostsSortField, HostsFields, + HostRiskSeverity, } from '../../../../common/search_strategy/security_solution/hosts'; import { Direction } from '../../../../common/search_strategy'; import { HostEcs, OsEcs } from '../../../../common/ecs/host'; +import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features'; const tableType = hostsModel.HostsTableType.hosts; @@ -47,7 +49,8 @@ export type HostsTableColumns = [ Columns, Columns, Columns, - Columns + Columns, + Columns? ]; const rowItems: ItemsPerRow[] = [ @@ -124,8 +127,12 @@ const HostsTableComponent: React.FC = ({ }, [direction, sortField, type, dispatch] ); + const riskyHostsFeatureEnabled = useIsExperimentalFeatureEnabled('riskyHostsEnabled'); - const hostsColumns = useMemo(() => getHostsColumns(), []); + const hostsColumns = useMemo( + () => getHostsColumns(riskyHostsFeatureEnabled), + [riskyHostsFeatureEnabled] + ); const sorting = useMemo(() => getSorting(sortField, direction), [sortField, direction]); diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/translations.ts b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/translations.ts index 773a052dc71d0..88c01f695b940 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/translations.ts +++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/translations.ts @@ -32,6 +32,14 @@ export const FIRST_LAST_SEEN_TOOLTIP = i18n.translate( } ); +export const HOST_RISK_TOOLTIP = i18n.translate( + 'xpack.securitySolution.hostsTable.hostRiskToolTip', + { + defaultMessage: + 'Host risk classifcation is determined by host risk score. Hosts classified as Critical or High are indicated as risky.', + } +); + export const OS = i18n.translate('xpack.securitySolution.hostsTable.osTitle', { defaultMessage: 'Operating system', }); @@ -40,6 +48,10 @@ export const VERSION = i18n.translate('xpack.securitySolution.hostsTable.version defaultMessage: 'Version', }); +export const HOST_RISK = i18n.translate('xpack.securitySolution.hostsTable.hostRiskTitle', { + defaultMessage: 'Host risk classification', +}); + export const ROWS_5 = i18n.translate('xpack.securitySolution.hostsTable.rows', { values: { numRows: 5 }, defaultMessage: '{numRows} {numRows, plural, =0 {rows} =1 {row} other {rows}}', diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx index 1030ea4c5e65b..d5f0b16fbb7b6 100644 --- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/risky_hosts/index.tsx @@ -8,19 +8,16 @@ import { EuiFlexGroup, EuiFlexItem, - EuiHealth, EuiHorizontalRule, EuiIcon, EuiPanel, EuiTitle, EuiText, - transparentize, } from '@elastic/eui'; import React from 'react'; -import styled, { css } from 'styled-components'; +import styled from 'styled-components'; import { euiLightVars } from '@kbn/ui-shared-deps-src/theme'; import { InspectButtonContainer, InspectButton } from '../../../../common/components/inspect'; - import { HostsKpiBaseComponentLoader } from '../common'; import * as i18n from './translations'; @@ -31,37 +28,10 @@ import { import { useInspectQuery } from '../../../../common/hooks/use_inspect_query'; import { useErrorToast } from '../../../../common/hooks/use_error_toast'; +import { HostRiskScore } from '../../common/host_risk_score'; const QUERY_ID = 'hostsKpiRiskyHostsQuery'; -const HOST_RISK_SEVERITY_COLOUR = { - Unknown: euiLightVars.euiColorMediumShade, - Low: euiLightVars.euiColorVis0, - Moderate: euiLightVars.euiColorWarning, - High: euiLightVars.euiColorVis9_behindText, - Critical: euiLightVars.euiColorDanger, -}; - -const HostRiskBadge = styled.div<{ $severity: HostRiskSeverity }>` - ${({ theme, $severity }) => css` - width: fit-content; - padding-right: ${theme.eui.paddingSizes.s}; - padding-left: ${theme.eui.paddingSizes.xs}; - - ${($severity === 'Critical' || $severity === 'High') && - css` - background-color: ${transparentize(theme.eui.euiColorDanger, 0.2)}; - border-radius: 999px; // pill shaped - `}; - `} -`; - -const HostRisk: React.FC<{ severity: HostRiskSeverity }> = ({ severity }) => ( - - {severity} - -); - const HostCount = styled(EuiText)` font-weight: bold; `; @@ -124,7 +94,7 @@ const RiskyHostsComponent: React.FC<{ - + @@ -136,7 +106,7 @@ const RiskyHostsComponent: React.FC<{ - + diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx index cd9f01e2fd67c..d3785c842af90 100644 --- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx +++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/risky_hosts/index.tsx @@ -11,7 +11,11 @@ import { useEffect, useState } from 'react'; import { useObservable, withOptionalSignal } from '@kbn/securitysolution-hook-utils'; import { createFilter } from '../../../../common/containers/helpers'; -import { HostsKpiQueries, RequestBasicOptions } from '../../../../../common/search_strategy'; +import { + getHostRiskIndex, + HostsKpiQueries, + RequestBasicOptions, +} from '../../../../../common/search_strategy'; import { isCompleteResponse, @@ -21,7 +25,6 @@ import type { DataPublicPluginStart } from '../../../../../../../../src/plugins/ import type { HostsKpiRiskyHostsStrategyResponse } from '../../../../../common/search_strategy/security_solution/hosts/kpi/risky_hosts'; import { useKibana } from '../../../../common/lib/kibana'; import { isIndexNotFoundError } from '../../../../common/utils/exceptions'; -import { getHostRiskIndex } from '../../../../helpers'; export type RiskyHostsScoreRequestOptions = RequestBasicOptions; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a676ca8779f6a..98d63e1917f73 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -324,8 +324,10 @@ export class Plugin implements ISecuritySolutionPlugin { const securitySolutionSearchStrategy = securitySolutionSearchStrategyProvider( depsStart.data, - endpointContext + endpointContext, + depsStart.spaces?.spacesService?.getSpaceId ); + plugins.data.search.registerSearchStrategy( 'securitySolutionSearchStrategy', securitySolutionSearchStrategy diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts index ce640f7d367d6..e039eff160241 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/__mocks__/index.ts @@ -5,7 +5,13 @@ * 2.0. */ +import { + KibanaRequest, + SavedObjectsClientContract, +} from '../../../../../../../../../../src/core/server'; +import { elasticsearchServiceMock } from '../../../../../../../../../../src/core/server/mocks'; import type { IEsSearchResponse } from '../../../../../../../../../../src/plugins/data/common'; +import { allowedExperimentalValues } from '../../../../../../../common/experimental_features'; import { Direction, @@ -14,6 +20,8 @@ import { HostsQueries, HostsRequestOptions, } from '../../../../../../../common/search_strategy'; +import { EndpointAppContextService } from '../../../../../../endpoint/endpoint_app_context_services'; +import { EndpointAppContext } from '../../../../../../endpoint/types'; export const mockOptions: HostsRequestOptions = { defaultIndex: [ @@ -833,3 +841,19 @@ export const expectedDsl = { 'winlogbeat-*', ], }; + +export const mockDeps = { + esClient: elasticsearchServiceMock.createScopedClusterClient(), + savedObjectsClient: {} as SavedObjectsClientContract, + endpointContext: { + logFactory: { + get: jest.fn(), + }, + config: jest.fn().mockResolvedValue({}), + experimentalFeatures: { + ...allowedExperimentalValues, + }, + service: {} as EndpointAppContextService, + } as EndpointAppContext, + request: {} as KibanaRequest, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts index 7fc43be9b800e..2739b912c42db 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.test.ts @@ -14,7 +14,30 @@ import { mockOptions, mockSearchStrategyResponse, formattedSearchStrategyResponse, + mockDeps as defaultMockDeps, } from './__mocks__'; +import { get } from 'lodash/fp'; + +class IndexNotFoundException extends Error { + meta: { body: { error: { type: string } } }; + + constructor() { + super(); + this.meta = { body: { error: { type: 'index_not_found_exception' } } }; + } +} + +const mockDeps = (riskyHostsEnabled = true) => ({ + ...defaultMockDeps, + spaceId: 'test-space', + endpointContext: { + ...defaultMockDeps.endpointContext, + experimentalFeatures: { + ...defaultMockDeps.endpointContext.experimentalFeatures, + riskyHostsEnabled, + }, + }, +}); describe('allHosts search strategy', () => { const buildAllHostsQuery = jest.spyOn(buildQuery, 'buildHostsQuery'); @@ -46,8 +69,79 @@ describe('allHosts search strategy', () => { describe('parse', () => { test('should parse data correctly', async () => { - const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse); + const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockDeps(false)); expect(result).toMatchObject(formattedSearchStrategyResponse); }); + + test('should enhance data with risk score', async () => { + const risk = 'TEST_RISK_SCORE'; + const hostName: string = get( + 'aggregations.host_data.buckets[0].key', + mockSearchStrategyResponse.rawResponse + ); + const mockedDeps = mockDeps(); + + (mockedDeps.esClient.asCurrentUser.search as jest.Mock).mockResolvedValue({ + body: { + hits: { + hits: [ + { + _source: { + risk, + host: { + name: hostName, + }, + }, + }, + ], + }, + }, + }); + + const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockedDeps); + + expect(result.edges[0].node.risk).toBe(risk); + }); + + test('should not enhance data when feature flag is disabled', async () => { + const risk = 'TEST_RISK_SCORE'; + const hostName: string = get( + 'aggregations.host_data.buckets[0].key', + mockSearchStrategyResponse.rawResponse + ); + const mockedDeps = mockDeps(false); + + (mockedDeps.esClient.asCurrentUser.search as jest.Mock).mockResolvedValue({ + body: { + hits: { + hits: [ + { + _source: { + risk, + host: { + name: hostName, + }, + }, + }, + ], + }, + }, + }); + + const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockedDeps); + + expect(result.edges[0].node.risk).toBeUndefined(); + }); + + test("should not enhance data when index doesn't exist", async () => { + const mockedDeps = mockDeps(); + (mockedDeps.esClient.asCurrentUser.search as jest.Mock).mockImplementation(() => { + throw new IndexNotFoundException(); + }); + + const result = await allHosts.parse(mockOptions, mockSearchStrategyResponse, mockedDeps); + + expect(result.edges[0].node.risk).toBeUndefined(); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts index 987420f4bf4bd..9e6abfe49d949 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/index.ts @@ -14,13 +14,22 @@ import { HostsStrategyResponse, HostsQueries, HostsRequestOptions, + HostsRiskScore, + HostsEdges, } from '../../../../../../common/search_strategy/security_solution/hosts'; +import { getHostRiskIndex } from '../../../../../../common/search_strategy'; + import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionFactory } from '../../types'; import { buildHostsQuery } from './query.all_hosts.dsl'; import { formatHostEdgesData, HOSTS_FIELDS } from './helpers'; +import { IScopedClusterClient } from '../../../../../../../../../src/core/server'; + +import { buildHostsRiskScoreQuery } from '../risk_score/query.hosts_risk.dsl'; + import { buildHostsQueryEntities } from './query.all_hosts_entities.dsl'; +import { EndpointAppContext } from '../../../../../endpoint/types'; export const allHosts: SecuritySolutionFactory = { buildDsl: (options: HostsRequestOptions) => { @@ -31,7 +40,12 @@ export const allHosts: SecuritySolutionFactory = { }, parse: async ( options: HostsRequestOptions, - response: IEsSearchResponse + response: IEsSearchResponse, + deps?: { + esClient: IScopedClusterClient; + spaceId?: string; + endpointContext: EndpointAppContext; + } ): Promise => { const { activePage, cursorStart, fakePossibleCount, querySize } = options.pagination; const totalCount = getOr(0, 'aggregations.host_count.value', response.rawResponse); @@ -48,10 +62,17 @@ export const allHosts: SecuritySolutionFactory = { }; const showMorePagesIndicator = totalCount > fakeTotalCount; + const hostNames = buckets.map(getOr('', 'key')); + + const enhancedEdges = + deps?.spaceId && deps?.endpointContext.experimentalFeatures.riskyHostsEnabled + ? await enhanceEdges(edges, hostNames, deps.spaceId, deps.esClient) + : edges; + return { ...response, inspect, - edges, + edges: enhancedEdges, totalCount, pageInfo: { activePage: activePage ?? 0, @@ -62,6 +83,54 @@ export const allHosts: SecuritySolutionFactory = { }, }; +async function enhanceEdges( + edges: HostsEdges[], + hostNames: string[], + spaceId: string, + esClient: IScopedClusterClient +): Promise { + const hostRiskData = await getHostRiskData(esClient, spaceId, hostNames); + + const hostsRiskByHostName: Record | undefined = hostRiskData?.hits.hits.reduce( + (acc, hit) => ({ + ...acc, + [hit._source?.host.name ?? '']: hit._source?.risk, + }), + {} + ); + + return hostsRiskByHostName + ? edges.map(({ node, cursor }) => ({ + node: { + ...node, + risk: hostsRiskByHostName[node._id ?? ''], + }, + cursor, + })) + : edges; +} + +async function getHostRiskData( + esClient: IScopedClusterClient, + spaceId: string, + hostNames: string[] +) { + try { + const hostRiskResponse = await esClient.asCurrentUser.search( + buildHostsRiskScoreQuery({ + defaultIndex: [getHostRiskIndex(spaceId)], + hostNames, + }) + ); + return hostRiskResponse.body; + } catch (error) { + if (error?.meta?.body?.error?.type !== 'index_not_found_exception') { + throw error; + } + return undefined; + } +} + export const allHostsEntities: SecuritySolutionFactory = { buildDsl: (options: HostsRequestOptions) => { if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/risk_score/query.hosts_risk.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/risk_score/query.hosts_risk.dsl.ts index 05bb496a7444e..182ad7892204f 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/risk_score/query.hosts_risk.dsl.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/risk_score/query.hosts_risk.dsl.ts @@ -9,7 +9,7 @@ import { HostsRiskScoreRequestOptions } from '../../../../../../common/search_st export const buildHostsRiskScoreQuery = ({ timerange, - hostName, + hostNames, defaultIndex, }: HostsRiskScoreRequestOptions) => { const filter = []; @@ -26,8 +26,8 @@ export const buildHostsRiskScoreQuery = ({ }); } - if (hostName) { - filter.push({ term: { 'host.name': hostName } }); + if (hostNames) { + filter.push({ terms: { 'host.name': hostNames } }); } const dslQuery = { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts index 4fe65b7e219f3..83a6096e51abf 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/types.ts @@ -31,6 +31,7 @@ export interface SecuritySolutionFactory { savedObjectsClient: SavedObjectsClientContract; endpointContext: EndpointAppContext; request: KibanaRequest; + spaceId?: string; } ) => Promise>; } diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index c10c63db62ee3..7786962d86083 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -20,6 +20,7 @@ import { import { securitySolutionFactory } from './factory'; import { SecuritySolutionFactory } from './factory/types'; import { EndpointAppContext } from '../../endpoint/types'; +import { KibanaRequest } from '../../../../../../src/core/server'; function isObj(req: unknown): req is Record { return typeof req === 'object' && req !== null; @@ -34,7 +35,8 @@ function assertValidRequestType( export const securitySolutionSearchStrategyProvider = ( data: PluginStart, - endpointContext: EndpointAppContext + endpointContext: EndpointAppContext, + getSpaceId?: (request: KibanaRequest) => string ): ISearchStrategy, StrategyResponseType> => { const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); @@ -60,6 +62,7 @@ export const securitySolutionSearchStrategyProvider = Date: Mon, 13 Dec 2021 05:53:15 -0600 Subject: [PATCH 118/145] [data view management] fix multiple fields w/o conflicts (#121004) * display and allow sorting on fields with multiple types that may or may not conflict * update tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../indexed_fields_table.test.tsx.snap | 4 +-- .../table/__snapshots__/table.test.tsx.snap | 7 ++++++ .../components/table/table.test.tsx | 11 +++++++- .../components/table/table.tsx | 4 ++- .../indexed_fields_table.tsx | 25 +++++++------------ .../edit_index_pattern/tabs/tabs.tsx | 16 ++++++------ 6 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap index fb463d0a5fb18..8b6e0a1682750 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/__snapshots__/indexed_fields_table.test.tsx.snap @@ -116,7 +116,7 @@ exports[`IndexedFieldsTable IndexedFieldsTable with rollup index pattern should "isUserEditable": false, "kbnType": undefined, "name": "conflictingField", - "type": "conflict", + "type": "keyword, long", }, Object { "displayName": "amount", @@ -274,7 +274,7 @@ exports[`IndexedFieldsTable should render normally 1`] = ` "isUserEditable": false, "kbnType": undefined, "name": "conflictingField", - "type": "conflict", + "type": "keyword, long", }, Object { "displayName": "amount", diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap index 100100106127b..2b6cf62baf221 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/__snapshots__/table.test.tsx.snap @@ -107,6 +107,7 @@ exports[`Table render name 2`] = ` exports[`Table should render conflicting type 1`] = ` + text, long `; +exports[`Table should render mixed, non-conflicting type 1`] = ` + + keyword, constant_keyword + +`; + exports[`Table should render normal field name 1`] = ` Elastic diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx index ec18665ccbaf3..dd78b00f9775e 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.test.tsx @@ -126,7 +126,7 @@ describe('Table', () => { const tableCell = shallow( renderTable() .prop('columns')[1] - .render('conflict', { + .render('text, long', { kbnType: 'conflict', conflictDescriptions: { keyword: ['index_a'], long: ['index_b'] }, }) @@ -134,6 +134,15 @@ describe('Table', () => { expect(tableCell).toMatchSnapshot(); }); + test('should render mixed, non-conflicting type', () => { + const tableCell = shallow( + renderTable().prop('columns')[1].render('keyword, constant_keyword', { + kbnType: 'string', + }) + ); + expect(tableCell).toMatchSnapshot(); + }); + test('should allow edits', () => { const editField = jest.fn(); diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx index e08b153f0b262..6a82d0380629c 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/components/table/table.tsx @@ -349,9 +349,11 @@ export class Table extends PureComponent { } renderFieldType(type: string, field: IndexedFieldItem) { + const conflictDescription = + field.conflictDescriptions && field.conflictDescriptions[field.name]; return ( - {type !== 'conflict' ? type : ''} + {type === 'conflict' && conflictDescription ? '' : type} {field.conflictDescriptions ? getConflictBtn(field.name, field.conflictDescriptions, this.props.openModal) : ''} diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx index 1e0d36f465be5..a72c87655fd63 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/indexed_fields_table/indexed_fields_table.tsx @@ -7,7 +7,6 @@ */ import React, { Component } from 'react'; -import { i18n } from '@kbn/i18n'; import { createSelector } from 'reselect'; import { OverlayStart } from 'src/core/public'; import { IndexPatternField, IndexPattern } from '../../../../../../plugins/data/public'; @@ -68,25 +67,12 @@ class IndexedFields extends Component) => f.value); const fieldWildcardMatch = fieldWildcardMatcher(sourceFilters || []); - const getDisplayEsType = (arr: string[]): string => { - const length = arr.length; - if (length < 1) { - return ''; - } - if (length > 1) { - return i18n.translate('indexPatternManagement.editIndexPattern.fields.conflictType', { - defaultMessage: 'conflict', - }); - } - return arr[0]; - }; - return ( (fields && fields.map((field) => { return { ...field.spec, - type: getDisplayEsType(field.esTypes || []), + type: field.esTypes?.join(', ') || '', kbnType: field.type, displayName: field.displayName, format: indexPattern.getFormatterForFieldNoDefault(field.name)?.type?.title || '', @@ -117,7 +103,14 @@ class IndexedFields extends Component field.type === indexedFieldTypeFilter); + // match conflict fields + fields = fields.filter((field) => { + if (indexedFieldTypeFilter === 'conflict' && field.kbnType === 'conflict') { + return true; + } + // match one of multiple types on a field + return field.esTypes?.length && field.esTypes?.indexOf(indexedFieldTypeFilter) !== -1; + }); } return fields; diff --git a/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx b/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx index c79871dbc8d71..b5940fa8d1bb0 100644 --- a/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx +++ b/src/plugins/data_view_management/public/components/edit_index_pattern/tabs/tabs.tsx @@ -93,13 +93,6 @@ export function Tabs({ const closeEditorHandler = useRef<() => void | undefined>(); const { DeleteRuntimeFieldProvider } = dataViewFieldEditor; - const conflict = i18n.translate( - 'indexPatternManagement.editIndexPattern.fieldTypes.conflictType', - { - defaultMessage: 'conflict', - } - ); - const refreshFilters = useCallback(() => { const tempIndexedFieldTypes: string[] = []; const tempScriptedFieldLanguages: string[] = []; @@ -109,8 +102,13 @@ export function Tabs({ tempScriptedFieldLanguages.push(field.lang); } } else { + // for conflicted fields, add conflict as a type + if (field.type === 'conflict') { + tempIndexedFieldTypes.push('conflict'); + } if (field.esTypes) { - tempIndexedFieldTypes.push(field.esTypes.length === 1 ? field.esTypes[0] : conflict); + // add all types, may be multiple + field.esTypes.forEach((item) => tempIndexedFieldTypes.push(item)); } } }); @@ -119,7 +117,7 @@ export function Tabs({ setScriptedFieldLanguages( convertToEuiSelectOption(tempScriptedFieldLanguages, 'scriptedFieldLanguages') ); - }, [indexPattern, conflict]); + }, [indexPattern]); const closeFieldEditor = useCallback(() => { if (closeEditorHandler.current) { From 31946d5a477a307493d58ddf1fdfa837e5a8253b Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:43:37 +0100 Subject: [PATCH 119/145] [RAC][Refactoring] Rename alerting types in Infra (#121061) --- .../alerting/logs/log_threshold/types.ts | 67 +++++++++---------- .../log_threshold/components/alert_flyout.tsx | 4 +- .../components/expression_editor/criteria.tsx | 14 ++-- .../criterion_preview_chart.tsx | 28 ++++---- .../components/expression_editor/editor.tsx | 30 ++++----- .../expression_editor/threshold.tsx | 4 +- .../expression_editor/type_switcher.tsx | 4 +- .../log_threshold/log_threshold_rule_type.ts | 8 +-- .../alerting/log_threshold/validation.ts | 8 +-- x-pack/plugins/infra/server/features.ts | 12 ++-- .../log_threshold_executor.test.ts | 52 +++++++------- .../log_threshold/log_threshold_executor.ts | 44 ++++++------ .../register_log_threshold_rule_type.ts | 8 +-- 13 files changed, 140 insertions(+), 143 deletions(-) diff --git a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts index 6da0bb58e4e85..dba94d2c8fd93 100644 --- a/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts +++ b/x-pack/plugins/infra/common/alerting/logs/log_threshold/types.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import * as rt from 'io-ts'; import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; -export const LOG_DOCUMENT_COUNT_ALERT_TYPE_ID = 'logs.alert.document.count'; +export const LOG_DOCUMENT_COUNT_RULE_TYPE_ID = 'logs.alert.document.count'; const ThresholdTypeRT = rt.keyof({ count: null, @@ -143,7 +143,7 @@ export type TimeUnit = rt.TypeOf; export const timeSizeRT = rt.number; export const groupByRT = rt.array(rt.string); -const RequiredAlertParamsRT = rt.type({ +const RequiredRuleParamsRT = rt.type({ // NOTE: "count" would be better named as "threshold", but this would require a // migration of encrypted saved objects, so we'll keep "count" until it's problematic. count: ThresholdRT, @@ -151,72 +151,69 @@ const RequiredAlertParamsRT = rt.type({ timeSize: timeSizeRT, }); -const partialRequiredAlertParamsRT = rt.partial(RequiredAlertParamsRT.props); -export type PartialRequiredAlertParams = rt.TypeOf; +const partialRequiredRuleParamsRT = rt.partial(RequiredRuleParamsRT.props); +export type PartialRequiredRuleParams = rt.TypeOf; -const OptionalAlertParamsRT = rt.partial({ +const OptionalRuleParamsRT = rt.partial({ groupBy: groupByRT, }); -export const countAlertParamsRT = rt.intersection([ +export const countRuleParamsRT = rt.intersection([ rt.type({ criteria: countCriteriaRT, - ...RequiredAlertParamsRT.props, + ...RequiredRuleParamsRT.props, }), rt.partial({ - ...OptionalAlertParamsRT.props, + ...OptionalRuleParamsRT.props, }), ]); -export type CountAlertParams = rt.TypeOf; +export type CountRuleParams = rt.TypeOf; -export const partialCountAlertParamsRT = rt.intersection([ +export const partialCountRuleParamsRT = rt.intersection([ rt.type({ criteria: partialCountCriteriaRT, - ...RequiredAlertParamsRT.props, + ...RequiredRuleParamsRT.props, }), rt.partial({ - ...OptionalAlertParamsRT.props, + ...OptionalRuleParamsRT.props, }), ]); -export type PartialCountAlertParams = rt.TypeOf; +export type PartialCountRuleParams = rt.TypeOf; -export const ratioAlertParamsRT = rt.intersection([ +export const ratioRuleParamsRT = rt.intersection([ rt.type({ criteria: ratioCriteriaRT, - ...RequiredAlertParamsRT.props, + ...RequiredRuleParamsRT.props, }), rt.partial({ - ...OptionalAlertParamsRT.props, + ...OptionalRuleParamsRT.props, }), ]); -export type RatioAlertParams = rt.TypeOf; +export type RatioRuleParams = rt.TypeOf; -export const partialRatioAlertParamsRT = rt.intersection([ +export const partialRatioRuleParamsRT = rt.intersection([ rt.type({ criteria: partialRatioCriteriaRT, - ...RequiredAlertParamsRT.props, + ...RequiredRuleParamsRT.props, }), rt.partial({ - ...OptionalAlertParamsRT.props, + ...OptionalRuleParamsRT.props, }), ]); -export type PartialRatioAlertParams = rt.TypeOf; +export type PartialRatioRuleParams = rt.TypeOf; -export const alertParamsRT = rt.union([countAlertParamsRT, ratioAlertParamsRT]); -export type AlertParams = rt.TypeOf; +export const ruleParamsRT = rt.union([countRuleParamsRT, ratioRuleParamsRT]); +export type RuleParams = rt.TypeOf; -export const partialAlertParamsRT = rt.union([ - partialCountAlertParamsRT, - partialRatioAlertParamsRT, -]); -export type PartialAlertParams = rt.TypeOf; +export const partialRuleParamsRT = rt.union([partialCountRuleParamsRT, partialRatioRuleParamsRT]); +export type PartialRuleParams = rt.TypeOf; -export const isRatioAlert = (criteria: PartialCriteria): criteria is PartialRatioCriteria => { +export const isRatioRule = (criteria: PartialCriteria): criteria is PartialRatioCriteria => { return criteria.length > 0 && Array.isArray(criteria[0]) ? true : false; }; -export const isRatioAlertParams = (params: AlertParams): params is RatioAlertParams => { - return isRatioAlert(params.criteria); +export const isRatioRuleParams = (params: RuleParams): params is RatioRuleParams => { + return isRatioRule(params.criteria); }; export const getNumerator = (criteria: C): C[0] => { @@ -229,8 +226,8 @@ export const getDenominator = ( return criteria[1]; }; -export const hasGroupBy = (alertParams: AlertParams) => { - const { groupBy } = alertParams; +export const hasGroupBy = (params: RuleParams) => { + const { groupBy } = params; return groupBy && groupBy.length > 0 ? true : false; }; @@ -339,8 +336,8 @@ export const isOptimizedGroupedSearchQueryResponse = ( }; export const isOptimizableGroupedThreshold = ( - selectedComparator: AlertParams['count']['comparator'], - selectedValue?: AlertParams['count']['value'] + selectedComparator: RuleParams['count']['comparator'], + selectedValue?: RuleParams['count']['value'] ) => { if (selectedComparator === Comparator.GT) { return true; diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_flyout.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_flyout.tsx index 346b44218b612..bee7f93a538be 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/alert_flyout.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useContext, useMemo } from 'react'; import { TriggerActionsContext } from '../../../utils/triggers_actions_context'; -import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../../../../common/alerting/logs/log_threshold/types'; +import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../../../../common/alerting/logs/log_threshold/types'; interface Props { visible?: boolean; @@ -25,7 +25,7 @@ export const AlertFlyout = (props: Props) => { consumer: 'logs', onClose: onCloseFlyout, canChangeTrigger: false, - alertTypeId: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + alertTypeId: LOG_DOCUMENT_COUNT_RULE_TYPE_ID, metadata: { isInternal: true, }, diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx index 9ff9b602fac3b..1be120210984f 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/criteria.tsx @@ -12,12 +12,12 @@ import { i18n } from '@kbn/i18n'; import { IFieldType } from 'src/plugins/data/public'; import { Criterion } from './criterion'; import { - PartialAlertParams, + PartialRuleParams, PartialCountCriteria as PartialCountCriteriaType, PartialCriteria as PartialCriteriaType, PartialCriterion as PartialCriterionType, PartialRatioCriteria as PartialRatioCriteriaType, - isRatioAlert, + isRatioRule, getNumerator, getDenominator, } from '../../../../../common/alerting/logs/log_threshold/types'; @@ -38,7 +38,7 @@ interface SharedProps { criteria?: PartialCriteriaType; defaultCriterion: PartialCriterionType; errors: Errors['criteria']; - alertParams: PartialAlertParams; + ruleParams: PartialRuleParams; sourceId: string; updateCriteria: (criteria: PartialCriteriaType) => void; } @@ -49,7 +49,7 @@ export const Criteria: React.FC = (props) => { const { criteria, errors } = props; if (!criteria || criteria.length === 0) return null; - return !isRatioAlert(criteria) ? ( + return !isRatioRule(criteria) ? ( ) : ( @@ -57,7 +57,7 @@ export const Criteria: React.FC = (props) => { }; interface CriteriaWrapperProps { - alertParams: SharedProps['alertParams']; + ruleParams: SharedProps['ruleParams']; fields: SharedProps['fields']; updateCriterion: (idx: number, params: PartialCriterionType) => void; removeCriterion: (idx: number) => void; @@ -76,7 +76,7 @@ const CriteriaWrapper: React.FC = (props) => { criteria, fields, errors, - alertParams, + ruleParams, sourceId, isRatio = false, } = props; @@ -103,7 +103,7 @@ const CriteriaWrapper: React.FC = (props) => { arrowDisplay="right" > ; sourceId: string; showThreshold: boolean; } export const CriterionPreview: React.FC = ({ - alertParams, + ruleParams, chartCriterion, sourceId, showThreshold, @@ -69,12 +69,12 @@ export const CriterionPreview: React.FC = ({ const params = { criteria, count: { - comparator: alertParams.count.comparator, - value: alertParams.count.value, + comparator: ruleParams.count.comparator, + value: ruleParams.count.value, }, - timeSize: alertParams.timeSize, - timeUnit: alertParams.timeUnit, - groupBy: alertParams.groupBy, + timeSize: ruleParams.timeSize, + timeUnit: ruleParams.timeUnit, + groupBy: ruleParams.groupBy, }; try { @@ -83,11 +83,11 @@ export const CriterionPreview: React.FC = ({ return null; } }, [ - alertParams.timeSize, - alertParams.timeUnit, - alertParams.groupBy, - alertParams.count.comparator, - alertParams.count.value, + ruleParams.timeSize, + ruleParams.timeUnit, + ruleParams.groupBy, + ruleParams.count.comparator, + ruleParams.count.value, chartCriterion, ]); @@ -102,7 +102,7 @@ export const CriterionPreview: React.FC = ({ : NUM_BUCKETS / 4 } // Display less data for groups due to space limitations sourceId={sourceId} - threshold={alertParams.count} + threshold={ruleParams.count} chartAlertParams={chartAlertParams} showThreshold={showThreshold} /> diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx index 02b827d5915dd..312662286595c 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/editor.tsx @@ -16,11 +16,11 @@ import { } from '../../../../../../triggers_actions_ui/public'; import { Comparator, - isRatioAlert, - PartialAlertParams, - PartialCountAlertParams, + isRatioRule, + PartialRuleParams, + PartialCountRuleParams, PartialCriteria as PartialCriteriaType, - PartialRatioAlertParams, + PartialRatioRuleParams, ThresholdType, timeUnitRT, isOptimizableGroupedThreshold, @@ -64,9 +64,9 @@ const createDefaultCriterion = ( ? { field: DEFAULT_FIELD, comparator: Comparator.EQ, value } : { field: undefined, comparator: undefined, value: undefined }; -const createDefaultCountAlertParams = ( +const createDefaultCountRuleParams = ( availableFields: LogIndexField[] -): PartialCountAlertParams => ({ +): PartialCountRuleParams => ({ ...DEFAULT_BASE_EXPRESSION, count: { value: 75, @@ -75,9 +75,9 @@ const createDefaultCountAlertParams = ( criteria: [createDefaultCriterion(availableFields, 'error')], }); -const createDefaultRatioAlertParams = ( +const createDefaultRatioRuleParams = ( availableFields: LogIndexField[] -): PartialRatioAlertParams => ({ +): PartialRatioRuleParams => ({ ...DEFAULT_BASE_EXPRESSION, count: { value: 2, @@ -90,7 +90,7 @@ const createDefaultRatioAlertParams = ( }); export const ExpressionEditor: React.FC< - AlertTypeParamsExpressionProps + AlertTypeParamsExpressionProps > = (props) => { const isInternal = props.metadata?.isInternal ?? false; const [sourceId] = useSourceId(); @@ -159,7 +159,7 @@ export const SourceStatusWrapper: React.FC = ({ children }) => { ); }; -export const Editor: React.FC> = +export const Editor: React.FC> = (props) => { const { setAlertParams, alertParams, errors } = props; const [hasSetDefaults, setHasSetDefaults] = useState(false); @@ -231,7 +231,7 @@ export const Editor: React.FC createDefaultCountAlertParams(supportedFields), + () => createDefaultCountRuleParams(supportedFields), [supportedFields] ); @@ -240,7 +240,7 @@ export const Editor: React.FC @@ -286,7 +286,7 @@ export const Editor: React.FC - {alertParams.criteria && !isRatioAlert(alertParams.criteria) && criteriaComponent} + {alertParams.criteria && !isRatioRule(alertParams.criteria) && criteriaComponent} - {alertParams.criteria && isRatioAlert(alertParams.criteria) && criteriaComponent} + {alertParams.criteria && isRatioRule(alertParams.criteria) && criteriaComponent} {shouldShowGroupByOptimizationWarning && ( <> diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/threshold.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/threshold.tsx index af4f5dc1c8115..fdc60daceb715 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/threshold.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/threshold.tsx @@ -23,7 +23,7 @@ import { IErrorObject } from '../../../../../../triggers_actions_ui/public/types import { Comparator, ComparatorToi18nMap, - AlertParams, + RuleParams, } from '../../../../../common/alerting/logs/log_threshold/types'; const thresholdPrefix = i18n.translate('xpack.infra.logs.alertFlyout.thresholdPrefix', { @@ -49,7 +49,7 @@ const getComparatorOptions = (): Array<{ interface Props { comparator?: Comparator; value?: number; - updateThreshold: (params: Partial) => void; + updateThreshold: (params: Partial) => void; errors: IErrorObject; } diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/type_switcher.tsx b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/type_switcher.tsx index cde97dad20613..46e865d3acbe0 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/type_switcher.tsx +++ b/x-pack/plugins/infra/public/alerting/log_threshold/components/expression_editor/type_switcher.tsx @@ -11,7 +11,7 @@ import { EuiFlexItem, EuiFlexGroup, EuiPopover, EuiSelect, EuiExpression } from import { PartialCriteria, ThresholdType, - isRatioAlert, + isRatioRule, } from '../../../../../common/alerting/logs/log_threshold/types'; import { ExpressionLike } from './editor'; @@ -51,7 +51,7 @@ interface Props { } const getThresholdType = (criteria: PartialCriteria): ThresholdType => { - return isRatioAlert(criteria) ? 'ratio' : 'count'; + return isRatioRule(criteria) ? 'ratio' : 'count'; }; export const TypeSwitcher: React.FC = ({ criteria, updateType }) => { diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts index b0a8737a994a1..56c28074bd5fb 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_rule_type.ts @@ -9,15 +9,15 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ObservabilityRuleTypeModel } from '../../../../observability/public'; import { - LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, - PartialAlertParams, + LOG_DOCUMENT_COUNT_RULE_TYPE_ID, + PartialRuleParams, } from '../../../common/alerting/logs/log_threshold'; import { formatRuleData } from './rule_data_formatters'; import { validateExpression } from './validation'; -export function createLogThresholdRuleType(): ObservabilityRuleTypeModel { +export function createLogThresholdRuleType(): ObservabilityRuleTypeModel { return { - id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + id: LOG_DOCUMENT_COUNT_RULE_TYPE_ID, description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', { defaultMessage: 'Alert when the log aggregation exceeds the threshold.', }), diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts b/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts index 41c07ee79344b..740805006785b 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/validation.ts @@ -11,10 +11,10 @@ import { isNumber, isFinite } from 'lodash'; import { IErrorObject, ValidationResult } from '../../../../triggers_actions_ui/public'; import { PartialCountCriteria, - isRatioAlert, + isRatioRule, getNumerator, getDenominator, - PartialRequiredAlertParams, + PartialRequiredRuleParams, PartialCriteria, } from '../../../common/alerting/logs/log_threshold/types'; @@ -50,7 +50,7 @@ export function validateExpression({ count, criteria, timeSize, -}: PartialRequiredAlertParams & { +}: PartialRequiredRuleParams & { criteria: PartialCriteria; }): ValidationResult { const validationResult = { errors: {} }; @@ -122,7 +122,7 @@ export function validateExpression({ return _errors; }; - if (!isRatioAlert(criteria)) { + if (!isRatioRule(criteria)) { const criteriaErrors = getCriterionErrors(criteria); errors.criteria[0] = criteriaErrors; } else { diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index fe0570c4950f8..361565c3672c5 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; -import { LOG_DOCUMENT_COUNT_ALERT_TYPE_ID } from '../common/alerting/logs/log_threshold/types'; +import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../common/alerting/logs/log_threshold/types'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from './lib/alerting/inventory_metric_threshold/types'; import { METRIC_THRESHOLD_ALERT_TYPE_ID } from './lib/alerting/metric_threshold/types'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/server'; @@ -83,7 +83,7 @@ export const LOGS_FEATURE = { management: { insightsAndAlerting: ['triggersActions'], }, - alerting: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], + alerting: [LOG_DOCUMENT_COUNT_RULE_TYPE_ID], privileges: { all: { app: ['infra', 'logs', 'kibana'], @@ -95,10 +95,10 @@ export const LOGS_FEATURE = { }, alerting: { rule: { - all: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], + all: [LOG_DOCUMENT_COUNT_RULE_TYPE_ID], }, alert: { - all: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], + all: [LOG_DOCUMENT_COUNT_RULE_TYPE_ID], }, }, management: { @@ -112,10 +112,10 @@ export const LOGS_FEATURE = { api: ['infra'], alerting: { rule: { - read: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], + read: [LOG_DOCUMENT_COUNT_RULE_TYPE_ID], }, alert: { - read: [LOG_DOCUMENT_COUNT_ALERT_TYPE_ID], + read: [LOG_DOCUMENT_COUNT_RULE_TYPE_ID], }, }, management: { diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts index b5cf05512b353..90f9c508e1038 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts @@ -18,7 +18,7 @@ import { import { Comparator, AlertStates, - AlertParams, + RuleParams, Criterion, UngroupedSearchQueryResponse, GroupedSearchQueryResponse, @@ -126,7 +126,7 @@ const expectedNegativeFilterClauses = [ }, ]; -const baseAlertParams: Pick = { +const baseRuleParams: Pick = { count: { comparator: Comparator.GT, value: 5, @@ -165,27 +165,27 @@ describe('Log threshold executor', () => { }); describe('Criteria filter building', () => { test('Handles positive criteria', () => { - const alertParams: AlertParams = { - ...baseAlertParams, + const ruleParams: RuleParams = { + ...baseRuleParams, criteria: positiveCriteria, }; - const filters = buildFiltersFromCriteria(alertParams, TIMESTAMP_FIELD); + const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD); expect(filters.mustFilters).toEqual(expectedPositiveFilterClauses); }); test('Handles negative criteria', () => { - const alertParams: AlertParams = { - ...baseAlertParams, + const ruleParams: RuleParams = { + ...baseRuleParams, criteria: negativeCriteria, }; - const filters = buildFiltersFromCriteria(alertParams, TIMESTAMP_FIELD); + const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD); expect(filters.mustNotFilters).toEqual(expectedNegativeFilterClauses); }); test('Handles time range', () => { - const alertParams: AlertParams = { ...baseAlertParams, criteria: [] }; - const filters = buildFiltersFromCriteria(alertParams, TIMESTAMP_FIELD); + const ruleParams: RuleParams = { ...baseRuleParams, criteria: [] }; + const filters = buildFiltersFromCriteria(ruleParams, TIMESTAMP_FIELD); expect(typeof filters.rangeFilter.range[TIMESTAMP_FIELD].gte).toBe('number'); expect(typeof filters.rangeFilter.range[TIMESTAMP_FIELD].lte).toBe('number'); expect(filters.rangeFilter.range[TIMESTAMP_FIELD].format).toBe('epoch_millis'); @@ -199,12 +199,12 @@ describe('Log threshold executor', () => { describe('ES queries', () => { describe('Query generation', () => { it('Correctly generates ungrouped queries', () => { - const alertParams: AlertParams = { - ...baseAlertParams, + const ruleParams: RuleParams = { + ...baseRuleParams, criteria: [...positiveCriteria, ...negativeCriteria], }; const query = getUngroupedESQuery( - alertParams, + ruleParams, TIMESTAMP_FIELD, FILEBEAT_INDEX, runtimeMappings @@ -248,13 +248,13 @@ describe('Log threshold executor', () => { describe('Correctly generates grouped queries', () => { it('When using an optimizable threshold comparator', () => { - const alertParams: AlertParams = { - ...baseAlertParams, + const ruleParams: RuleParams = { + ...baseRuleParams, groupBy: ['host.name'], criteria: [...positiveCriteria, ...negativeCriteria], }; const query = getGroupedESQuery( - alertParams, + ruleParams, TIMESTAMP_FIELD, FILEBEAT_INDEX, runtimeMappings @@ -313,10 +313,10 @@ describe('Log threshold executor', () => { }); it('When not using an optimizable threshold comparator', () => { - const alertParams: AlertParams = { - ...baseAlertParams, + const ruleParams: RuleParams = { + ...baseRuleParams, count: { - ...baseAlertParams.count, + ...baseRuleParams.count, comparator: Comparator.LT, }, groupBy: ['host.name'], @@ -324,7 +324,7 @@ describe('Log threshold executor', () => { }; const query = getGroupedESQuery( - alertParams, + ruleParams, TIMESTAMP_FIELD, FILEBEAT_INDEX, runtimeMappings @@ -408,8 +408,8 @@ describe('Log threshold executor', () => { describe('Can process ungrouped results', () => { test('It handles the ALERT state correctly', () => { const alertUpdaterMock = jest.fn(); - const alertParams = { - ...baseAlertParams, + const ruleParams = { + ...baseRuleParams, criteria: [positiveCriteria[0]], }; const results = { @@ -421,7 +421,7 @@ describe('Log threshold executor', () => { } as UngroupedSearchQueryResponse; processUngroupedResults( results, - alertParams, + ruleParams, alertsMock.createAlertInstanceFactory, alertUpdaterMock ); @@ -445,8 +445,8 @@ describe('Log threshold executor', () => { describe('Can process grouped results', () => { test('It handles the ALERT state correctly', () => { const alertUpdaterMock = jest.fn(); - const alertParams = { - ...baseAlertParams, + const ruleParams = { + ...baseRuleParams, criteria: [positiveCriteria[0]], groupBy: ['host.name', 'event.dataset'], }; @@ -485,7 +485,7 @@ describe('Log threshold executor', () => { ] as GroupedSearchQueryResponse['aggregations']['groups']['buckets']; processGroupByResults( results, - alertParams, + ruleParams, alertsMock.createAlertInstanceFactory, alertUpdaterMock ); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts index a41c70f5c2869..daf9b486da9a0 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.ts @@ -22,11 +22,11 @@ import { AlertTypeState as RuleTypeState, } from '../../../../../alerting/server'; import { - AlertParams, - alertParamsRT, + RuleParams, + ruleParamsRT, AlertStates, Comparator, - CountAlertParams, + CountRuleParams, CountCriteria, Criterion, getDenominator, @@ -36,8 +36,8 @@ import { hasGroupBy, isOptimizableGroupedThreshold, isOptimizedGroupedSearchQueryResponse, - isRatioAlertParams, - RatioAlertParams, + isRatioRuleParams, + RatioRuleParams, UngroupedSearchQueryResponse, UngroupedSearchQueryResponseRT, } from '../../../../common/alerting/logs/log_threshold'; @@ -54,7 +54,7 @@ import { } from './reason_formatters'; export type LogThresholdActionGroups = ActionGroupIdsOf; -export type LogThresholdRuleTypeParams = AlertParams; +export type LogThresholdRuleTypeParams = RuleParams; export type LogThresholdRuleTypeState = RuleTypeState; // no specific state used export type LogThresholdAlertState = AlertState; // no specific state used export type LogThresholdAlertContext = AlertContext; // no specific instance context used @@ -116,9 +116,9 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => ); try { - const validatedParams = decodeOrThrow(alertParamsRT)(params); + const validatedParams = decodeOrThrow(ruleParamsRT)(params); - if (!isRatioAlertParams(validatedParams)) { + if (!isRatioRuleParams(validatedParams)) { await executeAlert( validatedParams, timestampField, @@ -143,7 +143,7 @@ export const createLogThresholdExecutor = (libs: InfraBackendLibs) => }); async function executeAlert( - alertParams: CountAlertParams, + alertParams: CountRuleParams, timestampField: string, indexPattern: string, runtimeMappings: estypes.MappingRuntimeFields, @@ -174,7 +174,7 @@ async function executeAlert( } async function executeRatioAlert( - alertParams: RatioAlertParams, + alertParams: RatioRuleParams, timestampField: string, indexPattern: string, runtimeMappings: estypes.MappingRuntimeFields, @@ -182,12 +182,12 @@ async function executeRatioAlert( alertFactory: LogThresholdAlertFactory ) { // Ratio alert params are separated out into two standard sets of alert params - const numeratorParams: AlertParams = { + const numeratorParams: RuleParams = { ...alertParams, criteria: getNumerator(alertParams.criteria), }; - const denominatorParams: AlertParams = { + const denominatorParams: RuleParams = { ...alertParams, criteria: getDenominator(alertParams.criteria), }; @@ -228,7 +228,7 @@ async function executeRatioAlert( } const getESQuery = ( - alertParams: Omit & { criteria: CountCriteria }, + alertParams: Omit & { criteria: CountCriteria }, timestampField: string, indexPattern: string, runtimeMappings: estypes.MappingRuntimeFields @@ -240,7 +240,7 @@ const getESQuery = ( export const processUngroupedResults = ( results: UngroupedSearchQueryResponse, - params: CountAlertParams, + params: CountRuleParams, alertFactory: LogThresholdAlertFactory, alertUpdater: AlertUpdater ) => { @@ -271,7 +271,7 @@ export const processUngroupedResults = ( export const processUngroupedRatioResults = ( numeratorResults: UngroupedSearchQueryResponse, denominatorResults: UngroupedSearchQueryResponse, - params: RatioAlertParams, + params: RatioRuleParams, alertFactory: LogThresholdAlertFactory, alertUpdater: AlertUpdater ) => { @@ -344,7 +344,7 @@ const getReducedGroupByResults = ( export const processGroupByResults = ( results: GroupedSearchQueryResponse['aggregations']['groups']['buckets'], - params: CountAlertParams, + params: CountRuleParams, alertFactory: LogThresholdAlertFactory, alertUpdater: AlertUpdater ) => { @@ -385,7 +385,7 @@ export const processGroupByResults = ( export const processGroupByRatioResults = ( numeratorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'], denominatorResults: GroupedSearchQueryResponse['aggregations']['groups']['buckets'], - params: RatioAlertParams, + params: RatioRuleParams, alertFactory: LogThresholdAlertFactory, alertUpdater: AlertUpdater ) => { @@ -457,7 +457,7 @@ export const updateAlert: AlertUpdater = (alert, state, actions) => { }; export const buildFiltersFromCriteria = ( - params: Pick & { criteria: CountCriteria }, + params: Pick & { criteria: CountCriteria }, timestampField: string ) => { const { timeSize, timeUnit, criteria } = params; @@ -508,11 +508,11 @@ export const buildFiltersFromCriteria = ( }; export const getGroupedESQuery = ( - params: Pick & { + params: Pick & { criteria: CountCriteria; count: { - comparator: AlertParams['count']['comparator']; - value?: AlertParams['count']['value']; + comparator: RuleParams['count']['comparator']; + value?: RuleParams['count']['value']; }; }, timestampField: string, @@ -619,7 +619,7 @@ export const getGroupedESQuery = ( }; export const getUngroupedESQuery = ( - params: Pick & { criteria: CountCriteria }, + params: Pick & { criteria: CountCriteria }, timestampField: string, index: string, runtimeMappings: estypes.MappingRuntimeFields diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts index 05dc2682fc3b7..9a2902bfb8cd4 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_rule_type.ts @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import { PluginSetupContract } from '../../../../../alerting/server'; import { createLogThresholdExecutor, FIRED_ACTIONS } from './log_threshold_executor'; import { - LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, - alertParamsRT, + LOG_DOCUMENT_COUNT_RULE_TYPE_ID, + ruleParamsRT, } from '../../../../common/alerting/logs/log_threshold'; import { InfraBackendLibs } from '../../infra_types'; import { decodeOrThrow } from '../../../../common/runtime_types'; @@ -82,13 +82,13 @@ export async function registerLogThresholdRuleType( } alertingPlugin.registerType({ - id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, + id: LOG_DOCUMENT_COUNT_RULE_TYPE_ID, name: i18n.translate('xpack.infra.logs.alertName', { defaultMessage: 'Log threshold', }), validate: { params: { - validate: (params) => decodeOrThrow(alertParamsRT)(params), + validate: (params) => decodeOrThrow(ruleParamsRT)(params), }, }, defaultActionGroupId: FIRED_ACTIONS.id, From b58445da8e36d3946fc7d74f5d8e98e16ac505be Mon Sep 17 00:00:00 2001 From: vladpro25 <91911546+vladpro25@users.noreply.github.com> Date: Mon, 13 Dec 2021 15:24:46 +0200 Subject: [PATCH 120/145] Dev Tools Console: Expose the error_trace parameter for completion (#120290) * Expose the error_trace parameter for completion * Dev Tools Console: Expose the error_trace parameter for completion * Dev Tools Console: Expose the error_trace parameter for completion Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/spec_definitions/json/overrides/search.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/plugins/console/server/lib/spec_definitions/json/overrides/search.json diff --git a/src/plugins/console/server/lib/spec_definitions/json/overrides/search.json b/src/plugins/console/server/lib/spec_definitions/json/overrides/search.json new file mode 100644 index 0000000000000..1028422b303f2 --- /dev/null +++ b/src/plugins/console/server/lib/spec_definitions/json/overrides/search.json @@ -0,0 +1,7 @@ +{ + "search": { + "url_params": { + "error_trace": true + } + } +} From e582c96f849cdb5c0c4a064f794328795dc77e8f Mon Sep 17 00:00:00 2001 From: vladpro25 <91911546+vladpro25@users.noreply.github.com> Date: Mon, 13 Dec 2021 15:25:09 +0200 Subject: [PATCH 121/145] Change suggestions for Sampler and Diversified sampler aggregations (#119355) * Change suggestions for Sampler and Diversified sampler aggregations * Change suggestions for Sampler and Diversified sampler aggregations * Change suggestions for Sampler and Diversified sampler aggregations Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lib/spec_definitions/js/aggregations.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts b/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts index fc9e66e17685c..9403d6d56e486 100644 --- a/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts +++ b/src/plugins/console/server/lib/spec_definitions/js/aggregations.ts @@ -111,8 +111,13 @@ const rules = { filters: {}, }, diversified_sampler: { - shard_size: '', - field: '', + shard_size: 100, + field: '{field}', + max_docs_per_value: 1, + execution_hint: { + __template: 'global_ordinals', + __one_of: ['global_ordinals', 'map', 'bytes_hash'], + }, }, min: simple_metric, max: simple_metric, @@ -446,13 +451,7 @@ const rules = { }, sampler: { __template: {}, - field: '{field}', - script: { - // populated by a global rule - }, shard_size: 100, - max_docs_per_value: 3, - execution_hint: { __one_of: ['map', 'global_ordinals', 'bytes_hash'] }, }, children: { __template: { From d916a37cdfb29372e36ea36f0e9de767d72b0428 Mon Sep 17 00:00:00 2001 From: Tobias Stadler Date: Mon, 13 Dec 2021 14:26:58 +0100 Subject: [PATCH 122/145] [APM] Prefer host.name over host.hostname (#119737) (#119952) --- .../src/lib/apm/apm_fields.ts | 3 + .../src/lib/apm/service.ts | 1 + .../test/scenarios/01_simple_trace.test.ts | 2 + .../01_simple_trace.test.ts.snap | 30 ++++++ .../elasticsearch_fieldnames.test.ts.snap | 16 +-- .../apm/common/elasticsearch_fieldnames.ts | 4 +- .../components/app/service_logs/index.tsx | 4 +- .../__snapshots__/queries.test.ts.snap | 6 +- .../routes/service_nodes/get_service_nodes.ts | 2 +- .../services/get_service_infrastructure.ts | 4 +- .../constants/elasticsearch_fieldnames.ts | 2 +- .../service_nodes/get_service_nodes.spec.ts | 98 +++++++++++++++++++ .../get_service_node_metadata.spec.ts | 91 +++++++++++++++++ 13 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts create mode 100644 x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts diff --git a/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts b/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts index a7a826d144d0e..e0a48fdcf2b89 100644 --- a/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/apm_fields.ts @@ -15,6 +15,9 @@ export type ApmApplicationMetricFields = Partial<{ 'system.cpu.total.norm.pct': number; 'system.process.memory.rss.bytes': number; 'system.process.cpu.total.norm.pct': number; + 'jvm.memory.heap.used': number; + 'jvm.memory.non_heap.used': number; + 'jvm.thread.count': number; }>; export type ApmUserAgentFields = Partial<{ diff --git a/packages/elastic-apm-synthtrace/src/lib/apm/service.ts b/packages/elastic-apm-synthtrace/src/lib/apm/service.ts index 16917821c7ee4..d55f60d86e4db 100644 --- a/packages/elastic-apm-synthtrace/src/lib/apm/service.ts +++ b/packages/elastic-apm-synthtrace/src/lib/apm/service.ts @@ -15,6 +15,7 @@ export class Service extends Entity { return new Instance({ ...this.fields, ['service.node.name']: instanceName, + 'host.name': instanceName, 'container.id': instanceName, }); } diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts b/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts index b38d34266f3ac..a78f1ec987bcf 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts @@ -70,6 +70,7 @@ describe('simple trace', () => { 'agent.name': 'java', 'container.id': 'instance-1', 'event.outcome': 'success', + 'host.name': 'instance-1', 'processor.event': 'transaction', 'processor.name': 'transaction', 'service.environment': 'production', @@ -92,6 +93,7 @@ describe('simple trace', () => { 'agent.name': 'java', 'container.id': 'instance-1', 'event.outcome': 'success', + 'host.name': 'instance-1', 'parent.id': '0000000000000300', 'processor.event': 'span', 'processor.name': 'transaction', diff --git a/packages/elastic-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap b/packages/elastic-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap index 76a76d41ec81d..1a5fca39e9fd9 100644 --- a/packages/elastic-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap +++ b/packages/elastic-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap @@ -7,6 +7,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -24,6 +25,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000000", "processor.event": "span", "processor.name": "transaction", @@ -43,6 +45,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -60,6 +63,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000004", "processor.event": "span", "processor.name": "transaction", @@ -79,6 +83,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -96,6 +101,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000008", "processor.event": "span", "processor.name": "transaction", @@ -115,6 +121,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -132,6 +139,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000012", "processor.event": "span", "processor.name": "transaction", @@ -151,6 +159,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -168,6 +177,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000016", "processor.event": "span", "processor.name": "transaction", @@ -187,6 +197,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -204,6 +215,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000020", "processor.event": "span", "processor.name": "transaction", @@ -223,6 +235,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -240,6 +253,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000024", "processor.event": "span", "processor.name": "transaction", @@ -259,6 +273,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -276,6 +291,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000028", "processor.event": "span", "processor.name": "transaction", @@ -295,6 +311,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -312,6 +329,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000032", "processor.event": "span", "processor.name": "transaction", @@ -331,6 +349,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -348,6 +367,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000036", "processor.event": "span", "processor.name": "transaction", @@ -367,6 +387,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -384,6 +405,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000040", "processor.event": "span", "processor.name": "transaction", @@ -403,6 +425,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -420,6 +443,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000044", "processor.event": "span", "processor.name": "transaction", @@ -439,6 +463,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -456,6 +481,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000048", "processor.event": "span", "processor.name": "transaction", @@ -475,6 +501,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -492,6 +519,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000052", "processor.event": "span", "processor.name": "transaction", @@ -511,6 +539,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "processor.event": "transaction", "processor.name": "transaction", "service.environment": "production", @@ -528,6 +557,7 @@ Array [ "agent.name": "java", "container.id": "instance-1", "event.outcome": "success", + "host.name": "instance-1", "parent.id": "0000000000000056", "processor.event": "span", "processor.name": "transaction", diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 6da21bf2bf2c7..5dd3588674179 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -73,11 +73,11 @@ Object { } `; -exports[`Error HOST_NAME 1`] = `"my hostname"`; +exports[`Error HOST_HOSTNAME 1`] = `"my hostname"`; -exports[`Error HOST_OS_PLATFORM 1`] = `undefined`; +exports[`Error HOST_NAME 1`] = `undefined`; -exports[`Error HOSTNAME 1`] = `undefined`; +exports[`Error HOST_OS_PLATFORM 1`] = `undefined`; exports[`Error HTTP_REQUEST_METHOD 1`] = `undefined`; @@ -314,12 +314,12 @@ exports[`Span FID_FIELD 1`] = `undefined`; exports[`Span HOST 1`] = `undefined`; +exports[`Span HOST_HOSTNAME 1`] = `undefined`; + exports[`Span HOST_NAME 1`] = `undefined`; exports[`Span HOST_OS_PLATFORM 1`] = `undefined`; -exports[`Span HOSTNAME 1`] = `undefined`; - exports[`Span HTTP_REQUEST_METHOD 1`] = `undefined`; exports[`Span HTTP_RESPONSE_STATUS_CODE 1`] = `undefined`; @@ -555,11 +555,11 @@ Object { } `; -exports[`Transaction HOST_NAME 1`] = `"my hostname"`; +exports[`Transaction HOST_HOSTNAME 1`] = `"my hostname"`; -exports[`Transaction HOST_OS_PLATFORM 1`] = `undefined`; +exports[`Transaction HOST_NAME 1`] = `undefined`; -exports[`Transaction HOSTNAME 1`] = `undefined`; +exports[`Transaction HOST_OS_PLATFORM 1`] = `undefined`; exports[`Transaction HTTP_REQUEST_METHOD 1`] = `"GET"`; diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index b42c23ee2df94..5c7c953d8d900 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -113,8 +113,8 @@ export const METRICSET_NAME = 'metricset.name'; export const LABEL_NAME = 'labels.name'; export const HOST = 'host'; -export const HOST_NAME = 'host.hostname'; -export const HOSTNAME = 'host.name'; +export const HOST_HOSTNAME = 'host.hostname'; // Do not use. Please use `HOST_NAME` instead. +export const HOST_NAME = 'host.name'; export const HOST_OS_PLATFORM = 'host.os.platform'; export const CONTAINER_ID = 'container.id'; export const KUBERNETES = 'kubernetes'; diff --git a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx index 9b4d8e7d52a28..4f1c517d14b26 100644 --- a/x-pack/plugins/apm/public/components/app/service_logs/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_logs/index.tsx @@ -17,7 +17,7 @@ import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { CONTAINER_ID, - HOSTNAME, + HOST_NAME, SERVICE_NAME, } from '../../../../common/elasticsearch_fieldnames'; import { useApmParams } from '../../../hooks/use_apm_params'; @@ -103,7 +103,7 @@ export const getInfrastructureKQLFilter = ( const infraAttributes = containerIds.length ? containerIds.map((id) => `${CONTAINER_ID}: "${id}"`) - : hostNames.map((id) => `${HOSTNAME}: "${id}"`); + : hostNames.map((id) => `${HOST_NAME}: "${id}"`); const infraAttributesJoined = infraAttributes.join(' or '); diff --git a/x-pack/plugins/apm/server/routes/service_nodes/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/routes/service_nodes/__snapshots__/queries.test.ts.snap index e0591a90b1c19..5022521c46914 100644 --- a/x-pack/plugins/apm/server/routes/service_nodes/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/routes/service_nodes/__snapshots__/queries.test.ts.snap @@ -17,7 +17,7 @@ Object { }, "host": Object { "terms": Object { - "field": "host.hostname", + "field": "host.name", "size": 1, }, }, @@ -74,7 +74,7 @@ Object { }, "host": Object { "terms": Object { - "field": "host.hostname", + "field": "host.name", "size": 1, }, }, @@ -145,7 +145,7 @@ Object { "top_metrics": Object { "metrics": Array [ Object { - "field": "host.hostname", + "field": "host.name", }, ], "sort": Object { diff --git a/x-pack/plugins/apm/server/routes/service_nodes/get_service_nodes.ts b/x-pack/plugins/apm/server/routes/service_nodes/get_service_nodes.ts index ebd56cb9768ce..58c105289be9c 100644 --- a/x-pack/plugins/apm/server/routes/service_nodes/get_service_nodes.ts +++ b/x-pack/plugins/apm/server/routes/service_nodes/get_service_nodes.ts @@ -109,7 +109,7 @@ const getServiceNodes = async ({ name: bucket.key as string, cpu: bucket.cpu.value, heapMemory: bucket.heapMemory.value, - hostName: bucket.latest.top?.[0]?.metrics?.['host.hostname'] as + hostName: bucket.latest.top?.[0]?.metrics?.[HOST_NAME] as | string | null | undefined, diff --git a/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts b/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts index cda0beb6b2d70..79d7ff4f1f41e 100644 --- a/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts +++ b/x-pack/plugins/apm/server/routes/services/get_service_infrastructure.ts @@ -12,7 +12,7 @@ import { ProcessorEvent } from '../../../common/processor_event'; import { SERVICE_NAME, CONTAINER_ID, - HOSTNAME, + HOST_NAME, } from '../../../common/elasticsearch_fieldnames'; export const getServiceInfrastructure = async ({ @@ -57,7 +57,7 @@ export const getServiceInfrastructure = async ({ }, hostNames: { terms: { - field: HOSTNAME, + field: HOST_NAME, size: 500, }, }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts index 1b20b82c1202c..35873a31150ac 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/elasticsearch_fieldnames.ts @@ -106,7 +106,7 @@ export const METRIC_JAVA_GC_TIME = 'jvm.gc.time'; export const LABEL_NAME = 'labels.name'; export const HOST = 'host'; -export const HOST_NAME = 'host.hostname'; +export const HOST_HOSTNAME = 'host.hostname'; export const HOST_OS_PLATFORM = 'host.os.platform'; export const CONTAINER_ID = 'container.id'; export const KUBERNETES = 'kubernetes'; diff --git a/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts new file mode 100644 index 0000000000000..8dac8f7dea4f9 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/service_nodes/get_service_nodes.spec.ts @@ -0,0 +1,98 @@ +/* + * 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 { apm, timerange } from '@elastic/apm-synthtrace'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const registry = getService('registry'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const serviceName = 'synth-go'; + const instanceName = 'instance-a'; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/serviceNodes', + params: { + path: { serviceName }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + kuery: '', + environment: 'ENVIRONMENT_ALL', + }, + }, + }); + } + + registry.when('Service nodes when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await callApi(); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "serviceNodes": Array [], + } + `); + }); + }); + + registry.when( + 'Service nodes when data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + before(async () => { + const instance = apm.service(serviceName, 'production', 'go').instance(instanceName); + await synthtraceEsClient.index( + timerange(start, end) + .interval('1m') + .rate(1) + .flatMap((timestamp) => + instance + .appMetrics({ + 'system.process.cpu.total.norm.pct': 1, + 'jvm.memory.heap.used': 1000, + 'jvm.memory.non_heap.used': 100, + 'jvm.thread.count': 25, + }) + .timestamp(timestamp) + .serialize() + ) + ); + }); + after(() => synthtraceEsClient.clean()); + + it('returns service nodes', async () => { + const response = await callApi(); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "serviceNodes": Array [ + Object { + "cpu": 1, + "heapMemory": 1000, + "hostName": "instance-a", + "name": "instance-a", + "nonHeapMemory": 100, + "threadCount": 25, + }, + ], + } + `); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.ts new file mode 100644 index 0000000000000..1317ce7e9a1ee --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/get_service_node_metadata.spec.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 expect from '@kbn/expect'; +import { apm, timerange } from '@elastic/apm-synthtrace'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const registry = getService('registry'); + const synthtraceEsClient = getService('synthtraceEsClient'); + + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + const serviceName = 'synth-go'; + const instanceName = 'instance-a'; + + async function callApi() { + return await apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/node/{serviceNodeName}/metadata', + params: { + path: { serviceName, serviceNodeName: instanceName }, + query: { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + kuery: '', + }, + }, + }); + } + + registry.when( + 'Service node metadata when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await callApi(); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "containerId": "N/A", + "host": "N/A", + } + `); + }); + } + ); + + registry.when( + 'Service node metadata when data is loaded', + { config: 'basic', archives: ['apm_mappings_only_8.0.0'] }, + () => { + before(async () => { + const instance = apm.service(serviceName, 'production', 'go').instance(instanceName); + await synthtraceEsClient.index( + timerange(start, end) + .interval('1m') + .rate(1) + .flatMap((timestamp) => + instance + .transaction('GET /api/product/list') + .timestamp(timestamp) + .duration(1000) + .success() + .serialize() + ) + ); + }); + after(() => synthtraceEsClient.clean()); + + it('returns service node metadata', async () => { + const response = await callApi(); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "containerId": "instance-a", + "host": "instance-a", + } + `); + }); + } + ); +} From c743c97c2d4f4b28465c7486d22d13431e178a59 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Mon, 13 Dec 2021 14:31:25 +0100 Subject: [PATCH 123/145] Fix wrong runtime field format on alert table (#120744) * Fix wrong runtime field format on alert table * Fix CI Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/timeline/index.ts | 2 +- .../common/types/timeline/columns/index.tsx | 4 +- .../body/column_headers/helpers.test.tsx | 102 ++++++++++++++++++ .../t_grid/body/column_headers/helpers.tsx | 9 +- 4 files changed, 107 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index 60fd126e6fd85..442986870ac94 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -633,7 +633,7 @@ export interface ColumnHeaderResult { category?: Maybe; columnHeaderType?: Maybe; description?: Maybe; - example?: Maybe; + example?: Maybe; indexes?: Maybe; id?: Maybe; name?: Maybe; diff --git a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx index 88caa779d0592..fd758e74df0e9 100644 --- a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx +++ b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx @@ -76,8 +76,8 @@ export type ColumnHeaderOptions = Pick< tGridCellActions?: TGridCellAction[]; category?: string; columnHeaderType: ColumnHeaderType; - description?: string; - example?: string; + description?: string | null; + example?: string | number | null; format?: string; linkField?: string; placeholder?: string; diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx index 9de28b870aadb..73287c3cf5cec 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx @@ -319,6 +319,108 @@ describe('helpers', () => { getColumnHeaders([headerDoesNotMatchBrowserField], mockBrowserFields).map(omit('display')) ).toEqual(expected); }); + + describe('augment the `header` with metadata from `browserFields`', () => { + test('it should augment the `header` when field category is base', () => { + const fieldName = 'test_field'; + const testField = { + aggregatable: true, + category: 'base', + description: + 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', + example: '2016-05-23T08:05:34.853Z', + format: 'date', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: fieldName, + searchable: true, + type: 'date', + }; + + const browserField = { base: { fields: { [fieldName]: testField } } }; + + const header: ColumnHeaderOptions = { + columnHeaderType: 'not-filtered', + id: fieldName, + }; + + expect( + getColumnHeaders([header], browserField).map( + omit(['display', 'actions', 'isSortable', 'defaultSortDirection', 'schema']) + ) + ).toEqual([ + { + ...header, + ...browserField.base.fields[fieldName], + }, + ]); + }); + + test("it should augment the `header` when field is top level and name isn't splittable", () => { + const fieldName = 'testFieldName'; + const testField = { + aggregatable: true, + category: fieldName, + description: 'test field description', + example: '2016-05-23T08:05:34.853Z', + format: 'date', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: fieldName, + searchable: true, + type: 'date', + }; + + const browserField = { [fieldName]: { fields: { [fieldName]: testField } } }; + + const header: ColumnHeaderOptions = { + columnHeaderType: 'not-filtered', + id: fieldName, + }; + + expect( + getColumnHeaders([header], browserField).map( + omit(['display', 'actions', 'isSortable', 'defaultSortDirection', 'schema']) + ) + ).toEqual([ + { + ...header, + ...browserField[fieldName].fields[fieldName], + }, + ]); + }); + + test('it should augment the `header` when field is splittable', () => { + const fieldName = 'test.field.splittable'; + const testField = { + aggregatable: true, + category: 'test', + description: 'test field description', + example: '2016-05-23T08:05:34.853Z', + format: 'date', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + name: fieldName, + searchable: true, + type: 'date', + }; + + const browserField = { test: { fields: { [fieldName]: testField } } }; + + const header: ColumnHeaderOptions = { + columnHeaderType: 'not-filtered', + id: fieldName, + }; + + expect( + getColumnHeaders([header], browserField).map( + omit(['display', 'actions', 'isSortable', 'defaultSortDirection', 'schema']) + ) + ).toEqual([ + { + ...header, + ...browserField.test.fields[fieldName], + }, + ]); + }); + }); }); describe('getActionsColumnWidth', () => { diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.tsx index aa2fc3f964c3c..1c2ac89119abb 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.tsx @@ -7,7 +7,7 @@ import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme'; import { EuiDataGridColumnActions } from '@elastic/eui'; -import { get, keyBy } from 'lodash/fp'; +import { keyBy } from 'lodash/fp'; import React from 'react'; import type { @@ -91,17 +91,12 @@ export const getColumnHeaders = ( const browserFieldByName = getAllFieldsByName(browserFields); return headers ? headers.map((header) => { - const splitHeader = header.id.split('.'); // source.geo.city_name -> [source, geo, city_name] - const browserField: Partial | undefined = browserFieldByName[header.id]; // augment the header with metadata from browserFields: const augmentedHeader = { ...header, - ...get( - [splitHeader.length > 1 ? splitHeader[0] : 'base', 'fields', header.id], - browserFields - ), + ...browserField, schema: header.schema ?? getSchema(browserField?.type), }; From 30786a6e71428b5e92ab6b3a0351c21fe9ec0faa Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 13 Dec 2021 13:40:43 +0000 Subject: [PATCH 124/145] skip flaky suite (#104249) --- .../functional/apps/uptime/feature_controls/uptime_security.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts index c1ba546864a53..4d4acbe6242ba 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts @@ -136,7 +136,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('no uptime privileges', () => { + // FLAKY: https://github.com/elastic/kibana/issues/104249 + describe.skip('no uptime privileges', () => { before(async () => { await security.role.create('no_uptime_privileges_role', { elasticsearch: { From 862f1a9239701eb7c8ddcb0b75273f8c9287f5b2 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 13 Dec 2021 13:44:52 +0000 Subject: [PATCH 125/145] skip flaky suite (#120440) --- .../apps/observability/alerts/pagination.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/observability_functional/apps/observability/alerts/pagination.ts b/x-pack/test/observability_functional/apps/observability/alerts/pagination.ts index 79eadac3d7e40..9e80ea8f9e526 100644 --- a/x-pack/test/observability_functional/apps/observability/alerts/pagination.ts +++ b/x-pack/test/observability_functional/apps/observability/alerts/pagination.ts @@ -89,7 +89,8 @@ export default ({ getService }: FtrProviderContext) => { }); }); - describe('Pagination controls', () => { + // FLAKY: https://github.com/elastic/kibana/issues/120440 + describe.skip('Pagination controls', () => { before(async () => { await (await observability.alerts.pagination.getPageSizeSelector()).click(); await (await observability.alerts.pagination.getTenRowsPageSelector()).click(); From 38aa34f4964f80bc76006e0c852c3e5516305e11 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 13 Dec 2021 13:47:32 +0000 Subject: [PATCH 126/145] skip flaky suite (#119267) --- x-pack/test/security_api_integration/tests/audit/audit_log.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/security_api_integration/tests/audit/audit_log.ts b/x-pack/test/security_api_integration/tests/audit/audit_log.ts index bb4c27976c857..7322a2638767b 100644 --- a/x-pack/test/security_api_integration/tests/audit/audit_log.ts +++ b/x-pack/test/security_api_integration/tests/audit/audit_log.ts @@ -44,7 +44,8 @@ export default function ({ getService }: FtrProviderContext) { const retry = getService('retry'); const { username, password } = getService('config').get('servers.kibana'); - describe('Audit Log', function () { + // FLAKY: https://github.com/elastic/kibana/issues/119267 + describe.skip('Audit Log', function () { const logFilePath = Path.resolve(__dirname, '../../fixtures/audit/audit.log'); const logFile = new FileWrapper(logFilePath); From 6e62f5a6e12df77a3b829c714549698cce0f39a6 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 13 Dec 2021 13:51:20 +0000 Subject: [PATCH 127/145] skip flaky suite (#117780) --- .../functional/apps/maps/embeddable/tooltip_filter_actions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index 09301fec40771..400c687d6af88 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -41,7 +41,8 @@ export default function ({ getPageObjects, getService }) { await security.testUser.restoreDefaults(); }); - describe('apply filter to current view', () => { + // FLAKY: https://github.com/elastic/kibana/issues/117780 + describe.skip('apply filter to current view', () => { before(async () => { await loadDashboardAndOpenTooltip(); }); From 205f77c8ff99954c7077f7701b6994657b40fd7b Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Mon, 13 Dec 2021 15:11:51 +0100 Subject: [PATCH 128/145] [Security Solution] Fixes upgrade tests (#120163) * avoids the threshold test execution when the version is not the correct * fixes version format * updates details to align with the new data * changes the assertion of the reason field depending on the version * improves the navigation to the rule * removes flakiness * adds missing commit Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../custom_query_rule.spec.ts | 30 ++++++++++----- .../detection_rules}/threshold_rule.spec.ts | 37 ++++++++++++------- .../cases}/import_case.spec.ts | 21 ++++++----- .../timeline}/import_timeline.spec.ts | 15 +++----- x-pack/plugins/security_solution/package.json | 1 + .../test/security_solution_cypress/runner.ts | 11 +++++- 6 files changed, 72 insertions(+), 43 deletions(-) rename x-pack/plugins/security_solution/cypress/upgrade_integration/{ => detections/detection_rules}/custom_query_rule.spec.ts (80%) rename x-pack/plugins/security_solution/cypress/upgrade_integration/{ => detections/detection_rules}/threshold_rule.spec.ts (80%) rename x-pack/plugins/security_solution/cypress/upgrade_integration/{ => threat_hunting/cases}/import_case.spec.ts (90%) rename x-pack/plugins/security_solution/cypress/upgrade_integration/{ => threat_hunting/timeline}/import_timeline.spec.ts (93%) diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/custom_query_rule.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/custom_query_rule.spec.ts similarity index 80% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/custom_query_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/custom_query_rule.spec.ts index e4464ae43dd62..efc0d290ac728 100644 --- a/x-pack/plugins/security_solution/cypress/upgrade_integration/custom_query_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/custom_query_rule.spec.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import semver from 'semver'; import { DESTINATION_IP, HOST_NAME, @@ -14,8 +15,8 @@ import { SEVERITY, SOURCE_IP, USER_NAME, -} from '../screens/alerts'; -import { SERVER_SIDE_EVENT_COUNT } from '../screens/alerts_detection_rules'; +} from '../../../screens/alerts'; +import { SERVER_SIDE_EVENT_COUNT } from '../../../screens/alerts_detection_rules'; import { ADDITIONAL_LOOK_BACK_DETAILS, ABOUT_DETAILS, @@ -31,13 +32,16 @@ import { SCHEDULE_DETAILS, SEVERITY_DETAILS, TIMELINE_TEMPLATE_DETAILS, -} from '../screens/rule_details'; +} from '../../../screens/rule_details'; -import { waitForPageToBeLoaded } from '../tasks/common'; -import { waitForRulesTableToBeLoaded, goToTheRuleDetailsOf } from '../tasks/alerts_detection_rules'; -import { loginAndWaitForPage } from '../tasks/login'; +import { waitForPageToBeLoaded } from '../../../tasks/common'; +import { + waitForRulesTableToBeLoaded, + goToTheRuleDetailsOf, +} from '../../../tasks/alerts_detection_rules'; +import { loginAndWaitForPage } from '../../../tasks/login'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; const EXPECTED_NUMBER_OF_ALERTS = '1'; @@ -63,8 +67,8 @@ const rule = { severity: 'Low', riskScore: '7', timelineTemplate: 'none', - runsEvery: '10s', - lookBack: '179999990s', + runsEvery: '24h', + lookBack: '49976h', timeline: 'None', }; @@ -100,10 +104,16 @@ describe('After an upgrade, the custom query rule', () => { }); it('Displays the alert details at the tgrid', () => { + let expectedReason; + if (semver.gt(Cypress.env('ORIGINAL_VERSION'), '7.15.0')) { + expectedReason = alert.reason; + } else { + expectedReason = '-'; + } cy.get(RULE_NAME).should('have.text', alert.rule); cy.get(SEVERITY).should('have.text', alert.severity); cy.get(RISK_SCORE).should('have.text', alert.riskScore); - cy.get(REASON).should('have.text', alert.reason).type('{rightarrow}'); + cy.get(REASON).should('have.text', expectedReason).type('{rightarrow}'); cy.get(HOST_NAME).should('have.text', alert.hostName); cy.get(USER_NAME).should('have.text', alert.username); cy.get(PROCESS_NAME).should('have.text', alert.processName); diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/threshold_rule.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/threshold_rule.spec.ts similarity index 80% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/threshold_rule.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/threshold_rule.spec.ts index b6dbcd0e3232c..16949c9b34c63 100644 --- a/x-pack/plugins/security_solution/cypress/upgrade_integration/threshold_rule.spec.ts +++ b/x-pack/plugins/security_solution/cypress/upgrade_integration/detections/detection_rules/threshold_rule.spec.ts @@ -4,9 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - -import { HOST_NAME, REASON, RISK_SCORE, RULE_NAME, SEVERITY } from '../screens/alerts'; -import { SERVER_SIDE_EVENT_COUNT } from '../screens/alerts_detection_rules'; +import semver from 'semver'; +import { HOST_NAME, REASON, RISK_SCORE, RULE_NAME, SEVERITY } from '../../../screens/alerts'; +import { SERVER_SIDE_EVENT_COUNT } from '../../../screens/alerts_detection_rules'; import { ADDITIONAL_LOOK_BACK_DETAILS, ABOUT_DETAILS, @@ -23,14 +23,17 @@ import { SEVERITY_DETAILS, THRESHOLD_DETAILS, TIMELINE_TEMPLATE_DETAILS, -} from '../screens/rule_details'; +} from '../../../screens/rule_details'; -import { expandFirstAlert } from '../tasks/alerts'; -import { waitForPageToBeLoaded } from '../tasks/common'; -import { waitForRulesTableToBeLoaded, goToRuleDetails } from '../tasks/alerts_detection_rules'; -import { loginAndWaitForPage } from '../tasks/login'; +import { expandFirstAlert } from '../../../tasks/alerts'; +import { waitForPageToBeLoaded } from '../../../tasks/common'; +import { + goToTheRuleDetailsOf, + waitForRulesTableToBeLoaded, +} from '../../../tasks/alerts_detection_rules'; +import { loginAndWaitForPage } from '../../../tasks/login'; -import { DETECTIONS_RULE_MANAGEMENT_URL } from '../urls/navigation'; +import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation'; import { OVERVIEW_HOST_NAME, OVERVIEW_RISK_SCORE, @@ -40,7 +43,7 @@ import { OVERVIEW_THRESHOLD_COUNT, OVERVIEW_THRESHOLD_VALUE, SUMMARY_VIEW, -} from '../screens/alerts_details'; +} from '../../../screens/alerts_details'; const EXPECTED_NUMBER_OF_ALERTS = '1'; @@ -61,8 +64,8 @@ const rule = { severity: 'Medium', riskScore: '17', timelineTemplate: 'none', - runsEvery: '60s', - lookBack: '2999999m', + runsEvery: '24h', + lookBack: '49976h', timeline: 'None', thresholdField: 'host.name', threholdValue: '1', @@ -72,7 +75,7 @@ describe('After an upgrade, the threshold rule', () => { before(() => { loginAndWaitForPage(DETECTIONS_RULE_MANAGEMENT_URL); waitForRulesTableToBeLoaded(); - goToRuleDetails(); + goToTheRuleDetailsOf(rule.name); waitForPageToBeLoaded(); }); @@ -104,10 +107,16 @@ describe('After an upgrade, the threshold rule', () => { }); it('Displays the alert details in the TGrid', () => { + let expectedReason; + if (semver.gt(Cypress.env('ORIGINAL_VERSION'), '7.15.0')) { + expectedReason = alert.reason; + } else { + expectedReason = '-'; + } cy.get(RULE_NAME).should('have.text', alert.rule); cy.get(SEVERITY).should('have.text', alert.severity); cy.get(RISK_SCORE).should('have.text', alert.riskScore); - cy.get(REASON).should('have.text', alert.reason); + cy.get(REASON).should('have.text', expectedReason); cy.get(HOST_NAME).should('have.text', alert.hostName); }); diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/import_case.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/cases/import_case.spec.ts similarity index 90% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/import_case.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/cases/import_case.spec.ts index eb72dea9be7e8..e97cebeff00b5 100644 --- a/x-pack/plugins/security_solution/cypress/upgrade_integration/import_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/cases/import_case.spec.ts @@ -15,7 +15,7 @@ import { ALL_CASES_OPEN_CASES_STATS, ALL_CASES_REPORTER, ALL_CASES_IN_PROGRESS_STATUS, -} from '../screens/all_cases'; +} from '../../../screens/all_cases'; import { CASES_TAGS, CASE_CONNECTOR, @@ -25,16 +25,19 @@ import { CASE_IN_PROGRESS_STATUS, CASE_SWITCH, CASE_USER_ACTION, -} from '../screens/case_details'; -import { CASES_PAGE } from '../screens/kibana_navigation'; +} from '../../../screens/case_details'; +import { CASES_PAGE } from '../../../screens/kibana_navigation'; -import { goToCaseDetails } from '../tasks/all_cases'; -import { deleteCase } from '../tasks/case_details'; -import { navigateFromKibanaCollapsibleTo, openKibanaNavigation } from '../tasks/kibana_navigation'; -import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; -import { importCase } from '../tasks/saved_objects'; +import { goToCaseDetails } from '../../../tasks/all_cases'; +import { deleteCase } from '../../../tasks/case_details'; +import { + navigateFromKibanaCollapsibleTo, + openKibanaNavigation, +} from '../../../tasks/kibana_navigation'; +import { loginAndWaitForPageWithoutDateRange } from '../../../tasks/login'; +import { importCase } from '../../../tasks/saved_objects'; -import { KIBANA_SAVED_OBJECTS } from '../urls/navigation'; +import { KIBANA_SAVED_OBJECTS } from '../../../urls/navigation'; const CASE_NDJSON = '7_16_case.ndjson'; const importedCase = { diff --git a/x-pack/plugins/security_solution/cypress/upgrade_integration/import_timeline.spec.ts b/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/timeline/import_timeline.spec.ts similarity index 93% rename from x-pack/plugins/security_solution/cypress/upgrade_integration/import_timeline.spec.ts rename to x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/timeline/import_timeline.spec.ts index f3b3f14e9c260..253a1c9c59b0b 100644 --- a/x-pack/plugins/security_solution/cypress/upgrade_integration/import_timeline.spec.ts +++ b/x-pack/plugins/security_solution/cypress/upgrade_integration/threat_hunting/timeline/import_timeline.spec.ts @@ -28,7 +28,7 @@ import { TIMELINE_QUERY, TIMELINE_TITLE, USER_KPI, -} from '../screens/timeline'; +} from '../../../screens/timeline'; import { NOTE, TIMELINES_USERNAME, @@ -36,19 +36,19 @@ import { TIMELINES_DESCRIPTION, TIMELINES_NOTES_COUNT, TIMELINES_PINNED_EVENT_COUNT, -} from '../screens/timelines'; +} from '../../../screens/timelines'; -import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { loginAndWaitForPageWithoutDateRange } from '../../../tasks/login'; import { closeTimeline, deleteTimeline, goToCorrelationTab, goToNotesTab, goToPinnedTab, -} from '../tasks/timeline'; -import { expandNotes, importTimeline, openTimeline } from '../tasks/timelines'; +} from '../../../tasks/timeline'; +import { expandNotes, importTimeline, openTimeline } from '../../../tasks/timelines'; -import { TIMELINES_URL } from '../urls/navigation'; +import { TIMELINES_URL } from '../../../urls/navigation'; const timeline = '7_15_timeline.ndjson'; const username = 'elastic'; @@ -64,7 +64,6 @@ const timelineDetails = { }; const detectionAlert = { - timestamp: 'Nov 17, 2021 @ 09:36:25.499', message: '—', eventCategory: 'file', eventAction: 'initial_scan', @@ -149,7 +148,6 @@ describe('Import timeline after upgrade', () => { cy.get(NOTES_TAB_BUTTON).should('have.text', timelineDetails.notesTab); cy.get(PINNED_TAB_BUTTON).should('have.text', timelineDetails.pinnedTab); - cy.get(QUERY_EVENT_TABLE_CELL).eq(0).should('contain', detectionAlert.timestamp); cy.get(QUERY_EVENT_TABLE_CELL).eq(1).should('contain', detectionAlert.message); cy.get(QUERY_EVENT_TABLE_CELL).eq(2).should('contain', detectionAlert.eventCategory); cy.get(QUERY_EVENT_TABLE_CELL).eq(3).should('contain', detectionAlert.eventAction); @@ -196,7 +194,6 @@ describe('Import timeline after upgrade', () => { it('Displays the correct timeline details inside the pinned tab', () => { goToPinnedTab(); - cy.get(PINNED_EVENT_TABLE_CELL).eq(0).should('contain', detectionAlert.timestamp); cy.get(PINNED_EVENT_TABLE_CELL).eq(1).should('contain', detectionAlert.message); cy.get(PINNED_EVENT_TABLE_CELL).eq(2).should('contain', detectionAlert.eventCategory); cy.get(PINNED_EVENT_TABLE_CELL).eq(3).should('contain', detectionAlert.eventAction); diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json index 371ac66004f48..821550f21919a 100644 --- a/x-pack/plugins/security_solution/package.json +++ b/x-pack/plugins/security_solution/package.json @@ -19,6 +19,7 @@ "cypress:run-as-ci": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/cli_config.ts", "cypress:run-as-ci:firefox": "node --max-old-space-size=2048 ../../../scripts/functional_tests --config ../../test/security_solution_cypress/config.firefox.ts", "cypress:run:upgrade": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/upgrade_integration", + "cypress:run:upgrade:old": "yarn cypress:run:reporter --browser chrome --config integrationFolder=./cypress/upgrade_integration --spec ./cypress/upgrade_integration/threat_hunting/**/*.spec.ts,./cypress/upgrade_integration/detections/**/custom_query_rule.spec.ts; status=$?; yarn junit:merge && exit $status", "junit:merge": "../../../node_modules/.bin/mochawesome-merge ../../../target/kibana-security-solution/cypress/results/mochawesome*.json > ../../../target/kibana-security-solution/cypress/results/output.json && ../../../node_modules/.bin/marge ../../../target/kibana-security-solution/cypress/results/output.json --reportDir ../../../target/kibana-security-solution/cypress/results && mkdir -p ../../../target/junit && cp ../../../target/kibana-security-solution/cypress/results/*.xml ../../../target/junit/", "test:generate": "node scripts/endpoint/resolver_generator" } diff --git a/x-pack/test/security_solution_cypress/runner.ts b/x-pack/test/security_solution_cypress/runner.ts index a111c327b1ac6..b537be4e80b41 100644 --- a/x-pack/test/security_solution_cypress/runner.ts +++ b/x-pack/test/security_solution_cypress/runner.ts @@ -10,6 +10,7 @@ import Url from 'url'; import { withProcRunner } from '@kbn/dev-utils'; +import semver from 'semver'; import { FtrProviderContext } from './ftr_provider_context'; export async function SecuritySolutionCypressCliTestRunner({ getService }: FtrProviderContext) { @@ -117,11 +118,18 @@ export async function SecuritySolutionCypressUpgradeCliTestRunner({ getService, }: FtrProviderContext) { const log = getService('log'); + let command = ''; + + if (semver.gt(process.env.ORIGINAL_VERSION!, '7.10.0')) { + command = 'cypress:run:upgrade'; + } else { + command = 'cypress:run:upgrade:old'; + } await withProcRunner(log, async (procs) => { await procs.run('cypress', { cmd: 'yarn', - args: ['cypress:run:upgrade'], + args: [command], cwd: resolve(__dirname, '../../plugins/security_solution'), env: { FORCE_COLOR: '1', @@ -129,6 +137,7 @@ export async function SecuritySolutionCypressUpgradeCliTestRunner({ CYPRESS_ELASTICSEARCH_URL: process.env.TEST_ES_URL, CYPRESS_ELASTICSEARCH_USERNAME: process.env.TEST_ES_USER, CYPRESS_ELASTICSEARCH_PASSWORD: process.env.TEST_ES_PASS, + CYPRESS_ORIGINAL_VERSION: process.env.ORIGINAL_VERSION, ...process.env, }, wait: true, From b0442e396b360f788524cfe38de62fa31fc1d789 Mon Sep 17 00:00:00 2001 From: Stratoula Kalafateli Date: Mon, 13 Dec 2021 16:21:48 +0200 Subject: [PATCH 129/145] [Lens] Applies new time axis for area and line charts with breakdown dimension (#120891) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../xy_visualization/expression.test.tsx | 111 ++++++++++++++++++ .../public/xy_visualization/expression.tsx | 3 +- 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 65425b04129d3..027165a2eb5d0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -636,6 +636,117 @@ describe('xy_expression', () => { `); }); + describe('axis time', () => { + const defaultTimeLayer: LayerArgs = { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'line', + xAccessor: 'c', + accessors: ['a', 'b'], + splitAccessor: 'd', + columnToLabel: '{"a": "Label A", "b": "Label B", "d": "Label D"}', + xScaleType: 'time', + yScaleType: 'linear', + isHistogram: true, + palette: mockPaletteOutput, + }; + test('it should disable the new time axis for a line time layer when isHistogram is set to false', () => { + const { data } = sampleArgs(); + + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).first().prop('timeAxisLayerCount'); + + expect(axisStyle).toBe(0); + }); + test('it should enable the new time axis for a line time layer when isHistogram is set to true', () => { + const { data } = sampleArgs(); + const timeLayerArgs = createArgsWithLayers([defaultTimeLayer]); + + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).first().prop('timeAxisLayerCount'); + + expect(axisStyle).toBe(3); + }); + test('it should disable the new time axis for a vertical bar with break down dimension', () => { + const { data } = sampleArgs(); + const timeLayer: LayerArgs = { + ...defaultTimeLayer, + seriesType: 'bar', + }; + const timeLayerArgs = createArgsWithLayers([timeLayer]); + + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).first().prop('timeAxisLayerCount'); + + expect(axisStyle).toBe(0); + }); + + test('it should enable the new time axis for a stacked vertical bar with break down dimension', () => { + const { data } = sampleArgs(); + const timeLayer: LayerArgs = { + ...defaultTimeLayer, + seriesType: 'bar_stacked', + }; + const timeLayerArgs = createArgsWithLayers([timeLayer]); + + const instance = shallow( + + ); + + const axisStyle = instance.find(Axis).first().prop('timeAxisLayerCount'); + + expect(axisStyle).toBe(3); + }); + }); describe('endzones', () => { const { args } = sampleArgs(); const data: LensMultiTable = { diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 38709ae21d8fd..9c4c56281dae0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -562,9 +562,8 @@ export function XYChart({ } as LegendPositionConfig; const isHistogramModeEnabled = filteredLayers.some( - ({ isHistogram, seriesType, splitAccessor }) => + ({ isHistogram, seriesType }) => isHistogram && - (seriesType.includes('stacked') || !splitAccessor) && (seriesType.includes('stacked') || !seriesType.includes('bar') || !chartHasMoreThanOneBarSeries) From b6753241ed424d236c0ddfcc4f94437c188a7f12 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Mon, 13 Dec 2021 16:46:42 +0100 Subject: [PATCH 130/145] [Security Solution] host isolation exceptions listing under policy integration details tab (#120361) --- .../found_exception_list_item_schema.mock.ts | 8 +- .../public/management/common/constants.ts | 1 + .../public/management/common/routing.ts | 31 +++- .../public/management/common/utils.ts | 21 +++ .../host_isolation_exceptions/view/hooks.ts | 38 ++-- .../host_isolation_exceptions_list.test.tsx | 8 +- .../view/host_isolation_exceptions_list.tsx | 7 +- .../public/management/pages/policy/index.tsx | 2 + .../selectors/policy_common_selectors.ts | 18 +- .../selectors/policy_settings_selectors.ts | 8 +- .../components/empty_unassigned.tsx | 53 ++++++ .../components/empty_unexisting.tsx | 50 ++++++ .../components/list.test.tsx | 94 ++++++++++ .../components/list.tsx | 143 +++++++++++++++ .../host_isolation_exceptions_tab.test.tsx | 118 +++++++++++++ .../host_isolation_exceptions_tab.tsx | 164 ++++++++++++++++++ .../pages/policy/view/policy_details.test.tsx | 9 + .../pages/policy/view/tabs/policy_tabs.tsx | 69 +++++--- .../management/services/policies/hooks.ts | 4 +- 19 files changed, 793 insertions(+), 53 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unassigned.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.test.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx diff --git a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts index d06ab90e84168..18143d765cae9 100644 --- a/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/found_exception_list_item_schema.mock.ts @@ -9,9 +9,11 @@ import type { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-l import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock'; -export const getFoundExceptionListItemSchemaMock = (): FoundExceptionListItemSchema => ({ - data: [getExceptionListItemSchemaMock()], +export const getFoundExceptionListItemSchemaMock = ( + count: number = 1 +): FoundExceptionListItemSchema => ({ + data: Array.from({ length: count }, getExceptionListItemSchemaMock), page: 1, per_page: 1, - total: 1, + total: count, }); 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 e35c07edb65a4..d6603896c9cf8 100644 --- a/x-pack/plugins/security_solution/public/management/common/constants.ts +++ b/x-pack/plugins/security_solution/public/management/common/constants.ts @@ -14,6 +14,7 @@ export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_PATH}/:tabName(${A export const MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/settings`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/trustedApps`; export const MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/eventFilters`; +export const MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/hostIsolationExceptions`; /** @deprecated use the paths defined above instead */ export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`; export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`; diff --git a/x-pack/plugins/security_solution/public/management/common/routing.ts b/x-pack/plugins/security_solution/public/management/common/routing.ts index 9b4792567f9a8..44a389c5a3a46 100644 --- a/x-pack/plugins/security_solution/public/management/common/routing.ts +++ b/x-pack/plugins/security_solution/public/management/common/routing.ts @@ -6,10 +6,16 @@ */ import { isEmpty } from 'lodash/fp'; -import { generatePath } from 'react-router-dom'; // eslint-disable-next-line import/no-nodejs-modules import querystring from 'querystring'; - +import { generatePath } from 'react-router-dom'; +import { appendSearch } from '../../common/components/link_to/helpers'; +import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types'; +import { EventFiltersPageLocation } from '../pages/event_filters/types'; +import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types'; +import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types'; +import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state'; +import { AdministrationSubTab } from '../types'; import { MANAGEMENT_DEFAULT_PAGE, MANAGEMENT_DEFAULT_PAGE_SIZE, @@ -19,17 +25,11 @@ import { MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICIES_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_TRUSTED_APPS_PATH, } from './constants'; -import { AdministrationSubTab } from '../types'; -import { appendSearch } from '../../common/components/link_to/helpers'; -import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types'; -import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state'; -import { EventFiltersPageLocation } from '../pages/event_filters/types'; -import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types'; -import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types'; // Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150 type ExactKeys = Exclude extends never ? T1 : never; @@ -390,3 +390,16 @@ export const getHostIsolationExceptionsListPath = ( querystring.stringify(normalizeHostIsolationExceptionsPageLocation(location)) )}`; }; + +export const getPolicyHostIsolationExceptionsPath = ( + policyId: string, + location?: Partial +) => { + const path = generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, { + tabName: AdministrationSubTab.policies, + policyId, + }); + return `${path}${appendSearch( + querystring.stringify(normalizePolicyDetailsArtifactsListPageLocation(location)) + )}`; +}; diff --git a/x-pack/plugins/security_solution/public/management/common/utils.ts b/x-pack/plugins/security_solution/public/management/common/utils.ts index 3fbe5662f338c..e9f93e85bdb60 100644 --- a/x-pack/plugins/security_solution/public/management/common/utils.ts +++ b/x-pack/plugins/security_solution/public/management/common/utils.ts @@ -49,3 +49,24 @@ export const parsePoliciesToKQL = (includedPolicies: string, excludedPolicies: s return `(${kuery.join(' AND ')})`; }; + +/** + * Takes a list of policies (string[]) and an existing kuery + * (string) and returns an unified KQL with and AND + * @param policies string[] a list of policies ids + * @param kuery string an existing KQL. + */ +export const parsePoliciesAndFilterToKql = ({ + policies, + kuery, +}: { + policies?: string[]; + kuery?: string; +}): string | undefined => { + if (!policies || !policies.length) { + return kuery; + } + + const policiesKQL = parsePoliciesToKQL(policies.join(','), ''); + return `(${policiesKQL})${kuery ? ` AND (${kuery})` : ''}`; +}; diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts index 3cac7e7e466d6..f814cc9726deb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/hooks.ts @@ -22,7 +22,7 @@ import { MANAGEMENT_STORE_HOST_ISOLATION_EXCEPTIONS_NAMESPACE, } from '../../../common/constants'; import { getHostIsolationExceptionsListPath } from '../../../common/routing'; -import { parseQueryFilterToKQL } from '../../../common/utils'; +import { parsePoliciesAndFilterToKql, parseQueryFilterToKQL } from '../../../common/utils'; import { getHostIsolationExceptionItems, getHostIsolationExceptionSummary, @@ -87,23 +87,37 @@ export function useCanSeeHostIsolationExceptionsMenu(): boolean { const SEARCHABLE_FIELDS: Readonly = [`name`, `description`, `entries.value`]; -export function useFetchHostIsolationExceptionsList(): QueryObserverResult< - FoundExceptionListItemSchema, - ServerApiError -> { +export function useFetchHostIsolationExceptionsList({ + filter, + page, + perPage, + policies, + enabled = true, +}: { + filter?: string; + page: number; + perPage: number; + policies?: string[]; + enabled?: boolean; +}): QueryObserverResult { const http = useHttp(); - const location = useHostIsolationExceptionsSelector(getCurrentLocation); return useQuery( - ['hostIsolationExceptions', 'list', location.filter, location.page_size, location.page_index], + ['hostIsolationExceptions', 'list', filter, perPage, page, policies], () => { + const kql = parsePoliciesAndFilterToKql({ + policies, + kuery: filter ? parseQueryFilterToKQL(filter, SEARCHABLE_FIELDS) : undefined, + }); + return getHostIsolationExceptionItems({ http, - page: location.page_index + 1, - perPage: location.page_size, - filter: parseQueryFilterToKQL(location.filter, SEARCHABLE_FIELDS) || undefined, + page: page + 1, + perPage, + filter: kql, }); - } + }, + { enabled } ); } @@ -114,7 +128,7 @@ export function useGetHostIsolationExceptionFormEntry({ }: { id?: string; onSuccess: (data: CreateExceptionListItemSchema | UpdateExceptionListItemSchema) => void; - onError: (error: ServerApiError) => void; + onError?: (error: ServerApiError) => void; }): QueryObserverResult { const http = useHttp(); return useQuery( diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx index 94bd6ea73d7fa..c53371167c536 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.test.tsx @@ -91,7 +91,9 @@ describe('When on the host isolation exceptions page', () => { describe('And data exists', () => { beforeEach(async () => { - getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock); + getHostIsolationExceptionItemsMock.mockImplementation(() => + getFoundExceptionListItemSchemaMock(1) + ); }); it('should show loading indicator while retrieving data and hide it when it gets it', async () => { @@ -185,7 +187,9 @@ describe('When on the host isolation exceptions page', () => { describe('has canIsolateHost privileges', () => { beforeEach(async () => { setEndpointPrivileges({ canIsolateHost: true }); - getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock); + getHostIsolationExceptionItemsMock.mockImplementation(() => + getFoundExceptionListItemSchemaMock(1) + ); }); it('should show the create flyout when the add button is pressed', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx index d4dcb105cdb51..d883f47b34ff5 100644 --- a/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/host_isolation_exceptions/view/host_isolation_exceptions_list.tsx @@ -55,7 +55,12 @@ export const HostIsolationExceptionsList = () => { const [itemToDelete, setItemToDelete] = useState(null); - const { isLoading, data, error, refetch } = useFetchHostIsolationExceptionsList(); + const { isLoading, data, error, refetch } = useFetchHostIsolationExceptionsList({ + filter: location.filter, + page: location.page_index, + perPage: location.page_size, + }); + const toasts = useToasts(); // load the list of policies> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx index e2c817b47a1b6..249345a0a0ad8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/index.tsx @@ -13,6 +13,7 @@ import { MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD, + MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, } from '../../common/constants'; import { NotFoundPage } from '../../../app/404'; import { getPolicyDetailPath } from '../../common/routing'; @@ -25,6 +26,7 @@ export const PolicyContainer = memo(() => { MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, ]} exact component={PolicyDetails} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts index 5ac0ebca82ebb..40953b927e935 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_common_selectors.ts @@ -7,12 +7,13 @@ import { matchPath } from 'react-router-dom'; import { createSelector } from 'reselect'; -import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types'; import { MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, } from '../../../../../common/constants'; +import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types'; /** * Returns current artifacts location @@ -37,7 +38,7 @@ export const isOnPolicyFormView: PolicyDetailsSelector = createSelector } ); -/** Returns a boolean of whether the user is on the policy trusted app page or not */ +/** Returns a boolean of whether the user is on the policy trusted apps page or not */ export const isOnPolicyTrustedAppsView: PolicyDetailsSelector = createSelector( getUrlLocationPathname, (pathname) => { @@ -62,3 +63,16 @@ export const isOnPolicyEventFiltersView: PolicyDetailsSelector = create ); } ); + +/** Returns a boolean of whether the user is on the host isolation exceptions page or not */ +export const isOnHostIsolationExceptionsView: PolicyDetailsSelector = createSelector( + getUrlLocationPathname, + (pathname) => { + return ( + matchPath(pathname ?? '', { + path: MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, + exact: true, + }) !== null + ); + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts index 05b8edae48efd..a1a4c62d70734 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors/policy_settings_selectors.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { createSelector } from 'reselect'; import { matchPath } from 'react-router-dom'; +import { createSelector } from 'reselect'; import { ILicense } from '../../../../../../../../licensing/common/types'; import { unsetPolicyFeaturesAccordingToLicenseLevel } from '../../../../../../../common/license/policy_config'; import { PolicyDetailsState } from '../../../types'; @@ -20,6 +20,7 @@ import { import { policyFactory as policyConfigFactory } from '../../../../../../../common/endpoint/models/policy_config'; import { MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, } from '../../../../../common/constants'; @@ -28,6 +29,7 @@ import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/ser import { isOnPolicyTrustedAppsView, isOnPolicyEventFiltersView, + isOnHostIsolationExceptionsView, isOnPolicyFormView, } from './policy_common_selectors'; @@ -90,7 +92,8 @@ export const needsToRefresh = (state: Immutable): boolean => export const isOnPolicyDetailsPage = (state: Immutable) => isOnPolicyFormView(state) || isOnPolicyTrustedAppsView(state) || - isOnPolicyEventFiltersView(state); + isOnPolicyEventFiltersView(state) || + isOnHostIsolationExceptionsView(state); /** Returns the license info fetched from the license service */ export const license = (state: Immutable) => { @@ -107,6 +110,7 @@ export const policyIdFromParams: (state: Immutable) => strin MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH, MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH, + MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, ], exact: true, })?.params?.policyId ?? '' diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unassigned.tsx new file mode 100644 index 0000000000000..e9f7fefe312da --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unassigned.tsx @@ -0,0 +1,53 @@ +/* + * 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 { EuiEmptyPrompt, EuiLink, EuiPageTemplate } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +export const PolicyHostIsolationExceptionsEmptyUnassigned = ({ + policyName, + toHostIsolationList, +}: { + policyName: string; + toHostIsolationList: string; +}) => { + return ( + + + + + } + body={ + + } + actions={[ + + + , + ]} + /> + + ); +}; + +PolicyHostIsolationExceptionsEmptyUnassigned.displayName = + 'PolicyHostIsolationExceptionsEmptyUnassigned'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx new file mode 100644 index 0000000000000..da3a48ac2570a --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/empty_unexisting.tsx @@ -0,0 +1,50 @@ +/* + * 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 { EuiButton, EuiEmptyPrompt, EuiPageTemplate } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React from 'react'; + +export const PolicyHostIsolationExceptionsEmptyUnexisting = ({ + toHostIsolationList, +}: { + toHostIsolationList: string; +}) => { + return ( + + + + + } + body={ + + } + actions={ + + + + } + /> + + ); +}; + +PolicyHostIsolationExceptionsEmptyUnexisting.displayName = + 'PolicyHostIsolationExceptionsEmptyUnexisting'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx new file mode 100644 index 0000000000000..cb126afac24e8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.test.tsx @@ -0,0 +1,94 @@ +/* + * 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 { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { act } from '@testing-library/react'; +import React from 'react'; +import uuid from 'uuid'; +import { getPolicyHostIsolationExceptionsPath } from '../../../../../common/routing'; +import { getFoundExceptionListItemSchemaMock } from '../../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; +import { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../../common/mock/endpoint'; +import { PolicyHostIsolationExceptionsList } from './list'; +import userEvent from '@testing-library/user-event'; + +const emptyList = { + data: [], + page: 1, + per_page: 10, + total: 0, +}; + +describe('Policy details host isolation exceptions tab', () => { + let policyId: string; + let render: ( + exceptions: FoundExceptionListItemSchema + ) => ReturnType; + let renderResult: ReturnType; + let history: AppContextTestRender['history']; + let mockedContext: AppContextTestRender; + + beforeEach(() => { + policyId = uuid.v4(); + mockedContext = createAppRootMockRenderer(); + ({ history } = mockedContext); + render = (exceptions: FoundExceptionListItemSchema) => + (renderResult = mockedContext.render( + + )); + + act(() => { + history.push(getPolicyHostIsolationExceptionsPath(policyId)); + }); + }); + + it('should display a searchbar and count even with no exceptions', () => { + render(emptyList); + expect( + renderResult.getByTestId('policyDetailsHostIsolationExceptionsSearchCount') + ).toHaveTextContent('Showing 0 exceptions'); + expect(renderResult.getByTestId('searchField')).toBeTruthy(); + }); + + it('should render the list of exceptions collapsed and expand it when clicked', () => { + // render 3 + render(getFoundExceptionListItemSchemaMock(3)); + expect(renderResult.getAllByTestId('hostIsolationExceptions-collapsed-list-card')).toHaveLength( + 3 + ); + expect( + renderResult.queryAllByTestId( + 'hostIsolationExceptions-collapsed-list-card-criteriaConditions' + ) + ).toHaveLength(0); + }); + + it('should expand an item when expand is clicked', () => { + render(getFoundExceptionListItemSchemaMock(1)); + expect(renderResult.getAllByTestId('hostIsolationExceptions-collapsed-list-card')).toHaveLength( + 1 + ); + + userEvent.click( + renderResult.getByTestId('hostIsolationExceptions-collapsed-list-card-header-expandCollapse') + ); + + expect( + renderResult.queryAllByTestId( + 'hostIsolationExceptions-collapsed-list-card-criteriaConditions' + ) + ).toHaveLength(1); + }); + + it('should change the address location when a filter is applied', () => { + render(getFoundExceptionListItemSchemaMock(1)); + userEvent.type(renderResult.getByTestId('searchField'), 'search me{enter}'); + expect(history.location.search).toBe('?filter=search%20me'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx new file mode 100644 index 0000000000000..4bc52d7f191c1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/components/list.tsx @@ -0,0 +1,143 @@ +/* + * 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 { EuiSpacer, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import React, { useCallback, useMemo, useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { + MANAGEMENT_DEFAULT_PAGE_SIZE, + MANAGEMENT_PAGE_SIZE_OPTIONS, +} from '../../../../../common/constants'; +import { getPolicyHostIsolationExceptionsPath } from '../../../../../common/routing'; +import { + ArtifactCardGrid, + ArtifactCardGridProps, +} from '../../../../../components/artifact_card_grid'; +import { useEndpointPoliciesToArtifactPolicies } from '../../../../../components/artifact_entry_card/hooks/use_endpoint_policies_to_artifact_policies'; +import { SearchExceptions } from '../../../../../components/search_exceptions'; +import { useGetEndpointSpecificPolicies } from '../../../../../services/policies/hooks'; +import { getCurrentArtifactsLocation } from '../../../store/policy_details/selectors'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; + +export const PolicyHostIsolationExceptionsList = ({ + exceptions, + policyId, +}: { + exceptions: FoundExceptionListItemSchema; + policyId: string; +}) => { + const history = useHistory(); + // load the list of policies> + const policiesRequest = useGetEndpointSpecificPolicies(); + const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation); + + const [expandedItemsMap, setExpandedItemsMap] = useState>(new Map()); + + const pagination = { + totalItemCount: exceptions?.total ?? 0, + pageSize: exceptions?.per_page ?? MANAGEMENT_DEFAULT_PAGE_SIZE, + pageSizeOptions: [...MANAGEMENT_PAGE_SIZE_OPTIONS], + pageIndex: (exceptions?.page ?? 1) - 1, + }; + + const handlePageChange = useCallback( + ({ pageIndex, pageSize }) => { + history.push( + getPolicyHostIsolationExceptionsPath(policyId, { + ...urlParams, + // If user changed page size, then reset page index back to the first page + page_index: pageIndex, + page_size: pageSize, + }) + ); + }, + [history, policyId, urlParams] + ); + + const handleSearchInput = useCallback( + (filter: string) => { + history.push( + getPolicyHostIsolationExceptionsPath(policyId, { + ...urlParams, + filter, + }) + ); + }, + [history, policyId, urlParams] + ); + + const artifactCardPolicies = useEndpointPoliciesToArtifactPolicies(policiesRequest.data?.items); + + const provideCardProps: ArtifactCardGridProps['cardComponentProps'] = (item) => { + return { + expanded: expandedItemsMap.get(item.id) || false, + actions: [], + policies: artifactCardPolicies, + }; + }; + + const handleExpandCollapse: ArtifactCardGridProps['onExpandCollapse'] = ({ + expanded, + collapsed, + }) => { + const newExpandedMap = new Map(expandedItemsMap); + for (const item of expanded) { + newExpandedMap.set(item.id, true); + } + for (const item of collapsed) { + newExpandedMap.set(item.id, false); + } + setExpandedItemsMap(newExpandedMap); + }; + + const totalItemsCountLabel = useMemo(() => { + return i18n.translate( + 'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.totalItemCount', + { + defaultMessage: 'Showing {totalItemsCount, plural, one {# exception} other {# exceptions}}', + values: { totalItemsCount: pagination.totalItemCount }, + } + ); + }, [pagination.totalItemCount]); + + return ( + <> + + + + {totalItemsCountLabel} + + + + + ); +}; +PolicyHostIsolationExceptionsList.displayName = 'PolicyHostIsolationExceptionsList'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.test.tsx new file mode 100644 index 0000000000000..3ccb4ea4d445e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.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 { getFoundExceptionListItemSchemaMock } from '../../../../../../../lists/common/schemas/response/found_exception_list_item_schema.mock'; +import { EndpointDocGenerator } from '../../../../../../common/endpoint/generate_data'; +import { PolicyData } from '../../../../../../common/endpoint/types'; +import { + AppContextTestRender, + createAppRootMockRenderer, +} from '../../../../../common/mock/endpoint'; +import { getPolicyHostIsolationExceptionsPath } from '../../../../common/routing'; +import { getHostIsolationExceptionItems } from '../../../host_isolation_exceptions/service'; +import { PolicyHostIsolationExceptionsTab } from './host_isolation_exceptions_tab'; + +jest.mock('../../../host_isolation_exceptions/service'); + +const getHostIsolationExceptionItemsMock = getHostIsolationExceptionItems as jest.Mock; + +const endpointGenerator = new EndpointDocGenerator('seed'); + +const emptyList = { + data: [], + page: 1, + per_page: 10, + total: 0, +}; + +describe('Policy details host isolation exceptions tab', () => { + let policyId: string; + let policy: PolicyData; + let render: () => ReturnType; + let renderResult: ReturnType; + let history: AppContextTestRender['history']; + let mockedContext: AppContextTestRender; + + beforeEach(() => { + getHostIsolationExceptionItemsMock.mockClear(); + policy = endpointGenerator.generatePolicyPackagePolicy(); + policyId = policy.id; + mockedContext = createAppRootMockRenderer(); + ({ history } = mockedContext); + render = () => + (renderResult = mockedContext.render()); + + history.push(getPolicyHostIsolationExceptionsPath(policyId)); + }); + + it('should display display a "loading" state while requests happen', async () => { + const promises: Array<() => void> = []; + getHostIsolationExceptionItemsMock.mockImplementation(() => { + return new Promise((resolve) => promises.push(resolve)); + }); + render(); + expect(await renderResult.findByTestId('policyHostIsolationExceptionsTabLoading')).toBeTruthy(); + // prevent memory leaks + promises.forEach((resolve) => resolve()); + }); + + it("should display an 'unexistent' empty state if there are no host isolation exceptions at all", async () => { + // mock no data for all requests + getHostIsolationExceptionItemsMock.mockResolvedValue({ + ...emptyList, + }); + render(); + expect( + await renderResult.findByTestId('policy-host-isolation-exceptions-empty-unexisting') + ).toBeTruthy(); + }); + + it("should display an 'unassigned' empty state if there are no host isolation exceptions assigned", async () => { + // mock no data for all requests + getHostIsolationExceptionItemsMock.mockImplementation((params) => { + // no filter = fetch all exceptions + if (!params.filter) { + return { + ...emptyList, + total: 1, + }; + } + return { + ...emptyList, + }; + }); + render(); + expect( + await renderResult.findByTestId('policy-host-isolation-exceptions-empty-unassigned') + ).toBeTruthy(); + }); + + it('Should display the count of total assigned policies', async () => { + getHostIsolationExceptionItemsMock.mockImplementation(() => { + return getFoundExceptionListItemSchemaMock(4); + }); + render(); + expect( + await renderResult.findByTestId('policyHostIsolationExceptionsTabSubtitle') + ).toHaveTextContent('There are 4 exceptions associated with this policy'); + }); + + it('should apply a filter when requested from location search params', async () => { + history.push(getPolicyHostIsolationExceptionsPath(policyId, { filter: 'my filter' })); + getHostIsolationExceptionItemsMock.mockImplementation(() => { + return getFoundExceptionListItemSchemaMock(4); + }); + render(); + expect(getHostIsolationExceptionItemsMock).toHaveBeenLastCalledWith({ + filter: `((exception-list-agnostic.attributes.tags:"policy:${policyId}" OR exception-list-agnostic.attributes.tags:"policy:all")) AND ((exception-list-agnostic.attributes.name:(*my*filter*) OR exception-list-agnostic.attributes.description:(*my*filter*) OR exception-list-agnostic.attributes.entries.value:(*my*filter*)))`, + http: mockedContext.coreStart.http, + page: 1, + perPage: 10, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx new file mode 100644 index 0000000000000..c2df907917c5e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/host_isolation_exceptions/host_isolation_exceptions_tab.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { + EuiLink, + EuiPageContent, + EuiPageHeader, + EuiPageHeaderSection, + EuiProgress, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; +import { APP_UI_ID } from '../../../../../../common/constants'; +import { PolicyData } from '../../../../../../common/endpoint/types'; +import { useAppUrl } from '../../../../../common/lib/kibana'; +import { + MANAGEMENT_DEFAULT_PAGE, + MANAGEMENT_DEFAULT_PAGE_SIZE, +} from '../../../../common/constants'; +import { getHostIsolationExceptionsListPath } from '../../../../common/routing'; +import { useFetchHostIsolationExceptionsList } from '../../../host_isolation_exceptions/view/hooks'; +import { getCurrentArtifactsLocation } from '../../store/policy_details/selectors'; +import { usePolicyDetailsSelector } from '../policy_hooks'; +import { PolicyHostIsolationExceptionsEmptyUnexisting } from './components/empty_unexisting'; +import { PolicyHostIsolationExceptionsEmptyUnassigned } from './components/empty_unassigned'; +import { PolicyHostIsolationExceptionsList } from './components/list'; + +export const PolicyHostIsolationExceptionsTab = ({ policy }: { policy: PolicyData }) => { + const { getAppUrl } = useAppUrl(); + + const policyId = policy.id; + + const location = usePolicyDetailsSelector(getCurrentArtifactsLocation); + const toHostIsolationList = getAppUrl({ + appId: APP_UI_ID, + path: getHostIsolationExceptionsListPath(), + }); + + const allPolicyExceptionsListRequest = useFetchHostIsolationExceptionsList({ + page: MANAGEMENT_DEFAULT_PAGE, + perPage: MANAGEMENT_DEFAULT_PAGE_SIZE, + policies: [policyId, 'all'], + }); + + const policySearchedExceptionsListRequest = useFetchHostIsolationExceptionsList({ + filter: location.filter, + page: location.page_index, + perPage: location.page_size, + policies: [policyId, 'all'], + }); + + const allExceptionsListRequest = useFetchHostIsolationExceptionsList({ + page: MANAGEMENT_DEFAULT_PAGE, + perPage: MANAGEMENT_DEFAULT_PAGE_SIZE, + // only do this request if no assigned policies found + enabled: allPolicyExceptionsListRequest.data?.total === 0, + }); + + const hasNoAssignedOrExistingExceptions = allPolicyExceptionsListRequest.data?.total === 0; + const hasNoExistingExceptions = allExceptionsListRequest.data?.total === 0; + + const subTitle = useMemo(() => { + const link = ( + + + + ); + + return policySearchedExceptionsListRequest.data ? ( + + ) : null; + }, [ + allPolicyExceptionsListRequest.data?.total, + getAppUrl, + policySearchedExceptionsListRequest.data, + toHostIsolationList, + ]); + + const isLoading = + policySearchedExceptionsListRequest.isLoading || + allPolicyExceptionsListRequest.isLoading || + allExceptionsListRequest.isLoading || + !policy; + + // render non-existent or non-assigned messages + if (!isLoading && (hasNoAssignedOrExistingExceptions || hasNoExistingExceptions)) { + if (hasNoExistingExceptions) { + return ( + + ); + } else { + return ( + + ); + } + } + + // render header and list + return !isLoading && policySearchedExceptionsListRequest.data ? ( +
+ + + +

+ {i18n.translate( + 'xpack.securitySolution.endpoint.policy.hostIsolationExceptions.list.title', + { + defaultMessage: 'Assigned host isolation exceptions', + } + )} +

+
+ + + + +

{subTitle}

+
+
+
+ + + + + +
+ ) : ( + + ); +}; +PolicyHostIsolationExceptionsTab.displayName = 'PolicyHostIsolationExceptionsTab'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 4159b1ed62f65..fa99d72523531 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -127,6 +127,7 @@ describe('Policy Details', () => { expect(pageTitle).toHaveLength(1); expect(pageTitle.text()).toEqual(policyPackagePolicy.name); }); + it('should navigate to list if back to link is clicked', async () => { policyView.update(); @@ -135,6 +136,7 @@ describe('Policy Details', () => { backToListLink.simulate('click', { button: 0 }); expect(history.location.pathname).toEqual(endpointListPath); }); + it('should display agent stats', async () => { await asyncActions; policyView.update(); @@ -143,6 +145,7 @@ describe('Policy Details', () => { expect(agentsSummary).toHaveLength(1); expect(agentsSummary.text()).toBe('Total agents5Healthy3Unhealthy1Offline1'); }); + it('should display event filters tab', async () => { await asyncActions; policyView.update(); @@ -151,5 +154,11 @@ describe('Policy Details', () => { expect(eventFiltersTab).toHaveLength(1); expect(eventFiltersTab.text()).toBe('Event filters'); }); + + it('should display the host isolation exceptions tab', async () => { + await asyncActions; + policyView.update(); + expect(policyView.find('#hostIsolationExceptions')).toBeTruthy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx index efb113f38df1c..e2c2ef939603e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/tabs/policy_tabs.tsx @@ -5,34 +5,37 @@ * 2.0. */ +import { EuiSpacer, EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React, { useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; -import { EuiTabbedContent, EuiSpacer, EuiTabbedContentTab } from '@elastic/eui'; - -import { usePolicyDetailsSelector } from '../policy_hooks'; +import { PolicyData } from '../../../../../../common/endpoint/types'; import { + getPolicyDetailPath, + getPolicyEventFiltersPath, + getPolicyHostIsolationExceptionsPath, + getPolicyTrustedAppsPath, +} from '../../../../common/routing'; +import { + isOnHostIsolationExceptionsView, + isOnPolicyEventFiltersView, isOnPolicyFormView, isOnPolicyTrustedAppsView, - isOnPolicyEventFiltersView, - policyIdFromParams, policyDetails, + policyIdFromParams, } from '../../store/policy_details/selectors'; - -import { PolicyTrustedAppsLayout } from '../trusted_apps/layout'; import { PolicyEventFiltersLayout } from '../event_filters/layout'; +import { PolicyHostIsolationExceptionsTab } from '../host_isolation_exceptions/host_isolation_exceptions_tab'; import { PolicyFormLayout } from '../policy_forms/components'; -import { - getPolicyDetailPath, - getPolicyTrustedAppsPath, - getPolicyEventFiltersPath, -} from '../../../../common/routing'; +import { usePolicyDetailsSelector } from '../policy_hooks'; +import { PolicyTrustedAppsLayout } from '../trusted_apps/layout'; export const PolicyTabs = React.memo(() => { const history = useHistory(); const isInSettingsTab = usePolicyDetailsSelector(isOnPolicyFormView); const isInTrustedAppsTab = usePolicyDetailsSelector(isOnPolicyTrustedAppsView); const isInEventFilters = usePolicyDetailsSelector(isOnPolicyEventFiltersView); + const isInHostIsolationExceptionsTab = usePolicyDetailsSelector(isOnHostIsolationExceptionsView); const policyId = usePolicyDetailsSelector(policyIdFromParams); const policyItem = usePolicyDetailsSelector(policyDetails); @@ -74,6 +77,21 @@ export const PolicyTabs = React.memo(() => { ), }, + { + id: 'hostIsolationExceptions', + name: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.tabs.isInHostIsolationExceptions', + { + defaultMessage: 'Host isolation exceptions', + } + ), + content: ( + <> + + + + ), + }, ], [policyItem] ); @@ -87,19 +105,30 @@ export const PolicyTabs = React.memo(() => { initialTab = tabs[1]; } else if (isInEventFilters) { initialTab = tabs[2]; + } else if (isInHostIsolationExceptionsTab) { + initialTab = tabs[3]; } return initialTab; - }, [isInSettingsTab, isInTrustedAppsTab, isInEventFilters, tabs]); + }, [isInSettingsTab, isInTrustedAppsTab, isInEventFilters, isInHostIsolationExceptionsTab, tabs]); const onTabClickHandler = useCallback( (selectedTab: EuiTabbedContentTab) => { - const path = - selectedTab.id === 'settings' - ? getPolicyDetailPath(policyId) - : selectedTab.id === 'trustedApps' - ? getPolicyTrustedAppsPath(policyId) - : getPolicyEventFiltersPath(policyId); + let path: string = ''; + switch (selectedTab.id) { + case 'settings': + path = getPolicyDetailPath(policyId); + break; + case 'trustedApps': + path = getPolicyTrustedAppsPath(policyId); + break; + case 'hostIsolationExceptions': + path = getPolicyHostIsolationExceptionsPath(policyId); + break; + case 'eventFilters': + path = getPolicyEventFiltersPath(policyId); + break; + } history.push(path); }, [history, policyId] diff --git a/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts b/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts index d59b2ca984131..1a0c7ec74d451 100644 --- a/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts +++ b/x-pack/plugins/security_solution/public/management/services/policies/hooks.ts @@ -13,8 +13,8 @@ import { sendGetEndpointSpecificPackagePolicies } from './policies'; export function useGetEndpointSpecificPolicies({ onError, }: { - onError: (error: ServerApiError) => void; -}): QueryObserverResult { + onError?: (error: ServerApiError) => void; +} = {}): QueryObserverResult { const http = useHttp(); return useQuery( ['endpointSpecificPolicies'], From 43654e95451d16ef1fa8be31297e9eee578319f8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 13 Dec 2021 17:25:49 +0100 Subject: [PATCH 131/145] wait for vis before asserting on it (#121083) --- x-pack/test/functional/apps/lens/smokescreen.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 7cacee6446723..7064b11113fad 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -59,6 +59,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await listingTable.searchForItemWithName('Afancilenstest'); await PageObjects.lens.clickVisualizeListItemTitle('Afancilenstest'); await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.waitForVisualization(); expect(await PageObjects.lens.getTitle()).to.eql('Afancilenstest'); @@ -80,6 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { keepOpen: true, }); await PageObjects.lens.addFilterToAgg(`geo.src : CN`); + await PageObjects.lens.waitForVisualization(); // Verify that the field was persisted from the transition expect(await PageObjects.lens.getFiltersAggLabels()).to.eql([`ip : *`, `geo.src : CN`]); From 93cac0ca03c4b4be3372bb849fae33d805e96a03 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 13 Dec 2021 09:29:41 -0700 Subject: [PATCH 132/145] =?UTF-8?q?[maps]=20fix=20flaky=20test=20functiona?= =?UTF-8?q?l/apps/maps/embeddable/dashboard=C2=B7js=20(#120771)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * unskip * .only * add retry around failing section * waitForLayersToLoad * remove only * eslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../functional/apps/maps/embeddable/dashboard.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js index c92567e246736..a670ebdb4ea23 100644 --- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js @@ -18,8 +18,7 @@ export default function ({ getPageObjects, getService }) { const retry = getService('retry'); const security = getService('security'); - // FLAKY: https://github.com/elastic/kibana/issues/113993 - describe.skip('embed in dashboard', () => { + describe('embed in dashboard', () => { before(async () => { await security.testUser.setRoles( [ @@ -96,8 +95,15 @@ export default function ({ getPageObjects, getService }) { it('should apply new container state (time, query, filters) to embeddable', async () => { await filterBar.selectIndexPattern('logstash-*'); await filterBar.addFilter('machine.os', 'is', 'win 8'); - await filterBar.selectIndexPattern('meta_for_geo_shapes*'); - await filterBar.addFilter('shape_name', 'is', 'alpha'); // runtime fields do not have autocomplete + await PageObjects.maps.waitForLayersToLoad(); + + // retry is fix for flaky test https://github.com/elastic/kibana/issues/113993 + // timing issue where click for addFilter opens filter pill created above instead of clicking addFilter + await retry.try(async () => { + await filterBar.selectIndexPattern('meta_for_geo_shapes*'); + await filterBar.addFilter('shape_name', 'is', 'alpha'); // runtime fields do not have autocomplete + }); + await PageObjects.maps.waitForLayersToLoad(); const { rawResponse: gridResponse } = await PageObjects.maps.getResponseFromDashboardPanel( 'geo grid vector grid example' From 8550977c321a5d5302acf29d03b1a1e26970a4d7 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 13 Dec 2021 09:31:09 -0700 Subject: [PATCH 133/145] =?UTF-8?q?[maps]=20fix=20test/functional/apps/map?= =?UTF-8?q?s/embeddable/tooltip=5Ffilter=5Factions=C2=B7js=20(#120854)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [maps] fix test/functional/apps/maps/embeddable/tooltip_filter_actions·js * add method to wait for layers with collapsed layer control * type * unskip flaky test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/layer_control.test.tsx.snap | 4 ++++ .../right_side_controls/layer_control/layer_control.tsx | 2 ++ .../apps/maps/embeddable/tooltip_filter_actions.js | 7 ++++--- x-pack/test/functional/page_objects/gis_page.ts | 8 ++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap index 047f0087c559f..7d0c67ff41797 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/__snapshots__/layer_control.test.tsx.snap @@ -89,6 +89,7 @@ exports[`LayerControl isLayerTOCOpen Should render expand button 1`] = ` aria-label="Expand layers panel" className="mapLayerControl__openLayerTOCButton" color="text" + data-test-subj="mapExpandLayerControlButton" iconType="menuLeft" onClick={[Function]} /> @@ -106,6 +107,7 @@ exports[`LayerControl isLayerTOCOpen Should render expand button with error icon aria-label="Expand layers panel" className="mapLayerControl__openLayerTOCButton" color="text" + data-test-subj="mapExpandLayerControlButton" iconType="alert" onClick={[Function]} /> @@ -123,6 +125,7 @@ exports[`LayerControl isLayerTOCOpen spinner icon Should not render expand butto aria-label="Expand layers panel" className="mapLayerControl__openLayerTOCButton" color="text" + data-test-subj="mapExpandLayerControlButton" iconType="menuLeft" onClick={[Function]} /> @@ -139,6 +142,7 @@ exports[`LayerControl isLayerTOCOpen spinner icon Should render expand button wi @@ -66,6 +67,7 @@ function renderExpandButton({ onClick={onClick} iconType={hasErrors ? 'alert' : 'menuLeft'} aria-label={expandLabel} + data-test-subj="mapExpandLayerControlButton" /> ); } diff --git a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js index 400c687d6af88..c3c014447a36d 100644 --- a/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js +++ b/x-pack/test/functional/apps/maps/embeddable/tooltip_filter_actions.js @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; export default function ({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['common', 'dashboard', 'discover', 'maps']); + const PageObjects = getPageObjects(['common', 'dashboard', 'discover', 'header', 'maps']); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); @@ -41,8 +41,7 @@ export default function ({ getPageObjects, getService }) { await security.testUser.restoreDefaults(); }); - // FLAKY: https://github.com/elastic/kibana/issues/117780 - describe.skip('apply filter to current view', () => { + describe('apply filter to current view', () => { before(async () => { await loadDashboardAndOpenTooltip(); }); @@ -54,6 +53,8 @@ export default function ({ getPageObjects, getService }) { it('should create filters when create filter button is clicked', async () => { await testSubjects.click('mapTooltipCreateFilterButton'); + await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); + await PageObjects.maps.waitForLayersToLoadMinimizedLayerControl(); const numFilters = await filterBar.getFilterCount(); expect(numFilters).to.be(1); diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 629402a0a4768..89b80cf4ccf95 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -106,6 +106,14 @@ export class GisPageObject extends FtrService { }); } + async waitForLayersToLoadMinimizedLayerControl() { + this.log.debug('Wait for layers to load (minimized layer control)'); + await this.retry.try(async () => { + const tableOfContents = await this.testSubjects.find('mapExpandLayerControlButton'); + await tableOfContents.waitForDeletedByCssSelector('.euiLoadingSpinner'); + }); + } + async waitForLayerDeleted(layerName: string) { this.log.debug('Wait for layer deleted'); await this.retry.waitFor('Layer to be deleted', async () => { From 5a9d4cb35c6e7d85b02fe784b0120e2cd6c13d10 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 13 Dec 2021 09:32:39 -0700 Subject: [PATCH 134/145] =?UTF-8?q?[maps]=20fix=20flaky=20test=20/function?= =?UTF-8?q?al/apps/maps/add=5Flayer=5Fpanel=C2=B7js=20(#120990)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [maps] fix flaky test /functional/apps/maps/add_layer_panel·js * eslint Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../geo_index_pattern_select.test.tsx.snap | 2 ++ .../components/geo_index_pattern_select.tsx | 1 + .../functional/apps/maps/add_layer_panel.js | 19 ++++++------------- .../test/functional/page_objects/gis_page.ts | 12 +++++++++++- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap b/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap index 070000a2f6b98..fc10eceb45601 100644 --- a/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap +++ b/x-pack/plugins/maps/public/components/__snapshots__/geo_index_pattern_select.test.tsx.snap @@ -14,6 +14,7 @@ exports[`should render 1`] = ` labelType="label" > { placeholder={getDataViewSelectPlaceholder()} onNoIndexPatterns={this._onNoIndexPatterns} isClearable={false} + data-test-subj="mapGeoIndexPatternSelect" /> diff --git a/x-pack/test/functional/apps/maps/add_layer_panel.js b/x-pack/test/functional/apps/maps/add_layer_panel.js index 37c4c2c9b14c6..b11694679e172 100644 --- a/x-pack/test/functional/apps/maps/add_layer_panel.js +++ b/x-pack/test/functional/apps/maps/add_layer_panel.js @@ -13,14 +13,12 @@ export default function ({ getService, getPageObjects }) { const security = getService('security'); describe('Add layer panel', () => { - const LAYER_NAME = 'World Countries'; - before(async () => { - await security.testUser.setRoles(['global_maps_all']); + await security.testUser.setRoles(['global_maps_all', 'test_logstash_reader']); await PageObjects.maps.openNewMap(); await PageObjects.maps.clickAddLayer(); - await PageObjects.maps.selectEMSBoundariesSource(); - await PageObjects.maps.selectVectorLayer(LAYER_NAME); + await PageObjects.maps.selectDocumentsSource(); + await PageObjects.maps.selectGeoIndexPatternLayer('logstash-*'); }); after(async () => { @@ -28,7 +26,7 @@ export default function ({ getService, getPageObjects }) { }); it('should show unsaved layer in layer TOC', async () => { - const vectorLayerExists = await PageObjects.maps.doesLayerExist(LAYER_NAME); + const vectorLayerExists = await PageObjects.maps.doesLayerExist('logstash-*'); expect(vectorLayerExists).to.be(true); }); @@ -36,17 +34,12 @@ export default function ({ getService, getPageObjects }) { const mapSaveButton = await testSubjects.find('mapSaveButton'); const isDisabled = await mapSaveButton.getAttribute('disabled'); expect(isDisabled).to.be('true'); - - const panelOpen = await PageObjects.maps.isLayerAddPanelOpen(); - expect(panelOpen).to.be(true); - const vectorLayerExists = await PageObjects.maps.doesLayerExist(LAYER_NAME); - expect(vectorLayerExists).to.be(true); }); it('should remove layer on cancel', async () => { - await PageObjects.maps.cancelLayerAdd(LAYER_NAME); + await PageObjects.maps.cancelLayerAdd('logstash-*'); - const vectorLayerExists = await PageObjects.maps.doesLayerExist(LAYER_NAME); + const vectorLayerExists = await PageObjects.maps.doesLayerExist('logstash-*'); expect(vectorLayerExists).to.be(false); }); }); diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 89b80cf4ccf95..c0e035e4ec4e0 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -529,6 +529,17 @@ export class GisPageObject extends FtrService { await this.waitForLayersToLoad(); } + async selectDocumentsSource() { + this.log.debug(`Select Documents source`); + await this.testSubjects.click('documents'); + } + + async selectGeoIndexPatternLayer(name: string) { + this.log.debug(`Select index pattern ${name}`); + await this.comboBox.set('mapGeoIndexPatternSelect', name); + await this.waitForLayersToLoad(); + } + async selectEMSBoundariesSource() { this.log.debug(`Select Elastic Maps Service boundaries source`); await this.testSubjects.click('emsBoundaries'); @@ -547,7 +558,6 @@ export class GisPageObject extends FtrService { await this.waitForLayersToLoad(); } - // Returns first layer by default async selectVectorLayer(vectorLayerName: string) { this.log.debug(`Select EMS vector layer ${vectorLayerName}`); if (!vectorLayerName) { From 322e2a48aa75700dc132b8dc217e5733f3fd3473 Mon Sep 17 00:00:00 2001 From: Sergi Massaneda Date: Mon, 13 Dec 2021 17:34:01 +0100 Subject: [PATCH 135/145] [Cases] Fix flaky test on form submit (#121073) * change to use simulate click for submitting * lint * type check --- .../public/components/create/form.test.tsx | 48 +++++++------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/cases/public/components/create/form.test.tsx b/x-pack/plugins/cases/public/components/create/form.test.tsx index 9f1e2e6c6dda3..9a4af9bdc3a62 100644 --- a/x-pack/plugins/cases/public/components/create/form.test.tsx +++ b/x-pack/plugins/cases/public/components/create/form.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { mount } from 'enzyme'; -import { act, render, waitFor } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import { useForm, Form, FormHook } from '../../common/shared_imports'; import { useGetTags } from '../../containers/use_get_tags'; import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/mock'; import { schema, FormProps } from './schema'; -import { CreateCaseForm, CreateCaseFormFields, CreateCaseFormProps } from './form'; +import { CreateCaseForm, CreateCaseFormProps } from './form'; import { useCaseConfigure } from '../../containers/configure/use_configure'; import { useCaseConfigureResponse } from '../configure_cases/__mock__'; import { TestProviders } from '../../common/mock'; @@ -114,34 +114,22 @@ describe('CreateCaseForm', () => { expect(queryByText('Sync alert')).not.toBeInTheDocument(); }); - describe('CreateCaseFormFields', () => { - it('should render spinner when loading', async () => { - const wrapper = mount( - - - - ); - - await act(async () => { - globalForm.setFieldValue('title', 'title'); - globalForm.setFieldValue('description', 'description'); - globalForm.submit(); - // For some weird reason this is needed to pass the test. - // It does not do anything useful - await wrapper.find(`[data-test-subj="caseTitle"]`); - await wrapper.update(); - }); - - await waitFor(() => { - expect( - wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists() - ).toBeTruthy(); - }); + it('should render spinner when loading', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy(); + + await act(async () => { + globalForm.setFieldValue('title', 'title'); + globalForm.setFieldValue('description', 'description'); + await wrapper.find(`button[data-test-subj="create-case-submit"]`).simulate('click'); + wrapper.update(); }); + + expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy(); }); }); From 9b5efb27335a30eae265c626fe45628ca8a003e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 13 Dec 2021 11:39:24 -0500 Subject: [PATCH 136/145] [APM] Add comparision to service maps popover (#120839) * adding comparison to service maps * adding tests * addressing pr comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../service_map/Popover/backend_contents.tsx | 27 ++- .../service_map/Popover/service_contents.tsx | 29 ++- .../app/service_map/Popover/stats_list.tsx | 122 ++++++++----- .../components/app/service_map/index.tsx | 2 +- .../get_service_map_backend_node_info.ts | 28 ++- .../get_service_map_service_node_info.ts | 34 +++- .../apm/server/routes/service_map/route.ts | 39 ++-- .../tests/service_maps/service_maps.spec.ts | 166 +++++++++++++++--- 8 files changed, 334 insertions(+), 113 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx index 8fa93e22a90fe..a1e4cbe67e65c 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/backend_contents.tsx @@ -12,12 +12,20 @@ import { METRIC_TYPE } from '@kbn/analytics'; import React from 'react'; import { useUiTracker } from '../../../../../../observability/public'; import { ContentsProps } from '.'; -import { NodeStats } from '../../../../../common/service_map'; import { useAnyOfApmParams } from '../../../../hooks/use_apm_params'; import { useApmRouter } from '../../../../hooks/use_apm_router'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { ApmRoutes } from '../../../routing/apm_route_config'; import { StatsList } from './stats_list'; +import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; + +type BackendReturn = APIReturnType<'GET /internal/apm/service-map/backend'>; + +const INITIAL_STATE: Partial = { + currentPeriod: undefined, + previousPeriod: undefined, +}; export function BackendContents({ nodeData, @@ -30,11 +38,20 @@ export function BackendContents({ '/services/{serviceName}/service-map' ); + const { comparisonEnabled, comparisonType } = query; + + const { offset } = getTimeRangeComparison({ + start, + end, + comparisonEnabled, + comparisonType, + }); + const apmRouter = useApmRouter(); const backendName = nodeData.label; - const { data = { transactionStats: {} } as NodeStats, status } = useFetcher( + const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { if (backendName) { return callApmApi({ @@ -45,15 +62,13 @@ export function BackendContents({ environment, start, end, + offset, }, }, }); } }, - [environment, backendName, start, end], - { - preservePreviousData: false, - } + [environment, backendName, start, end, offset] ); const isLoading = status === FETCH_STATUS.LOADING; diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx index 10d558e648376..b0ca933e64819 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/service_contents.tsx @@ -17,12 +17,21 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useApmParams } from '../../../../hooks/use_apm_params'; import type { ContentsProps } from '.'; -import { NodeStats } from '../../../../../common/service_map'; import { useApmRouter } from '../../../../hooks/use_apm_router'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { AnomalyDetection } from './anomaly_detection'; import { StatsList } from './stats_list'; import { useTimeRange } from '../../../../hooks/use_time_range'; +import { getTimeRangeComparison } from '../../../shared/time_comparison/get_time_range_comparison'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; + +type ServiceNodeReturn = + APIReturnType<'GET /internal/apm/service-map/service/{serviceName}'>; + +const INITIAL_STATE: ServiceNodeReturn = { + currentPeriod: {}, + previousPeriod: undefined, +}; export function ServiceContents({ onFocusClick, @@ -42,28 +51,32 @@ export function ServiceContents({ throw new Error('Expected rangeFrom and rangeTo to be set'); } - const { rangeFrom, rangeTo } = query; + const { rangeFrom, rangeTo, comparisonEnabled, comparisonType } = query; const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + const { offset } = getTimeRangeComparison({ + start, + end, + comparisonEnabled, + comparisonType, + }); + const serviceName = nodeData.id!; - const { data = { transactionStats: {} } as NodeStats, status } = useFetcher( + const { data = INITIAL_STATE, status } = useFetcher( (callApmApi) => { if (serviceName && start && end) { return callApmApi({ endpoint: 'GET /internal/apm/service-map/service/{serviceName}', params: { path: { serviceName }, - query: { environment, start, end }, + query: { environment, start, end, offset }, }, }); } }, - [environment, serviceName, start, end], - { - preservePreviousData: false, - } + [environment, serviceName, start, end, offset] ); const isLoading = status === FETCH_STATUS.LOADING; diff --git a/x-pack/plugins/apm/public/components/app/service_map/Popover/stats_list.tsx b/x-pack/plugins/apm/public/components/app/service_map/Popover/stats_list.tsx index 002c480503454..1b8e1f64859f4 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/Popover/stats_list.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/Popover/stats_list.tsx @@ -14,15 +14,18 @@ import { import { i18n } from '@kbn/i18n'; import { isNumber } from 'lodash'; import React, { useMemo } from 'react'; -import { NodeStats } from '../../../../../common/service_map'; import { asDuration, asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; import { Coordinate } from '../../../../../typings/timeseries'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { SparkPlot, Color } from '../../../shared/charts/spark_plot'; +type ServiceNodeReturn = + APIReturnType<'GET /internal/apm/service-map/service/{serviceName}'>; + function LoadingSpinner() { return ( ; } interface Item { title: string; valueLabel: string | null; timeseries?: Coordinate[]; + previousPeriodTimeseries?: Coordinate[]; color: Color; } export function StatsList({ data, isLoading }: StatsListProps) { + const { currentPeriod = {}, previousPeriod } = data; const { cpuUsage, failedTransactionsRate, memoryUsage, transactionStats } = - data; + currentPeriod; const hasData = [ cpuUsage?.value, @@ -78,10 +83,10 @@ export function StatsList({ data, isLoading }: StatsListProps) { defaultMessage: 'Latency (avg.)', } ), - valueLabel: isNumber(transactionStats?.latency?.value) - ? asDuration(transactionStats?.latency?.value) - : null, - timeseries: transactionStats?.latency?.timeseries, + valueLabel: asDuration(currentPeriod?.transactionStats?.latency?.value), + timeseries: currentPeriod?.transactionStats?.latency?.timeseries, + previousPeriodTimeseries: + previousPeriod?.transactionStats?.latency?.timeseries, color: 'euiColorVis1', }, { @@ -91,24 +96,35 @@ export function StatsList({ data, isLoading }: StatsListProps) { defaultMessage: 'Throughput (avg.)', } ), - valueLabel: asTransactionRate(transactionStats?.throughput?.value), - timeseries: transactionStats?.throughput?.timeseries, + valueLabel: asTransactionRate( + currentPeriod?.transactionStats?.throughput?.value + ), + timeseries: currentPeriod?.transactionStats?.throughput?.timeseries, + previousPeriodTimeseries: + previousPeriod?.transactionStats?.throughput?.timeseries, color: 'euiColorVis0', }, { title: i18n.translate('xpack.apm.serviceMap.errorRatePopoverStat', { defaultMessage: 'Failed transaction rate (avg.)', }), - valueLabel: asPercent(failedTransactionsRate?.value, 1, ''), - timeseries: failedTransactionsRate?.timeseries, + valueLabel: asPercent( + currentPeriod?.failedTransactionsRate?.value, + 1, + '' + ), + timeseries: currentPeriod?.failedTransactionsRate?.timeseries, + previousPeriodTimeseries: + previousPeriod?.failedTransactionsRate?.timeseries, color: 'euiColorVis7', }, { title: i18n.translate('xpack.apm.serviceMap.avgCpuUsagePopoverStat', { defaultMessage: 'CPU usage (avg.)', }), - valueLabel: asPercent(cpuUsage?.value, 1, ''), - timeseries: cpuUsage?.timeseries, + valueLabel: asPercent(currentPeriod?.cpuUsage?.value, 1, ''), + timeseries: currentPeriod?.cpuUsage?.timeseries, + previousPeriodTimeseries: previousPeriod?.cpuUsage?.timeseries, color: 'euiColorVis3', }, { @@ -118,15 +134,16 @@ export function StatsList({ data, isLoading }: StatsListProps) { defaultMessage: 'Memory usage (avg.)', } ), - valueLabel: asPercent(memoryUsage?.value, 1, ''), - timeseries: memoryUsage?.timeseries, + valueLabel: asPercent(currentPeriod?.memoryUsage?.value, 1, ''), + timeseries: currentPeriod?.memoryUsage?.timeseries, + previousPeriodTimeseries: previousPeriod?.memoryUsage?.timeseries, color: 'euiColorVis8', }, ], - [cpuUsage, failedTransactionsRate, memoryUsage, transactionStats] + [currentPeriod, previousPeriod] ); - if (isLoading) { + if (isLoading && !hasData) { return ; } @@ -136,38 +153,47 @@ export function StatsList({ data, isLoading }: StatsListProps) { return ( - {items.map(({ title, valueLabel, timeseries, color }) => { - if (!valueLabel) { - return null; + {items.map( + ({ + title, + valueLabel, + timeseries, + color, + previousPeriodTimeseries, + }) => { + if (!valueLabel) { + return null; + } + return ( + + + + + {title} + + + + {timeseries ? ( + + ) : ( +
{valueLabel}
+ )} +
+
+
+ ); } - return ( - - - - - {title} - - - - {timeseries ? ( - - ) : ( -
{valueLabel}
- )} -
-
-
- ); - })} + )}
); } diff --git a/x-pack/plugins/apm/public/components/app/service_map/index.tsx b/x-pack/plugins/apm/public/components/app/service_map/index.tsx index 0ec1e6630003a..ff19029243d07 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/index.tsx @@ -180,7 +180,7 @@ export function ServiceMap({ return ( <> - +
{ return withApmSpan('get_service_map_backend_node_stats', async () => { const { apmEventClient } = setup; + const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); - const { intervalString } = getBucketSize({ start, end, numBuckets: 20 }); + const { intervalString } = getBucketSize({ + start: startWithOffset, + end: endWithOffset, + numBuckets: 20, + }); const subAggs = { latency_sum: { @@ -66,7 +78,7 @@ export function getServiceMapBackendNodeInfo({ bool: { filter: [ { term: { [SPAN_DESTINATION_SERVICE_RESOURCE]: backendName } }, - ...rangeQuery(start, end), + ...rangeQuery(startWithOffset, endWithOffset), ...environmentQuery(environment), ], }, @@ -78,7 +90,7 @@ export function getServiceMapBackendNodeInfo({ field: '@timestamp', fixed_interval: intervalString, min_doc_count: 0, - extended_bounds: { min: start, max: end }, + extended_bounds: { min: startWithOffset, max: endWithOffset }, }, aggs: subAggs, }, @@ -95,8 +107,8 @@ export function getServiceMapBackendNodeInfo({ const avgFailedTransactionsRate = failedTransactionsRateCount / count; const latency = latencySum / count; const throughput = calculateThroughputWithRange({ - start, - end, + start: startWithOffset, + end: endWithOffset, value: count, }); @@ -116,7 +128,7 @@ export function getServiceMapBackendNodeInfo({ timeseries: response.aggregations?.timeseries ? getFailedTransactionRateTimeSeries( response.aggregations.timeseries.buckets - ) + ).map(({ x, y }) => ({ x: x + offsetInMs, y })) : undefined, }, transactionStats: { @@ -125,7 +137,7 @@ export function getServiceMapBackendNodeInfo({ timeseries: response.aggregations?.timeseries.buckets.map( (bucket) => { return { - x: bucket.key, + x: bucket.key + offsetInMs, y: calculateThroughputWithRange({ start, end, @@ -139,7 +151,7 @@ export function getServiceMapBackendNodeInfo({ value: latency, timeseries: response.aggregations?.timeseries.buckets.map( (bucket) => ({ - x: bucket.key, + x: bucket.key + offsetInMs, y: bucket.latency_sum.value, }) ), diff --git a/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts index 545fb4dbc4606..ec6c13de76fb1 100644 --- a/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/routes/service_map/get_service_map_service_node_info.ts @@ -22,6 +22,7 @@ import { TRANSACTION_REQUEST, } from '../../../common/transaction_types'; import { environmentQuery } from '../../../common/utils/environment_query'; +import { getOffsetInMs } from '../../../common/utils/get_offset_in_ms'; import { getBucketSizeForAggregatedTransactions } from '../../lib/helpers/get_bucket_size_for_aggregated_transactions'; import { Setup } from '../../lib/helpers/setup_request'; import { @@ -43,6 +44,7 @@ interface Options { searchAggregatedTransactions: boolean; start: number; end: number; + offset?: string; } interface TaskParameters { @@ -57,6 +59,7 @@ interface TaskParameters { intervalString: string; bucketSize: number; numBuckets: number; + offsetInMs: number; } export function getServiceMapServiceNodeInfo({ @@ -66,11 +69,18 @@ export function getServiceMapServiceNodeInfo({ searchAggregatedTransactions, start, end, + offset, }: Options): Promise { return withApmSpan('get_service_map_node_stats', async () => { + const { offsetInMs, startWithOffset, endWithOffset } = getOffsetInMs({ + start, + end, + offset, + }); + const filter: ESFilter[] = [ { term: { [SERVICE_NAME]: serviceName } }, - ...rangeQuery(start, end), + ...rangeQuery(startWithOffset, endWithOffset), ...environmentQuery(environment), ]; @@ -90,11 +100,12 @@ export function getServiceMapServiceNodeInfo({ minutes, serviceName, setup, - start, - end, + start: startWithOffset, + end: endWithOffset, intervalString, bucketSize, numBuckets, + offsetInMs, }; const [failedTransactionsRate, transactionStats, cpuUsage, memoryUsage] = @@ -121,6 +132,7 @@ async function getFailedTransactionsRateStats({ start, end, numBuckets, + offsetInMs, }: TaskParameters): Promise { return withApmSpan('get_error_rate_for_service_map_node', async () => { const { average, timeseries } = await getFailedTransactionRate({ @@ -133,7 +145,10 @@ async function getFailedTransactionsRateStats({ kuery: '', numBuckets, }); - return { value: average, timeseries }; + return { + value: average, + timeseries: timeseries.map(({ x, y }) => ({ x: x + offsetInMs, y })), + }; }); } @@ -145,6 +160,7 @@ async function getTransactionStats({ start, end, intervalString, + offsetInMs, }: TaskParameters): Promise { const { apmEventClient } = setup; @@ -204,7 +220,7 @@ async function getTransactionStats({ latency: { value: response.aggregations?.duration.value ?? null, timeseries: response.aggregations?.timeseries.buckets.map((bucket) => ({ - x: bucket.key, + x: bucket.key + offsetInMs, y: bucket.latency.value, })), }, @@ -212,7 +228,7 @@ async function getTransactionStats({ value: totalRequests > 0 ? totalRequests / minutes : null, timeseries: response.aggregations?.timeseries.buckets.map((bucket) => { return { - x: bucket.key, + x: bucket.key + offsetInMs, y: bucket.doc_count ?? 0, }; }), @@ -226,6 +242,7 @@ async function getCpuStats({ intervalString, start, end, + offsetInMs, }: TaskParameters): Promise { const { apmEventClient } = setup; @@ -266,7 +283,7 @@ async function getCpuStats({ return { value: response.aggregations?.avgCpuUsage.value ?? null, timeseries: response.aggregations?.timeseries.buckets.map((bucket) => ({ - x: bucket.key, + x: bucket.key + offsetInMs, y: bucket.cpuAvg.value, })), }; @@ -278,6 +295,7 @@ function getMemoryStats({ intervalString, start, end, + offsetInMs, }: TaskParameters) { return withApmSpan('get_memory_stats_for_service_map_node', async () => { const { apmEventClient } = setup; @@ -324,7 +342,7 @@ function getMemoryStats({ return { value: response.aggregations?.avgMemoryUsage.value ?? null, timeseries: response.aggregations?.timeseries.buckets.map((bucket) => ({ - x: bucket.key, + x: bucket.key + offsetInMs, y: bucket.memoryAvg.value, })), }; diff --git a/x-pack/plugins/apm/server/routes/service_map/route.ts b/x-pack/plugins/apm/server/routes/service_map/route.ts index 97d0c01ed6a44..6b002e913204b 100644 --- a/x-pack/plugins/apm/server/routes/service_map/route.ts +++ b/x-pack/plugins/apm/server/routes/service_map/route.ts @@ -17,7 +17,7 @@ import { getServiceMapBackendNodeInfo } from './get_service_map_backend_node_inf import { getServiceMapServiceNodeInfo } from './get_service_map_service_node_info'; import { createApmServerRoute } from '../apm_routes/create_apm_server_route'; import { createApmServerRouteRepository } from '../apm_routes/create_apm_server_route_repository'; -import { environmentRt, rangeRt } from '../default_api_types'; +import { environmentRt, offsetRt, rangeRt } from '../default_api_types'; const serviceMapRoute = createApmServerRoute({ endpoint: 'GET /internal/apm/service-map', @@ -75,7 +75,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ path: t.type({ serviceName: t.string, }), - query: t.intersection([environmentRt, rangeRt]), + query: t.intersection([environmentRt, rangeRt, offsetRt]), }), options: { tags: ['access:apm'] }, handler: async (resources) => { @@ -91,7 +91,7 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ const { path: { serviceName }, - query: { environment, start, end }, + query: { environment, start, end, offset }, } = params; const searchAggregatedTransactions = await getSearchAggregatedTransactions({ @@ -102,14 +102,23 @@ const serviceMapServiceNodeRoute = createApmServerRoute({ kuery: '', }); - return getServiceMapServiceNodeInfo({ + const commonProps = { environment, setup, serviceName, searchAggregatedTransactions, start, end, - }); + }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + getServiceMapServiceNodeInfo(commonProps), + offset + ? getServiceMapServiceNodeInfo({ ...commonProps, offset }) + : undefined, + ]); + + return { currentPeriod, previousPeriod }; }, }); @@ -120,6 +129,7 @@ const serviceMapBackendNodeRoute = createApmServerRoute({ t.type({ backendName: t.string }), environmentRt, rangeRt, + offsetRt, ]), }), options: { tags: ['access:apm'] }, @@ -135,16 +145,19 @@ const serviceMapBackendNodeRoute = createApmServerRoute({ const setup = await setupRequest(resources); const { - query: { backendName, environment, start, end }, + query: { backendName, environment, start, end, offset }, } = params; - return getServiceMapBackendNodeInfo({ - environment, - setup, - backendName, - start, - end, - }); + const commonProps = { environment, setup, backendName, start, end }; + + const [currentPeriod, previousPeriod] = await Promise.all([ + getServiceMapBackendNodeInfo(commonProps), + offset + ? getServiceMapBackendNodeInfo({ ...commonProps, offset }) + : undefined, + ]); + + return { currentPeriod, previousPeriod }; }, }); diff --git a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.spec.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.spec.ts index d4f0e350071bf..454b129464c00 100644 --- a/x-pack/test/apm_api_integration/tests/service_maps/service_maps.spec.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.spec.ts @@ -6,7 +6,7 @@ */ import expect from '@kbn/expect'; -import { isEmpty, orderBy, uniq } from 'lodash'; +import { first, isEmpty, last, orderBy, uniq } from 'lodash'; import { ServiceConnectionNode } from '../../../../plugins/apm/common/service_map'; import { ApmApiError, SupertestReturnType } from '../../common/apm_api_supertest'; import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; @@ -90,11 +90,11 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) it('returns an object with nulls', async () => { [ - response.body.failedTransactionsRate?.value, - response.body.memoryUsage?.value, - response.body.cpuUsage?.value, - response.body.transactionStats?.latency?.value, - response.body.transactionStats?.throughput?.value, + response.body.currentPeriod?.failedTransactionsRate?.value, + response.body.currentPeriod?.memoryUsage?.value, + response.body.currentPeriod?.cpuUsage?.value, + response.body.currentPeriod?.transactionStats?.latency?.value, + response.body.currentPeriod?.transactionStats?.throughput?.value, ].forEach((value) => { expect(value).to.be.eql(null); }); @@ -122,7 +122,7 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); it('returns undefined values', () => { - expect(response.body).to.eql({ transactionStats: {} }); + expect(response.body.currentPeriod).to.eql({ transactionStats: {} }); }); }); }); @@ -343,23 +343,31 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); it('returns some error rate', () => { - expect(response.body.failedTransactionsRate?.value).to.eql(0); - expect(response.body.failedTransactionsRate?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.failedTransactionsRate?.value).to.eql(0); + expect( + response.body.currentPeriod?.failedTransactionsRate?.timeseries?.length + ).to.be.greaterThan(0); }); it('returns some latency', () => { - expect(response.body.transactionStats?.latency?.value).to.be.greaterThan(0); - expect(response.body.transactionStats?.latency?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.transactionStats?.latency?.value).to.be.greaterThan(0); + expect( + response.body.currentPeriod?.transactionStats?.latency?.timeseries?.length + ).to.be.greaterThan(0); }); it('returns some throughput', () => { - expect(response.body.transactionStats?.throughput?.value).to.be.greaterThan(0); - expect(response.body.transactionStats?.throughput?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.transactionStats?.throughput?.value).to.be.greaterThan( + 0 + ); + expect( + response.body.currentPeriod?.transactionStats?.throughput?.timeseries?.length + ).to.be.greaterThan(0); }); it('returns some cpu usage', () => { - expect(response.body.cpuUsage?.value).to.be.greaterThan(0); - expect(response.body.cpuUsage?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.cpuUsage?.value).to.be.greaterThan(0); + expect(response.body.currentPeriod?.cpuUsage?.timeseries?.length).to.be.greaterThan(0); }); }); @@ -384,18 +392,134 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) }); it('returns some error rate', () => { - expect(response.body.failedTransactionsRate?.value).to.eql(0); - expect(response.body.failedTransactionsRate?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.failedTransactionsRate?.value).to.eql(0); + expect( + response.body.currentPeriod?.failedTransactionsRate?.timeseries?.length + ).to.be.greaterThan(0); }); it('returns some latency', () => { - expect(response.body.transactionStats?.latency?.value).to.be.greaterThan(0); - expect(response.body.transactionStats?.latency?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.transactionStats?.latency?.value).to.be.greaterThan(0); + expect( + response.body.currentPeriod?.transactionStats?.latency?.timeseries?.length + ).to.be.greaterThan(0); }); it('returns some throughput', () => { - expect(response.body.transactionStats?.throughput?.value).to.be.greaterThan(0); - expect(response.body.transactionStats?.throughput?.timeseries?.length).to.be.greaterThan(0); + expect(response.body.currentPeriod?.transactionStats?.throughput?.value).to.be.greaterThan( + 0 + ); + expect( + response.body.currentPeriod?.transactionStats?.throughput?.timeseries?.length + ).to.be.greaterThan(0); + }); + }); + + describe('With comparison', () => { + describe('/internal/apm/service-map/backend', () => { + let response: BackendResponse; + before(async () => { + response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/service-map/backend`, + params: { + query: { + backendName: 'postgresql', + start: metadata.start, + end: metadata.end, + environment: 'ENVIRONMENT_ALL', + offset: '5m', + }, + }, + }); + }); + + it('returns some data', () => { + const { currentPeriod, previousPeriod } = response.body; + [ + currentPeriod.failedTransactionsRate, + previousPeriod?.failedTransactionsRate, + currentPeriod.transactionStats?.latency, + previousPeriod?.transactionStats?.latency, + currentPeriod.transactionStats?.throughput, + previousPeriod?.transactionStats?.throughput, + ].map((value) => expect(value?.timeseries?.length).to.be.greaterThan(0)); + }); + + it('has same start time for both periods', () => { + const { currentPeriod, previousPeriod } = response.body; + expect(first(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal( + first(previousPeriod?.failedTransactionsRate?.timeseries)?.x + ); + }); + + it('has same end time for both periods', () => { + const { currentPeriod, previousPeriod } = response.body; + expect(last(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal( + last(previousPeriod?.failedTransactionsRate?.timeseries)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + const { currentPeriod, previousPeriod } = response.body; + expect(currentPeriod.failedTransactionsRate?.timeseries?.length).to.be( + previousPeriod?.failedTransactionsRate?.timeseries?.length + ); + }); + }); + + describe('/internal/apm/service-map/service/{serviceName}', () => { + let response: ServiceNodeResponse; + before(async () => { + response = await apmApiClient.readUser({ + endpoint: `GET /internal/apm/service-map/service/{serviceName}`, + params: { + path: { serviceName: 'opbeans-node' }, + query: { + start: metadata.start, + end: metadata.end, + environment: 'ENVIRONMENT_ALL', + offset: '5m', + }, + }, + }); + }); + + it('returns some data', () => { + const { currentPeriod, previousPeriod } = response.body; + [ + currentPeriod.failedTransactionsRate, + previousPeriod?.failedTransactionsRate, + currentPeriod.transactionStats?.latency, + previousPeriod?.transactionStats?.latency, + currentPeriod.transactionStats?.throughput, + previousPeriod?.transactionStats?.throughput, + currentPeriod.cpuUsage, + previousPeriod?.cpuUsage, + currentPeriod.memoryUsage, + previousPeriod?.memoryUsage, + ].map((value) => expect(value?.timeseries?.length).to.be.greaterThan(0)); + }); + + it('has same start time for both periods', () => { + const { currentPeriod, previousPeriod } = response.body; + expect(first(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal( + first(previousPeriod?.failedTransactionsRate?.timeseries)?.x + ); + }); + + it('has same end time for both periods', () => { + const { currentPeriod, previousPeriod } = response.body; + expect(last(currentPeriod.failedTransactionsRate?.timeseries)?.x).to.equal( + last(previousPeriod?.failedTransactionsRate?.timeseries)?.x + ); + }); + + it('returns same number of buckets for both periods', () => { + const { currentPeriod, previousPeriod } = response.body; + expect(currentPeriod.failedTransactionsRate?.timeseries?.length).to.be( + previousPeriod?.failedTransactionsRate?.timeseries?.length + ); + }); }); }); }); From 6c4c5e12995caa56d66add3f40741ae640ea3445 Mon Sep 17 00:00:00 2001 From: Stacey Gammon Date: Mon, 13 Dec 2021 11:47:03 -0500 Subject: [PATCH 137/145] Some updates to our dev docs (#120981) * Master -> main update branching strategy with make it minor * Follow the new dev docs process and keep nav inside this repo * add back nav links that are in a different repo Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- dev_docs/api_welcome.mdx | 2 +- dev_docs/contributing/best_practices.mdx | 9 +- dev_docs/contributing/code_walkthrough.mdx | 2 +- dev_docs/contributing/how_we_use_github.mdx | 26 ++- dev_docs/tutorials/submit_a_pull_request.mdx | 4 +- nav-kibana-dev.docnav.json | 160 ++++++++++++++++++ src/dev/precommit_hook/casing_check_config.js | 3 + 7 files changed, 186 insertions(+), 20 deletions(-) create mode 100644 nav-kibana-dev.docnav.json diff --git a/dev_docs/api_welcome.mdx b/dev_docs/api_welcome.mdx index cca911cc6cdd0..cf88bf7eec0da 100644 --- a/dev_docs/api_welcome.mdx +++ b/dev_docs/api_welcome.mdx @@ -44,7 +44,7 @@ This documentation is being automatically generated using an There is one extra step required to have your API docs show up in the _navigation_ of the docs system. Follow the instructions to learn how to configure the navigation menu. The nav file you need to - edit is: [https://github.com/elastic/elastic-docs/blob/master/config/nav-kibana-dev.ts](https://github.com/elastic/elastic-docs/blob/master/config/nav-kibana-dev.ts) + edit is: [https://github.com/elastic/elastic-docs/blob/main/config/nav-kibana-dev.ts](https://github.com/elastic/elastic-docs/blob/main/config/nav-kibana-dev.ts) Your API docs will exist in the top level [`api_docs` folder](https://github.com/elastic/kibana/tree/main/api_docs) and will use a doc id of the pattern `kib${PluginName}PluginApi`. diff --git a/dev_docs/contributing/best_practices.mdx b/dev_docs/contributing/best_practices.mdx index d0ae34155d7eb..d7aa42946eac3 100644 --- a/dev_docs/contributing/best_practices.mdx +++ b/dev_docs/contributing/best_practices.mdx @@ -141,6 +141,9 @@ export type foo: string | AnInterface; Running Kibana with `yarn start --run-examples` will include all [example plugins](https://github.com/elastic/kibana/tree/main/examples). These are tested examples of platform services in use. We strongly encourage anyone providing a platform level service or to include a tutorial that links to a tested example plugin. This is better than relying on copied code snippets, which can quickly get out of date. +You can also visit these [examples plugins hosted online](https://demo.kibana.dev/8.0/app/home). Note that because anonymous access is enabled, some +of the demos are currently not working. + ## Performance Build with scalability in mind. @@ -150,6 +153,8 @@ Build with scalability in mind. - Consider large data sets, that span a long time range - Consider slow internet and low bandwidth environments + + ## Accessibility Did you know Kibana makes a public statement about our commitment to creating an accessible product for people @@ -202,13 +207,13 @@ Kibana code base, try not to contribute to this volatility. Doing this can: All of the above contributes to more bugs being found in the QA cycle and can cause a delay in the release. Prefer instead to merge your large change right _after_ feature freeze. If you are worried about missing your initial release version goals, review our -[release train philophy](https://github.com/elastic/dev/blob/master/shared/time-based-releases.md). It's okay! +. It's okay! ### Size -When possible, build features with incrementals sets of small and focused PRs, but don't check in unused code, and don't expose any feature on master that you would not be comfortable releasing. +When possible, build features with incremental sets of small and focused PRs, but don't check in unused code, and don't expose any feature on main that you would not be comfortable releasing. ![product_stages](../assets/product_stages.png) diff --git a/dev_docs/contributing/code_walkthrough.mdx b/dev_docs/contributing/code_walkthrough.mdx index 62965add07578..74995c246503c 100644 --- a/dev_docs/contributing/code_walkthrough.mdx +++ b/dev_docs/contributing/code_walkthrough.mdx @@ -21,7 +21,7 @@ Managed by the operations team to contain Jenkins settings. Can be ignored by fo ## [.github](https://github.com/elastic/kibana/tree/main/.github) -Contains GitHub configuration settings. This file contains issue templates, and the [CODEOWNERS](https://github.com/elastic/kibana/blob/main/.github/CODEOWNERS) file. It's important for teams to keep the CODEOWNERS file up-to-date so the right team is pinged for a code owner review on PRs that edit certain files. Note that the `CODEOWNERS` file only exists on the main/master branch, and is not backported to other branches in the repo. +Contains GitHub configuration settings. This file contains issue templates, and the [CODEOWNERS](https://github.com/elastic/kibana/blob/main/.github/CODEOWNERS) file. It's important for teams to keep the CODEOWNERS file up-to-date so the right team is pinged for a code owner review on PRs that edit certain files. Note that the `CODEOWNERS` file only exists on the main branch, and is not backported to other branches in the repo. ## [api_docs](https://github.com/elastic/kibana/tree/main/api_docs) diff --git a/dev_docs/contributing/how_we_use_github.mdx b/dev_docs/contributing/how_we_use_github.mdx index 38391874b87bf..247fca97335c7 100644 --- a/dev_docs/contributing/how_we_use_github.mdx +++ b/dev_docs/contributing/how_we_use_github.mdx @@ -15,16 +15,14 @@ We follow the [GitHub forking model](https://help.github.com/articles/fork-a-rep At Elastic, all products in the stack, including Kibana, are released at the same time with the same version number. Most of these projects have the following branching strategy: -- master is the next major version. -- `.x` is the next minor version. -- `.` is the next release of a minor version, including patch releases. +- `main` points to the next minor version. +- `.` is the previously released minor version, including patch releases. -As an example, let’s assume that the 7.x branch is currently a not-yet-released 7.6.0. Once 7.6.0 has reached feature freeze, it will be branched to 7.6 and 7.x will be updated to reflect 7.7.0. The release of 7.6.0 and subsequent patch releases will be cut from the 7.6 branch. At any time, you can verify the current version of a branch by inspecting the version attribute in the package.json file within the Kibana source. +As an example, let’s assume that the main branch is currently a not-yet-released 8.1.0. Once 8.1.0 has reached feature freeze, it will be branched to 8.1 and main will be updated to reflect 8.2.0. The release of 8.1.0 and subsequent patch releases will be cut from the 8.1 branch. At any time, you can verify the current version of a branch by inspecting the version attribute in the package.json file within the Kibana source. -Pull requests are made into the master branch and then backported when it is safe and appropriate. +Pull requests are made into the main branch and only backported when it is safe and appropriate. -- Breaking changes do not get backported and only go into master. -- All non-breaking changes can be backported to the `.x` branch. +- Breaking changes can _only_ be made to `main` if there has been at least an 18 month deprecation period _and_ the breaking change has been approved. Telemetry showing current usage is crucial for gaining approval. - Features should not be backported to a `.` branch. - Bug fixes can be backported to a `.` branch if the changes are safe and appropriate. Safety is a judgment call you make based on factors like the bug’s severity, test coverage, confidence in the changes, etc. Your reasoning should be included in the pull request description. - Documentation changes can be backported to any branch at any time. @@ -63,26 +61,26 @@ In order to assist with developer tooling we ask that all Elastic engineers use Rebasing can be tricky, and fixing merge conflicts can be even trickier because it involves force pushing. This is all compounded by the fact that attempting to push a rebased branch remotely will be rejected by git, and you’ll be prompted to do a pull, which is not at all what you should do (this will really mess up your branch’s history). -Here’s how you should rebase master onto your branch, and how to fix merge conflicts when they arise. +Here’s how you should rebase main onto your branch, and how to fix merge conflicts when they arise. -First, make sure master is up-to-date. +First, make sure main is up-to-date. ```bash -git checkout master +git checkout main git fetch upstream -git rebase upstream/master +git rebase upstream/main ``` -Then, check out your branch and rebase master on top of it, which will apply all of the new commits on master to your branch, and then apply all of your branch’s new commits after that. +Then, check out your branch and rebase main on top of it, which will apply all of the new commits on main to your branch, and then apply all of your branch’s new commits after that. ```bash git checkout name-of-your-branch -git rebase master +git rebase main ``` You want to make sure there are no merge conflicts. If there are merge conflicts, git will pause the rebase and allow you to fix the conflicts before continuing. -You can use git status to see which files contain conflicts. They’ll be the ones that aren’t staged for commit. Open those files, and look for where git has marked the conflicts. Resolve the conflicts so that the changes you want to make to the code have been incorporated in a way that doesn’t destroy work that’s been done in master. Refer to master’s commit history on GitHub if you need to gain a better understanding of how code is conflicting and how best to resolve it. +You can use git status to see which files contain conflicts. They’ll be the ones that aren’t staged for commit. Open those files, and look for where git has marked the conflicts. Resolve the conflicts so that the changes you want to make to the code have been incorporated in a way that doesn’t destroy work that’s been done in main. Refer to main commit history on GitHub if you need to gain a better understanding of how code is conflicting and how best to resolve it. Once you’ve resolved all of the merge conflicts, use git add -A to stage them to be committed, and then use git rebase --continue to tell git to continue the rebase. diff --git a/dev_docs/tutorials/submit_a_pull_request.mdx b/dev_docs/tutorials/submit_a_pull_request.mdx index 5436ebf24e03e..f7d530f6cec66 100644 --- a/dev_docs/tutorials/submit_a_pull_request.mdx +++ b/dev_docs/tutorials/submit_a_pull_request.mdx @@ -23,7 +23,7 @@ After cloning your fork and navigating to the directory containing your fork: ```bash # Make sure you currently have the branch checked out off of which you'd like to work -git checkout master +git checkout main # Create a new branch git checkout -b fix-typos-in-readme @@ -76,7 +76,7 @@ See [Pull request review guidelines](https://www.elastic.co/guide/en/kibana/mast ## Updating your PR with upstream -If your pull request hasn't been updated with the latest code from the upstream/target branch, e.g. `master`, in the last 48 hours, it won't be able to merge until it is updated. This is to help prevent problems that could occur by merging stale code into upstream, e.g. something new was recently merged that is incompatible with something in your pull request. +If your pull request hasn't been updated with the latest code from the upstream/target branch, e.g. `main`, in the last 48 hours, it won't be able to merge until it is updated. This is to help prevent problems that could occur by merging stale code into upstream, e.g. something new was recently merged that is incompatible with something in your pull request. As an alternative to using `git` to manually update your branch, you can leave a comment on your pull request with the text `@elasticmachine merge upstream`. This will automatically update your branch and kick off CI for it. diff --git a/nav-kibana-dev.docnav.json b/nav-kibana-dev.docnav.json new file mode 100644 index 0000000000000..384652f373358 --- /dev/null +++ b/nav-kibana-dev.docnav.json @@ -0,0 +1,160 @@ +{ + "mission": "Kibana Developer Guide", + "id": "kibDevDocs", + "landingPageId": "kibDevDocsWelcome", + "icon": "logoKibana", + "description": "Developer documentation for building custom Kibana plugins and extending Kibana functionality.", + "items": [ + { + "category": "Getting started", + "items": [ + { "id": "kibDevDocsWelcome" }, + { "id": "kibDevTutorialSetupDevEnv" }, + { "id": "kibHelloWorldApp" }, + { "id": "kibDevAddData" }, + { "id": "kibTroubleshooting" } + ] + }, + { + "category": "Key concepts", + "items": [ + { "id": "kibPlatformIntro" }, + { "id": "kibDevAnatomyOfAPlugin" }, + { "id": "kibDevPerformance" }, + { "id": "kibBuildingBlocks" }, + { "id": "kibDevDocsSavedObjectsIntro", "label": "Saved objects" }, + { "id": "kibDevDocsPersistableStateIntro" }, + { "id": "kibDataPlugin", "label": "Data" }, + { "id": "kibCoreLogging" }, + { "id": "kibUsageCollectionPlugin" }, + { "id": "kibDataViewsKeyConcepts" }, + { "id": "kibDevKeyConceptsNavigation" } + ] + }, + { + "category": "Tutorials", + "items": [ + { "id": "kibDevTutorialTestingPlugins" }, + { "id": "kibDevTutorialSavedObject" }, + { "id": "kibDevTutorialSubmitPullRequest" }, + { "id": "kibDevTutorialExpressions" }, + { "id": "kibDevDocsKPTTutorial" }, + { "id": "kibDevTutorialDataSearchAndSessions", "label": "data.search" }, + { "id": "kibDevTutorialDataViews" }, + { "id": "kibDevTutorialDebugging" }, + { + "id": "kibDevTutorialBuildingDistributable", + "label": "Building a Kibana distributable" + }, + { "id": "kibDevTutorialServerEndpoint" } + ] + }, + { + "category": "Contributing", + "items": [ + { "id": "kibRepoStructure" }, + { "id": "kibDevPrinciples" }, + { "id": "kibStandards" }, + { "id": "ktRFCProcess" }, + { "id": "kibBestPractices" }, + { "id": "kibStyleGuide" }, + { "id": "kibGitHub" } + ] + }, + { + "category": "Contributors Newsletters", + "items": [ + { "id": "kibNovember2021ContributorNewsletter" }, + { "id": "kibOctober2021ContributorNewsletter" }, + { "id": "kibSeptember2021ContributorNewsletter" }, + { "id": "kibAugust2021ContributorNewsletter" }, + { "id": "kibJuly2021ContributorNewsletter" }, + { "id": "kibJune2021ContributorNewsletter" }, + { "id": "kibMay2021ContributorNewsletter" }, + { "id": "kibApril2021ContributorNewsletter" }, + { "id": "kibMarch2021ContributorNewsletter" } + ] + }, + { + "category": "API documentation", + "items": [ + { "id": "kibDevDocsApiWelcome" }, + { "id": "kibDevDocsPluginDirectory" }, + { "id": "kibDevDocsDeprecationsByPlugin" }, + { "id": "kibDevDocsDeprecationsByApi" }, + { "id": "kibCorePluginApi" }, + { "id": "kibCoreApplicationPluginApi" }, + { "id": "kibCoreChromePluginApi" }, + { "id": "kibCoreHttpPluginApi" }, + { "id": "kibCoreSavedObjectsPluginApi" }, + { "id": "kibFieldFormatsPluginApi" }, + { "id": "kibDataPluginApi" }, + { "id": "kibDataAutocompletePluginApi" }, + { "id": "kibDataEnhancedPluginApi" }, + { "id": "kibDataViewsPluginApi" }, + { "id": "kibDataQueryPluginApi" }, + { "id": "kibDataSearchPluginApi" }, + { "id": "kibDataUiPluginApi" }, + { "id": "kibBfetchPluginApi" }, + { "id": "kibAlertingPluginApi" }, + { "id": "kibTaskManagerPluginApi" }, + { "id": "kibActionsPluginApi" }, + { "id": "kibEventLogPluginApi" }, + { "id": "kibTriggersActionsUiPluginApi" }, + { "id": "kibCasesPluginApi" }, + { "id": "kibChartsPluginApi" }, + { "id": "kibDashboardPluginApi" }, + { "id": "kibDevToolsPluginApi" }, + { "id": "kibDiscoverPluginApi" }, + { "id": "kibEmbeddablePluginApi" }, + { "id": "kibEncryptedSavedObjectsPluginApi" }, + { "id": "kibEnterpriseSearchPluginApi" }, + { "id": "kibEsUiSharedPluginApi" }, + { "id": "kibExpressionsPluginApi" }, + { "id": "kibFeaturesPluginApi" }, + { "id": "kibFileUploadPluginApi" }, + { "id": "kibFleetPluginApi" }, + { "id": "kibGlobalSearchPluginApi" }, + { "id": "kibHomePluginApi" }, + { "id": "kibInspectorPluginApi" }, + { "id": "kibKibanaReactPluginApi" }, + { "id": "kibKibanaUtilsPluginApi" }, + { "id": "kibLensPluginApi" }, + { "id": "kibLicenseManagementPluginApi" }, + { "id": "kibLicensingPluginApi" }, + { "id": "kibListsPluginApi" }, + { "id": "kibManagementPluginApi" }, + { "id": "kibMapsPluginApi" }, + { "id": "kibMlPluginApi" }, + { "id": "kibMonitoringPluginApi" }, + { "id": "kibNavigationPluginApi" }, + { "id": "kibNewsfeedPluginApi" }, + { "id": "kibObservabilityPluginApi" }, + { "id": "kibRemoteClustersPluginApi" }, + { "id": "kibReportingPluginApi" }, + { "id": "kibRollupPluginApi" }, + { "id": "kibRuntimeFieldsPluginApi" }, + { "id": "kibSavedObjectsManagementPluginApi" }, + { "id": "kibSavedObjectsTaggingOssPluginApi" }, + { "id": "kibSavedObjectsTaggingPluginApi" }, + { "id": "kibSavedObjectsPluginApi" }, + { "id": "kibSecuritySolutionPluginApi" }, + { "id": "kibSecurityPluginApi" }, + { "id": "kibSharePluginApi" }, + { "id": "kibSnapshotRestorePluginApi" }, + { "id": "kibSpacesPluginApi" }, + { "id": "kibStackAlertsPluginApi" }, + { "id": "kibTelemetryCollectionManagerPluginApi" }, + { "id": "kibTelemetryCollectionXpackPluginApi" }, + { "id": "kibTelemetryManagementSectionPluginApi" }, + { "id": "kibTelemetryPluginApi" }, + { "id": "kibUiActionsEnhancedPluginApi" }, + { "id": "kibUiActionsPluginApi" }, + { "id": "kibUrlForwardingPluginApi" }, + { "id": "kibUsageCollectionPluginApi" }, + { "id": "kibVisTypeTimeseriesPluginApi" }, + { "id": "kibVisualizationsPluginApi" } + ] + } + ] +} diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index e3d9688e60962..e885180cdb803 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -42,6 +42,9 @@ export const IGNORE_FILE_GLOBS = [ 'test/package/Vagrantfile', '**/test/**/fixtures/**/*', + // Required to match the name in the docs.elastic.dev repo. + 'nav-kibana-dev.docnav.json', + // filename must match language code which requires capital letters '**/translations/*.json', From cc9be33dad0ae710019ebaac93292ea0095b602f Mon Sep 17 00:00:00 2001 From: Michael Olorunnisola Date: Mon, 13 Dec 2021 11:56:40 -0500 Subject: [PATCH 138/145] [Security Solutio][Investigations] Update Timeline Details API with ECS field (#120683) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/types/timeline/index.ts | 2 - .../__snapshots__/index.test.tsx.snap | 2641 ---------------- .../side_panel/event_details/footer.tsx | 5 +- .../side_panel/event_details/index.tsx | 7 +- .../components/side_panel/index.test.tsx | 2685 ++++++++++++++++- .../timelines/containers/details/index.tsx | 13 +- .../timeline/events/details/index.ts | 2 + .../t_grid/body/events/stateful_event.tsx | 3 +- .../t_grid/body/row_action/index.tsx | 3 +- .../search_strategy/timeline/eql/helpers.ts | 4 +- .../timeline/factory/events/all/index.ts | 5 +- .../timeline/factory/events/details/index.ts | 4 +- .../factory/helpers/build_ecs_objects.test.ts | 134 + .../factory/helpers/build_ecs_objects.ts | 33 + .../helpers/build_fields_request.test.ts | 34 + .../factory/helpers/build_fields_request.ts | 18 + .../build_object_for_field_path.test.ts | 179 ++ .../helpers/build_object_for_field_path.ts | 37 + .../{events/all => helpers}/constants.ts | 3 +- .../format_timeline_data.test.ts} | 179 +- .../format_timeline_data.ts} | 76 +- .../helpers/get_nested_parent_path.test.ts | 39 + .../factory/helpers/get_nested_parent_path.ts | 18 + .../timeline/factory/helpers/get_timestamp.ts | 17 + 24 files changed, 3214 insertions(+), 2927 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/build_ecs_objects.test.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/build_ecs_objects.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/build_fields_request.test.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/build_fields_request.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/build_object_for_field_path.test.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/build_object_for_field_path.ts rename x-pack/plugins/timelines/server/search_strategy/timeline/factory/{events/all => helpers}/constants.ts (98%) rename x-pack/plugins/timelines/server/search_strategy/timeline/factory/{events/all/helpers.test.ts => helpers/format_timeline_data.test.ts} (72%) rename x-pack/plugins/timelines/server/search_strategy/timeline/factory/{events/all/helpers.ts => helpers/format_timeline_data.ts} (60%) create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.test.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/get_nested_parent_path.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/helpers/get_timestamp.ts diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts index 442986870ac94..ac8fb19e00df5 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts @@ -22,7 +22,6 @@ import { import { FlowTarget } from '../../search_strategy/security_solution/network'; import { errorSchema } from '../../detection_engine/schemas/response/error_schema'; import { Direction, Maybe } from '../../search_strategy'; -import { Ecs } from '../../ecs'; export * from './actions'; export * from './cells'; @@ -503,7 +502,6 @@ export type TimelineExpandedEventType = eventId: string; indexName: string; refetch?: () => void; - ecsData?: Ecs; }; } | EmptyObject; diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 80d8e8f9b9e26..0000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,2641 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Details Panel Component DetailsPanel: rendering it should not render the DetailsPanel if an expanded detail with a panelView, but not params have been set 1`] = ` - -`; - -exports[`Details Panel Component DetailsPanel: rendering it should not render the DetailsPanel if no expanded detail has been set in the reducer 1`] = ` - -`; - -exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should render the Details Panel when the panelView is set and the associated params are set 1`] = ` -.c0 { - -webkit-flex: 0 1 auto; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - margin-top: 8px; -} - - - - - - -
- -
- - -
- - - -
-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should render the Event Details view in the Details Panel when the panelView is eventDetail and the eventId is set 1`] = `null`; - -exports[`Details Panel Component DetailsPanel:EventDetails: rendering it should render the Event Details view of the Details Panel in the flyout when the panelView is eventDetail and the eventId is set 1`] = ` -Array [ - .c0 { - -webkit-flex: 0 1 auto; - -ms-flex: 0 1 auto; - flex: 0 1 auto; - margin-top: 8px; -} - -.c1 .euiFlyoutBody__overflow { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - overflow: hidden; -} - -.c1 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent { - -webkit-flex: 1; - -ms-flex: 1; - flex: 1; - overflow: hidden; - padding: 0 16px 16px; -} - - -
- + +
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `); }); test('it should render the Event Details view of the Details Panel in the flyout when the panelView is eventDetail and the eventId is set', () => { @@ -156,17 +379,584 @@ describe('Details Panel Component', () => { ); - expect(wrapper.find('[data-test-subj="timeline:details-panel:flyout"]')).toMatchSnapshot(); - }); + expect(wrapper.find('[data-test-subj="timeline:details-panel:flyout"]')) + .toMatchInlineSnapshot(` + Array [ + .c0 { + -webkit-flex: 0 1 auto; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + margin-top: 8px; + } - test('it should render the Event Details view in the Details Panel when the panelView is eventDetail and the eventId is set', () => { - const wrapper = mount( - - - - ); + .c1 .euiFlyoutBody__overflow { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + overflow: hidden; + } + + .c1 .euiFlyoutBody__overflow .euiFlyoutBody__overflowContent { + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + overflow: hidden; + padding: 0 16px 16px; + } + + +
+