From 00c180787fc570ec61035cd17ec3b507719e9b46 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Thu, 17 Jun 2021 13:47:00 -0700 Subject: [PATCH 01/35] [FTR] Stabilize SSLP functional tests (#102553) * Removes spaces check, since spaces should always be available * Disables Monitoring, SecuritySolutions, and Reporting in SSPL tests until #102552 is completed Signed-off-by: Tyler Smalley --- .../kbn-es-archiver/src/actions/empty_kibana_index.ts | 3 +-- packages/kbn-es-archiver/src/actions/unload.ts | 3 +-- .../src/lib/indices/delete_index_stream.test.ts | 4 ++-- .../src/lib/indices/delete_index_stream.ts | 9 ++------- .../kbn-es-archiver/src/lib/indices/kibana_index.ts | 10 ---------- test/functional/config.js | 7 ++++++- 6 files changed, 12 insertions(+), 24 deletions(-) diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index dbc455bbd2f8f..24a1de10b2b1d 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -22,9 +22,8 @@ export async function emptyKibanaIndexAction({ kbnClient: KbnClient; }) { const stats = createStats('emptyKibanaIndex', log); - const kibanaPluginIds = await kbnClient.plugins.getEnabledIds(); - await cleanKibanaIndices({ client, stats, log, kibanaPluginIds }); + await cleanKibanaIndices({ client, stats, log }); await migrateKibanaIndex(kbnClient); stats.createdIndex('.kibana'); return stats.toJSON(); diff --git a/packages/kbn-es-archiver/src/actions/unload.ts b/packages/kbn-es-archiver/src/actions/unload.ts index d8bc013b40991..98bae36095b88 100644 --- a/packages/kbn-es-archiver/src/actions/unload.ts +++ b/packages/kbn-es-archiver/src/actions/unload.ts @@ -37,7 +37,6 @@ export async function unloadAction({ }) { const name = relative(REPO_ROOT, inputDir); const stats = createStats(name, log); - const kibanaPluginIds = await kbnClient.plugins.getEnabledIds(); const files = prioritizeMappings(await readDirectory(inputDir)); for (const filename of files) { @@ -47,7 +46,7 @@ export async function unloadAction({ createReadStream(resolve(inputDir, filename)) as Readable, ...createParseArchiveStreams({ gzip: isGzip(filename) }), createFilterRecordsStream('index'), - createDeleteIndexStream(client, stats, log, kibanaPluginIds), + createDeleteIndexStream(client, stats, log), ] as [Readable, ...Writable[]]); } diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts index 3198ba86207f0..241d4a8944546 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.test.ts @@ -28,7 +28,7 @@ describe('esArchiver: createDeleteIndexStream()', () => { await createPromiseFromStreams([ createListStream([createStubIndexRecord('index1')]), - createDeleteIndexStream(client, stats, log, []), + createDeleteIndexStream(client, stats, log), ]); sinon.assert.notCalled(stats.deletedIndex as sinon.SinonSpy); @@ -43,7 +43,7 @@ describe('esArchiver: createDeleteIndexStream()', () => { await createPromiseFromStreams([ createListStream([createStubIndexRecord('index1')]), - createDeleteIndexStream(client, stats, log, []), + createDeleteIndexStream(client, stats, log), ]); sinon.assert.calledOnce(stats.deletedIndex as sinon.SinonSpy); diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts index e1552b5ed1e3b..7765419bb9d15 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts @@ -14,12 +14,7 @@ import { Stats } from '../stats'; import { deleteIndex } from './delete_index'; import { cleanKibanaIndices } from './kibana_index'; -export function createDeleteIndexStream( - client: KibanaClient, - stats: Stats, - log: ToolingLog, - kibanaPluginIds: string[] -) { +export function createDeleteIndexStream(client: KibanaClient, stats: Stats, log: ToolingLog) { return new Transform({ readableObjectMode: true, writableObjectMode: true, @@ -29,7 +24,7 @@ export function createDeleteIndexStream( const { index } = record.value; if (index.startsWith('.kibana')) { - await cleanKibanaIndices({ client, stats, log, kibanaPluginIds }); + await cleanKibanaIndices({ client, stats, log }); } else { await deleteIndex({ client, stats, log, index }); } diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index 0712d2789a91a..635e432468846 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -96,21 +96,11 @@ export async function cleanKibanaIndices({ client, stats, log, - kibanaPluginIds, }: { client: KibanaClient; stats: Stats; log: ToolingLog; - kibanaPluginIds: string[]; }) { - if (!kibanaPluginIds.includes('spaces')) { - return await deleteKibanaIndices({ - client, - stats, - log, - }); - } - while (true) { const resp = await client.deleteByQuery( { diff --git a/test/functional/config.js b/test/functional/config.js index eac21e5a45618..bab1148cf372a 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -42,9 +42,14 @@ export default async function ({ readConfigFile }) { serverArgs: [ ...commonConfig.get('kbnTestServer.serverArgs'), '--telemetry.optIn=false', - '--xpack.security.enabled=false', '--savedObjects.maxImportPayloadBytes=10485760', '--xpack.maps.showMapVisualizationTypes=true', + + // to be re-enabled once kibana/issues/102552 is completed + '--xpack.security.enabled=false', + '--monitoring.enabled=false', + '--xpack.reporting.enabled=false', + '--enterpriseSearch.enabled=false', ], }, From b5f0bc9faab9dbadd242f2c089069b754fc6d347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Thu, 17 Jun 2021 16:54:38 -0400 Subject: [PATCH 02/35] [Security Solution] adds wrapSequences method (RAC) (#102106) adds wrapSequences method --- .../signals/executors/eql.test.ts | 2 ++ .../detection_engine/signals/executors/eql.ts | 30 ++++++++----------- .../signals/filter_duplicate_signals.test.ts | 12 ++++---- .../signals/filter_duplicate_signals.ts | 26 ++++++++++++---- .../signals/search_after_bulk_create.test.ts | 5 +++- .../signals/signal_rule_alert_type.ts | 8 +++++ .../lib/detection_engine/signals/types.ts | 9 ++++-- .../signals/wrap_hits_factory.ts | 19 ++++++------ .../signals/wrap_sequences_factory.ts | 24 +++++++++++++++ .../security_solution/server/plugin.ts | 6 ++-- .../tests/generating_signals.ts | 18 +++++------ 11 files changed, 107 insertions(+), 52 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts index e7af3d484dfbd..a1d7d03f313db 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.test.ts @@ -78,6 +78,8 @@ describe('eql_executor', () => { logger, searchAfterSize, bulkCreate: jest.fn(), + wrapHits: jest.fn(), + wrapSequences: jest.fn(), }); expect(response.warningMessages.length).toEqual(1); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts index a187b73069682..e08f519e9761a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/executors/eql.ts @@ -21,18 +21,19 @@ import { isOutdated } from '../../migrations/helpers'; import { getIndexVersion } from '../../routes/index/get_index_version'; import { MIN_EQL_RULE_INDEX_VERSION } from '../../routes/index/get_signals_template'; import { EqlRuleParams } from '../../schemas/rule_schemas'; -import { buildSignalFromEvent, buildSignalGroupFromSequence } from '../build_bulk_body'; import { getInputIndex } from '../get_input_output_index'; -import { filterDuplicateSignals } from '../filter_duplicate_signals'; + import { AlertAttributes, BulkCreate, + WrapHits, + WrapSequences, EqlSignalSearchResponse, RuleRangeTuple, SearchAfterAndBulkCreateReturnType, - WrappedSignalHit, + SimpleHit, } from '../types'; -import { createSearchAfterReturnType, makeFloatString, wrapSignal } from '../utils'; +import { createSearchAfterReturnType, makeFloatString } from '../utils'; export const eqlExecutor = async ({ rule, @@ -43,6 +44,8 @@ export const eqlExecutor = async ({ logger, searchAfterSize, bulkCreate, + wrapHits, + wrapSequences, }: { rule: SavedObject>; tuple: RuleRangeTuple; @@ -52,6 +55,8 @@ export const eqlExecutor = async ({ logger: Logger; searchAfterSize: number; bulkCreate: BulkCreate; + wrapHits: WrapHits; + wrapSequences: WrapSequences; }): Promise => { const result = createSearchAfterReturnType(); const ruleParams = rule.attributes.params; @@ -104,27 +109,18 @@ export const eqlExecutor = async ({ const eqlSignalSearchEnd = performance.now(); const eqlSearchDuration = makeFloatString(eqlSignalSearchEnd - eqlSignalSearchStart); result.searchAfterTimes = [eqlSearchDuration]; - let newSignals: WrappedSignalHit[] | undefined; + let newSignals: SimpleHit[] | undefined; if (response.hits.sequences !== undefined) { - newSignals = response.hits.sequences.reduce( - (acc: WrappedSignalHit[], sequence) => - acc.concat(buildSignalGroupFromSequence(sequence, rule, ruleParams.outputIndex)), - [] - ); + newSignals = wrapSequences(response.hits.sequences); } else if (response.hits.events !== undefined) { - newSignals = filterDuplicateSignals( - rule.id, - response.hits.events.map((event) => - wrapSignal(buildSignalFromEvent(event, rule, true), ruleParams.outputIndex) - ) - ); + newSignals = wrapHits(response.hits.events); } else { throw new Error( 'eql query response should have either `sequences` or `events` but had neither' ); } - if (newSignals.length > 0) { + if (newSignals?.length) { const insertResult = await bulkCreate(newSignals); result.bulkCreateTimes.push(insertResult.bulkCreateDuration); result.createdSignalsCount += insertResult.createdItemsCount; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts index 5c4af83c3b03e..0098d50fc01ef 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.test.ts @@ -36,11 +36,13 @@ const mockSignals = [ ]; describe('filterDuplicateSignals', () => { - it('filters duplicate signals', () => { - expect(filterDuplicateSignals(mockRuleId1, mockSignals).length).toEqual(1); - }); + describe('detection engine implementation', () => { + it('filters duplicate signals', () => { + expect(filterDuplicateSignals(mockRuleId1, mockSignals, false).length).toEqual(1); + }); - it('does not filter non-duplicate signals', () => { - expect(filterDuplicateSignals(mockRuleId3, mockSignals).length).toEqual(2); + it('does not filter non-duplicate signals', () => { + expect(filterDuplicateSignals(mockRuleId3, mockSignals, false).length).toEqual(2); + }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts index a648c05306289..0b9859fad7688 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/filter_duplicate_signals.ts @@ -5,10 +5,26 @@ * 2.0. */ -import { WrappedSignalHit } from './types'; +import { SimpleHit, WrappedSignalHit } from './types'; -export const filterDuplicateSignals = (ruleId: string, signals: WrappedSignalHit[]) => { - return signals.filter( - (doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId) - ); +const isWrappedSignalHit = ( + signals: SimpleHit[], + isRuleRegistryEnabled: boolean +): signals is WrappedSignalHit[] => { + return !isRuleRegistryEnabled; +}; + +export const filterDuplicateSignals = ( + ruleId: string, + signals: SimpleHit[], + isRuleRegistryEnabled: boolean +) => { + if (isWrappedSignalHit(signals, isRuleRegistryEnabled)) { + return signals.filter( + (doc) => !doc._source.signal?.ancestors.some((ancestor) => ancestor.rule === ruleId) + ); + } else { + // TODO: filter duplicate signals for RAC + return []; + } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 184b49c2d6c7b..21c1402861e6e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -66,7 +66,10 @@ describe('searchAfterAndBulkCreate', () => { buildRuleMessage, false ); - wrapHits = wrapHitsFactory({ ruleSO, signalsIndex: DEFAULT_SIGNALS_INDEX }); + wrapHits = wrapHitsFactory({ + ruleSO, + signalsIndex: DEFAULT_SIGNALS_INDEX, + }); }); test('should return success with number of searches less than max signals', async () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index bb1e50c14d401..32bd6d71bfb1d 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -67,6 +67,7 @@ import { } from '../schemas/rule_schemas'; import { bulkCreateFactory } from './bulk_create_factory'; import { wrapHitsFactory } from './wrap_hits_factory'; +import { wrapSequencesFactory } from './wrap_sequences_factory'; export const signalRulesAlertType = ({ logger, @@ -233,6 +234,11 @@ export const signalRulesAlertType = ({ signalsIndex: params.outputIndex, }); + const wrapSequences = wrapSequencesFactory({ + ruleSO: savedObject, + signalsIndex: params.outputIndex, + }); + if (isMlRule(type)) { const mlRuleSO = asTypeSpecificSO(savedObject, machineLearningRuleParams); for (const tuple of tuples) { @@ -313,6 +319,8 @@ export const signalRulesAlertType = ({ searchAfterSize, bulkCreate, logger, + wrapHits, + wrapSequences, }); } } else { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 8a6ce91b2575a..c399454b9888b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -25,6 +25,7 @@ import { BaseHit, RuleAlertAction, SearchTypes, + EqlSequence, } from '../../../../common/detection_engine/types'; import { ListClient } from '../../../../../lists/server'; import { Logger, SavedObject } from '../../../../../../../src/core/server'; @@ -257,9 +258,11 @@ export type SignalsEnrichment = (signals: SignalSearchResponse) => Promise(docs: Array>) => Promise>; -export type WrapHits = ( - hits: Array> -) => Array>; +export type SimpleHit = BaseHit<{ '@timestamp': string }>; + +export type WrapHits = (hits: Array>) => SimpleHit[]; + +export type WrapSequences = (sequences: Array>) => SimpleHit[]; export interface SearchAfterAndBulkCreateParams { tuple: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts index 3f3e4ef3631bd..d5c05bc890332 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_hits_factory.ts @@ -5,12 +5,7 @@ * 2.0. */ -import { - SearchAfterAndBulkCreateParams, - SignalSourceHit, - WrapHits, - WrappedSignalHit, -} from './types'; +import { SearchAfterAndBulkCreateParams, WrapHits, WrappedSignalHit } from './types'; import { generateId } from './utils'; import { buildBulkBody } from './build_bulk_body'; import { filterDuplicateSignals } from './filter_duplicate_signals'; @@ -25,11 +20,15 @@ export const wrapHitsFactory = ({ const wrappedDocs: WrappedSignalHit[] = events.flatMap((doc) => [ { _index: signalsIndex, - // TODO: bring back doc._version - _id: generateId(doc._index, doc._id, '', ruleSO.attributes.params.ruleId ?? ''), - _source: buildBulkBody(ruleSO, doc as SignalSourceHit), + _id: generateId( + doc._index, + doc._id, + String(doc._version), + ruleSO.attributes.params.ruleId ?? '' + ), + _source: buildBulkBody(ruleSO, doc), }, ]); - return filterDuplicateSignals(ruleSO.id, wrappedDocs); + return filterDuplicateSignals(ruleSO.id, wrappedDocs, false); }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts new file mode 100644 index 0000000000000..c53ea7b7ebe72 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/wrap_sequences_factory.ts @@ -0,0 +1,24 @@ +/* + * 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 { SearchAfterAndBulkCreateParams, WrappedSignalHit, WrapSequences } from './types'; +import { buildSignalGroupFromSequence } from './build_bulk_body'; + +export const wrapSequencesFactory = ({ + ruleSO, + signalsIndex, +}: { + ruleSO: SearchAfterAndBulkCreateParams['ruleSO']; + signalsIndex: string; +}): WrapSequences => (sequences) => + sequences.reduce( + (acc: WrappedSignalHit[], sequence) => [ + ...acc, + ...buildSignalGroupFromSequence(sequence, ruleSO, signalsIndex), + ], + [] + ); diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index a0f466512cc1d..7e4d0989af413 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -208,8 +208,10 @@ export class Plugin implements IPlugin core.getStartServices().then(([coreStart]) => coreStart); @@ -293,7 +295,7 @@ export class Plugin implements IPlugin { parents: [ { rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it - id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + id: '82421e2f4e96058baaa2ed87abbe565403b45edf36348c2b79a4f0e8cc1cd055', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -242,7 +242,7 @@ export default ({ getService }: FtrProviderContext) => { }, { rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it - id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + id: '82421e2f4e96058baaa2ed87abbe565403b45edf36348c2b79a4f0e8cc1cd055', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -252,7 +252,7 @@ export default ({ getService }: FtrProviderContext) => { depth: 2, parent: { rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it - id: 'acf538fc082adf970012be166527c4d9fc120f0015f145e0a466a3ceb32db606', + id: '82421e2f4e96058baaa2ed87abbe565403b45edf36348c2b79a4f0e8cc1cd055', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1265,7 +1265,7 @@ export default ({ getService }: FtrProviderContext) => { parents: [ { rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it - id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + id: 'c4db4921f2d9152865fd6518c2a2ef3471738e49f607a21319048c69a303f83f', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1280,7 +1280,7 @@ export default ({ getService }: FtrProviderContext) => { }, { rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it - id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + id: 'c4db4921f2d9152865fd6518c2a2ef3471738e49f607a21319048c69a303f83f', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1290,7 +1290,7 @@ export default ({ getService }: FtrProviderContext) => { depth: 2, parent: { rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it - id: 'b63bcc90b9393f94899991397a3c2df2f3f5c6ebf56440434500f1e1419df7c9', + id: 'c4db4921f2d9152865fd6518c2a2ef3471738e49f607a21319048c69a303f83f', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1423,7 +1423,7 @@ export default ({ getService }: FtrProviderContext) => { parents: [ { rule: signalNoRule.parents[0].rule, // rule id is always changing so skip testing it - id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + id: '0733d5d2eaed77410a65eec95cfb2df099abc97289b78e2b0b406130e2dbdb33', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1438,7 +1438,7 @@ export default ({ getService }: FtrProviderContext) => { }, { rule: signalNoRule.ancestors[1].rule, // rule id is always changing so skip testing it - id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + id: '0733d5d2eaed77410a65eec95cfb2df099abc97289b78e2b0b406130e2dbdb33', type: 'signal', index: '.siem-signals-default-000001', depth: 1, @@ -1448,7 +1448,7 @@ export default ({ getService }: FtrProviderContext) => { depth: 2, parent: { rule: signalNoRule.parent?.rule, // parent.rule is always changing so skip testing it - id: 'd2114ed6553816f87d6707b5bc50b88751db73b0f4930433d0890474804aa179', + id: '0733d5d2eaed77410a65eec95cfb2df099abc97289b78e2b0b406130e2dbdb33', type: 'signal', index: '.siem-signals-default-000001', depth: 1, From 08ba5b3f4bf60d24710817bc970202b6656a6e13 Mon Sep 17 00:00:00 2001 From: Clint Andrew Hall Date: Thu, 17 Jun 2021 17:57:44 -0500 Subject: [PATCH 03/35] [canvas] Refactor Storybook from bespoke to standard configuration (#101962) --- packages/kbn-storybook/package.json | 2 +- packages/kbn-storybook/webpack.config.ts | 5 +- test/scripts/jenkins_storybook.sh | 3 - x-pack/package.json | 1 - x-pack/plugins/canvas/CONTRIBUTING.md | 25 +-- x-pack/plugins/canvas/scripts/storybook.js | 124 ----------- .../canvas/shareable_runtime/README.mdx | 2 +- x-pack/plugins/canvas/storybook/.babelrc | 9 - .../canvas/storybook/addon/src/register.tsx | 15 ++ x-pack/plugins/canvas/storybook/constants.js | 18 -- x-pack/plugins/canvas/storybook/constants.ts | 10 + .../plugins/canvas/storybook/dll_contexts.js | 13 -- x-pack/plugins/canvas/storybook/main.ts | 67 ++++-- x-pack/plugins/canvas/storybook/manager.ts | 23 -- x-pack/plugins/canvas/storybook/middleware.ts | 18 -- .../canvas/storybook/preview-head.html | 6 - x-pack/plugins/canvas/storybook/preview.ts | 3 - .../canvas/storybook/webpack.config.js | 210 ------------------ .../canvas/storybook/webpack.dll.config.js | 110 --------- 19 files changed, 82 insertions(+), 582 deletions(-) delete mode 100644 x-pack/plugins/canvas/scripts/storybook.js delete mode 100644 x-pack/plugins/canvas/storybook/.babelrc delete mode 100644 x-pack/plugins/canvas/storybook/constants.js create mode 100644 x-pack/plugins/canvas/storybook/constants.ts delete mode 100644 x-pack/plugins/canvas/storybook/dll_contexts.js delete mode 100644 x-pack/plugins/canvas/storybook/manager.ts delete mode 100644 x-pack/plugins/canvas/storybook/middleware.ts delete mode 100644 x-pack/plugins/canvas/storybook/preview-head.html delete mode 100644 x-pack/plugins/canvas/storybook/webpack.config.js delete mode 100644 x-pack/plugins/canvas/storybook/webpack.dll.config.js diff --git a/packages/kbn-storybook/package.json b/packages/kbn-storybook/package.json index 0e70f7c340a90..f2e4c9b3418b1 100644 --- a/packages/kbn-storybook/package.json +++ b/packages/kbn-storybook/package.json @@ -11,6 +11,6 @@ "scripts": { "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", - "watch": "yarn build --watch" + "kbn:watch": "yarn build --watch" } } \ No newline at end of file diff --git a/packages/kbn-storybook/webpack.config.ts b/packages/kbn-storybook/webpack.config.ts index b1efa43fa194b..972caf8d481fe 100644 --- a/packages/kbn-storybook/webpack.config.ts +++ b/packages/kbn-storybook/webpack.config.ts @@ -71,11 +71,12 @@ export default function ({ config: storybookConfig }: { config: Configuration }) ], }, resolve: { - // Tell Webpack about the scss extension - extensions: ['.scss'], + extensions: ['.js', '.ts', '.tsx', '.json'], + mainFields: ['browser', 'main'], alias: { core_app_image_assets: resolve(REPO_ROOT, 'src/core/public/core_app/images'), }, + symlinks: false, }, stats, }; diff --git a/test/scripts/jenkins_storybook.sh b/test/scripts/jenkins_storybook.sh index 5c99654f16cbe..73ab43fd02eba 100755 --- a/test/scripts/jenkins_storybook.sh +++ b/test/scripts/jenkins_storybook.sh @@ -2,9 +2,6 @@ source src/dev/ci_setup/setup_env.sh -cd "$XPACK_DIR/plugins/canvas" -node scripts/storybook --dll - cd "$KIBANA_DIR" yarn storybook --site apm diff --git a/x-pack/package.json b/x-pack/package.json index 04f808c89764d..84fd5ba081d8f 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -7,7 +7,6 @@ "scripts": { "github-checks-reporter": "../node_modules/.bin/github-checks-reporter", "kbn": "node ../scripts/kbn", - "kbn:bootstrap": "node plugins/canvas/scripts/storybook --clean", "start": "node ../scripts/kibana --dev", "build": "node --preserve-symlinks ../node_modules/.bin/gulp build", "test:jest": "node ../scripts/jest" diff --git a/x-pack/plugins/canvas/CONTRIBUTING.md b/x-pack/plugins/canvas/CONTRIBUTING.md index 538cc1592c3bc..d3bff67771244 100644 --- a/x-pack/plugins/canvas/CONTRIBUTING.md +++ b/x-pack/plugins/canvas/CONTRIBUTING.md @@ -113,27 +113,4 @@ Canvas uses [Storybook](https://storybook.js.org) to test and develop components ### Using Storybook -The Canvas Storybook instance can be started by running `node scripts/storybook` from the Canvas root directory. It has a number of options: - -``` -node scripts/storybook - - Storybook runner for Canvas. - - Options: - --clean Forces a clean of the Storybook DLL and exits. - --dll Cleans and builds the Storybook dependency DLL and exits. - --stats Produces a Webpack stats file. - --site Produces a site deployment of this Storybook. - --verbose, -v Log verbosely - --debug Log debug messages (less than verbose) - --quiet Only log errors - --silent Don't log anything - --help Show this message -``` - -### What about `kbn-storybook`? - -Canvas wants to move to the Kibana Storybook instance as soon as feasible. There are few tweaks Canvas makes to Storybook, so we're actively working with the maintainers to make that migration successful. - -In the meantime, people can test our progress by running `node scripts/storybook_new` from the Canvas root. +The Canvas Storybook instance can be started by running `yarn storybook canvas` from the Kibana root directory. diff --git a/x-pack/plugins/canvas/scripts/storybook.js b/x-pack/plugins/canvas/scripts/storybook.js deleted file mode 100644 index e6b8a66b9026f..0000000000000 --- a/x-pack/plugins/canvas/scripts/storybook.js +++ /dev/null @@ -1,124 +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. - */ - -const path = require('path'); -const fs = require('fs'); -const del = require('del'); -const { run } = require('@kbn/dev-utils'); -// This is included in the main Kibana package.json -// eslint-disable-next-line import/no-extraneous-dependencies -const storybook = require('@storybook/react/standalone'); -const execa = require('execa'); -const { DLL_OUTPUT } = require('./../storybook/constants'); - -const storybookOptions = { - configDir: path.resolve(__dirname, './../storybook'), - mode: 'dev', -}; - -run( - ({ log, flags }) => { - const { addon, dll, clean, stats, site } = flags; - - // Delete the existing DLL if we're cleaning or building. - if (clean || dll) { - del.sync([DLL_OUTPUT], { force: true }); - - if (clean) { - return; - } - } - - // Build the DLL if necessary. - if (fs.existsSync(DLL_OUTPUT)) { - log.info('storybook: DLL exists from previous build; skipping'); - } else { - log.info('storybook: Building DLL'); - execa.sync( - 'yarn', - [ - 'webpack', - '--config', - 'x-pack/plugins/canvas/storybook/webpack.dll.config.js', - ...(process.stdout.isTTY && !process.env.CI ? ['--progress'] : []), - '--hide-modules', - '--display-entrypoints', - 'false', - ], - { - cwd: path.resolve(__dirname, '../../../..'), - stdio: ['ignore', 'inherit', 'inherit'], - buffer: false, - } - ); - log.success('storybook: DLL built'); - } - - // If we're only building the DLL, we're done. - if (dll) { - return; - } - - // Build statistics and exit - if (stats) { - log.success('storybook: Generating Storybook statistics'); - storybook({ - ...storybookOptions, - smokeTest: true, - }); - return; - } - - // Build the addon - execa.sync('node', ['scripts/build'], { - cwd: path.resolve(__dirname, '../storybook/addon'), - stdio: ['ignore', 'inherit', 'inherit'], - buffer: false, - }); - - // Build site and exit - if (site) { - log.success('storybook: Generating Storybook site'); - storybook({ - ...storybookOptions, - mode: 'static', - outputDir: path.resolve(__dirname, './../storybook/build'), - }); - return; - } - - log.info('storybook: Starting Storybook'); - - if (addon) { - execa('node', ['scripts/build', '--watch'], { - cwd: path.resolve(__dirname, '../storybook/addon'), - stdio: ['ignore', 'inherit', 'inherit'], - buffer: false, - }); - } - - storybook({ - ...storybookOptions, - port: 9001, - }); - }, - { - description: ` - Storybook runner for Canvas. - `, - flags: { - boolean: ['addon', 'dll', 'clean', 'stats', 'site'], - help: ` - --addon Watch the addon source code for changes. - --clean Forces a clean of the Storybook DLL and exits. - --dll Cleans and builds the Storybook dependency DLL and exits. - --stats Produces a Webpack stats file. - --site Produces a site deployment of this Storybook. - `, - }, - } -); diff --git a/x-pack/plugins/canvas/shareable_runtime/README.mdx b/x-pack/plugins/canvas/shareable_runtime/README.mdx index 8f1c9e06ca788..a9d58bb3833d9 100644 --- a/x-pack/plugins/canvas/shareable_runtime/README.mdx +++ b/x-pack/plugins/canvas/shareable_runtime/README.mdx @@ -153,7 +153,7 @@ You can test this functionality in a number of ways. The easiest would be: ### Run the Canvas Storybook -From `/canvas`: `node scripts/storybook` +From `/kibana`: `yarn storybook canvas` ### Run the Jest Tests diff --git a/x-pack/plugins/canvas/storybook/.babelrc b/x-pack/plugins/canvas/storybook/.babelrc deleted file mode 100644 index c8f2d480f3a09..0000000000000 --- a/x-pack/plugins/canvas/storybook/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "env": { - "test": { - "plugins": [ - "require-context-hook" - ] - } - } -} diff --git a/x-pack/plugins/canvas/storybook/addon/src/register.tsx b/x-pack/plugins/canvas/storybook/addon/src/register.tsx index 2a0df578ff22c..1e99d6bdb74eb 100644 --- a/x-pack/plugins/canvas/storybook/addon/src/register.tsx +++ b/x-pack/plugins/canvas/storybook/addon/src/register.tsx @@ -9,6 +9,8 @@ import React from 'react'; import { addons, types } from '@storybook/addons'; import { AddonPanel } from '@storybook/components'; import { STORY_CHANGED } from '@storybook/core-events'; +import { create } from '@storybook/theming'; +import { PANEL_ID } from '@storybook/addon-actions'; import { ADDON_ID, EVENTS, ACTIONS_PANEL_ID } from './constants'; import { Panel } from './panel'; @@ -32,3 +34,16 @@ addons.register(ADDON_ID, (api) => { }, }); }); + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Canvas Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas', + }), + showPanel: true, + isFullscreen: false, + panelPosition: 'bottom', + isToolshown: true, + selectedPanel: PANEL_ID, +}); diff --git a/x-pack/plugins/canvas/storybook/constants.js b/x-pack/plugins/canvas/storybook/constants.js deleted file mode 100644 index c2a4f414588d4..0000000000000 --- a/x-pack/plugins/canvas/storybook/constants.js +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -const path = require('path'); - -const DLL_NAME = 'canvas_storybook_dll'; -const KIBANA_ROOT = path.resolve(__dirname, '../../../..'); -const DLL_OUTPUT = path.resolve(KIBANA_ROOT, 'built_assets', DLL_NAME); - -module.exports = { - DLL_NAME, - KIBANA_ROOT, - DLL_OUTPUT, -}; diff --git a/x-pack/plugins/canvas/storybook/constants.ts b/x-pack/plugins/canvas/storybook/constants.ts new file mode 100644 index 0000000000000..711b939179452 --- /dev/null +++ b/x-pack/plugins/canvas/storybook/constants.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import path from 'path'; + +export const KIBANA_ROOT = path.resolve(__dirname, '../../../..'); diff --git a/x-pack/plugins/canvas/storybook/dll_contexts.js b/x-pack/plugins/canvas/storybook/dll_contexts.js deleted file mode 100644 index 12f0723ae8b77..0000000000000 --- a/x-pack/plugins/canvas/storybook/dll_contexts.js +++ /dev/null @@ -1,13 +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. - */ - -// This file defines CSS and Legacy style contexts for use in the DLL. This file -// is also require'd in the Storybook config so that the Storybook Webpack instance -// is aware of them, and can load them from the DLL. - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -require('../../../../src/core/server/core_app/assets/legacy_light_theme.css'); diff --git a/x-pack/plugins/canvas/storybook/main.ts b/x-pack/plugins/canvas/storybook/main.ts index 79e64caffd3dc..80a8aeb14a804 100644 --- a/x-pack/plugins/canvas/storybook/main.ts +++ b/x-pack/plugins/canvas/storybook/main.ts @@ -5,23 +5,58 @@ * 2.0. */ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { existsSync } = require('fs'); -const { join } = require('path'); +import { resolve } from 'path'; +import webpackMerge from 'webpack-merge'; +import { defaultConfig } from '@kbn/storybook'; -// Check for DLL if we're not running in Jest -if ( - !existsSync(join(__dirname, '../../../../built_assets/canvas_storybook_dll/manifest.json')) && - !process.env.JEST_WORKER_ID -) { - // eslint-disable-next-line no-console - console.error( - 'No DLL found. Run `node scripts/storybook --dll` from the Canvas plugin directory.' - ); - process.exit(1); -} +import type { Configuration } from 'webpack'; + +import { KIBANA_ROOT } from './constants'; + +const canvasWebpack = { + module: { + rules: [ + // Enable CSS Modules in Storybook (Shareable Runtime) + { + test: /\.module\.s(a|c)ss$/, + loader: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 2, + modules: { + localIdentName: '[name]__[local]___[hash:base64:5]', + }, + }, + }, + { + loader: 'postcss-loader', + options: { + path: resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), + }, + }, + { + loader: 'sass-loader', + }, + ], + }, + // Exclude large-dependency, troublesome or irrelevant modules. + { + test: [ + resolve(KIBANA_ROOT, 'x-pack/plugins/canvas/public/components/embeddable_flyout'), + resolve(KIBANA_ROOT, 'x-pack/plugins/reporting/public'), + resolve(KIBANA_ROOT, 'src/plugins/kibana_legacy/public/angular'), + resolve(KIBANA_ROOT, 'src/plugins/kibana_legacy/public/paginate'), + ], + use: 'null-loader', + }, + ], + }, +}; module.exports = { - stories: ['../**/*.stories.tsx'], - addons: ['@storybook/addon-actions', '@storybook/addon-knobs', './addon/target/register'], + ...defaultConfig, + addons: [...(defaultConfig.addons || []), './addon/target/register'], + webpackFinal: (config: Configuration) => webpackMerge(config, canvasWebpack), }; diff --git a/x-pack/plugins/canvas/storybook/manager.ts b/x-pack/plugins/canvas/storybook/manager.ts deleted file mode 100644 index 27abc9eba3854..0000000000000 --- a/x-pack/plugins/canvas/storybook/manager.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { addons } from '@storybook/addons'; -import { create } from '@storybook/theming'; -import { PANEL_ID } from '@storybook/addon-actions'; - -addons.setConfig({ - theme: create({ - base: 'light', - brandTitle: 'Canvas Storybook', - brandUrl: 'https://github.com/elastic/kibana/tree/master/x-pack/plugins/canvas', - }), - showPanel: true, - isFullscreen: false, - panelPosition: 'bottom', - isToolshown: true, - selectedPanel: PANEL_ID, -}); diff --git a/x-pack/plugins/canvas/storybook/middleware.ts b/x-pack/plugins/canvas/storybook/middleware.ts deleted file mode 100644 index 1662c28b238d7..0000000000000 --- a/x-pack/plugins/canvas/storybook/middleware.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import path from 'path'; -// @ts-expect-error -import serve from 'serve-static'; - -// Extend the Storybook Middleware to include a route to access Legacy UI assets -module.exports = function (router: { get: (...args: any[]) => void }) { - router.get( - '/ui', - serve(path.resolve(__dirname, '../../../../../src/core/server/core_app/assets')) - ); -}; diff --git a/x-pack/plugins/canvas/storybook/preview-head.html b/x-pack/plugins/canvas/storybook/preview-head.html deleted file mode 100644 index f8a7de6ddbaf1..0000000000000 --- a/x-pack/plugins/canvas/storybook/preview-head.html +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/x-pack/plugins/canvas/storybook/preview.ts b/x-pack/plugins/canvas/storybook/preview.ts index 3c38f4ed5c682..f885a654cdab8 100644 --- a/x-pack/plugins/canvas/storybook/preview.ts +++ b/x-pack/plugins/canvas/storybook/preview.ts @@ -10,9 +10,6 @@ import { action } from '@storybook/addon-actions'; import { startServices } from '../public/services/stubs'; import { addDecorators } from './decorators'; -// Import the modules from the DLL. -import './dll_contexts'; - // Import Canvas CSS import '../public/style/index.scss'; diff --git a/x-pack/plugins/canvas/storybook/webpack.config.js b/x-pack/plugins/canvas/storybook/webpack.config.js deleted file mode 100644 index 77b8d343a2bea..0000000000000 --- a/x-pack/plugins/canvas/storybook/webpack.config.js +++ /dev/null @@ -1,210 +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. - */ - -const path = require('path'); -const webpack = require('webpack'); -const webpackMerge = require('webpack-merge'); -const { stringifyRequest } = require('loader-utils'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const { DLL_OUTPUT, KIBANA_ROOT } = require('./constants'); - -// Extend the Storybook Webpack config with some customizations -module.exports = async ({ config: storybookConfig }) => { - const config = { - module: { - rules: [ - // Include the React preset from Kibana for JS(X) and TS(X) - { - test: /\.(j|t)sx?$/, - exclude: /node_modules/, - loaders: 'babel-loader', - options: { - presets: [require.resolve('@kbn/babel-preset/webpack_preset')], - }, - }, - // Parse props data for .tsx files - // This is notoriously slow, and is making Storybook unusable. Disabling for now. - // See: https://github.com/storybookjs/storybook/issues/7998 - // - // { - // test: /\.tsx$/, - // // Exclude example files, as we don't display props info for them - // exclude: /\.examples.tsx$/, - // use: [ - // // Parse TS comments to create Props tables in the UI - // require.resolve('react-docgen-typescript-loader'), - // ], - // }, - // Enable SASS, but exclude CSS Modules in Storybook - { - test: /\.scss$/, - exclude: /\.module.(s(a|c)ss)$/, - use: [ - { loader: 'style-loader' }, - { loader: 'css-loader', options: { importLoaders: 2 } }, - { - loader: 'postcss-loader', - options: { - path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), - }, - }, - { - loader: 'sass-loader', - options: { - prependData(loaderContext) { - return `@import ${stringifyRequest( - loaderContext, - path.resolve( - KIBANA_ROOT, - 'src/core/public/core_app/styles/_globals_v7light.scss' - ) - )};\n`; - }, - sassOptions: { - includePaths: [path.resolve(KIBANA_ROOT, 'node_modules')], - }, - }, - }, - ], - }, - // Enable CSS Modules in Storybook (Shareable Runtime) - { - test: /\.module\.s(a|c)ss$/, - loader: [ - 'style-loader', - { - loader: 'css-loader', - options: { - importLoaders: 2, - modules: { - localIdentName: '[name]__[local]___[hash:base64:5]', - }, - }, - }, - { - loader: 'postcss-loader', - options: { - path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), - }, - }, - { - loader: 'sass-loader', - }, - ], - }, - { - test: /\.mjs$/, - include: /node_modules/, - type: 'javascript/auto', - }, - // Exclude large-dependency, troublesome or irrelevant modules. - { - test: [ - path.resolve(__dirname, '../public/components/embeddable_flyout'), - path.resolve(__dirname, '../../reporting/public'), - path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/angular'), - path.resolve(__dirname, '../../../../src/plugins/kibana_legacy/public/paginate'), - ], - use: 'null-loader', - }, - ], - }, - plugins: [ - // Reference the built DLL file of static(ish) dependencies, which are removed - // during kbn:bootstrap and rebuilt if missing. - new webpack.DllReferencePlugin({ - manifest: path.resolve(DLL_OUTPUT, 'manifest.json'), - context: KIBANA_ROOT, - }), - // Ensure jQuery is global for Storybook, specifically for the runtime. - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - }), - // Copy the DLL files to the Webpack build for use in the Storybook UI - new CopyWebpackPlugin({ - patterns: [ - { - from: path.resolve(DLL_OUTPUT, 'dll.js'), - to: 'dll.js', - }, - { - from: path.resolve(DLL_OUTPUT, 'dll.css'), - to: 'dll.css', - }, - ], - }), - // replace imports for `uiExports/*` modules with a synthetic module - // created by create_ui_exports_module.js - new webpack.NormalModuleReplacementPlugin(/^uiExports\//, (resource) => { - // uiExports used by Canvas - const extensions = { - hacks: [], - chromeNavControls: [], - }; - - // everything following the first / in the request is - // treated as a type of appExtension - const type = resource.request.slice(resource.request.indexOf('/') + 1); - - resource.request = [ - // the "val-loader" is used to execute create_ui_exports_module - // and use its return value as the source for the module in the - // bundle. This allows us to bypass writing to the file system - require.resolve('val-loader'), - '!', - require.resolve(KIBANA_ROOT + '/src/optimize/create_ui_exports_module'), - '?', - // this JSON is parsed by create_ui_exports_module and determines - // what require() calls it will execute within the bundle - JSON.stringify({ type, modules: extensions[type] || [] }), - ].join(''); - }), - - new webpack.NormalModuleReplacementPlugin( - /lib\/download_workpad/, - path.resolve(__dirname, '../tasks/mocks/downloadWorkpad') - ), - new webpack.NormalModuleReplacementPlugin( - /(lib)?\/custom_element_service/, - path.resolve(__dirname, '../tasks/mocks/customElementService') - ), - new webpack.NormalModuleReplacementPlugin( - /(lib)?\/ui_metric/, - path.resolve(__dirname, '../tasks/mocks/uiMetric') - ), - new webpack.NormalModuleReplacementPlugin( - /lib\/es_service/, - path.resolve(__dirname, '../tasks/mocks/esService') - ), - ], - resolve: { - extensions: ['.ts', '.tsx', '.scss', '.mjs', '.html'], - alias: { - 'ui/url/absolute_to_parsed_url': path.resolve( - __dirname, - '../tasks/mocks/uiAbsoluteToParsedUrl' - ), - }, - symlinks: false, - }, - }; - - // Find and alter the CSS rule to replace the Kibana public path string with a path - // to the route we've added in middleware.js - const cssRule = storybookConfig.module.rules.find((rule) => rule.test.source.includes('.css$')); - cssRule.use.push({ - loader: 'string-replace-loader', - options: { - search: '__REPLACE_WITH_PUBLIC_PATH__', - replace: '/', - flags: 'g', - }, - }); - - return webpackMerge(storybookConfig, config); -}; diff --git a/x-pack/plugins/canvas/storybook/webpack.dll.config.js b/x-pack/plugins/canvas/storybook/webpack.dll.config.js deleted file mode 100644 index c13fabe998921..0000000000000 --- a/x-pack/plugins/canvas/storybook/webpack.dll.config.js +++ /dev/null @@ -1,110 +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. - */ - -const path = require('path'); -const webpack = require('webpack'); -const MiniCssExtractPlugin = require('mini-css-extract-plugin'); - -const { DLL_NAME, DLL_OUTPUT, KIBANA_ROOT } = require('./constants'); - -// This is the Webpack config for the DLL of CSS and JS assets that are -// not expected to change during development. This saves compile and run -// times considerably. -module.exports = { - context: KIBANA_ROOT, - mode: 'development', - - // This is a (potentially growing) list of modules that can be safely - // included in the DLL. Only add to this list modules or other code - // which Storybook stories and their components would require, but don't - // change during development. - entry: [ - '@elastic/eui/dist/eui_theme_light.css', - '@kbn/ui-framework/dist/kui_light.css', - '@storybook/addon-actions/register', - '@storybook/core', - '@storybook/core/dist/server/common/polyfills.js', - '@storybook/react', - '@storybook/theming', - 'angular-mocks', - 'angular', - 'brace', - 'chroma-js', - 'highlight.js', - 'html-entities', - 'jsondiffpatch', - 'jquery', - 'lodash', - 'markdown-it', - 'monaco-editor', - 'prop-types', - 'react-ace', - 'react-beautiful-dnd', - 'react-dom', - 'react-focus-lock', - 'react-markdown', - 'react-monaco-editor', - 'react-resize-detector', - 'react-virtualized', - 'react', - 'recompose', - 'redux-actions', - 'remark-parse', - 'rxjs', - 'sinon', - 'tinycolor2', - // Include the DLL UI contexts from Kibana - require.resolve('./dll_contexts'), - ], - plugins: [ - // Produce the DLL and its manifest - new webpack.DllPlugin({ - name: DLL_NAME, - path: path.resolve(DLL_OUTPUT, 'manifest.json'), - }), - // Produce the DLL CSS file - new MiniCssExtractPlugin({ - filename: 'dll.css', - }), - ], - // Output the DLL JS file - output: { - path: DLL_OUTPUT, - filename: 'dll.js', - library: DLL_NAME, - }, - module: { - rules: [ - { - test: /\.css$/, - use: [ - { - loader: MiniCssExtractPlugin.loader, - options: {}, - }, - { loader: 'css-loader' }, - { - loader: 'string-replace-loader', - options: { - search: '__REPLACE_WITH_PUBLIC_PATH__', - replace: '/', - flags: 'g', - }, - }, - ], - }, - { - test: /\.(woff|woff2|ttf|eot|svg|ico)(\?|$)/, - loader: 'file-loader', - }, - ], - }, - node: { - fs: 'empty', - child_process: 'empty', - }, -}; From eea78b7737f343f60436348e8a59c81f8b886423 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 17 Jun 2021 19:52:31 -0600 Subject: [PATCH 04/35] [Maps] clean up feature editing name space to avoid conflicts with layer settings editing (#102516) * [Maps] clean up feature editing name space to avoid conflicts with layer settings editing * update vector_source * mvt_single_layer_vector_source udpates * review feedback --- x-pack/plugins/maps/common/constants.ts | 2 +- .../layers/vector_layer/vector_layer.tsx | 24 ++++++++++++------- .../es_search_source/es_search_source.tsx | 2 +- .../mvt_single_layer_vector_source.tsx | 2 +- .../sources/vector_source/vector_source.tsx | 4 ++-- .../toc_entry_actions_popover.tsx | 24 +++++++++++-------- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts index c49dd4fb619b5..37a8e8063c4ed 100644 --- a/x-pack/plugins/maps/common/constants.ts +++ b/x-pack/plugins/maps/common/constants.ts @@ -107,7 +107,7 @@ export const SOURCE_DATA_REQUEST_ID = 'source'; export const SOURCE_META_DATA_REQUEST_ID = `${SOURCE_DATA_REQUEST_ID}_${META_DATA_REQUEST_ID_SUFFIX}`; export const SOURCE_FORMATTERS_DATA_REQUEST_ID = `${SOURCE_DATA_REQUEST_ID}_${FORMATTERS_DATA_REQUEST_ID_SUFFIX}`; export const SOURCE_BOUNDS_DATA_REQUEST_ID = `${SOURCE_DATA_REQUEST_ID}_bounds`; -export const IS_EDITABLE_REQUEST_ID = 'isEditable'; +export const SUPPORTS_FEATURE_EDITING_REQUEST_ID = 'SUPPORTS_FEATURE_EDITING_REQUEST_ID'; export const MIN_ZOOM = 0; export const MAX_ZOOM = 24; diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx index bcd46568ef58e..8b4d25f4612cc 100644 --- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx +++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx @@ -28,7 +28,7 @@ import { FIELD_ORIGIN, KBN_TOO_MANY_FEATURES_IMAGE_ID, FieldFormatter, - IS_EDITABLE_REQUEST_ID, + SUPPORTS_FEATURE_EDITING_REQUEST_ID, } from '../../../../common/constants'; import { JoinTooltipProperty } from '../../tooltips/join_tooltip_property'; import { DataRequestAbortError } from '../../util/data_request'; @@ -177,10 +177,10 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } supportsFeatureEditing(): boolean { - const dataRequest = this.getDataRequest(IS_EDITABLE_REQUEST_ID); - const data = dataRequest?.getData() as { isEditable: boolean } | undefined; + const dataRequest = this.getDataRequest(SUPPORTS_FEATURE_EDITING_REQUEST_ID); + const data = dataRequest?.getData() as { supportsFeatureEditing: boolean } | undefined; - return data ? data.isEditable : false; + return data ? data.supportsFeatureEditing : false; } hasJoins() { @@ -678,7 +678,7 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { syncContext, source, }); - await this._syncIsEditable({ syncContext }); + await this._syncSupportsFeatureEditing({ syncContext, source }); if ( !sourceResult.featureCollection || !sourceResult.featureCollection.features.length || @@ -696,12 +696,18 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } } - async _syncIsEditable({ syncContext }: { syncContext: DataRequestContext }) { + async _syncSupportsFeatureEditing({ + syncContext, + source, + }: { + syncContext: DataRequestContext; + source: IVectorSource; + }) { if (syncContext.dataFilters.isReadOnly) { return; } const { startLoading, stopLoading, onLoadError } = syncContext; - const dataRequestId = IS_EDITABLE_REQUEST_ID; + const dataRequestId = SUPPORTS_FEATURE_EDITING_REQUEST_ID; const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`); const prevDataRequest = this.getDataRequest(dataRequestId); if (prevDataRequest) { @@ -709,8 +715,8 @@ export class VectorLayer extends AbstractLayer implements IVectorLayer { } try { startLoading(dataRequestId, requestToken); - const isEditable = await this.getSource().loadIsEditable(); - stopLoading(dataRequestId, requestToken, { isEditable }); + const supportsFeatureEditing = await source.supportsFeatureEditing(); + stopLoading(dataRequestId, requestToken, { supportsFeatureEditing }); } catch (error) { onLoadError(dataRequestId, requestToken, error.message); throw error; diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index a3f8ce8e51ede..a51e291574b70 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -389,7 +389,7 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return !!(scalingType === SCALING_TYPES.TOP_HITS && topHitsSplitField); } - async loadIsEditable(): Promise { + async supportsFeatureEditing(): Promise { if (!getMapAppConfig().enableDrawingFeature) { return false; } diff --git a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx index 2311774691acb..d58e71db2a9ab 100644 --- a/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.tsx @@ -228,7 +228,7 @@ export class MVTSingleLayerVectorSource return tooltips; } - async loadIsEditable(): Promise { + async supportsFeatureEditing(): Promise { return false; } } diff --git a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx index 84aad44c4fdbb..1194d571e344b 100644 --- a/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/vector_source/vector_source.tsx @@ -66,7 +66,7 @@ export interface IVectorSource extends ISource { getSupportedShapeTypes(): Promise; isBoundsAware(): boolean; getSourceTooltipContent(sourceDataRequest?: DataRequest): SourceTooltipConfig; - loadIsEditable(): Promise; + supportsFeatureEditing(): Promise; addFeature(geometry: Geometry | Position[]): Promise; } @@ -160,7 +160,7 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc throw new Error('Should implement VectorSource#addFeature'); } - async loadIsEditable(): Promise { + async supportsFeatureEditing(): Promise { return false; } } diff --git a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 526c157ceaabc..ab7a54be37404 100644 --- a/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -41,11 +41,15 @@ export interface Props { interface State { isPopoverOpen: boolean; supportsFeatureEditing: boolean; - canEditFeatures: boolean; + isFeatureEditingEnabled: boolean; } export class TOCEntryActionsPopover extends Component { - state: State = { isPopoverOpen: false, supportsFeatureEditing: false, canEditFeatures: false }; + state: State = { + isPopoverOpen: false, + supportsFeatureEditing: false, + isFeatureEditingEnabled: false, + }; private _isMounted = false; componentDidMount() { @@ -57,26 +61,26 @@ export class TOCEntryActionsPopover extends Component { } componentDidUpdate() { - this._checkLayerEditable(); + this._loadFeatureEditing(); } - async _checkLayerEditable() { + async _loadFeatureEditing() { if (!(this.props.layer instanceof VectorLayer)) { return; } const supportsFeatureEditing = this.props.layer.supportsFeatureEditing(); - const canEditFeatures = await this._getCanEditFeatures(); + const isFeatureEditingEnabled = await this._getIsFeatureEditingEnabled(); if ( !this._isMounted || (supportsFeatureEditing === this.state.supportsFeatureEditing && - canEditFeatures === this.state.canEditFeatures) + isFeatureEditingEnabled === this.state.isFeatureEditingEnabled) ) { return; } - this.setState({ supportsFeatureEditing, canEditFeatures }); + this.setState({ supportsFeatureEditing, isFeatureEditingEnabled }); } - async _getCanEditFeatures(): Promise { + async _getIsFeatureEditingEnabled(): Promise { const vectorLayer = this.props.layer as VectorLayer; const layerSource = await this.props.layer.getSource(); if (!(layerSource instanceof ESSearchSource)) { @@ -160,13 +164,13 @@ export class TOCEntryActionsPopover extends Component { name: EDIT_FEATURES_LABEL, icon: , 'data-test-subj': 'editLayerButton', - toolTipContent: this.state.canEditFeatures + toolTipContent: this.state.isFeatureEditingEnabled ? null : i18n.translate('xpack.maps.layerTocActions.editLayerTooltip', { defaultMessage: 'Edit features only supported for document layers without clustering, joins, or time filtering', }), - disabled: !this.state.canEditFeatures, + disabled: !this.state.isFeatureEditingEnabled, onClick: async () => { this._closePopover(); const supportedShapeTypes = await (this.props.layer.getSource() as ESSearchSource).getSupportedShapeTypes(); From 7e04e1795ccb89dea95fdbf6a943e4d9e530fb61 Mon Sep 17 00:00:00 2001 From: John Dorlus Date: Thu, 17 Jun 2021 23:18:37 -0400 Subject: [PATCH 05/35] Migrated Ingest Node Pipeline Functional Tests to use test_user (#102409) * Used test user and added appropriate kibana privileges for Ingest Node Pipeline functional tests. * Updated ingest pipelines test config to have read permissions in advanced settings. * Updated test to account for the fact that advanced settings will be visible due to kibana permissions. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../feature_controls/ingest_pipelines_security.ts | 4 +++- .../functional/apps/ingest_pipelines/ingest_pipelines.ts | 3 +++ x-pack/test/functional/config.js | 8 ++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts index ea3781de58f15..aed73d6c9858d 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/feature_controls/ingest_pipelines_security.ts @@ -60,7 +60,9 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('should render the "Ingest" section with ingest pipelines', async () => { await PageObjects.common.navigateToApp('management'); const sections = await managementMenu.getSections(); - expect(sections).to.have.length(1); + // We gave the ingest node pipelines user access to advanced settings to allow them to use ingest node pipelines. + // See https://github.com/elastic/kibana/pull/102409/ + expect(sections).to.have.length(2); expect(sections[0]).to.eql({ sectionId: 'ingest', sectionLinks: ['ingest_pipelines'], diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index 3c0cdf4c8060c..02819cd261534 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -18,10 +18,12 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'ingestPipelines']); const log = getService('log'); const es = getService('es'); + const security = getService('security'); describe('Ingest Pipelines', function () { this.tags('smoke'); before(async () => { + await security.testUser.setRoles(['ingest_pipelines_user']); await pageObjects.common.navigateToApp('ingestPipelines'); }); @@ -46,6 +48,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { after(async () => { // Delete the pipeline that was created await es.ingest.deletePipeline({ id: PIPELINE.name }); + await security.testUser.restoreDefaults(); }); }); }; diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 2679bb55ad341..cf05bd6e15898 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -514,6 +514,14 @@ export default async function ({ readConfigFile }) { elasticsearch: { cluster: ['manage_pipeline', 'cluster:monitor/nodes/info'], }, + kibana: [ + { + feature: { + advancedSettings: ['read'], + }, + spaces: ['*'], + }, + ], }, license_management_user: { From 6672baf9a279dd11a06c5712fc62bb2a555928c8 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 18 Jun 2021 08:31:58 +0200 Subject: [PATCH 06/35] remove search bar that's not working yet (#102550) fixes elastic/kibana/issues/102469 --- .../endpoint_hosts/view/details/endpoint_activity_log.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx index 4395e3965ea00..55479845bce0a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_activity_log.tsx @@ -10,8 +10,6 @@ import React, { memo, useCallback } from 'react'; import { EuiButton, EuiEmptyPrompt, EuiLoadingContent, EuiSpacer } from '@elastic/eui'; import { useDispatch } from 'react-redux'; import { LogEntry } from './components/log_entry'; -import * as i18 from '../translations'; -import { SearchBar } from '../../../../components/search_bar'; import { Immutable, ActivityLog } from '../../../../../../common/endpoint/types'; import { AsyncResourceState } from '../../../../state'; import { useEndpointSelector } from '../hooks'; @@ -32,8 +30,6 @@ export const EndpointActivityLog = memo( const activityLogError = useEndpointSelector(getActivityLogError); const dispatch = useDispatch<(a: EndpointAction) => void>(); const { page, pageSize } = useEndpointSelector(getActivityLogDataPaging); - // TODO - const onSearch = useCallback(() => {}, []); const getActivityLog = useCallback(() => { dispatch({ @@ -57,7 +53,6 @@ export const EndpointActivityLog = memo( /> ) : ( <> - {activityLogLoading ? ( From a8020ddb89d53c6e8ab0ca9453bea87a1bc4d939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Fri, 18 Jun 2021 09:33:25 +0200 Subject: [PATCH 07/35] Fixes wrong list exception type when creating endpoint event filters list (#102522) --- .../exception_lists/create_endoint_event_filters_list.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts b/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts index fda8de5da8aae..94a049d10cc45 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/create_endoint_event_filters_list.ts @@ -61,7 +61,7 @@ export const createEndpointEventFiltersList = async ({ os_types: [], tags: [], tie_breaker_id: tieBreaker ?? uuid.v4(), - type: 'endpoint', + type: 'endpoint_events', updated_by: user, version, }, From 7267f505a5414ccf9bf49dafb8f40033f75612d1 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Fri, 18 Jun 2021 10:57:17 +0300 Subject: [PATCH 08/35] [Cases] Route: Get all alerts attach to a case (#101878) Co-authored-by: Jonathan Buttner --- .../plugins/cases/common/api/cases/alerts.ts | 18 ++ .../plugins/cases/common/api/cases/index.ts | 1 + x-pack/plugins/cases/common/constants.ts | 1 + .../classes/client.casesclient.md | 28 +-- .../interfaces/attachments_add.addargs.md | 4 +- ...attachments_client.attachmentssubclient.md | 33 ++- .../attachments_delete.deleteallargs.md | 4 +- .../attachments_delete.deleteargs.md | 6 +- .../interfaces/attachments_get.findargs.md | 4 +- ...ttachments_get.getallalertsattachtocase.md | 21 ++ .../interfaces/attachments_get.getallargs.md | 6 +- .../interfaces/attachments_get.getargs.md | 4 +- .../attachments_update.updateargs.md | 6 +- .../interfaces/cases_client.casessubclient.md | 18 +- .../cases_get.caseidsbyalertidparams.md | 4 +- .../interfaces/cases_get.getparams.md | 6 +- .../interfaces/cases_push.pushparams.md | 4 +- .../configure_client.configuresubclient.md | 8 +- .../interfaces/stats_client.statssubclient.md | 2 +- .../sub_cases_client.subcasesclient.md | 8 +- .../user_actions_client.useractionget.md | 4 +- ...ser_actions_client.useractionssubclient.md | 2 +- .../cases_client/modules/attachments_get.md | 1 + .../docs/cases_client/modules/cases_get.md | 4 +- .../__snapshots__/audit_logger.test.ts.snap | 84 +++++++ .../cases/server/authorization/index.ts | 8 + .../cases/server/authorization/types.ts | 1 + .../cases/server/client/alerts/client.ts | 21 +- .../plugins/cases/server/client/alerts/get.ts | 9 +- .../cases/server/client/alerts/types.ts | 20 ++ .../server/client/alerts/update_status.ts | 2 +- .../cases/server/client/attachments/client.ts | 21 +- .../cases/server/client/attachments/get.ts | 71 +++++- .../cases/server/client/cases/update.ts | 2 +- x-pack/plugins/cases/server/client/client.ts | 2 +- x-pack/plugins/cases/server/client/mocks.ts | 1 + .../cases/server/client/sub_cases/update.ts | 2 +- x-pack/plugins/cases/server/common/utils.ts | 11 +- .../server/routes/api/comments/get_alerts.ts | 41 ++++ .../plugins/cases/server/routes/api/index.ts | 2 + .../cases/server/services/alerts/index.ts | 2 +- .../server/services/attachments/index.ts | 51 +++++ x-pack/plugins/cases/server/services/mocks.ts | 1 + .../case_api_integration/common/lib/utils.ts | 20 ++ .../alerts/get_alerts_attached_to_case.ts | 215 ++++++++++++++++++ .../security_and_spaces/tests/common/index.ts | 1 + 46 files changed, 674 insertions(+), 111 deletions(-) create mode 100644 x-pack/plugins/cases/common/api/cases/alerts.ts create mode 100644 x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md create mode 100644 x-pack/plugins/cases/server/routes/api/comments/get_alerts.ts create mode 100644 x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts diff --git a/x-pack/plugins/cases/common/api/cases/alerts.ts b/x-pack/plugins/cases/common/api/cases/alerts.ts new file mode 100644 index 0000000000000..1a1abb4cbb66a --- /dev/null +++ b/x-pack/plugins/cases/common/api/cases/alerts.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 * as rt from 'io-ts'; + +const AlertRt = rt.type({ + id: rt.string, + index: rt.string, + attached_at: rt.string, +}); + +export const AlertResponseRt = rt.array(AlertRt); + +export type AlertResponse = rt.TypeOf; diff --git a/x-pack/plugins/cases/common/api/cases/index.ts b/x-pack/plugins/cases/common/api/cases/index.ts index 0f78ca9b35377..ba8ad15d02e27 100644 --- a/x-pack/plugins/cases/common/api/cases/index.ts +++ b/x-pack/plugins/cases/common/api/cases/index.ts @@ -12,3 +12,4 @@ export * from './status'; export * from './user_actions'; export * from './sub_case'; export * from './constants'; +export * from './alerts'; diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index f0d3e8ccbcdea..317fe1d8ed144 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -51,6 +51,7 @@ export const CASE_TAGS_URL = `${CASES_URL}/tags`; export const CASE_USER_ACTIONS_URL = `${CASE_DETAILS_URL}/user_actions`; export const CASE_ALERTS_URL = `${CASES_URL}/alerts/{alert_id}`; +export const CASE_DETAILS_ALERTS_URL = `${CASE_DETAILS_URL}/alerts`; /** * Action routes diff --git a/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md b/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md index 98e2f284da4a6..a20f018cffeb8 100644 --- a/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md +++ b/x-pack/plugins/cases/docs/cases_client/classes/client.casesclient.md @@ -45,7 +45,7 @@ Client wrapper that contains accessor methods for individual entities within the **Returns:** [*CasesClient*](client.casesclient.md) -Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L28) +Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L28) ## Properties @@ -53,7 +53,7 @@ Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/2085a • `Private` `Readonly` **\_attachments**: [*AttachmentsSubClient*](../interfaces/attachments_client.attachmentssubclient.md) -Defined in: [client.ts:24](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L24) +Defined in: [client.ts:24](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L24) ___ @@ -61,7 +61,7 @@ ___ • `Private` `Readonly` **\_cases**: [*CasesSubClient*](../interfaces/cases_client.casessubclient.md) -Defined in: [client.ts:23](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L23) +Defined in: [client.ts:23](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L23) ___ @@ -69,7 +69,7 @@ ___ • `Private` `Readonly` **\_casesClientInternal**: *CasesClientInternal* -Defined in: [client.ts:22](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L22) +Defined in: [client.ts:22](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L22) ___ @@ -77,7 +77,7 @@ ___ • `Private` `Readonly` **\_configure**: [*ConfigureSubClient*](../interfaces/configure_client.configuresubclient.md) -Defined in: [client.ts:27](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L27) +Defined in: [client.ts:27](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L27) ___ @@ -85,7 +85,7 @@ ___ • `Private` `Readonly` **\_stats**: [*StatsSubClient*](../interfaces/stats_client.statssubclient.md) -Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L28) +Defined in: [client.ts:28](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L28) ___ @@ -93,7 +93,7 @@ ___ • `Private` `Readonly` **\_subCases**: [*SubCasesClient*](../interfaces/sub_cases_client.subcasesclient.md) -Defined in: [client.ts:26](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L26) +Defined in: [client.ts:26](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L26) ___ @@ -101,7 +101,7 @@ ___ • `Private` `Readonly` **\_userActions**: [*UserActionsSubClient*](../interfaces/user_actions_client.useractionssubclient.md) -Defined in: [client.ts:25](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L25) +Defined in: [client.ts:25](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L25) ## Accessors @@ -113,7 +113,7 @@ Retrieves an interface for interacting with attachments (comments) entities. **Returns:** [*AttachmentsSubClient*](../interfaces/attachments_client.attachmentssubclient.md) -Defined in: [client.ts:50](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L50) +Defined in: [client.ts:50](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L50) ___ @@ -125,7 +125,7 @@ Retrieves an interface for interacting with cases entities. **Returns:** [*CasesSubClient*](../interfaces/cases_client.casessubclient.md) -Defined in: [client.ts:43](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L43) +Defined in: [client.ts:43](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L43) ___ @@ -137,7 +137,7 @@ Retrieves an interface for interacting with the configuration of external connec **Returns:** [*ConfigureSubClient*](../interfaces/configure_client.configuresubclient.md) -Defined in: [client.ts:76](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L76) +Defined in: [client.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L76) ___ @@ -149,7 +149,7 @@ Retrieves an interface for retrieving statistics related to the cases entities. **Returns:** [*StatsSubClient*](../interfaces/stats_client.statssubclient.md) -Defined in: [client.ts:83](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L83) +Defined in: [client.ts:83](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L83) ___ @@ -163,7 +163,7 @@ Currently this functionality is disabled and will throw an error if this functio **Returns:** [*SubCasesClient*](../interfaces/sub_cases_client.subcasesclient.md) -Defined in: [client.ts:66](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L66) +Defined in: [client.ts:66](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L66) ___ @@ -175,4 +175,4 @@ Retrieves an interface for interacting with the user actions associated with the **Returns:** [*UserActionsSubClient*](../interfaces/user_actions_client.useractionssubclient.md) -Defined in: [client.ts:57](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/client.ts#L57) +Defined in: [client.ts:57](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/client.ts#L57) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md index 1bbca9167a5c2..d5233ab6d8cb4 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_add.addargs.md @@ -21,7 +21,7 @@ The arguments needed for creating a new attachment to a case. The case ID that this attachment will be associated with -Defined in: [attachments/add.ts:308](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/add.ts#L308) +Defined in: [attachments/add.ts:305](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/add.ts#L305) ___ @@ -31,4 +31,4 @@ ___ The attachment values. -Defined in: [attachments/add.ts:312](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/add.ts#L312) +Defined in: [attachments/add.ts:309](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/add.ts#L309) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md index e9f65bcf9915a..1a9a687aa812b 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_client.attachmentssubclient.md @@ -16,6 +16,7 @@ API for interacting with the attachments to a case. - [find](attachments_client.attachmentssubclient.md#find) - [get](attachments_client.attachmentssubclient.md#get) - [getAll](attachments_client.attachmentssubclient.md#getall) +- [getAllAlertsAttachToCase](attachments_client.attachmentssubclient.md#getallalertsattachtocase) - [update](attachments_client.attachmentssubclient.md#update) ## Methods @@ -34,7 +35,7 @@ Adds an attachment to a case. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [attachments/client.ts:25](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L25) +Defined in: [attachments/client.ts:35](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L35) ___ @@ -52,7 +53,7 @@ Deletes a single attachment for a specific case. **Returns:** *Promise* -Defined in: [attachments/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L33) +Defined in: [attachments/client.ts:43](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L43) ___ @@ -70,7 +71,7 @@ Deletes all attachments associated with a single case. **Returns:** *Promise* -Defined in: [attachments/client.ts:29](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L29) +Defined in: [attachments/client.ts:39](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L39) ___ @@ -88,7 +89,7 @@ Retrieves all comments matching the search criteria. **Returns:** *Promise*<[*ICommentsResponse*](typedoc_interfaces.icommentsresponse.md)\> -Defined in: [attachments/client.ts:37](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L37) +Defined in: [attachments/client.ts:47](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L47) ___ @@ -106,7 +107,7 @@ Retrieves a single attachment for a case. **Returns:** *Promise*<{ `comment`: *string* ; `owner`: *string* ; `type`: user } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* } & { `alertId`: *string* \| *string*[] ; `index`: *string* \| *string*[] ; `owner`: *string* ; `rule`: { id: string \| null; name: string \| null; } ; `type`: alert \| generatedAlert } & { `associationType`: AssociationType ; `created_at`: *string* ; `created_by`: { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `owner`: *string* ; `pushed_at`: ``null`` \| *string* ; `pushed_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } ; `updated_at`: ``null`` \| *string* ; `updated_by`: ``null`` \| { email: string \| null \| undefined; full\_name: string \| null \| undefined; username: string \| null \| undefined; } } & { `id`: *string* ; `version`: *string* }\> -Defined in: [attachments/client.ts:45](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L45) +Defined in: [attachments/client.ts:59](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L59) ___ @@ -124,7 +125,25 @@ Gets all attachments for a single case. **Returns:** *Promise*<[*IAllCommentsResponse*](typedoc_interfaces.iallcommentsresponse.md)\> -Defined in: [attachments/client.ts:41](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L41) +Defined in: [attachments/client.ts:55](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L55) + +___ + +### getAllAlertsAttachToCase + +▸ **getAllAlertsAttachToCase**(`params`: [*GetAllAlertsAttachToCase*](attachments_get.getallalertsattachtocase.md)): *Promise*<{ `attached_at`: *string* ; `id`: *string* ; `index`: *string* }[]\> + +Retrieves all alerts attach to a case given a single case ID + +#### Parameters + +| Name | Type | +| :------ | :------ | +| `params` | [*GetAllAlertsAttachToCase*](attachments_get.getallalertsattachtocase.md) | + +**Returns:** *Promise*<{ `attached_at`: *string* ; `id`: *string* ; `index`: *string* }[]\> + +Defined in: [attachments/client.ts:51](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L51) ___ @@ -144,4 +163,4 @@ The request must include all fields for the attachment. Even the fields that are **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [attachments/client.ts:51](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/client.ts#L51) +Defined in: [attachments/client.ts:65](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/client.ts#L65) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md index 26b00ac6e037e..437758a0147f2 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteallargs.md @@ -21,7 +21,7 @@ Parameters for deleting all comments of a case or sub case. The case ID to delete all attachments for -Defined in: [attachments/delete.ts:26](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L26) +Defined in: [attachments/delete.ts:31](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L31) ___ @@ -31,4 +31,4 @@ ___ If specified the caseID will be ignored and this value will be used to find a sub case for deleting all the attachments -Defined in: [attachments/delete.ts:30](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L30) +Defined in: [attachments/delete.ts:35](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L35) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md index f9d4038eb417a..1afa5679161d9 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_delete.deleteargs.md @@ -22,7 +22,7 @@ Parameters for deleting a single attachment of a case or sub case. The attachment ID to delete -Defined in: [attachments/delete.ts:44](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L44) +Defined in: [attachments/delete.ts:49](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L49) ___ @@ -32,7 +32,7 @@ ___ The case ID to delete an attachment from -Defined in: [attachments/delete.ts:40](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L40) +Defined in: [attachments/delete.ts:45](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L45) ___ @@ -42,4 +42,4 @@ ___ If specified the caseID will be ignored and this value will be used to find a sub case for deleting the attachment -Defined in: [attachments/delete.ts:48](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/delete.ts#L48) +Defined in: [attachments/delete.ts:53](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/delete.ts#L53) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md index dbbac0065be85..dc0da295b26d2 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.findargs.md @@ -21,7 +21,7 @@ Parameters for finding attachments of a case The case ID for finding associated attachments -Defined in: [attachments/get.ts:48](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L48) +Defined in: [attachments/get.ts:47](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L47) ___ @@ -48,4 +48,4 @@ Optional parameters for filtering the returned attachments | `sortOrder` | *undefined* \| ``"desc"`` \| ``"asc"`` | | `subCaseId` | *undefined* \| *string* | -Defined in: [attachments/get.ts:52](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L52) +Defined in: [attachments/get.ts:51](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L51) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md new file mode 100644 index 0000000000000..541d1cf8f1d80 --- /dev/null +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallalertsattachtocase.md @@ -0,0 +1,21 @@ +[Cases Client API Interface](../cases_client_api.md) / [attachments/get](../modules/attachments_get.md) / GetAllAlertsAttachToCase + +# Interface: GetAllAlertsAttachToCase + +[attachments/get](../modules/attachments_get.md).GetAllAlertsAttachToCase + +## Table of contents + +### Properties + +- [caseId](attachments_get.getallalertsattachtocase.md#caseid) + +## Properties + +### caseId + +• **caseId**: *string* + +The ID of the case to retrieve the alerts from + +Defined in: [attachments/get.ts:87](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L87) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md index dbd66291e22de..ae67f85e96fc0 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getallargs.md @@ -22,7 +22,7 @@ Parameters for retrieving all attachments of a case The case ID to retrieve all attachments for -Defined in: [attachments/get.ts:62](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L62) +Defined in: [attachments/get.ts:61](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L61) ___ @@ -32,7 +32,7 @@ ___ Optionally include the attachments associated with a sub case -Defined in: [attachments/get.ts:66](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L66) +Defined in: [attachments/get.ts:65](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L65) ___ @@ -42,4 +42,4 @@ ___ If included the case ID will be ignored and the attachments will be retrieved from the specified ID of the sub case -Defined in: [attachments/get.ts:70](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L70) +Defined in: [attachments/get.ts:69](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L69) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md index abfd4bb5958d3..2fc569985f980 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_get.getargs.md @@ -19,7 +19,7 @@ The ID of the attachment to retrieve -Defined in: [attachments/get.ts:81](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L81) +Defined in: [attachments/get.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L80) ___ @@ -29,4 +29,4 @@ ___ The ID of the case to retrieve an attachment from -Defined in: [attachments/get.ts:77](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/get.ts#L77) +Defined in: [attachments/get.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/get.ts#L76) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md index b571067175f62..4b2dd7b404e7a 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/attachments_update.updateargs.md @@ -22,7 +22,7 @@ Parameters for updating a single attachment The ID of the case that is associated with this attachment -Defined in: [attachments/update.ts:29](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/update.ts#L29) +Defined in: [attachments/update.ts:32](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/update.ts#L32) ___ @@ -32,7 +32,7 @@ ___ The ID of a sub case, if specified a sub case will be searched for to perform the attachment update instead of on a case -Defined in: [attachments/update.ts:37](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/update.ts#L37) +Defined in: [attachments/update.ts:40](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/update.ts#L40) ___ @@ -42,4 +42,4 @@ ___ The full attachment request with the fields updated with appropriate values -Defined in: [attachments/update.ts:33](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/attachments/update.ts#L33) +Defined in: [attachments/update.ts:36](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/attachments/update.ts#L36) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md index e7d7dea34d0ad..d86308720cb95 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_client.casessubclient.md @@ -36,7 +36,7 @@ Creates a case. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [cases/client.ts:48](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L48) +Defined in: [cases/client.ts:48](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L48) ___ @@ -56,7 +56,7 @@ Delete a case and all its comments. **Returns:** *Promise* -Defined in: [cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L72) +Defined in: [cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L72) ___ @@ -76,7 +76,7 @@ If the `owner` field is left empty then all the cases that the user has access t **Returns:** *Promise*<[*ICasesFindResponse*](typedoc_interfaces.icasesfindresponse.md)\> -Defined in: [cases/client.ts:54](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L54) +Defined in: [cases/client.ts:54](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L54) ___ @@ -94,7 +94,7 @@ Retrieves a single case with the specified ID. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [cases/client.ts:58](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L58) +Defined in: [cases/client.ts:58](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L58) ___ @@ -112,7 +112,7 @@ Retrieves the case IDs given a single alert ID **Returns:** *Promise* -Defined in: [cases/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L84) +Defined in: [cases/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L84) ___ @@ -131,7 +131,7 @@ Retrieves all the reporters across all accessible cases. **Returns:** *Promise*<{ `email`: *undefined* \| ``null`` \| *string* ; `full_name`: *undefined* \| ``null`` \| *string* ; `username`: *undefined* \| ``null`` \| *string* }[]\> -Defined in: [cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L80) +Defined in: [cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L80) ___ @@ -150,7 +150,7 @@ Retrieves all the tags across all cases the user making the request has access t **Returns:** *Promise* -Defined in: [cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L76) +Defined in: [cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L76) ___ @@ -168,7 +168,7 @@ Pushes a specific case to an external system. **Returns:** *Promise*<[*ICaseResponse*](typedoc_interfaces.icaseresponse.md)\> -Defined in: [cases/client.ts:62](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L62) +Defined in: [cases/client.ts:62](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L62) ___ @@ -186,4 +186,4 @@ Update the specified cases with the passed in values. **Returns:** *Promise*<[*ICasesResponse*](typedoc_interfaces.icasesresponse.md)\> -Defined in: [cases/client.ts:66](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/client.ts#L66) +Defined in: [cases/client.ts:66](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/client.ts#L66) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md index 1b8abba1a4071..274b7a8f2d431 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.caseidsbyalertidparams.md @@ -21,7 +21,7 @@ Parameters for finding cases IDs using an alert ID The alert ID to search for -Defined in: [cases/get.ts:47](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L47) +Defined in: [cases/get.ts:42](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L42) ___ @@ -37,4 +37,4 @@ The filtering options when searching for associated cases. | :------ | :------ | | `owner` | *undefined* \| *string* \| *string*[] | -Defined in: [cases/get.ts:51](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L51) +Defined in: [cases/get.ts:46](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L46) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md index 8c12b5533ac18..a528b7ce6256d 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_get.getparams.md @@ -22,7 +22,7 @@ The parameters for retrieving a case Case ID -Defined in: [cases/get.ts:122](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L122) +Defined in: [cases/get.ts:110](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L110) ___ @@ -32,7 +32,7 @@ ___ Whether to include the attachments for a case in the response -Defined in: [cases/get.ts:126](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L126) +Defined in: [cases/get.ts:114](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L114) ___ @@ -42,4 +42,4 @@ ___ Whether to include the attachments for all children of a case in the response -Defined in: [cases/get.ts:130](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L130) +Defined in: [cases/get.ts:118](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L118) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md index 9f1810e4f0cc2..979e30cb31d3f 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/cases_push.pushparams.md @@ -21,7 +21,7 @@ Parameters for pushing a case to an external system The ID of a case -Defined in: [cases/push.ts:53](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/push.ts#L53) +Defined in: [cases/push.ts:53](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/push.ts#L53) ___ @@ -31,4 +31,4 @@ ___ The ID of an external system to push to -Defined in: [cases/push.ts:57](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/push.ts#L57) +Defined in: [cases/push.ts:57](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/push.ts#L57) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md index 9b3827a57a9d3..cf69b101ce2bc 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/configure_client.configuresubclient.md @@ -31,7 +31,7 @@ Creates a configuration if one does not already exist. If one exists it is delet **Returns:** *Promise*<[*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\> -Defined in: [configure/client.ts:102](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L102) +Defined in: [configure/client.ts:98](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L98) ___ @@ -50,7 +50,7 @@ Retrieves the external connector configuration for a particular case owner. **Returns:** *Promise*<{} \| [*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\> -Defined in: [configure/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L84) +Defined in: [configure/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L80) ___ @@ -62,7 +62,7 @@ Retrieves the valid external connectors supported by the cases plugin. **Returns:** *Promise* -Defined in: [configure/client.ts:88](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L88) +Defined in: [configure/client.ts:84](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L84) ___ @@ -81,4 +81,4 @@ Updates a particular configuration with new values. **Returns:** *Promise*<[*ICasesConfigureResponse*](typedoc_interfaces.icasesconfigureresponse.md)\> -Defined in: [configure/client.ts:95](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/configure/client.ts#L95) +Defined in: [configure/client.ts:91](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/configure/client.ts#L91) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md index 7e01205395277..761b34b5205ec 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/stats_client.statssubclient.md @@ -29,4 +29,4 @@ Retrieves the total number of open, closed, and in-progress cases. **Returns:** *Promise*<{ `count_closed_cases`: *number* ; `count_in_progress_cases`: *number* ; `count_open_cases`: *number* }\> -Defined in: [stats/client.ts:34](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/stats/client.ts#L34) +Defined in: [stats/client.ts:34](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/stats/client.ts#L34) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md index 76df26524b7b0..c83c68620e8ac 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/sub_cases_client.subcasesclient.md @@ -31,7 +31,7 @@ Deletes the specified entities and their attachments. **Returns:** *Promise* -Defined in: [sub_cases/client.ts:60](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L60) +Defined in: [sub_cases/client.ts:68](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L68) ___ @@ -49,7 +49,7 @@ Retrieves the sub cases matching the search criteria. **Returns:** *Promise*<[*ISubCasesFindResponse*](typedoc_interfaces.isubcasesfindresponse.md)\> -Defined in: [sub_cases/client.ts:64](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L64) +Defined in: [sub_cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L72) ___ @@ -67,7 +67,7 @@ Retrieves a single sub case. **Returns:** *Promise*<[*ISubCaseResponse*](typedoc_interfaces.isubcaseresponse.md)\> -Defined in: [sub_cases/client.ts:68](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L68) +Defined in: [sub_cases/client.ts:76](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L76) ___ @@ -86,4 +86,4 @@ Updates the specified sub cases to the new values included in the request. **Returns:** *Promise*<[*ISubCasesResponse*](typedoc_interfaces.isubcasesresponse.md)\> -Defined in: [sub_cases/client.ts:72](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/sub_cases/client.ts#L72) +Defined in: [sub_cases/client.ts:80](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/sub_cases/client.ts#L80) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md index 2c0c084ab9b30..f992a4116c800 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionget.md @@ -21,7 +21,7 @@ Parameters for retrieving user actions for a particular case The ID of the case -Defined in: [user_actions/client.ts:19](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/user_actions/client.ts#L19) +Defined in: [user_actions/client.ts:19](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/user_actions/client.ts#L19) ___ @@ -31,4 +31,4 @@ ___ If specified then a sub case will be used for finding all the user actions -Defined in: [user_actions/client.ts:23](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/user_actions/client.ts#L23) +Defined in: [user_actions/client.ts:23](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/user_actions/client.ts#L23) diff --git a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md index f03667eccb858..e838a72159bef 100644 --- a/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md +++ b/x-pack/plugins/cases/docs/cases_client/interfaces/user_actions_client.useractionssubclient.md @@ -28,4 +28,4 @@ Retrieves all user actions for a particular case. **Returns:** *Promise*<[*ICaseUserActionsResponse*](typedoc_interfaces.icaseuseractionsresponse.md)\> -Defined in: [user_actions/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/user_actions/client.ts#L33) +Defined in: [user_actions/client.ts:33](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/user_actions/client.ts#L33) diff --git a/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md b/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md index 99358d6683256..6460511da79a1 100644 --- a/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md +++ b/x-pack/plugins/cases/docs/cases_client/modules/attachments_get.md @@ -7,5 +7,6 @@ ### Interfaces - [FindArgs](../interfaces/attachments_get.findargs.md) +- [GetAllAlertsAttachToCase](../interfaces/attachments_get.getallalertsattachtocase.md) - [GetAllArgs](../interfaces/attachments_get.getallargs.md) - [GetArgs](../interfaces/attachments_get.getargs.md) diff --git a/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md b/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md index 9e896881df17b..acfa0b918aa9a 100644 --- a/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md +++ b/x-pack/plugins/cases/docs/cases_client/modules/cases_get.md @@ -31,7 +31,7 @@ Retrieves the reporters from all the cases. **Returns:** *Promise* -Defined in: [cases/get.ts:279](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L279) +Defined in: [cases/get.ts:255](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L255) ___ @@ -50,4 +50,4 @@ Retrieves the tags from all the cases. **Returns:** *Promise* -Defined in: [cases/get.ts:217](https://github.com/jonathan-buttner/kibana/blob/2085a3b4480/x-pack/plugins/cases/server/client/cases/get.ts#L217) +Defined in: [cases/get.ts:205](https://github.com/jonathan-buttner/kibana/blob/0e98e105663/x-pack/plugins/cases/server/client/cases/get.ts#L205) diff --git a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap index 7f5b8406b89f3..3ca77944776b3 100644 --- a/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap +++ b/x-pack/plugins/cases/server/authorization/__snapshots__/audit_logger.test.ts.snap @@ -756,6 +756,90 @@ Object { } `; +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" with an error and entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "1", + "type": "cases-comments", + }, + }, + "message": "Failed attempt to access cases-comments [id=1] as owner \\"awesome\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" with an error but no entity 1`] = ` +Object { + "error": Object { + "code": "Error", + "message": "an error", + }, + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "failure", + "type": Array [ + "access", + ], + }, + "message": "Failed attempt to access a comments as any owners", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" without an error but with an entity 1`] = ` +Object { + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "kibana": Object { + "saved_object": Object { + "id": "5", + "type": "cases-comments", + }, + }, + "message": "User has accessed cases-comments [id=5] as owner \\"super\\"", +} +`; + +exports[`audit_logger log function event structure creates the correct audit event for operation: "getAlertsAttachedToCase" without an error or entity 1`] = ` +Object { + "event": Object { + "action": "case_comment_alerts_attach_to_case", + "category": Array [ + "database", + ], + "outcome": "success", + "type": Array [ + "access", + ], + }, + "message": "User has accessed a comments as any owners", +} +`; + exports[`audit_logger log function event structure creates the correct audit event for operation: "getAllComments" with an error and entity 1`] = ` Object { "error": Object { diff --git a/x-pack/plugins/cases/server/authorization/index.ts b/x-pack/plugins/cases/server/authorization/index.ts index 87683c55f5da0..90b89c7f75766 100644 --- a/x-pack/plugins/cases/server/authorization/index.ts +++ b/x-pack/plugins/cases/server/authorization/index.ts @@ -184,6 +184,14 @@ export const Operations: Record; updateStatus(args: AlertUpdateStatus): Promise; diff --git a/x-pack/plugins/cases/server/client/alerts/get.ts b/x-pack/plugins/cases/server/client/alerts/get.ts index 186f914aa2cd7..2048ccae4fa60 100644 --- a/x-pack/plugins/cases/server/client/alerts/get.ts +++ b/x-pack/plugins/cases/server/client/alerts/get.ts @@ -5,16 +5,11 @@ * 2.0. */ -import { AlertInfo } from '../../common'; -import { CasesClientGetAlertsResponse } from './types'; +import { CasesClientGetAlertsResponse, AlertGet } from './types'; import { CasesClientArgs } from '..'; -interface GetParams { - alertsInfo: AlertInfo[]; -} - export const get = async ( - { alertsInfo }: GetParams, + { alertsInfo }: AlertGet, clientArgs: CasesClientArgs ): Promise => { const { alertsService, scopedClusterClient, logger } = clientArgs; diff --git a/x-pack/plugins/cases/server/client/alerts/types.ts b/x-pack/plugins/cases/server/client/alerts/types.ts index 26a582d92e54b..95cd9ae33bff9 100644 --- a/x-pack/plugins/cases/server/client/alerts/types.ts +++ b/x-pack/plugins/cases/server/client/alerts/types.ts @@ -5,6 +5,9 @@ * 2.0. */ +import { CaseStatuses } from '../../../common/api'; +import { AlertInfo } from '../../common'; + interface Alert { id: string; index: string; @@ -17,3 +20,20 @@ interface Alert { } export type CasesClientGetAlertsResponse = Alert[]; + +/** + * Defines the fields necessary to update an alert's status. + */ +export interface UpdateAlertRequest { + id: string; + index: string; + status: CaseStatuses; +} + +export interface AlertUpdateStatus { + alerts: UpdateAlertRequest[]; +} + +export interface AlertGet { + alertsInfo: AlertInfo[]; +} diff --git a/x-pack/plugins/cases/server/client/alerts/update_status.ts b/x-pack/plugins/cases/server/client/alerts/update_status.ts index 3c7f60ecae15d..a0684b59241b0 100644 --- a/x-pack/plugins/cases/server/client/alerts/update_status.ts +++ b/x-pack/plugins/cases/server/client/alerts/update_status.ts @@ -5,8 +5,8 @@ * 2.0. */ -import { UpdateAlertRequest } from './client'; import { CasesClientArgs } from '..'; +import { UpdateAlertRequest } from './types'; interface UpdateAlertsStatusArgs { alerts: UpdateAlertRequest[]; diff --git a/x-pack/plugins/cases/server/client/attachments/client.ts b/x-pack/plugins/cases/server/client/attachments/client.ts index 6a0f2c71ddd91..a07633c0dd38c 100644 --- a/x-pack/plugins/cases/server/client/attachments/client.ts +++ b/x-pack/plugins/cases/server/client/attachments/client.ts @@ -5,14 +5,24 @@ * 2.0. */ -import { CommentResponse } from '../../../common'; +import { AlertResponse, CommentResponse } from '../../../common'; +import { CasesClient } from '../client'; import { CasesClientInternal } from '../client_internal'; import { IAllCommentsResponse, ICaseResponse, ICommentsResponse } from '../typedoc_interfaces'; import { CasesClientArgs } from '../types'; import { AddArgs, addComment } from './add'; import { DeleteAllArgs, deleteAll, DeleteArgs, deleteComment } from './delete'; -import { find, FindArgs, get, getAll, GetAllArgs, GetArgs } from './get'; +import { + find, + FindArgs, + get, + getAll, + getAllAlertsAttachToCase, + GetAllAlertsAttachToCase, + GetAllArgs, + GetArgs, +} from './get'; import { update, UpdateArgs } from './update'; /** @@ -35,6 +45,10 @@ export interface AttachmentsSubClient { * Retrieves all comments matching the search criteria. */ find(findArgs: FindArgs): Promise; + /** + * Retrieves all alerts attach to a case given a single case ID + */ + getAllAlertsAttachToCase(params: GetAllAlertsAttachToCase): Promise; /** * Gets all attachments for a single case. */ @@ -58,6 +72,7 @@ export interface AttachmentsSubClient { */ export const createAttachmentsSubClient = ( clientArgs: CasesClientArgs, + casesClient: CasesClient, casesClientInternal: CasesClientInternal ): AttachmentsSubClient => { const attachmentSubClient: AttachmentsSubClient = { @@ -65,6 +80,8 @@ export const createAttachmentsSubClient = ( deleteAll: (deleteAllArgs: DeleteAllArgs) => deleteAll(deleteAllArgs, clientArgs), delete: (deleteArgs: DeleteArgs) => deleteComment(deleteArgs, clientArgs), find: (findArgs: FindArgs) => find(findArgs, clientArgs), + getAllAlertsAttachToCase: (params: GetAllAlertsAttachToCase) => + getAllAlertsAttachToCase(params, clientArgs, casesClient), getAll: (getAllArgs: GetAllArgs) => getAll(getAllArgs, clientArgs), get: (getArgs: GetArgs) => get(getArgs, clientArgs), update: (updateArgs: UpdateArgs) => update(updateArgs, clientArgs), diff --git a/x-pack/plugins/cases/server/client/attachments/get.ts b/x-pack/plugins/cases/server/client/attachments/get.ts index 590038a200e48..4f4ade51f9a59 100644 --- a/x-pack/plugins/cases/server/client/attachments/get.ts +++ b/x-pack/plugins/cases/server/client/attachments/get.ts @@ -5,12 +5,14 @@ * 2.0. */ import Boom from '@hapi/boom'; -import { SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; import { + AlertResponse, AllCommentsResponse, AllCommentsResponseRt, AssociationType, + AttributesTypeAlerts, CommentAttributes, CommentResponse, CommentResponseRt, @@ -26,12 +28,14 @@ import { transformComments, flattenCommentSavedObject, flattenCommentSavedObjects, + getIDsAndIndicesAsArrays, } from '../../common'; import { defaultPage, defaultPerPage } from '../../routes/api'; import { CasesClientArgs } from '../types'; import { combineFilters, stringToKueryNode } from '../utils'; import { Operations } from '../../authorization'; import { includeFieldsRequiredForAuthentication } from '../../authorization/utils'; +import { CasesClient } from '../client'; /** * Parameters for finding attachments of a case @@ -76,6 +80,71 @@ export interface GetArgs { attachmentID: string; } +export interface GetAllAlertsAttachToCase { + /** + * The ID of the case to retrieve the alerts from + */ + caseId: string; +} + +const normalizeAlertResponse = (alerts: Array>): AlertResponse => + alerts.reduce((acc: AlertResponse, alert) => { + const { ids, indices } = getIDsAndIndicesAsArrays(alert.attributes); + + if (ids.length !== indices.length) { + return acc; + } + + return [ + ...acc, + ...ids.map((id, index) => ({ + id, + index: indices[index], + attached_at: alert.attributes.created_at, + })), + ]; + }, []); + +/** + * Retrieves all alerts attached to a specific case. + * + * @ignore + */ +export const getAllAlertsAttachToCase = async ( + { caseId }: GetAllAlertsAttachToCase, + clientArgs: CasesClientArgs, + casesClient: CasesClient +): Promise => { + const { unsecuredSavedObjectsClient, authorization, attachmentService } = clientArgs; + + // This will perform an authorization check to ensure the user has access to the parent case + const theCase = await casesClient.cases.get({ + id: caseId, + includeComments: false, + includeSubCaseComments: false, + }); + + const { + filter: authorizationFilter, + ensureSavedObjectsAreAuthorized, + } = await authorization.getAuthorizationFilter(Operations.getAlertsAttachedToCase); + + const alerts = await attachmentService.getAllAlertsAttachToCase({ + unsecuredSavedObjectsClient, + caseId: theCase.id, + filter: authorizationFilter, + }); + + ensureSavedObjectsAreAuthorized( + alerts.map((alert) => ({ + owner: alert.attributes.owner, + id: alert.id, + })) + ); + + return normalizeAlertResponse(alerts); +}; + /** * Retrieves the attachments for a case entity. This support pagination. * diff --git a/x-pack/plugins/cases/server/client/cases/update.ts b/x-pack/plugins/cases/server/client/cases/update.ts index 5726cfe44f697..e5d9e1cddeee6 100644 --- a/x-pack/plugins/cases/server/client/cases/update.ts +++ b/x-pack/plugins/cases/server/client/cases/update.ts @@ -52,7 +52,7 @@ import { isCommentRequestTypeAlertOrGenAlert, transformCaseConnectorToEsConnector, } from '../../common'; -import { UpdateAlertRequest } from '../alerts/client'; +import { UpdateAlertRequest } from '../alerts/types'; import { CasesClientInternal } from '../client_internal'; import { CasesClientArgs } from '..'; import { Operations, OwnerEntity } from '../../authorization'; diff --git a/x-pack/plugins/cases/server/client/client.ts b/x-pack/plugins/cases/server/client/client.ts index b7b1dd46d003d..37e4cd52681e5 100644 --- a/x-pack/plugins/cases/server/client/client.ts +++ b/x-pack/plugins/cases/server/client/client.ts @@ -30,7 +30,7 @@ export class CasesClient { constructor(args: CasesClientArgs) { this._casesClientInternal = createCasesClientInternal(args); this._cases = createCasesSubClient(args, this, this._casesClientInternal); - this._attachments = createAttachmentsSubClient(args, this._casesClientInternal); + this._attachments = createAttachmentsSubClient(args, this, this._casesClientInternal); this._userActions = createUserActionsSubClient(args); this._subCases = createSubCasesClient(args, this._casesClientInternal); this._configure = createConfigurationSubClient(args, this._casesClientInternal); diff --git a/x-pack/plugins/cases/server/client/mocks.ts b/x-pack/plugins/cases/server/client/mocks.ts index 10b298995f87a..f6a36369c0b03 100644 --- a/x-pack/plugins/cases/server/client/mocks.ts +++ b/x-pack/plugins/cases/server/client/mocks.ts @@ -43,6 +43,7 @@ const createAttachmentsSubClientMock = (): AttachmentsSubClientMock => { getAll: jest.fn(), get: jest.fn(), update: jest.fn(), + getAllAlertsAttachToCase: jest.fn(), }; }; diff --git a/x-pack/plugins/cases/server/client/sub_cases/update.ts b/x-pack/plugins/cases/server/client/sub_cases/update.ts index c2c5f8719e64a..be671a8087f8e 100644 --- a/x-pack/plugins/cases/server/client/sub_cases/update.ts +++ b/x-pack/plugins/cases/server/client/sub_cases/update.ts @@ -44,7 +44,7 @@ import { isCommentRequestTypeAlertOrGenAlert, flattenSubCaseSavedObject, } from '../../common'; -import { UpdateAlertRequest } from '../../client/alerts/client'; +import { UpdateAlertRequest } from '../../client/alerts/types'; import { CasesClientArgs } from '../types'; import { CasesClientInternal } from '../client_internal'; diff --git a/x-pack/plugins/cases/server/common/utils.ts b/x-pack/plugins/cases/server/common/utils.ts index 54124260ae5d2..70ecc50df0f48 100644 --- a/x-pack/plugins/cases/server/common/utils.ts +++ b/x-pack/plugins/cases/server/common/utils.ts @@ -34,7 +34,7 @@ import { SubCasesFindResponse, User, } from '../../common'; -import { UpdateAlertRequest } from '../client/alerts/client'; +import { UpdateAlertRequest } from '../client/alerts/types'; /** * Default sort field for querying saved objects. @@ -271,17 +271,12 @@ const getAndValidateAlertInfoFromComment = (comment: CommentRequest): AlertInfo[ /** * Builds an AlertInfo object accumulating the alert IDs and indices for the passed in alerts. */ -export const getAlertInfoFromComments = (comments: CommentRequest[] | undefined): AlertInfo[] => { - if (comments === undefined) { - return []; - } - - return comments.reduce((acc: AlertInfo[], comment) => { +export const getAlertInfoFromComments = (comments: CommentRequest[] = []): AlertInfo[] => + comments.reduce((acc: AlertInfo[], comment) => { const alertInfo = getAndValidateAlertInfoFromComment(comment); acc.push(...alertInfo); return acc; }, []); -}; type NewCommentArgs = CommentRequest & { associationType: AssociationType; diff --git a/x-pack/plugins/cases/server/routes/api/comments/get_alerts.ts b/x-pack/plugins/cases/server/routes/api/comments/get_alerts.ts new file mode 100644 index 0000000000000..9c0bfac4d9c6e --- /dev/null +++ b/x-pack/plugins/cases/server/routes/api/comments/get_alerts.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema } from '@kbn/config-schema'; + +import { RouteDeps } from '../types'; +import { wrapError } from '../utils'; +import { CASE_DETAILS_ALERTS_URL } from '../../../../common/constants'; + +export function initGetAllAlertsAttachToCaseApi({ router, logger }: RouteDeps) { + router.get( + { + path: CASE_DETAILS_ALERTS_URL, + validate: { + params: schema.object({ + case_id: schema.string({ minLength: 1 }), + }), + }, + }, + async (context, request, response) => { + try { + const caseId = request.params.case_id; + + const casesClient = await context.cases.getCasesClient(); + + return response.ok({ + body: await casesClient.attachments.getAllAlertsAttachToCase({ caseId }), + }); + } catch (error) { + logger.error( + `Failed to retrieve alert ids for this case id: ${request.params.case_id}: ${error}` + ); + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/cases/server/routes/api/index.ts b/x-pack/plugins/cases/server/routes/api/index.ts index 4f6db0c1081b1..011464a73396f 100644 --- a/x-pack/plugins/cases/server/routes/api/index.ts +++ b/x-pack/plugins/cases/server/routes/api/index.ts @@ -39,6 +39,7 @@ import { initFindSubCasesApi } from './sub_case/find_sub_cases'; import { initDeleteSubCasesApi } from './sub_case/delete_sub_cases'; import { ENABLE_CASE_CONNECTOR } from '../../../common'; import { initGetCaseIdsByAlertIdApi } from './cases/alerts/get_cases'; +import { initGetAllAlertsAttachToCaseApi } from './comments/get_alerts'; /** * Default page number when interacting with the saved objects API. @@ -89,4 +90,5 @@ export function initCaseApi(deps: RouteDeps) { initGetTagsApi(deps); // Alerts initGetCaseIdsByAlertIdApi(deps); + initGetAllAlertsAttachToCaseApi(deps); } diff --git a/x-pack/plugins/cases/server/services/alerts/index.ts b/x-pack/plugins/cases/server/services/alerts/index.ts index 4b2460d4b68cd..e33b0385bc123 100644 --- a/x-pack/plugins/cases/server/services/alerts/index.ts +++ b/x-pack/plugins/cases/server/services/alerts/index.ts @@ -12,7 +12,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, Logger } from 'kibana/server'; import { MAX_ALERTS_PER_SUB_CASE } from '../../../common'; import { AlertInfo, createCaseError } from '../../common'; -import { UpdateAlertRequest } from '../../client/alerts/client'; +import { UpdateAlertRequest } from '../../client/alerts/types'; export type AlertServiceContract = PublicMethodsOf; diff --git a/x-pack/plugins/cases/server/services/attachments/index.ts b/x-pack/plugins/cases/server/services/attachments/index.ts index 685b088701f68..c2d9b4826fc14 100644 --- a/x-pack/plugins/cases/server/services/attachments/index.ts +++ b/x-pack/plugins/cases/server/services/attachments/index.ts @@ -7,12 +7,23 @@ import { Logger, SavedObject, SavedObjectReference } from 'kibana/server'; +import { KueryNode } from '../../../../../../src/plugins/data/common'; import { + AttributesTypeAlerts, CASE_COMMENT_SAVED_OBJECT, CommentAttributes as AttachmentAttributes, CommentPatchAttributes as AttachmentPatchAttributes, + CASE_SAVED_OBJECT, + MAX_DOCS_PER_PAGE, + CommentType, } from '../../../common'; import { ClientArgs } from '..'; +import { buildFilter, combineFilters } from '../../client/utils'; + +interface GetAllAlertsAttachToCaseArgs extends ClientArgs { + caseId: string; + filter?: KueryNode; +} interface GetAttachmentArgs extends ClientArgs { attachmentId: string; @@ -39,6 +50,46 @@ interface BulkUpdateAttachmentArgs extends ClientArgs { export class AttachmentService { constructor(private readonly log: Logger) {} + /** + * Retrieves all the alerts attached to a case. + */ + public async getAllAlertsAttachToCase({ + unsecuredSavedObjectsClient, + caseId, + filter, + }: GetAllAlertsAttachToCaseArgs): Promise>> { + try { + this.log.debug(`Attempting to GET all alerts for case id ${caseId}`); + const alertsFilter = buildFilter({ + filters: [CommentType.alert, CommentType.generatedAlert], + field: 'type', + operator: 'or', + type: CASE_COMMENT_SAVED_OBJECT, + }); + + const combinedFilter = combineFilters([alertsFilter, filter]); + + const finder = unsecuredSavedObjectsClient.createPointInTimeFinder({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + sortField: 'created_at', + sortOrder: 'asc', + filter: combinedFilter, + perPage: MAX_DOCS_PER_PAGE, + }); + + let result: Array> = []; + for await (const userActionSavedObject of finder.find()) { + result = result.concat(userActionSavedObject.saved_objects); + } + + return result; + } catch (error) { + this.log.error(`Error on GET all alerts for case id ${caseId}: ${error}`); + throw error; + } + } + public async get({ unsecuredSavedObjectsClient, attachmentId, diff --git a/x-pack/plugins/cases/server/services/mocks.ts b/x-pack/plugins/cases/server/services/mocks.ts index ce9aec942220a..c82624cd50ab6 100644 --- a/x-pack/plugins/cases/server/services/mocks.ts +++ b/x-pack/plugins/cases/server/services/mocks.ts @@ -104,6 +104,7 @@ export const createAttachmentServiceMock = (): AttachmentServiceMock => { create: jest.fn(), update: jest.fn(), bulkUpdate: jest.fn(), + getAllAlertsAttachToCase: jest.fn(), }; // the cast here is required because jest.Mocked tries to include private members and would throw an error diff --git a/x-pack/test/case_api_integration/common/lib/utils.ts b/x-pack/test/case_api_integration/common/lib/utils.ts index 9ed5d84e54621..63be1736405fc 100644 --- a/x-pack/test/case_api_integration/common/lib/utils.ts +++ b/x-pack/test/case_api_integration/common/lib/utils.ts @@ -45,6 +45,7 @@ import { CasesStatusResponse, CasesConfigurationsResponse, CaseUserActionsResponse, + AlertResponse, } from '../../../../plugins/cases/common/api'; import { getPostCaseRequest, postCollectionReq, postCommentGenAlertReq } from './mock'; import { getCaseUserActionUrl, getSubCasesUrl } from '../../../../plugins/cases/common/api/helpers'; @@ -1102,3 +1103,22 @@ export const pushCase = async ({ return res; }; + +export const getAlertsAttachedToCase = async ({ + supertest, + caseId, + expectedHttpCode = 200, + auth = { user: superUser, space: null }, +}: { + supertest: st.SuperTest; + caseId: string; + expectedHttpCode?: number; + auth?: { user: User; space: string | null }; +}): Promise => { + const { body: theCase } = await supertest + .get(`${getSpaceUrlPrefix(auth?.space)}${CASES_URL}/${caseId}/alerts`) + .auth(auth.user.username, auth.user.password) + .expect(expectedHttpCode); + + return theCase; +}; diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts new file mode 100644 index 0000000000000..d7b4e82c017db --- /dev/null +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/alerts/get_alerts_attached_to_case.ts @@ -0,0 +1,215 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; + +import { getPostCaseRequest, postCommentAlertReq } from '../../../../common/lib/mock'; +import { + createCase, + createComment, + deleteAllCaseItems, + getAlertsAttachedToCase, +} from '../../../../common/lib/utils'; +import { + globalRead, + noKibanaPrivileges, + obsOnly, + obsOnlyRead, + obsSec, + obsSecRead, + secOnly, + secOnlyRead, + superUser, +} from '../../../../common/lib/authentication/users'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext): void => { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('get all alerts attach to a case', () => { + afterEach(async () => { + await deleteAllCaseItems(es); + }); + + it('should return all alerts for the specified case id', async () => { + const theCase = await createCase(supertest, getPostCaseRequest()); + + await createComment({ supertest, caseId: theCase.id, params: postCommentAlertReq }); + const updatedCase = await createComment({ + supertest, + caseId: theCase.id, + params: { ...postCommentAlertReq, alertId: 'test-id-2', index: 'test-index-2' }, + }); + + const alerts = await getAlertsAttachedToCase({ supertest, caseId: theCase.id }); + expect(alerts).to.eql([ + { + id: 'test-id', + index: 'test-index', + attached_at: updatedCase.comments![0].created_at, + }, + { + id: 'test-id-2', + index: 'test-index-2', + attached_at: updatedCase.comments![1].created_at, + }, + ]); + }); + + it('should return a 404 when case does not exist', async () => { + await getAlertsAttachedToCase({ supertest, caseId: 'not-exists', expectedHttpCode: 404 }); + }); + + it('should return a 404 when case id is empty', async () => { + await getAlertsAttachedToCase({ supertest, caseId: '', expectedHttpCode: 404 }); + }); + + describe('rbac', () => { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + it('should return the correct alert', async () => { + const secOnlyAuth = { user: secOnly, space: 'space1' }; + const obsOnlyAuth = { user: obsOnly, space: 'space1' }; + + const [case1, case2] = await Promise.all([ + createCase(supertestWithoutAuth, getPostCaseRequest(), 200, secOnlyAuth), + createCase( + supertestWithoutAuth, + getPostCaseRequest({ owner: 'observabilityFixture' }), + 200, + obsOnlyAuth + ), + ]); + + const [case2WithComments] = await Promise.all([ + createComment({ + supertest: supertestWithoutAuth, + caseId: case2.id, + params: { ...postCommentAlertReq, alertId: 'test-id-3', owner: 'observabilityFixture' }, + auth: obsOnlyAuth, + }), + createComment({ + supertest: supertestWithoutAuth, + caseId: case1.id, + params: postCommentAlertReq, + auth: secOnlyAuth, + }), + ]); + + // This call cannot be made inside the Promise.all call + // as there will be a race condition between the two calls + // and a 409 version conflict will be thrown + const case1WithComments = await createComment({ + supertest: supertestWithoutAuth, + caseId: case1.id, + params: { ...postCommentAlertReq, alertId: 'test-id-2' }, + auth: secOnlyAuth, + }); + + for (const scenario of [ + { + user: globalRead, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + { + user: superUser, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + { + user: secOnly, + cases: [{ theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }], + }, + { + user: secOnlyRead, + cases: [{ theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }], + }, + { + user: obsOnly, + cases: [{ theCase: case2WithComments, expectedAlerts: ['test-id-3'] }], + }, + { + user: obsOnlyRead, + cases: [{ theCase: case2WithComments, expectedAlerts: ['test-id-3'] }], + }, + { + user: obsSecRead, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + { + user: obsSec, + cases: [ + { theCase: case1WithComments, expectedAlerts: ['test-id', 'test-id-2'] }, + { theCase: case2WithComments, expectedAlerts: ['test-id-3'] }, + ], + }, + ]) { + for (const theCase of scenario.cases) { + const res = await getAlertsAttachedToCase({ + supertest: supertestWithoutAuth, + caseId: theCase.theCase.id, + auth: { + user: scenario.user, + space: 'space1', + }, + }); + + expect(res.length).to.eql(theCase.expectedAlerts.length); + + for (const [index, alertId] of theCase.expectedAlerts.entries()) { + expect(res[index]).to.eql({ + id: alertId, + index: 'test-index', + attached_at: theCase.theCase.comments![index].created_at, + }); + } + } + } + }); + + for (const scenario of [ + { user: noKibanaPrivileges, space: 'space1' }, + { user: obsOnly, space: 'space1' }, + { user: obsOnlyRead, space: 'space1' }, + { user: secOnly, space: 'space2' }, + ]) { + it(`User ${scenario.user.username} with role(s) ${scenario.user.roles.join()} and space ${ + scenario.space + } - should not get alerts`, async () => { + const caseInfo = await createCase(supertest, getPostCaseRequest(), 200, { + user: superUser, + space: scenario.space, + }); + + await createComment({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + params: postCommentAlertReq, + auth: { user: superUser, space: scenario.space }, + }); + + await getAlertsAttachedToCase({ + supertest: supertestWithoutAuth, + caseId: caseInfo.id, + auth: { user: scenario.user, space: scenario.space }, + expectedHttpCode: 403, + }); + }); + } + }); + }); +}; diff --git a/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts b/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts index 9d35d5ec82fc5..9b24de26245f4 100644 --- a/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts +++ b/x-pack/test/case_api_integration/security_and_spaces/tests/common/index.ts @@ -18,6 +18,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./comments/patch_comment')); loadTestFile(require.resolve('./comments/post_comment')); loadTestFile(require.resolve('./alerts/get_cases')); + loadTestFile(require.resolve('./alerts/get_alerts_attached_to_case')); loadTestFile(require.resolve('./cases/delete_cases')); loadTestFile(require.resolve('./cases/find_cases')); loadTestFile(require.resolve('./cases/get_case')); From 0ef1c3d735a83f1716d1f03793c00579886779f9 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Fri, 18 Jun 2021 09:31:29 +0100 Subject: [PATCH 09/35] [ML] Anomaly detection job custom_settings improvements (#102099) * [ML] Anomaly detection job custom_settings improvements * filter improvements * translations * fixing types * fixing tests * one more test fix * fixing bug with expanded row Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../anomaly_detection_jobs/summary_job.ts | 1 + .../job_details/extract_job_details.js | 28 ++++++++--- .../components/job_details/job_details.js | 6 ++- .../jobs_list_view/jobs_list_view.js | 50 +++++++++++++++---- .../jobs/jobs_list/components/utils.js | 39 ++++++++++----- .../ml/server/models/job_service/jobs.ts | 1 + .../hooks/use_installed_security_jobs.test.ts | 1 + .../common/components/ml_popover/api.mock.ts | 8 +++ .../hooks/use_security_jobs.test.ts | 1 + .../hooks/use_security_jobs_helpers.test.tsx | 1 + .../hooks/use_security_jobs_helpers.tsx | 1 + .../__snapshots__/jobs_table.test.tsx.snap | 3 ++ .../jobs_table_filters.test.tsx.snap | 3 ++ 13 files changed, 114 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts index 624056fdf3b82..e9e89a3c99771 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts @@ -36,6 +36,7 @@ export interface MlSummaryJob { earliestStartTimestampMs?: number; awaitingNodeAssignment: boolean; alertingRules?: MlAnomalyDetectionAlertRule[]; + jobTags: Record; } export interface AuditMessage { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js index 673484f08e196..dea8fdd30e372 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/extract_job_details.js @@ -26,19 +26,33 @@ export function extractJobDetails(job, basePath, refreshJobList) { items: filterObjects(job, true).map(formatValues), }; + const { job_tags: tags, custom_urls: urls, ...settings } = job.custom_settings ?? {}; const customUrl = { id: 'customUrl', title: i18n.translate('xpack.ml.jobsList.jobDetails.customUrlsTitle', { defaultMessage: 'Custom URLs', }), position: 'right', - items: [], + items: urls ? urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range]) : [], + }; + + const customSettings = { + id: 'analysisConfig', + title: i18n.translate('xpack.ml.jobsList.jobDetails.customSettingsTitle', { + defaultMessage: 'Custom settings', + }), + position: 'right', + items: settings ? filterObjects(settings, true, true) : [], + }; + + const jobTags = { + id: 'analysisConfig', + title: i18n.translate('xpack.ml.jobsList.jobDetails.jobTagsTitle', { + defaultMessage: 'Job tags', + }), + position: 'right', + items: tags ? filterObjects(tags) : [], }; - if (job.custom_settings && job.custom_settings.custom_urls) { - customUrl.items.push( - ...job.custom_settings.custom_urls.map((cu) => [cu.url_name, cu.url_value, cu.time_range]) - ); - } const node = { id: 'node', @@ -213,6 +227,8 @@ export function extractJobDetails(job, basePath, refreshJobList) { analysisConfig, analysisLimits, dataDescription, + customSettings, + jobTags, datafeed, counts, modelSizeStats, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js index 812d156421c16..b514c8433daf4 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_details/job_details.js @@ -41,7 +41,7 @@ export class JobDetailsUI extends Component { } render() { - const { job } = this.state; + const job = this.state.job ?? this.props.job; const { services: { http: { basePath }, @@ -67,6 +67,8 @@ export class JobDetailsUI extends Component { analysisConfig, analysisLimits, dataDescription, + customSettings, + jobTags, datafeed, counts, modelSizeStats, @@ -85,7 +87,7 @@ export class JobDetailsUI extends Component { content: ( ), time: job.open_time, diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index 214b7616cf927..bf8db538bc8ae 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -18,7 +18,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { isEqual } from 'lodash'; +import { isEqual, debounce } from 'lodash'; import { ml } from '../../../../services/ml_api_service'; import { checkForAutoStartDatafeed, filterJobs, loadFullJob } from '../utils'; @@ -43,6 +43,11 @@ import { JobListMlAnomalyAlertFlyout } from '../../../../../alerting/ml_alerting let deletingJobsRefreshTimeout = null; +const filterJobsDebounce = debounce((jobsSummaryList, filterClauses, callback) => { + const ss = filterJobs(jobsSummaryList, filterClauses); + callback(ss); +}, 500); + // 'isManagementTable' bool prop to determine when to configure table for use in Kibana management page export class JobsListView extends Component { constructor(props) { @@ -221,7 +226,7 @@ export class JobsListView extends Component { refreshSelectedJobs() { const selectedJobsIds = this.state.selectedJobs.map((j) => j.id); - const filteredJobIds = this.state.filteredJobsSummaryList.map((j) => j.id); + const filteredJobIds = (this.state.filteredJobsSummaryList ?? []).map((j) => j.id); // refresh the jobs stored as selected // only select those which are also in the filtered list @@ -232,9 +237,17 @@ export class JobsListView extends Component { this.setState({ selectedJobs }); } - setFilters = (query) => { - const filterClauses = (query && query.ast && query.ast.clauses) || []; - const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses); + setFilters = async (query) => { + if (query === null) { + this.setState( + { filteredJobsSummaryList: this.state.jobsSummaryList, filterClauses: [] }, + () => { + this.refreshSelectedJobs(); + } + ); + + return; + } this.props.onJobsViewStateUpdate( { @@ -244,11 +257,30 @@ export class JobsListView extends Component { this._isFiltersSet === false ); - this._isFiltersSet = true; + const filterClauses = (query && query.ast && query.ast.clauses) || []; - this.setState({ filteredJobsSummaryList, filterClauses }, () => { - this.refreshSelectedJobs(); - }); + if (filterClauses.length === 0) { + this.setState({ filteredJobsSummaryList: this.state.jobsSummaryList, filterClauses }, () => { + this.refreshSelectedJobs(); + }); + return; + } + + if (this._isFiltersSet === true) { + filterJobsDebounce(this.state.jobsSummaryList, filterClauses, (jobsSummaryList) => { + this.setState({ filteredJobsSummaryList: jobsSummaryList, filterClauses }, () => { + this.refreshSelectedJobs(); + }); + }); + } else { + // first use after page load, do not debounce. + const filteredJobsSummaryList = filterJobs(this.state.jobsSummaryList, filterClauses); + this.setState({ filteredJobsSummaryList, filterClauses }, () => { + this.refreshSelectedJobs(); + }); + } + + this._isFiltersSet = true; }; onRefreshClick = () => { diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 5b8fa5c672c6e..f004fb6bad49d 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -347,12 +347,18 @@ export function filterJobs(jobs, clauses) { // if it's an array of job ids if (c.field === 'id') { js = jobs.filter((job) => c.value.indexOf(jobProperty(job, c.field)) >= 0); - } else { + } else if (c.field === 'groups') { // the groups value is an array of group ids js = jobs.filter((job) => jobProperty(job, c.field).some((g) => c.value.indexOf(g) >= 0)); + } else if (c.field === 'job_tags') { + js = jobTagFilter(jobs, c.value); } } else { - js = jobs.filter((job) => jobProperty(job, c.field) === c.value); + if (c.field === 'job_tags') { + js = js = jobTagFilter(jobs, [c.value]); + } else { + js = jobs.filter((job) => jobProperty(job, c.field) === c.value); + } } } @@ -369,6 +375,25 @@ export function filterJobs(jobs, clauses) { return filteredJobs; } +function jobProperty(job, prop) { + const propMap = { + job_state: 'jobState', + datafeed_state: 'datafeedState', + groups: 'groups', + id: 'id', + job_tags: 'jobTags', + }; + return job[propMap[prop]]; +} + +function jobTagFilter(jobs, value) { + return jobs.filter((job) => { + const tags = jobProperty(job, 'job_tags'); + return Object.entries(tags) + .map((t) => t.join(':')) + .find((t) => value.some((t1) => t1 === t)); + }); +} // check to see if a job has been stored in mlJobService.tempJobCloningObjects // if it has, return an object with the minimum properties needed for the // start datafeed modal. @@ -390,13 +415,3 @@ export function checkForAutoStartDatafeed() { }; } } - -function jobProperty(job, prop) { - const propMap = { - job_state: 'jobState', - datafeed_state: 'datafeedState', - groups: 'groups', - id: 'id', - }; - return job[propMap[prop]]; -} diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index b7f8ce569641e..22bac1cb08e19 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -218,6 +218,7 @@ export function jobsProvider( deleting: job.deleting || undefined, awaitingNodeAssignment: isJobAwaitingNodeAssignment(job), alertingRules: job.alerting_rules, + jobTags: job.custom_settings?.job_tags ?? {}, }; if (jobIds.find((j) => j === tempJob.id)) { tempJob.fullJob = job; diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts index a8f4d09cd7873..403b33d9c08f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts @@ -55,6 +55,7 @@ describe('useInstalledSecurityJobs', () => { id: 'siem-api-rare_process_linux_ecs', isSingleMetricViewerJob: true, jobState: 'closed', + jobTags: {}, latestTimestampMs: 1557434782207, memory_status: 'hard_limit', processed_record_count: 582251, diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts index ac057dff15621..28d0ae179508d 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts @@ -48,6 +48,7 @@ export const mockOpenedJob: MlSummaryJob = { nodeName: 'siem-es', processed_record_count: 3425264, awaitingNodeAssignment: false, + jobTags: {}, }; export const mockJobsSummaryResponse: MlSummaryJob[] = [ @@ -67,6 +68,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ earliestTimestampMs: 1554327458406, isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'siem-api-rare_process_linux_ecs', @@ -83,6 +85,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ earliestTimestampMs: 1557353420495, isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'siem-api-rare_process_windows_ecs', @@ -97,6 +100,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ datafeedState: 'stopped', isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'siem-api-suspicious_login_activity_ecs', @@ -111,6 +115,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ datafeedState: 'stopped', isSingleMetricViewerJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, ]; @@ -520,6 +525,7 @@ export const mockSecurityJobs: SecurityJob[] = [ isInstalled: true, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { id: 'rare_process_by_host_linux_ecs', @@ -539,6 +545,7 @@ export const mockSecurityJobs: SecurityJob[] = [ isInstalled: true, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, { datafeedId: '', @@ -558,5 +565,6 @@ export const mockSecurityJobs: SecurityJob[] = [ isInstalled: false, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts index 3731bebd92624..3c91baa920da7 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts @@ -62,6 +62,7 @@ describe('useSecurityJobs', () => { isInstalled: true, isSingleMetricViewerJob: true, jobState: 'closed', + jobTags: {}, latestTimestampMs: 1557434782207, memory_status: 'hard_limit', moduleId: '', diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx index 8250807355648..7a488847cd583 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx @@ -44,6 +44,7 @@ describe('useSecurityJobsHelpers', () => { isInstalled: false, isSingleMetricViewerJob: false, jobState: '', + jobTags: {}, memory_status: '', moduleId: 'siem_auditbeat', processed_record_count: 0, diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx index 70a2a7c87225f..fe3803c88e4f7 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx @@ -44,6 +44,7 @@ export const moduleToSecurityJob = ( isInstalled: false, isElasticJob: true, awaitingNodeAssignment: false, + jobTags: {}, }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap index d64fb474c1fb3..2d2525b92deb1 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap @@ -46,6 +46,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "latestResultsTimestampMs": 1571022900000, "latestTimestampMs": 1571022859393, "memory_status": "ok", @@ -73,6 +74,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "memory_status": "ok", "moduleId": "siem_auditbeat", "processed_record_count": 0, @@ -96,6 +98,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "isInstalled": false, "isSingleMetricViewerJob": false, "jobState": "", + "jobTags": Object {}, "memory_status": "", "moduleId": "siem_winlogbeat", "processed_record_count": 0, diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap index 7367dbf7bdc0a..d66740ee5bb0e 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap @@ -49,6 +49,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "latestResultsTimestampMs": 1571022900000, "latestTimestampMs": 1571022859393, "memory_status": "ok", @@ -76,6 +77,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "isInstalled": true, "isSingleMetricViewerJob": true, "jobState": "closed", + "jobTags": Object {}, "memory_status": "ok", "moduleId": "siem_auditbeat", "processed_record_count": 0, @@ -99,6 +101,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "isInstalled": false, "isSingleMetricViewerJob": false, "jobState": "", + "jobTags": Object {}, "memory_status": "", "moduleId": "siem_winlogbeat", "processed_record_count": 0, From ae99824880fd756e72345aebf75883250c575df0 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Fri, 18 Jun 2021 10:45:23 +0200 Subject: [PATCH 10/35] [Lens] Fix wrong error detection on transition to Top values operation (#102384) --- .../operations/definitions/terms/index.tsx | 8 +++++--- .../operations/definitions/terms/terms.test.tsx | 4 ++-- .../indexpattern_datasource/operations/layer_helpers.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx index 8248976ab8452..7551b88039182 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/index.tsx @@ -194,14 +194,16 @@ export const termsOperation: OperationDefinition - [ + getErrorMessage: (layer, columnId, indexPattern) => { + const messages = [ ...(getInvalidFieldMessage( layer.columns[columnId] as FieldBasedIndexPatternColumn, indexPattern ) || []), getDisallowedTermsMessage(layer, columnId, indexPattern) || '', - ].filter(Boolean), + ].filter(Boolean); + return messages.length ? messages : undefined; + }, isTransferable: (column, newIndexPattern) => { const newField = newIndexPattern.getFieldByName(column.sourceField); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx index f5540732953ac..3b557461546ca 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/terms/terms.test.tsx @@ -996,8 +996,8 @@ describe('terms', () => { indexPatternId: '', }; }); - it('returns empty array', () => { - expect(termsOperation.getErrorMessage!(layer, 'col1', indexPattern)).toEqual([]); + it('returns undefined for no errors found', () => { + expect(termsOperation.getErrorMessage!(layer, 'col1', indexPattern)).toEqual(undefined); }); it('returns error message if the sourceField does not exist in index pattern', () => { layer = { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 4e3bcec4b6ca2..f0095b66e2ba6 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -627,7 +627,7 @@ export function canTransition({ Boolean(newColumn) && !newLayer.incompleteColumns?.[columnId] && filterOperations(newColumn) && - !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern) + !newDefinition.getErrorMessage?.(newLayer, columnId, indexPattern)?.length ); } catch (e) { return false; From e51886522525c361fa10798b806b7d5ac5240b24 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Fri, 18 Jun 2021 10:49:28 +0100 Subject: [PATCH 11/35] [ML] Remove blank job definition as it is unused and out-of-sync with Elasticsearch (#102506) This a companion to elastic/elasticsearch#74188. This PR is functionally a no-op, as the removed method was not called anywhere. But it is sensible to remove it to prevent it being called in the future now that it references fields that don't exist in Elasticsearch. --- .../application/services/job_service.js | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/x-pack/plugins/ml/public/application/services/job_service.js b/x-pack/plugins/ml/public/application/services/job_service.js index 3c93c8a1ae85a..8560cdd73153b 100644 --- a/x-pack/plugins/ml/public/application/services/job_service.js +++ b/x-pack/plugins/ml/public/application/services/job_service.js @@ -91,26 +91,6 @@ class JobService { }; } - getBlankJob() { - return { - job_id: '', - description: '', - groups: [], - analysis_config: { - bucket_span: '15m', - influencers: [], - detectors: [], - }, - data_description: { - time_field: '', - time_format: '', // 'epoch', - field_delimiter: '', - quote_character: '"', - format: 'delimited', - }, - }; - } - loadJobs() { return new Promise((resolve, reject) => { jobs = []; From ee1710cf395f9d9576c4db18dc9a9668bef1aaae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Fri, 18 Jun 2021 14:50:58 +0200 Subject: [PATCH 12/35] [Logs UI] Add `event.original` fallback to message reconstruction rules (#102236) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../message/builtin_rules/generic.test.ts | 54 ++++++++++++++ .../message/builtin_rules/generic.ts | 73 ++++++++++--------- 2 files changed, 94 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts index ae5a45c61d3b5..ba8eab91e3456 100644 --- a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts @@ -186,4 +186,58 @@ describe('Generic Rules', () => { `); }); }); + + describe('event.original fallback', () => { + test('includes the event.dataset if present', () => { + const flattenedDocument = { + '@timestamp': ['2016-12-26T16:22:13.000Z'], + 'event.dataset': ['generic.test'], + 'event.original': ['TEST_MESSAGE'], + }; + + expect(format(flattenedDocument, {})).toMatchInlineSnapshot(` + Array [ + Object { + "constant": "[", + }, + Object { + "field": "event.dataset", + "highlights": Array [], + "value": Array [ + "generic.test", + ], + }, + Object { + "constant": "] ", + }, + Object { + "field": "event.original", + "highlights": Array [], + "value": Array [ + "TEST_MESSAGE", + ], + }, + ] + `); + }); + + test('includes the original message', () => { + const flattenedDocument = { + '@timestamp': ['2016-12-26T16:22:13.000Z'], + 'event.original': ['TEST_MESSAGE'], + }; + + expect(format(flattenedDocument, {})).toMatchInlineSnapshot(` + Array [ + Object { + "field": "event.original", + "highlights": Array [], + "value": Array [ + "TEST_MESSAGE", + ], + }, + ] + `); + }); + }); }); diff --git a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts index c16d65a75b3e0..07b6cf03e2c5d 100644 --- a/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts @@ -8,40 +8,15 @@ import { LogMessageFormattingRule } from '../rule_types'; const BUILTIN_GENERIC_MESSAGE_FIELDS = ['message', '@message']; +const BUILTIN_FALLBACK_MESSAGE_FIELDS = ['log.original', 'event.original']; -export const getGenericRules = (genericMessageFields: string[]) => [ - ...Array.from(new Set([...genericMessageFields, ...BUILTIN_GENERIC_MESSAGE_FIELDS])).reduce< - LogMessageFormattingRule[] - >((genericRules, fieldName) => [...genericRules, ...createGenericRulesForField(fieldName)], []), - { - when: { - exists: ['event.dataset', 'log.original'], - }, - format: [ - { - constant: '[', - }, - { - field: 'event.dataset', - }, - { - constant: '] ', - }, - { - field: 'log.original', - }, - ], - }, - { - when: { - exists: ['log.original'], - }, - format: [ - { - field: 'log.original', - }, - ], - }, +export const getGenericRules = (genericMessageFields: string[]): LogMessageFormattingRule[] => [ + ...Array.from(new Set([...genericMessageFields, ...BUILTIN_GENERIC_MESSAGE_FIELDS])).flatMap( + createGenericRulesForField + ), + ...BUILTIN_FALLBACK_MESSAGE_FIELDS.filter( + (fieldName) => !genericMessageFields.includes(fieldName) + ).flatMap(createFallbackRulesForField), ]; const createGenericRulesForField = (fieldName: string) => [ @@ -172,3 +147,35 @@ const createGenericRulesForField = (fieldName: string) => [ ], }, ]; + +const createFallbackRulesForField = (fieldName: string) => [ + { + when: { + exists: ['event.dataset', fieldName], + }, + format: [ + { + constant: '[', + }, + { + field: 'event.dataset', + }, + { + constant: '] ', + }, + { + field: fieldName, + }, + ], + }, + { + when: { + exists: [fieldName], + }, + format: [ + { + field: fieldName, + }, + ], + }, +]; From b9f64b78259f7dd6809a16597077015114b674c4 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Fri, 18 Jun 2021 15:57:33 +0300 Subject: [PATCH 13/35] TSVB visualizations with no timefield do not render after upgrading from 7.12.1 to 7.13.0 (#102494) * TSVB visualizations with no timefield do not render after upgrading from 7.12.1 to 7.13.0 Part of: #100778 * fix CI --- .../server/lib/get_vis_data.ts | 7 +- .../lib/cached_index_pattern_fetcher.ts | 13 ++- .../get_interval_and_timefield.test.ts | 4 +- .../vis_data/get_interval_and_timefield.ts | 9 +- .../server/lib/vis_data/get_table_data.ts | 16 ++- .../series/date_histogram.js | 6 +- .../series/date_histogram.test.js | 36 +++++-- .../series/positive_rate.js | 6 +- .../series/positive_rate.test.js | 10 +- .../request_processors/series/query.js | 18 +++- .../request_processors/series/query.test.js | 102 +++++++++++++++--- .../table/date_histogram.js | 13 ++- .../request_processors/table/positive_rate.js | 13 ++- .../request_processors/table/query.js | 15 ++- .../series/build_request_body.test.ts | 5 +- .../lib/vis_data/series/get_request_params.ts | 16 ++- 16 files changed, 226 insertions(+), 63 deletions(-) diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index 5cdea62af9536..dd45812f4ebfc 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -38,10 +38,9 @@ export async function getVisData( indexPatternsService, uiSettings, searchStrategyRegistry: framework.searchStrategyRegistry, - cachedIndexPatternFetcher: getCachedIndexPatternFetcher( - indexPatternsService, - Boolean(panel.use_kibana_indexes) - ), + cachedIndexPatternFetcher: getCachedIndexPatternFetcher(indexPatternsService, { + fetchKibanaIndexForStringIndexes: Boolean(panel.use_kibana_indexes), + }), }; return panel.type === PANEL_TYPES.TABLE diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts index 26ea191ab9217..5f989a50ca639 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/cached_index_pattern_fetcher.ts @@ -13,12 +13,19 @@ import type { IndexPatternValue, FetchedIndexPattern } from '../../../../common/ export const getCachedIndexPatternFetcher = ( indexPatternsService: IndexPatternsService, - fetchKibanaIndexForStringIndexes: boolean = false + globalOptions: { + fetchKibanaIndexForStringIndexes: boolean; + } = { + fetchKibanaIndexForStringIndexes: false, + } ) => { const cache = new Map(); - return async (indexPatternValue: IndexPatternValue): Promise => { - const key = getIndexPatternKey(indexPatternValue); + return async ( + indexPatternValue: IndexPatternValue, + fetchKibanaIndexForStringIndexes: boolean = globalOptions.fetchKibanaIndexForStringIndexes + ): Promise => { + const key = `${getIndexPatternKey(indexPatternValue)}:${fetchKibanaIndexForStringIndexes}`; if (cache.has(key)) { return cache.get(key); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts index 9520b876a5810..0d1ca9cba022a 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts @@ -16,7 +16,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { const panel = { time_field: '@timestamp', interval: 'auto' } as Panel; const series = {} as Series; - expect(getIntervalAndTimefield(panel, series, index)).toEqual({ + expect(getIntervalAndTimefield(panel, index, series)).toEqual({ timeField: '@timestamp', interval: 'auto', }); @@ -30,7 +30,7 @@ describe('getIntervalAndTimefield(panel, series)', () => { series_time_field: 'time', } as unknown) as Series; - expect(getIntervalAndTimefield(panel, series, index)).toEqual({ + expect(getIntervalAndTimefield(panel, index, series)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts index 90fb722c54f5b..0e90dfe77e814 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts @@ -7,12 +7,13 @@ */ import { AUTO_INTERVAL } from '../../../common/constants'; -import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; import { validateField } from '../../../common/fields_utils'; -export function getIntervalAndTimefield(panel: Panel, series: Series, index: FetchedIndexPattern) { +import type { FetchedIndexPattern, Panel, Series } from '../../../common/types'; + +export function getIntervalAndTimefield(panel: Panel, index: FetchedIndexPattern, series?: Series) { const timeField = - (series.override_index_pattern ? series.series_time_field : panel.time_field) || + (series?.override_index_pattern ? series.series_time_field : panel.time_field) || index.indexPattern?.timeFieldName; if (panel.use_kibana_indexes) { @@ -22,7 +23,7 @@ export function getIntervalAndTimefield(panel: Panel, series: Series, index: Fet let interval = panel.interval; let maxBars = panel.max_bars; - if (series.override_index_pattern) { + if (series?.override_index_pattern) { interval = series.series_interval || AUTO_INTERVAL; maxBars = series.series_max_bars; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts index 82411ed70d8ee..db2e027f7815c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts @@ -25,6 +25,7 @@ import type { VisTypeTimeseriesVisDataRequest, } from '../../types'; import type { Panel } from '../../../common/types'; +import { getIntervalAndTimefield } from './get_interval_and_timefield'; export async function getTableData( requestContext: VisTypeTimeseriesRequestHandlerContext, @@ -66,6 +67,18 @@ export async function getTableData( return panel.pivot_id; }; + const buildSeriesMetaParams = async () => { + let index = panelIndex; + + /** This part of code is required to try to get the default timefield for string indices. + * The rest of the functionality available for Kibana indexes should not be active **/ + if (!panel.use_kibana_indexes && index.indexPatternString) { + index = await services.cachedIndexPatternFetcher(index.indexPatternString, true); + } + + return getIntervalAndTimefield(panel, index); + }; + const meta = { type: panel.type, uiRestrictions: capabilities.uiRestrictions, @@ -78,7 +91,8 @@ export async function getTableData( services.esQueryConfig, panelIndex, capabilities, - services.uiSettings + services.uiSettings, + buildSeriesMetaParams ); const [resp] = await searchStrategy.search(requestContext, req, [ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js index 253612c0274ad..cec3e82d5e37c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js @@ -9,7 +9,6 @@ import { overwrite } from '../../helpers'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { offsetTime } from '../../offset_time'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; @@ -22,13 +21,14 @@ export function dateHistogram( esQueryConfig, seriesIndex, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams ) { return (next) => async (doc) => { const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, seriesIndex); + const { timeField, interval, maxBars } = await buildSeriesMetaParams(); const { from, to } = offsetTime(req, series.offset_time); let bucketInterval; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index 2cd7a213b273e..08b9801254c2e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -8,6 +8,7 @@ import { DefaultSearchCapabilities } from '../../../search_strategies/capabilities/default_search_capabilities'; import { dateHistogram } from './date_histogram'; +import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { UI_SETTINGS } from '../../../../../../data/common'; describe('dateHistogram(req, panel, series)', () => { @@ -18,6 +19,7 @@ describe('dateHistogram(req, panel, series)', () => { let config; let indexPattern; let uiSettings; + let buildSeriesMetaParams; beforeEach(() => { req = { @@ -44,14 +46,24 @@ describe('dateHistogram(req, panel, series)', () => { uiSettings = { get: async (key) => (key === UI_SETTINGS.HISTOGRAM_MAX_BARS ? 100 : 50), }; + buildSeriesMetaParams = jest.fn(async () => { + return getIntervalAndTimefield(panel, indexPattern, series); + }); }); test('calls next when finished', async () => { const next = jest.fn(); - await dateHistogram(req, panel, series, config, indexPattern, capabilities, uiSettings)(next)( - {} - ); + await dateHistogram( + req, + panel, + series, + config, + indexPattern, + capabilities, + uiSettings, + buildSeriesMetaParams + )(next)({}); expect(next.mock.calls.length).toEqual(1); }); @@ -65,7 +77,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ @@ -106,7 +119,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ @@ -150,7 +164,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ @@ -197,7 +212,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc.aggs.test.aggs.timeseries.auto_date_histogram).toBeUndefined(); @@ -219,7 +235,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc.aggs.test.meta).toMatchInlineSnapshot(` @@ -242,7 +259,8 @@ describe('dateHistogram(req, panel, series)', () => { config, indexPattern, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js index 208321a98737e..91016384794c4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js @@ -7,7 +7,6 @@ */ import { getBucketSize } from '../../helpers/get_bucket_size'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { bucketTransform } from '../../helpers/bucket_transform'; import { overwrite } from '../../helpers'; import { UI_SETTINGS } from '../../../../../../data/common'; @@ -58,12 +57,13 @@ export function positiveRate( esQueryConfig, seriesIndex, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); + const { interval } = await buildSeriesMetaParams(); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); if (series.metrics.some(filter)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js index b79e8de13062c..aac0063a54bef 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.test.js @@ -12,6 +12,7 @@ describe('positiveRate(req, panel, series)', () => { let series; let req; let uiSettings; + let buildSeriesMetaParams; beforeEach(() => { panel = { @@ -42,6 +43,9 @@ describe('positiveRate(req, panel, series)', () => { uiSettings = { get: async () => 50, }; + buildSeriesMetaParams = jest.fn().mockResolvedValue({ + interval: 'auto', + }); }); test('calls next when finished', async () => { @@ -53,7 +57,8 @@ describe('positiveRate(req, panel, series)', () => { {}, {}, { maxBucketsLimit: 2000, getValidTimeInterval: jest.fn(() => '1d') }, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(next.mock.calls.length).toEqual(1); @@ -68,7 +73,8 @@ describe('positiveRate(req, panel, series)', () => { {}, {}, { maxBucketsLimit: 2000, getValidTimeInterval: jest.fn(() => '1d') }, - uiSettings + uiSettings, + buildSeriesMetaParams )(next)({}); expect(doc).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js index a5f4e17289e06..5031a0f2ec185 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js @@ -7,18 +7,28 @@ */ import { offsetTime } from '../../offset_time'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, series, esQueryConfig, seriesIndex) { - return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, series, seriesIndex); +export function query( + req, + panel, + series, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { + return (next) => async (doc) => { + const { timeField } = await buildSeriesMetaParams(); const { from, to } = offsetTime(req, series.offset_time); doc.size = 0; + const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter; const queries = !ignoreGlobalFilter ? req.body.query : []; const filters = !ignoreGlobalFilter ? req.body.filters : []; + doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js index b3e88dbf1c6b9..849ae2e71bffc 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js @@ -13,6 +13,7 @@ describe('query', () => { let series; let req; let seriesIndex; + let buildSeriesMetaParams; const config = { allowLeadingWildcards: true, @@ -35,17 +36,32 @@ describe('query', () => { }; series = { id: 'test' }; seriesIndex = {}; + buildSeriesMetaParams = jest.fn().mockResolvedValue({ + timeField: panel.time_field, + interval: panel.interval, + }); }); - test('calls next when finished', () => { + test('calls next when finished', async () => { const next = jest.fn(); - query(req, panel, series, config, seriesIndex)(next)({}); + await query(req, panel, series, config, seriesIndex, null, null, buildSeriesMetaParams)(next)( + {} + ); expect(next.mock.calls.length).toEqual(1); }); - test('returns doc with query for timerange', () => { + test('returns doc with query for timerange', async () => { const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -69,10 +85,19 @@ describe('query', () => { }); }); - test('returns doc with query for timerange (offset by 1h)', () => { + test('returns doc with query for timerange (offset by 1h)', async () => { series.offset_time = '1h'; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -96,7 +121,7 @@ describe('query', () => { }); }); - test('returns doc with global query', () => { + test('returns doc with global query', async () => { req.body.filters = [ { bool: { @@ -111,7 +136,16 @@ describe('query', () => { }, ]; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -147,10 +181,19 @@ describe('query', () => { }); }); - test('returns doc with series filter', () => { + test('returns doc with series filter', async () => { series.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -188,7 +231,7 @@ describe('query', () => { }, }); }); - test('returns doc with panel filter and global', () => { + test('returns doc with panel filter and global', async () => { req.body.filters = [ { bool: { @@ -204,7 +247,16 @@ describe('query', () => { ]; panel.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -255,7 +307,7 @@ describe('query', () => { }); }); - test('returns doc with panel filter (ignoring globals)', () => { + test('returns doc with panel filter (ignoring globals)', async () => { req.body.filters = [ { bool: { @@ -272,7 +324,16 @@ describe('query', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; panel.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -311,7 +372,7 @@ describe('query', () => { }); }); - test('returns doc with panel filter (ignoring globals from series)', () => { + test('returns doc with panel filter (ignoring globals from series)', async () => { req.body.filters = [ { bool: { @@ -328,7 +389,16 @@ describe('query', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; series.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config, seriesIndex)(next)({}); + const doc = await query( + req, + panel, + series, + config, + seriesIndex, + null, + null, + buildSeriesMetaParams + )(next)({}); expect(doc).toEqual({ size: 0, query: { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js index 92ac4078a3835..f37c27f74184f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js @@ -9,17 +9,24 @@ import { overwrite } from '../../helpers'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { getTimerange } from '../../helpers/get_timerange'; import { calculateAggRoot } from './calculate_agg_root'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; const { dateHistogramInterval } = search.aggs; -export function dateHistogram(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { +export function dateHistogram( + req, + panel, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval } = getIntervalAndTimefield(panel, {}, seriesIndex); + const { timeField, interval } = await buildSeriesMetaParams(); const { from, to } = getTimerange(req); const meta = { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js index 3390362b56115..dafb2741d6ab4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js @@ -7,15 +7,22 @@ */ import { getBucketSize } from '../../helpers/get_bucket_size'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { calculateAggRoot } from './calculate_agg_root'; import { createPositiveRate, filter } from '../series/positive_rate'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function positiveRate(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { +export function positiveRate( + req, + panel, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); + const { interval } = await buildSeriesMetaParams(); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js index 66783e0cdfaef..7e555557ee1f7 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js @@ -7,12 +7,19 @@ */ import { getTimerange } from '../../helpers/get_timerange'; -import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, esQueryConfig, seriesIndex) { - return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, {}, seriesIndex); +export function query( + req, + panel, + esQueryConfig, + seriesIndex, + capabilities, + uiSettings, + buildSeriesMetaParams +) { + return (next) => async (doc) => { + const { timeField } = await buildSeriesMetaParams(); const { from, to } = getTimerange(req); doc.size = 0; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts index 46acbb27e15e1..14b4fc89712e2 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.test.ts @@ -89,7 +89,10 @@ describe('buildRequestBody(req)', () => { capabilities, { get: async () => 50, - } + }, + jest.fn().mockResolvedValue({ + timeField: '@timestamp', + }) ); expect(doc).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts index c565e2b86eaa3..a2248308dc571 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts @@ -7,6 +7,7 @@ */ import { buildRequestBody } from './build_request_body'; +import { getIntervalAndTimefield } from '../get_interval_and_timefield'; import type { FetchedIndexPattern, Panel, Series } from '../../../../common/types'; import type { @@ -34,6 +35,18 @@ export async function getSeriesRequestParams( seriesIndex = await cachedIndexPatternFetcher(series.series_index_pattern ?? ''); } + const buildSeriesMetaParams = async () => { + let index = seriesIndex; + + /** This part of code is required to try to get the default timefield for string indices. + * The rest of the functionality available for Kibana indexes should not be active **/ + if (!panel.use_kibana_indexes && index.indexPatternString) { + index = await cachedIndexPatternFetcher(index.indexPatternString, true); + } + + return getIntervalAndTimefield(panel, index, series); + }; + const request = await buildRequestBody( req, panel, @@ -41,7 +54,8 @@ export async function getSeriesRequestParams( esQueryConfig, seriesIndex, capabilities, - uiSettings + uiSettings, + buildSeriesMetaParams ); return { From d65416c60c803092eb17b80e77836e5ef772a429 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Fri, 18 Jun 2021 10:26:10 -0400 Subject: [PATCH 14/35] [Security Solution][Endpoint][Host Isolation] Fixes bug where host isolation/unisolation works from alert details (#102581) --- .../detections/components/host_isolation/index.tsx | 10 +++++----- .../detections/components/host_isolation/isolate.tsx | 6 +++--- .../components/host_isolation/unisolate.tsx | 6 +++--- .../containers/detection_engine/alerts/api.test.ts | 6 +++--- .../containers/detection_engine/alerts/api.ts | 12 ++++++------ .../detection_engine/alerts/use_host_isolation.tsx | 8 ++++---- .../detection_engine/alerts/use_host_unisolation.tsx | 8 ++++---- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx index 2ca8416841497..42d53f97d478b 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/index.tsx @@ -26,9 +26,9 @@ export const HostIsolationPanel = React.memo( cancelCallback: () => void; isolateAction: string; }) => { - const agentId = useMemo(() => { - const findAgentId = find({ category: 'agent', field: 'agent.id' }, details)?.values; - return findAgentId ? findAgentId[0] : ''; + const endpointId = useMemo(() => { + const findEndpointId = find({ category: 'agent', field: 'agent.id' }, details)?.values; + return findEndpointId ? findEndpointId[0] : ''; }, [details]); const hostName = useMemo(() => { @@ -87,7 +87,7 @@ export const HostIsolationPanel = React.memo( return isolateAction === 'isolateHost' ? ( ) : ( { const hostIsolated = await isolateHost(); diff --git a/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx b/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx index e72a0d2de61bc..71f7cadda2f68 100644 --- a/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/host_isolation/unisolate.tsx @@ -18,13 +18,13 @@ import { useHostUnisolation } from '../../containers/detection_engine/alerts/use export const UnisolateHost = React.memo( ({ - agentId, + endpointId, hostName, cases, caseIds, cancelCallback, }: { - agentId: string; + endpointId: string; hostName: string; cases: ReactNode; caseIds: string[]; @@ -33,7 +33,7 @@ export const UnisolateHost = React.memo( const [comment, setComment] = useState(''); const [isUnIsolated, setIsUnIsolated] = useState(false); - const { loading, unIsolateHost } = useHostUnisolation({ agentId, comment, caseIds }); + const { loading, unIsolateHost } = useHostUnisolation({ endpointId, comment, caseIds }); const confirmHostUnIsolation = useCallback(async () => { const hostIsolated = await unIsolateHost(); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts index 0c3159e0719e6..b944cb640b719 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.test.ts @@ -178,19 +178,19 @@ describe('Detections Alerts API', () => { test('check parameter url', async () => { await createHostIsolation({ - agentId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', + endpointId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', comment: 'commento', caseIds: ['88c04a90-b19c-11eb-b838-bf3c7840b969'], }); expect(postMock).toHaveBeenCalledWith('/api/endpoint/isolate', { body: - '{"agent_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', + '{"endpoint_ids":["fd8a122b-4c54-4c05-b295-e5f8381fc59d"],"comment":"commento","case_ids":["88c04a90-b19c-11eb-b838-bf3c7840b969"]}', }); }); test('happy path', async () => { const hostIsolationResponse = await createHostIsolation({ - agentId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', + endpointId: 'fd8a122b-4c54-4c05-b295-e5f8381fc59d', comment: 'commento', caseIds: ['88c04a90-b19c-11eb-b838-bf3c7840b969'], }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts index 3baa6580b36fb..71eac547dcc8e 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/api.ts @@ -118,16 +118,16 @@ export const createSignalIndex = async ({ signal }: BasicSignals): Promise => isolateHost({ - agent_ids: [agentId], + endpoint_ids: [endpointId], comment, case_ids: caseIds, }); @@ -142,16 +142,16 @@ export const createHostIsolation = async ({ * @throws An error if response is not OK */ export const createHostUnIsolation = async ({ - agentId, + endpointId, comment = '', caseIds, }: { - agentId: string; + endpointId: string; comment?: string; caseIds?: string[]; }): Promise => unIsolateHost({ - agent_ids: [agentId], + endpoint_ids: [endpointId], comment, case_ids: caseIds, }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx index ad3c6e91c03fe..12426e05ba528 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_isolation.tsx @@ -16,13 +16,13 @@ interface HostIsolationStatus { } interface UseHostIsolationProps { - agentId: string; + endpointId: string; comment: string; caseIds?: string[]; } export const useHostIsolation = ({ - agentId, + endpointId, comment, caseIds, }: UseHostIsolationProps): HostIsolationStatus => { @@ -32,7 +32,7 @@ export const useHostIsolation = ({ const isolateHost = useCallback(async () => { try { setLoading(true); - const isolationStatus = await createHostIsolation({ agentId, comment, caseIds }); + const isolationStatus = await createHostIsolation({ endpointId, comment, caseIds }); setLoading(false); return isolationStatus.action ? true : false; } catch (error) { @@ -40,6 +40,6 @@ export const useHostIsolation = ({ addError(error.message, { title: HOST_ISOLATION_FAILURE }); return false; } - }, [agentId, comment, caseIds, addError]); + }, [endpointId, comment, caseIds, addError]); return { loading, isolateHost }; }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx index 1a0ecb0d15878..55119f7122e12 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_host_unisolation.tsx @@ -16,13 +16,13 @@ interface HostUnisolationStatus { } interface UseHostIsolationProps { - agentId: string; + endpointId: string; comment: string; caseIds?: string[]; } export const useHostUnisolation = ({ - agentId, + endpointId, comment, caseIds, }: UseHostIsolationProps): HostUnisolationStatus => { @@ -32,7 +32,7 @@ export const useHostUnisolation = ({ const unIsolateHost = useCallback(async () => { try { setLoading(true); - const isolationStatus = await createHostUnIsolation({ agentId, comment, caseIds }); + const isolationStatus = await createHostUnIsolation({ endpointId, comment, caseIds }); setLoading(false); return isolationStatus.action ? true : false; } catch (error) { @@ -40,6 +40,6 @@ export const useHostUnisolation = ({ addError(error.message, { title: HOST_ISOLATION_FAILURE }); return false; } - }, [agentId, comment, caseIds, addError]); + }, [endpointId, comment, caseIds, addError]); return { loading, unIsolateHost }; }; From 853de830c2c908d7c3601e76896c4edc0a9c2ff4 Mon Sep 17 00:00:00 2001 From: Diana Derevyankina <54894989+DziyanaDzeraviankina@users.noreply.github.com> Date: Fri, 18 Jun 2021 17:32:17 +0300 Subject: [PATCH 15/35] [TSVB] Index pattern select field disappear in Annotation tab (#102314) * [TSVB] Index pattern select field disappear in Annotation tab * Refactor AnnotationsEditor and move renderRow logic into AnnotationRow * Remove duplicated license, add useCallback to functions in AnnotationRow, remove cross refecrence and update types * Refactor AnnotationEditor and AnnotationRow * refactoring * remove extra props * fix nits Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alexey Antonov --- .../application/components/annotation_row.tsx | 280 +++++++++++++++ .../components/annotations_editor.js | 322 ------------------ .../components/annotations_editor.tsx | 113 ++++++ .../components/panel_config/timeseries.tsx | 4 +- .../public/application/components/yes_no.tsx | 8 +- 5 files changed, 398 insertions(+), 329 deletions(-) create mode 100644 src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx delete mode 100644 src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js create mode 100644 src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx new file mode 100644 index 0000000000000..715cf4d6709da --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/annotation_row.tsx @@ -0,0 +1,280 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useState, useEffect, useCallback, useMemo, ChangeEvent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { + EuiCode, + EuiComboBoxOptionOption, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSpacer, + htmlIdGenerator, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { getDataStart } from '../../services'; +import { KBN_FIELD_TYPES, Query } from '../../../../../plugins/data/public'; + +import { AddDeleteButtons } from './add_delete_buttons'; +import { ColorPicker } from './color_picker'; +import { FieldSelect } from './aggs/field_select'; +import { IndexPatternSelect } from './lib/index_pattern_select'; +import { QueryBarWrapper } from './query_bar_wrapper'; +import { YesNo } from './yes_no'; +import { fetchIndexPattern } from '../../../common/index_patterns_utils'; +import { getDefaultQueryLanguage } from './lib/get_default_query_language'; + +// @ts-expect-error not typed yet +import { IconSelect } from './icon_select/icon_select'; + +import type { Annotation, FetchedIndexPattern, IndexPatternValue } from '../../../common/types'; +import type { VisFields } from '../lib/fetch_fields'; + +const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; + +const INDEX_PATTERN_KEY = 'index_pattern'; +const TIME_FIELD_KEY = 'time_field'; + +export interface AnnotationRowProps { + annotation: Annotation; + fields: VisFields; + onChange: (partialModel: Partial) => void; + handleAdd: () => void; + handleDelete: () => void; +} + +const getAnnotationDefaults = () => ({ + fields: '', + template: '', + index_pattern: '', + query_string: { query: '', language: getDefaultQueryLanguage() }, +}); + +export const AnnotationRow = ({ + annotation, + fields, + onChange, + handleAdd, + handleDelete, +}: AnnotationRowProps) => { + const model = useMemo(() => ({ ...getAnnotationDefaults(), ...annotation }), [annotation]); + const htmlId = htmlIdGenerator(model.id); + + const [fetchedIndex, setFetchedIndex] = useState(null); + + useEffect(() => { + const updateFetchedIndex = async (index: IndexPatternValue) => { + const { indexPatterns } = getDataStart(); + + setFetchedIndex( + index + ? await fetchIndexPattern(index, indexPatterns) + : { + indexPattern: undefined, + indexPatternString: undefined, + } + ); + }; + + updateFetchedIndex(model.index_pattern); + }, [model.index_pattern]); + + const togglePanelActivation = useCallback( + () => + onChange({ + hidden: !model.hidden, + }), + [model.hidden, onChange] + ); + + const handleChange = useCallback( + (name: string) => ( + event: Array> | ChangeEvent + ) => + onChange({ + [name]: Array.isArray(event) ? event?.[0]?.value : event.target.value, + }), + [onChange] + ); + + const handleQueryChange = useCallback( + (filter: Query) => + onChange({ + query_string: filter, + }), + [onChange] + ); + + return ( +
+ + + + + + + + + + + + + } + restrict={RESTRICT_FIELDS} + value={model.time_field} + onChange={handleChange(TIME_FIELD_KEY)} + indexPattern={model.index_pattern} + fields={fields} + /> + + + + + + + + + } + fullWidth + > + + + + + + + + + + + + + + + + + + + + + } + > + + + + + + } + fullWidth + > + + + + + + } + helpText={ + + {'{{field}}'} }} + /> + + } + fullWidth + > + + + + + + + + + + +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js deleted file mode 100644 index 09ce57639b952..0000000000000 --- a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.js +++ /dev/null @@ -1,322 +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 { i18n } from '@kbn/i18n'; -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import _ from 'lodash'; -import { collectionActions } from './lib/collection_actions'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; -import { AddDeleteButtons } from './add_delete_buttons'; -import { ColorPicker } from './color_picker'; -import { FieldSelect } from './aggs/field_select'; -import uuid from 'uuid'; -import { IconSelect } from './icon_select/icon_select'; -import { YesNo } from './yes_no'; -import { QueryBarWrapper } from './query_bar_wrapper'; -import { getDefaultQueryLanguage } from './lib/get_default_query_language'; -import { - htmlIdGenerator, - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiSpacer, - EuiFieldText, - EuiTitle, - EuiButton, - EuiCode, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { IndexPatternSelect } from './lib/index_pattern_select'; - -function newAnnotation() { - return { - id: uuid.v1(), - color: '#F00', - index_pattern: '', - time_field: '', - icon: 'fa-tag', - ignore_global_filters: 1, - ignore_panel_filters: 1, - }; -} - -const RESTRICT_FIELDS = [KBN_FIELD_TYPES.DATE]; - -export class AnnotationsEditor extends Component { - constructor(props) { - super(props); - this.renderRow = this.renderRow.bind(this); - } - - handleChange(item, name) { - return (e) => { - const handleChange = collectionActions.handleChange.bind(null, this.props); - const part = {}; - part[name] = _.get(e, '[0].value', _.get(e, 'target.value')); - handleChange(_.assign({}, item, part)); - }; - } - - handleQueryChange = (model, filter) => { - const part = { query_string: filter }; - collectionActions.handleChange(this.props, { - ...model, - ...part, - }); - }; - renderRow(row) { - const defaults = { - fields: '', - template: '', - index_pattern: '', - query_string: { query: '', language: getDefaultQueryLanguage() }, - }; - const model = { ...defaults, ...row }; - const handleChange = (part) => { - const fn = collectionActions.handleChange.bind(null, this.props); - fn(_.assign({}, model, part)); - }; - const togglePanelActivation = () => { - handleChange({ - hidden: !model.hidden, - }); - }; - const htmlId = htmlIdGenerator(model.id); - const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation); - const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); - - return ( -
- - - - - - - - - - - - - } - restrict={RESTRICT_FIELDS} - value={model.time_field} - onChange={this.handleChange(model, 'time_field')} - indexPattern={model.index_pattern} - fields={this.props.fields} - fullWidth - /> - - - - - - - - - } - fullWidth - > - this.handleQueryChange(model, query)} - indexPatterns={[model.index_pattern]} - /> - - - - - - - - - - - - - - - - - - - - } - > - - - - - - } - fullWidth - > - - - - - - } - helpText={ - - {'{{field}}'} }} - /> - - } - fullWidth - > - - - - - - - - - - -
- ); - } - - render() { - const { model } = this.props; - let content; - if (!model.annotations || !model.annotations.length) { - const handleAdd = collectionActions.handleAdd.bind(null, this.props, newAnnotation); - content = ( - -

- -

- - - -
- ); - } else { - const annotations = model.annotations.map(this.renderRow); - content = ( -
- - - - - - - - {annotations} -
- ); - } - return
{content}
; - } -} - -AnnotationsEditor.defaultProps = { - name: 'annotations', -}; - -AnnotationsEditor.propTypes = { - fields: PropTypes.object, - model: PropTypes.object, - name: PropTypes.string, - onChange: PropTypes.func, - uiSettings: PropTypes.object, -}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx new file mode 100644 index 0000000000000..b3b4993d2ca06 --- /dev/null +++ b/src/plugins/vis_type_timeseries/public/application/components/annotations_editor.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import uuid from 'uuid'; +import { EuiSpacer, EuiTitle, EuiButton, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { AnnotationRow } from './annotation_row'; +import { collectionActions, CollectionActionsProps } from './lib/collection_actions'; + +import type { Panel, Annotation } from '../../../common/types'; +import type { VisFields } from '../lib/fetch_fields'; + +interface AnnotationsEditorProps { + fields: VisFields; + model: Panel; + onChange: (partialModel: Partial) => void; +} + +export const newAnnotation = () => ({ + id: uuid.v1(), + color: '#F00', + index_pattern: '', + time_field: '', + icon: 'fa-tag', + ignore_global_filters: 1, + ignore_panel_filters: 1, +}); + +const NoContent = ({ handleAdd }: { handleAdd: () => void }) => ( + +

+ +

+ + + +
+); + +const getCollectionActionsProps = (props: AnnotationsEditorProps) => + ({ + name: 'annotations', + ...props, + } as CollectionActionsProps); + +export const AnnotationsEditor = (props: AnnotationsEditorProps) => { + const { annotations } = props.model; + + const handleAdd = useCallback( + () => collectionActions.handleAdd(getCollectionActionsProps(props), newAnnotation), + [props] + ); + + const handleDelete = useCallback( + (annotation) => () => + collectionActions.handleDelete(getCollectionActionsProps(props), annotation), + [props] + ); + + const onChange = useCallback( + (annotation: Annotation) => { + return (part: Partial) => + collectionActions.handleChange(getCollectionActionsProps(props), { + ...annotation, + ...part, + }); + }, + [props] + ); + + return ( +
+ {annotations?.length ? ( +
+ + + + + + + {annotations.map((annotation) => ( + + ))} +
+ ) : ( + + )} +
+ ); +}; diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx index b57d72ea6bb30..e1d94f3bf3ebe 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/timeseries.tsx @@ -28,9 +28,8 @@ import { // @ts-expect-error not typed yet import { SeriesEditor } from '../series_editor'; // @ts-expect-error not typed yet -import { AnnotationsEditor } from '../annotations_editor'; -// @ts-expect-error not typed yet import { IndexPattern } from '../index_pattern'; +import { AnnotationsEditor } from '../annotations_editor'; import { createSelectHandler } from '../lib/create_select_handler'; import { ColorPicker } from '../color_picker'; import { YesNo } from '../yes_no'; @@ -162,7 +161,6 @@ export class TimeseriesPanelConfig extends Component< ); diff --git a/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx index 3c02deb177f9e..81ce4f50b0313 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/yes_no.tsx @@ -11,21 +11,21 @@ import { EuiRadio, htmlIdGenerator } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { TimeseriesVisParams } from '../../types'; -interface YesNoProps { - name: ParamName; +interface YesNoProps { + name: string; value: boolean | number | undefined; disabled?: boolean; 'data-test-subj'?: string; onChange: (partialModel: Partial) => void; } -export function YesNo({ +export function YesNo({ name, value, disabled, 'data-test-subj': dataTestSubj, onChange, -}: YesNoProps) { +}: YesNoProps) { const handleChange = useCallback( (val: number) => { return () => onChange({ [name]: val }); From cee33b004cfdabc870ab7f975396c0c70d06d2b1 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Fri, 18 Jun 2021 16:09:31 +0100 Subject: [PATCH 16/35] chore(NA): moving @kbn/ui-shared-deps into bazel (#101669) * chore(NA): moving @kbn/io-ts-utils into bazel * chore(NA): moving @kbn/ui-shared-deps into bazel * chore(NA): compelte working build for @kbn/ui-shared-deps * chore(NA): solve eslint problems * chore(NA): solve typechecking * chore(NA): debugger changes * chore(NA): update optimizer basic integration tests * chore(NA): ship kbn/ui-shared-deps metrics.json from new location at shared_built_assets * chore(NA): use correct ui-shared-deps metrics file location * chore(NA): remove webpack bazel config * chore(NA): implement improvements on webpack config * chore(NA): remove extra comment * chore(NA): try esbuild-loader minimizer * Revert "chore(NA): try esbuild-loader minimizer" This reverts commit bffc49aaaed17ee769bfe1b3290979d49c63afdc. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .buildkite/scripts/post_build_kibana.sh | 2 +- .eslintignore | 2 +- .eslintrc.js | 2 +- .i18nrc.json | 2 +- .../monorepo-packages.asciidoc | 1 + package.json | 3 +- packages/BUILD.bazel | 1 + packages/kbn-optimizer/package.json | 3 - .../basic_optimization.test.ts | 10 +- .../worker/populate_bundle_cache_plugin.ts | 15 +- packages/kbn-ui-shared-deps/BUILD.bazel | 145 ++++++++++++++++++ .../flot_charts/package.json | 4 + packages/kbn-ui-shared-deps/index.d.ts | 59 ------- packages/kbn-ui-shared-deps/package.json | 7 +- packages/kbn-ui-shared-deps/scripts/build.js | 98 ------------ .../kbn-ui-shared-deps/{ => src}/entry.js | 0 .../{ => src}/flot_charts/API.md | 0 .../{ => src}/flot_charts/index.js | 0 .../flot_charts/jquery_colorhelpers.js | 0 .../{ => src}/flot_charts/jquery_flot.js | 0 .../flot_charts/jquery_flot_axislabels.js | 0 .../flot_charts/jquery_flot_canvas.js | 0 .../flot_charts/jquery_flot_categories.js | 0 .../flot_charts/jquery_flot_crosshair.js | 0 .../flot_charts/jquery_flot_errorbars.js | 0 .../flot_charts/jquery_flot_fillbetween.js | 0 .../flot_charts/jquery_flot_image.js | 0 .../{ => src}/flot_charts/jquery_flot_log.js | 0 .../flot_charts/jquery_flot_navigate.js | 0 .../{ => src}/flot_charts/jquery_flot_pie.js | 0 .../flot_charts/jquery_flot_resize.js | 0 .../flot_charts/jquery_flot_selection.js | 0 .../flot_charts/jquery_flot_stack.js | 0 .../flot_charts/jquery_flot_symbol.js | 0 .../flot_charts/jquery_flot_threshold.js | 0 .../{ => src}/flot_charts/jquery_flot_time.js | 0 .../kbn-ui-shared-deps/{ => src}/index.js | 41 ++++- .../kbn-ui-shared-deps/{ => src}/polyfills.js | 0 .../{ => src}/public_path_loader.js | 0 .../{ => src}/public_path_module_creator.js | 0 .../kbn-ui-shared-deps/{ => src}/theme.ts | 0 .../kbn-ui-shared-deps/theme/package.json | 4 + packages/kbn-ui-shared-deps/tsconfig.json | 16 +- packages/kbn-ui-shared-deps/webpack.config.js | 108 ++++++------- test/scripts/jenkins_baseline.sh | 2 +- test/scripts/jenkins_build_kibana.sh | 2 +- test/scripts/jenkins_xpack_baseline.sh | 2 +- .../components/markdown_editor/types.ts | 1 - .../markdown_editor/plugins/index.ts | 1 - yarn.lock | 2 +- 50 files changed, 289 insertions(+), 244 deletions(-) create mode 100644 packages/kbn-ui-shared-deps/BUILD.bazel create mode 100644 packages/kbn-ui-shared-deps/flot_charts/package.json delete mode 100644 packages/kbn-ui-shared-deps/index.d.ts delete mode 100644 packages/kbn-ui-shared-deps/scripts/build.js rename packages/kbn-ui-shared-deps/{ => src}/entry.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/API.md (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/index.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_colorhelpers.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_axislabels.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_canvas.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_categories.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_crosshair.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_errorbars.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_fillbetween.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_image.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_log.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_navigate.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_pie.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_resize.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_selection.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_stack.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_symbol.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_threshold.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/flot_charts/jquery_flot_time.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/index.js (78%) rename packages/kbn-ui-shared-deps/{ => src}/polyfills.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/public_path_loader.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/public_path_module_creator.js (100%) rename packages/kbn-ui-shared-deps/{ => src}/theme.ts (100%) create mode 100644 packages/kbn-ui-shared-deps/theme/package.json diff --git a/.buildkite/scripts/post_build_kibana.sh b/.buildkite/scripts/post_build_kibana.sh index a4f8d71b77105..ad22a224f7c55 100755 --- a/.buildkite/scripts/post_build_kibana.sh +++ b/.buildkite/scripts/post_build_kibana.sh @@ -6,7 +6,7 @@ if [[ ! "${DISABLE_CI_STATS_SHIPPING:-}" ]]; then echo "--- Ship Kibana Distribution Metrics to CI Stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json fi echo "--- Upload Build Artifacts" diff --git a/.eslintignore b/.eslintignore index ce21d5bb31264..63cd01d6e90db 100644 --- a/.eslintignore +++ b/.eslintignore @@ -37,7 +37,7 @@ snapshots.js /packages/kbn-test/src/functional_test_runner/__tests__/fixtures/ /packages/kbn-test/src/functional_test_runner/lib/config/__tests__/fixtures/ /packages/kbn-ui-framework/dist -/packages/kbn-ui-shared-deps/flot_charts +/packages/kbn-ui-shared-deps/src/flot_charts /packages/kbn-monaco/src/painless/antlr # Bazel diff --git a/.eslintrc.js b/.eslintrc.js index c4883feff9b3c..40dd6a55a2a3f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1471,7 +1471,7 @@ module.exports = { }, }, { - files: ['packages/kbn-ui-shared-deps/flot_charts/**/*.js'], + files: ['packages/kbn-ui-shared-deps/src/flot_charts/**/*.js'], env: { jquery: true, }, diff --git a/.i18nrc.json b/.i18nrc.json index ad91042a2172d..0926f73722731 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -11,7 +11,7 @@ "uiActionsExamples": "examples/ui_action_examples", "share": "src/plugins/share", "home": "src/plugins/home", - "flot": "packages/kbn-ui-shared-deps/flot_charts", + "flot": "packages/kbn-ui-shared-deps/src/flot_charts", "charts": "src/plugins/charts", "esUi": "src/plugins/es_ui_shared", "devTools": "src/plugins/dev_tools", diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc index ba484c9c2884f..c211751c09b49 100644 --- a/docs/developer/getting-started/monorepo-packages.asciidoc +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -102,5 +102,6 @@ yarn kbn watch-bazel - @kbn/std - @kbn/telemetry-utils - @kbn/tinymath +- @kbn/ui-shared-deps - @kbn/utility-types - @kbn/utils diff --git a/package.json b/package.json index b8a52f14c52ca..310350baf7b2d 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,7 @@ "@kbn/std": "link:bazel-bin/packages/kbn-std", "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath", "@kbn/ui-framework": "link:packages/kbn-ui-framework", - "@kbn/ui-shared-deps": "link:packages/kbn-ui-shared-deps", + "@kbn/ui-shared-deps": "link:bazel-bin/packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types", "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/utils": "link:bazel-bin/packages/kbn-utils", @@ -291,6 +291,7 @@ "mapbox-gl-draw-rectangle-mode": "1.0.4", "markdown-it": "^10.0.0", "md5": "^2.1.0", + "mdast-util-to-hast": "10.0.1", "memoize-one": "^5.0.0", "mime": "^2.4.4", "mime-types": "^2.1.27", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 7bce6c256f2f3..6208910729625 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -46,6 +46,7 @@ filegroup( "//packages/kbn-std:build", "//packages/kbn-telemetry-tools:build", "//packages/kbn-tinymath:build", + "//packages/kbn-ui-shared-deps:build", "//packages/kbn-utility-types:build", "//packages/kbn-utils:build", ], diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index 54fcdc3bb130f..a6c8284ad15f6 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -9,8 +9,5 @@ "build": "../../node_modules/.bin/tsc", "kbn:bootstrap": "yarn build", "kbn:watch": "yarn build --watch" - }, - "dependencies": { - "@kbn/ui-shared-deps": "link:../kbn-ui-shared-deps" } } \ No newline at end of file diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts index 50c9e7e12904f..97a7f33be673d 100644 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts @@ -15,7 +15,7 @@ import cpy from 'cpy'; import del from 'del'; import { tap, filter } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/utils'; -import { ToolingLog } from '@kbn/dev-utils'; +import { ToolingLog, createReplaceSerializer } from '@kbn/dev-utils'; import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../index'; import { allValuesFrom } from '../common'; @@ -29,6 +29,8 @@ expect.addSnapshotSerializer({ test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT), }); +expect.addSnapshotSerializer(createReplaceSerializer(/\w+-fastbuild/, '-fastbuild')); + const log = new ToolingLog({ level: 'error', writeTo: { @@ -130,13 +132,13 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { expect(foo.cache.getModuleCount()).toBe(6); expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/index.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/lib.ts, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -153,6 +155,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /node_modules/@kbn/optimizer/postcss.config.js, /node_modules/css-loader/package.json, /node_modules/style-loader/package.json, + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts, @@ -162,7 +165,6 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8dark.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8light.scss, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); @@ -173,10 +175,10 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { expect(baz.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, - /packages/kbn-ui-shared-deps/public_path_module_creator.js, ] `); }); diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts index 6d296b9be089c..8d890b31b639d 100644 --- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts +++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts @@ -54,8 +54,19 @@ export class PopulateBundleCachePlugin { for (const module of compilation.modules) { if (isNormalModule(module)) { moduleCount += 1; - const path = getModulePath(module); - const parsedPath = parseFilePath(path); + let path = getModulePath(module); + let parsedPath = parseFilePath(path); + + if (parsedPath.dirs.includes('bazel-out')) { + const index = parsedPath.dirs.indexOf('bazel-out'); + path = Path.join( + workerConfig.repoRoot, + 'bazel-out', + ...parsedPath.dirs.slice(index + 1), + parsedPath.filename ?? '' + ); + parsedPath = parseFilePath(path); + } if (!parsedPath.dirs.includes('node_modules')) { referencedFiles.add(path); diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel new file mode 100644 index 0000000000000..c04f88a42cce0 --- /dev/null +++ b/packages/kbn-ui-shared-deps/BUILD.bazel @@ -0,0 +1,145 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") +load("@npm//webpack-cli:index.bzl", webpack = "webpack_cli") + +PKG_BASE_NAME = "kbn-ui-shared-deps" +PKG_REQUIRE_NAME = "@kbn/ui-shared-deps" + +SOURCE_FILES = glob( + [ + "src/**/*", + ], + exclude = [ + "**/*.md", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "flot_charts/package.json", + "theme/package.json", + "package.json", + "README.md" +] + +SRC_DEPS = [ + "//packages/elastic-datemath", + "//packages/elastic-safer-lodash-set", + "//packages/kbn-analytics", + "//packages/kbn-babel-preset", + "//packages/kbn-i18n", + "//packages/kbn-monaco", + "//packages/kbn-std", + "//packages/kbn-utils", + "@npm//@elastic/charts", + "@npm//@elastic/eui", + "@npm//@elastic/numeral", + "@npm//abortcontroller-polyfill", + "@npm//angular", + "@npm//babel-loader", + "@npm//compression-webpack-plugin", + "@npm//core-js", + "@npm//css-minimizer-webpack-plugin", + "@npm//css-loader", + "@npm//fflate", + "@npm//jquery", + "@npm//loader-utils", + # TODO: we can remove this once EUI patches the dependencies + "@npm//mdast-util-to-hast", + "@npm//mini-css-extract-plugin", + "@npm//moment", + "@npm//moment-timezone", + "@npm//raw-loader", + "@npm//react", + "@npm//react-dom", + "@npm//react-intl", + "@npm//react-is", + "@npm//react-router", + "@npm//react-router-dom", + "@npm//regenerator-runtime", + "@npm//resize-observer-polyfill", + "@npm//rison-node", + "@npm//rxjs", + "@npm//styled-components", + "@npm//symbol-observable", + "@npm//terser-webpack-plugin", + "@npm//url-loader", + "@npm//val-loader", + "@npm//whatwg-fetch" +] + +TYPES_DEPS = [ + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + allow_js = True, + declaration = True, + declaration_map = True, + incremental = True, + out_dir = "target", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +webpack( + name = "shared_built_assets", + data = DEPS + [ + "//:package.json", + ":srcs", + ":tsconfig", + ":webpack.config.js", + ], + output_dir = True, + args = [ + "--config", + "$(location webpack.config.js)", + "--output-path", + "$(@D)", + "--display=minimal" + ], +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = DEPS + [":tsc", ":shared_built_assets"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-ui-shared-deps/flot_charts/package.json b/packages/kbn-ui-shared-deps/flot_charts/package.json new file mode 100644 index 0000000000000..03d7ac348fcb9 --- /dev/null +++ b/packages/kbn-ui-shared-deps/flot_charts/package.json @@ -0,0 +1,4 @@ +{ + "main": "../target/flot_charts/index.js", + "types": "../target/flot_charts/index.d.ts" +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/index.d.ts b/packages/kbn-ui-shared-deps/index.d.ts deleted file mode 100644 index 5d2986daeeb3b..0000000000000 --- a/packages/kbn-ui-shared-deps/index.d.ts +++ /dev/null @@ -1,59 +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. - */ - -/** - * Absolute path to the distributable directory - */ -export const distDir: string; - -/** - * Filename of the main bundle file in the distributable directory - */ -export const jsFilename: string; - -/** - * Filename of files that must be loaded before the jsFilename - */ -export const jsDepFilenames: string[]; - -/** - * Filename of the unthemed css file in the distributable directory - */ -export const baseCssDistFilename: string; - -/** - * Filename of the dark-theme css file in the distributable directory - */ -export const darkCssDistFilename: string; - -/** - * Filename of the dark-theme css file in the distributable directory - */ -export const darkV8CssDistFilename: string; - -/** - * Filename of the light-theme css file in the distributable directory - */ -export const lightCssDistFilename: string; - -/** - * Filename of the light-theme css file in the distributable directory - */ -export const lightV8CssDistFilename: string; - -/** - * Externals mapping inteded to be used in a webpack config - */ -export const externals: { - [key: string]: string; -}; - -/** - * Webpack loader for configuring the public path lookup from `window.__kbnPublicPath__`. - */ -export const publicPathLoader: string; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 162606585c43e..5ec32ca059aa1 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -3,9 +3,6 @@ "version": "1.0.0", "private": true, "license": "SSPL-1.0 OR Elastic License 2.0", - "scripts": { - "build": "node scripts/build", - "kbn:bootstrap": "node scripts/build --dev", - "kbn:watch": "node scripts/build --dev --watch" - } + "main": "target/index.js", + "types": "target/index.d.ts" } \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/scripts/build.js b/packages/kbn-ui-shared-deps/scripts/build.js deleted file mode 100644 index 0993f78590246..0000000000000 --- a/packages/kbn-ui-shared-deps/scripts/build.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const Path = require('path'); - -const { run, createFailError } = require('@kbn/dev-utils'); -const webpack = require('webpack'); -const Stats = require('webpack/lib/Stats'); -const del = require('del'); - -const { getWebpackConfig } = require('../webpack.config'); - -const DIST_DIR = Path.resolve(__dirname, '../target'); - -run( - async ({ log, flags }) => { - log.info('cleaning previous build output'); - await del(DIST_DIR); - - const compiler = webpack( - getWebpackConfig({ - dev: flags.dev, - }) - ); - - /** @param {webpack.Stats} stats */ - const onCompilationComplete = async (stats) => { - const took = Math.round((stats.endTime - stats.startTime) / 1000); - - if (!stats.hasErrors() && !stats.hasWarnings()) { - log.success(`webpack completed in about ${took} seconds`); - return; - } - - throw createFailError( - `webpack failure in about ${took} seconds\n${stats.toString({ - colors: true, - ...Stats.presetToOptions('minimal'), - })}` - ); - }; - - if (flags.watch) { - compiler.hooks.done.tap('report on stats', (stats) => { - onCompilationComplete(stats).catch((error) => { - log.error(error.message); - }); - }); - - compiler.hooks.watchRun.tap('report on start', () => { - if (process.stdout.isTTY) { - process.stdout.cursorTo(0, 0); - process.stdout.clearScreenDown(); - } - - log.info('Running webpack compilation...'); - }); - - compiler.watch({}, (error) => { - if (error) { - log.error('Fatal webpack error'); - log.error(error); - process.exit(1); - } - }); - - return; - } - - log.info('running webpack'); - await onCompilationComplete( - await new Promise((resolve, reject) => { - compiler.run((error, stats) => { - if (error) { - reject(error); - } else { - resolve(stats); - } - }); - }) - ); - }, - { - description: 'build @kbn/ui-shared-deps', - flags: { - boolean: ['watch', 'dev'], - help: ` - --watch Run in watch mode - --dev Build development friendly version - `, - }, - } -); diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/src/entry.js similarity index 100% rename from packages/kbn-ui-shared-deps/entry.js rename to packages/kbn-ui-shared-deps/src/entry.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/API.md b/packages/kbn-ui-shared-deps/src/flot_charts/API.md similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/API.md rename to packages/kbn-ui-shared-deps/src/flot_charts/API.md diff --git a/packages/kbn-ui-shared-deps/flot_charts/index.js b/packages/kbn-ui-shared-deps/src/flot_charts/index.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/index.js rename to packages/kbn-ui-shared-deps/src/flot_charts/index.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_colorhelpers.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_colorhelpers.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_colorhelpers.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_colorhelpers.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_axislabels.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_axislabels.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_axislabels.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_axislabels.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_canvas.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_canvas.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_canvas.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_canvas.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_categories.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_categories.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_categories.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_categories.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_crosshair.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_crosshair.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_crosshair.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_crosshair.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_errorbars.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_errorbars.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_errorbars.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_errorbars.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_fillbetween.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_fillbetween.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_fillbetween.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_fillbetween.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_image.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_image.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_image.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_image.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_log.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_log.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_log.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_log.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_navigate.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_navigate.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_navigate.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_navigate.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_pie.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_pie.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_pie.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_resize.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_resize.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_resize.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_resize.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_selection.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_selection.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_selection.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_selection.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_stack.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_stack.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_stack.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_stack.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_symbol.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_symbol.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_symbol.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_symbol.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_threshold.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_threshold.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_threshold.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_threshold.js diff --git a/packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js b/packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_time.js similarity index 100% rename from packages/kbn-ui-shared-deps/flot_charts/jquery_flot_time.js rename to packages/kbn-ui-shared-deps/src/flot_charts/jquery_flot_time.js diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/src/index.js similarity index 78% rename from packages/kbn-ui-shared-deps/index.js rename to packages/kbn-ui-shared-deps/src/index.js index 877bf3df6c039..c5853dc091875 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/src/index.js @@ -8,14 +8,49 @@ const Path = require('path'); -exports.distDir = Path.resolve(__dirname, 'target'); +/** + * Absolute path to the distributable directory + */ +exports.distDir = Path.resolve(__dirname, '..', 'shared_built_assets'); + +/** + * Filename of files that must be loaded before the jsFilename + */ exports.jsDepFilenames = ['kbn-ui-shared-deps.@elastic.js']; + +/** + * Filename of the main bundle file in the distributable directory + */ exports.jsFilename = 'kbn-ui-shared-deps.js'; + +/** + * Filename of the unthemed css file in the distributable directory + */ exports.baseCssDistFilename = 'kbn-ui-shared-deps.css'; + +/** + * Filename of the light-theme css file in the distributable directory + */ exports.lightCssDistFilename = 'kbn-ui-shared-deps.v7.light.css'; + +/** + * Filename of the light-theme css file in the distributable directory + */ exports.lightV8CssDistFilename = 'kbn-ui-shared-deps.v8.light.css'; + +/** + * Filename of the dark-theme css file in the distributable directory + */ exports.darkCssDistFilename = 'kbn-ui-shared-deps.v7.dark.css'; + +/** + * Filename of the dark-theme css file in the distributable directory + */ exports.darkV8CssDistFilename = 'kbn-ui-shared-deps.v8.dark.css'; + +/** + * Externals mapping inteded to be used in a webpack config + */ exports.externals = { // stateful deps angular: '__kbnSharedDeps__.Angular', @@ -63,4 +98,8 @@ exports.externals = { '@elastic/safer-lodash-set': '__kbnSharedDeps__.SaferLodashSet', 'rison-node': '__kbnSharedDeps__.RisonNode', }; + +/** + * Webpack loader for configuring the public path lookup from `window.__kbnPublicPath__`. + */ exports.publicPathLoader = require.resolve('./public_path_loader'); diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/src/polyfills.js similarity index 100% rename from packages/kbn-ui-shared-deps/polyfills.js rename to packages/kbn-ui-shared-deps/src/polyfills.js diff --git a/packages/kbn-ui-shared-deps/public_path_loader.js b/packages/kbn-ui-shared-deps/src/public_path_loader.js similarity index 100% rename from packages/kbn-ui-shared-deps/public_path_loader.js rename to packages/kbn-ui-shared-deps/src/public_path_loader.js diff --git a/packages/kbn-ui-shared-deps/public_path_module_creator.js b/packages/kbn-ui-shared-deps/src/public_path_module_creator.js similarity index 100% rename from packages/kbn-ui-shared-deps/public_path_module_creator.js rename to packages/kbn-ui-shared-deps/src/public_path_module_creator.js diff --git a/packages/kbn-ui-shared-deps/theme.ts b/packages/kbn-ui-shared-deps/src/theme.ts similarity index 100% rename from packages/kbn-ui-shared-deps/theme.ts rename to packages/kbn-ui-shared-deps/src/theme.ts diff --git a/packages/kbn-ui-shared-deps/theme/package.json b/packages/kbn-ui-shared-deps/theme/package.json new file mode 100644 index 0000000000000..2d41937701a29 --- /dev/null +++ b/packages/kbn-ui-shared-deps/theme/package.json @@ -0,0 +1,4 @@ +{ + "main": "../target/theme.js", + "types": "../target/theme.d.ts" +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/tsconfig.json b/packages/kbn-ui-shared-deps/tsconfig.json index 88699027f85de..0fd49ede21830 100644 --- a/packages/kbn-ui-shared-deps/tsconfig.json +++ b/packages/kbn-ui-shared-deps/tsconfig.json @@ -1,10 +1,20 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "tsBuildInfoFile": "../../build/tsbuildinfo/packages/kbn-ui-shared-deps" + "allowJs": true, + "incremental": true, + "outDir": "./target", + "declaration": true, + "declarationMap": true, + "rootDir": "src", + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-ui-shared-deps/src", + "types": [ + "node", + "resize-observer-polyfill" + ] }, "include": [ - "index.d.ts", - "theme.ts" + "src/**/*", ] } diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index 76e6843bea2f8..438b1e0b2e77b 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -7,6 +7,7 @@ */ const Path = require('path'); +const Os = require('os'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); @@ -14,24 +15,23 @@ const TerserPlugin = require('terser-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const { REPO_ROOT } = require('@kbn/utils'); -const webpack = require('webpack'); const { RawSource } = require('webpack-sources'); -const UiSharedDeps = require('./index'); +const UiSharedDeps = require('./src/index'); const MOMENT_SRC = require.resolve('moment/min/moment-with-locales.js'); -exports.getWebpackConfig = ({ dev = false } = {}) => ({ - mode: dev ? 'development' : 'production', +module.exports = { + mode: 'production', entry: { - 'kbn-ui-shared-deps': './entry.js', + 'kbn-ui-shared-deps': './src/entry.js', 'kbn-ui-shared-deps.v7.dark': ['@elastic/eui/dist/eui_theme_dark.css'], 'kbn-ui-shared-deps.v7.light': ['@elastic/eui/dist/eui_theme_light.css'], 'kbn-ui-shared-deps.v8.dark': ['@elastic/eui/dist/eui_theme_amsterdam_dark.css'], 'kbn-ui-shared-deps.v8.light': ['@elastic/eui/dist/eui_theme_amsterdam_light.css'], }, context: __dirname, - devtool: dev ? '#cheap-source-map' : false, + devtool: 'cheap-source-map', output: { path: UiSharedDeps.distDir, filename: '[name].js', @@ -39,13 +39,14 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ devtoolModuleFilenameTemplate: (info) => `kbn-ui-shared-deps/${Path.relative(REPO_ROOT, info.absoluteResourcePath)}`, library: '__kbnSharedDeps__', + futureEmitAssets: true, }, module: { noParse: [MOMENT_SRC], rules: [ { - include: [require.resolve('./entry.js')], + include: [require.resolve('./src/entry.js')], use: [ { loader: UiSharedDeps.publicPathLoader, @@ -60,7 +61,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ use: [MiniCssExtractPlugin.loader, 'css-loader'], }, { - include: [require.resolve('./theme.ts')], + include: [require.resolve('./src/theme.ts')], use: [ { loader: 'babel-loader', @@ -71,7 +72,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ ], }, { - test: !dev ? /[\\\/]@elastic[\\\/]eui[\\\/].*\.js$/ : () => false, + test: /[\\\/]@elastic[\\\/]eui[\\\/].*\.js$/, use: [ { loader: 'babel-loader', @@ -110,6 +111,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ optimization: { minimizer: [ new CssMinimizerPlugin({ + parallel: Math.min(Os.cpus().length, 2), minimizerOptions: { preset: [ 'default', @@ -123,7 +125,7 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ cache: false, sourceMap: false, extractComments: false, - parallel: false, + parallel: Math.min(Os.cpus().length, 2), terserOptions: { compress: true, mangle: true, @@ -154,54 +156,44 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ new MiniCssExtractPlugin({ filename: '[name].css', }), - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': dev ? '"development"' : '"production"', + new CompressionPlugin({ + algorithm: 'brotliCompress', + filename: '[path].br', + test: /\.(js|css)$/, + cache: false, }), - ...(dev - ? [] - : [ - new CompressionPlugin({ - algorithm: 'brotliCompress', - filename: '[path].br', - test: /\.(js|css)$/, - cache: false, - }), - new CompressionPlugin({ - algorithm: 'gzip', - filename: '[path].gz', - test: /\.(js|css)$/, - cache: false, - }), - new (class MetricsPlugin { - apply(compiler) { - compiler.hooks.emit.tap('MetricsPlugin', (compilation) => { - const metrics = [ - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-js', - value: compilation.assets['kbn-ui-shared-deps.js'].size(), - }, - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-css', - value: - compilation.assets['kbn-ui-shared-deps.css'].size() + - compilation.assets['kbn-ui-shared-deps.v7.light.css'].size(), - }, - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-elastic', - value: compilation.assets['kbn-ui-shared-deps.@elastic.js'].size(), - }, - ]; + new CompressionPlugin({ + algorithm: 'gzip', + filename: '[path].gz', + test: /\.(js|css)$/, + cache: false, + }), + new (class MetricsPlugin { + apply(compiler) { + compiler.hooks.emit.tap('MetricsPlugin', (compilation) => { + const metrics = [ + { + group: 'page load bundle size', + id: 'kbnUiSharedDeps-js', + value: compilation.assets['kbn-ui-shared-deps.js'].size(), + }, + { + group: 'page load bundle size', + id: 'kbnUiSharedDeps-css', + value: + compilation.assets['kbn-ui-shared-deps.css'].size() + + compilation.assets['kbn-ui-shared-deps.v7.light.css'].size(), + }, + { + group: 'page load bundle size', + id: 'kbnUiSharedDeps-elastic', + value: compilation.assets['kbn-ui-shared-deps.@elastic.js'].size(), + }, + ]; - compilation.emitAsset( - 'metrics.json', - new RawSource(JSON.stringify(metrics, null, 2)) - ); - }); - } - })(), - ]), + compilation.emitAsset('metrics.json', new RawSource(JSON.stringify(metrics, null, 2))); + }); + } + })(), ], -}); +}; diff --git a/test/scripts/jenkins_baseline.sh b/test/scripts/jenkins_baseline.sh index 58d86cddf65fa..40bfc6e83ad1b 100755 --- a/test/scripts/jenkins_baseline.sh +++ b/test/scripts/jenkins_baseline.sh @@ -9,7 +9,7 @@ node scripts/build --debug --oss echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$PARENT_DIR/install/kibana" diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 32fb98929e21c..198723908cf48 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -38,7 +38,7 @@ if [[ -z "$CODE_COVERAGE" ]] ; then echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" diff --git a/test/scripts/jenkins_xpack_baseline.sh b/test/scripts/jenkins_xpack_baseline.sh index 93363687b39a9..8d5624949505a 100755 --- a/test/scripts/jenkins_xpack_baseline.sh +++ b/test/scripts/jenkins_xpack_baseline.sh @@ -10,7 +10,7 @@ node scripts/build --debug --no-oss echo " -> shipping metrics from build to ci-stats" node scripts/ship_ci_stats \ --metrics target/optimizer_bundle_metrics.json \ - --metrics packages/kbn-ui-shared-deps/target/metrics.json + --metrics node_modules/@kbn/ui-shared-deps/shared_built_assets/metrics.json linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" installDir="$KIBANA_DIR/install/kibana" diff --git a/x-pack/plugins/cases/public/components/markdown_editor/types.ts b/x-pack/plugins/cases/public/components/markdown_editor/types.ts index bb932f2fcfe22..ccc3c59c8977e 100644 --- a/x-pack/plugins/cases/public/components/markdown_editor/types.ts +++ b/x-pack/plugins/cases/public/components/markdown_editor/types.ts @@ -8,7 +8,6 @@ import { FunctionComponent } from 'react'; import { Plugin, PluggableList } from 'unified'; // Remove after this issue is resolved: https://github.com/elastic/eui/issues/4688 -// eslint-disable-next-line import/no-extraneous-dependencies import { Options as Remark2RehypeOptions } from 'mdast-util-to-hast'; // eslint-disable-next-line import/no-extraneous-dependencies import rehype2react from 'rehype-react'; diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts index c744ace91f434..d5846a9f9ea50 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/index.ts @@ -12,7 +12,6 @@ import { getDefaultEuiMarkdownUiPlugins, } from '@elastic/eui'; // Remove after this issue is resolved: https://github.com/elastic/eui/issues/4688 -// eslint-disable-next-line import/no-extraneous-dependencies import { Options as Remark2RehypeOptions } from 'mdast-util-to-hast'; import { FunctionComponent } from 'react'; // eslint-disable-next-line import/no-extraneous-dependencies diff --git a/yarn.lock b/yarn.lock index d0531ebfbaede..c9e139e68b592 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2792,7 +2792,7 @@ version "0.0.0" uid "" -"@kbn/ui-shared-deps@link:packages/kbn-ui-shared-deps": +"@kbn/ui-shared-deps@link:bazel-bin/packages/kbn-ui-shared-deps": version "0.0.0" uid "" From 303806de65bedfe1accef0eb6d346be53a1ed1f7 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Fri, 18 Jun 2021 17:30:53 +0200 Subject: [PATCH 17/35] [Exploratory View] Mobile experience (#99565) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Bryce Buchanan Co-authored-by: Alexander Wert --- .../apm_observability_overview_fetchers.ts | 4 +- .../lib/observability_overview/has_data.ts | 10 +- .../settings/apm_indices/get_apm_indices.ts | 16 +- .../server/routes/observability_overview.ts | 3 +- .../plugins/observability/common/typings.ts | 13 ++ .../components/app/empty_sections/index.tsx | 10 +- .../components/app/section/apm/index.test.tsx | 2 +- .../components/app/section/apm/index.tsx | 4 +- .../components/app/section/logs/index.tsx | 4 +- .../components/app/section/metrics/index.tsx | 4 +- .../components/app/section/uptime/index.tsx | 4 +- .../components/app/section/ux/index.test.tsx | 5 +- .../components/app/section/ux/index.tsx | 7 +- .../configurations/apm/field_formats.ts | 15 +- .../configurations/constants/constants.ts | 9 +- .../constants/elasticsearch_fieldnames.ts | 2 + .../configurations/constants/labels.ts | 97 +++++++++++- .../configurations/default_configs.ts | 12 +- .../configurations/lens_attributes.ts | 92 +++++++++-- .../mobile/device_distribution_config.ts | 49 ++++++ .../mobile/distribution_config.ts | 81 ++++++++++ .../mobile/kpi_over_time_config.ts | 102 ++++++++++++ .../configurations/mobile/mobile_fields.ts | 26 ++++ .../exploratory_view.test.tsx | 2 +- .../hooks/use_app_index_pattern.tsx | 31 ++-- .../series_builder/columns/data_types_col.tsx | 1 + .../series_builder/columns/report_filters.tsx | 2 + .../series_builder/series_builder.tsx | 21 ++- .../columns/filter_expanded.test.tsx | 4 + .../series_editor/columns/filter_expanded.tsx | 39 ++++- .../series_editor/columns/series_filter.tsx | 16 +- .../series_editor/series_editor.tsx | 7 +- .../shared/exploratory_view/types.ts | 12 +- .../utils/observability_index_patterns.ts | 4 + .../public/context/has_data_context.test.tsx | 146 +++++++++++------- .../public/context/has_data_context.tsx | 76 ++++++--- .../observability/public/data_handler.test.ts | 10 +- .../public/pages/home/index.test.tsx | 42 ++--- .../public/pages/overview/index.tsx | 4 +- .../pages/overview/overview.stories.tsx | 20 ++- .../typings/fetch_overview_data/index.ts | 19 ++- 41 files changed, 822 insertions(+), 205 deletions(-) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index 3a02efd05e5a5..ef61e25af4fc2 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -53,10 +53,8 @@ export const fetchObservabilityOverviewPageData = async ({ }; export async function getHasData() { - const res = await callApmApi({ + return await callApmApi({ endpoint: 'GET /api/apm/observability_overview/has_data', signal: null, }); - - return res.hasData; } diff --git a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts index 5c1a33e750e12..3b6993695f3de 100644 --- a/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts +++ b/x-pack/plugins/apm/server/lib/observability_overview/has_data.ts @@ -29,8 +29,14 @@ export async function getHasData({ setup }: { setup: Setup }) { 'observability_overview_has_apm_data', params ); - return response.hits.total.value > 0; + return { + hasData: response.hits.total.value > 0, + indices: setup.indices, + }; } catch (e) { - return false; + return { + hasData: false, + indices: setup.indices, + }; } } diff --git a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts index d8dbc242986a6..0ade96682b362 100644 --- a/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts +++ b/x-pack/plugins/apm/server/lib/settings/apm_indices/get_apm_indices.ts @@ -16,21 +16,11 @@ import { import { APMConfig } from '../../..'; import { APMRouteHandlerResources } from '../../../routes/typings'; import { withApmSpan } from '../../../utils/with_apm_span'; +import { ApmIndicesConfig } from '../../../../../observability/common/typings'; -type ISavedObjectsClient = Pick; +export { ApmIndicesConfig }; -export interface ApmIndicesConfig { - /* eslint-disable @typescript-eslint/naming-convention */ - 'apm_oss.sourcemapIndices': string; - 'apm_oss.errorIndices': string; - 'apm_oss.onboardingIndices': string; - 'apm_oss.spanIndices': string; - 'apm_oss.transactionIndices': string; - 'apm_oss.metricsIndices': string; - /* eslint-enable @typescript-eslint/naming-convention */ - apmAgentConfigurationIndex: string; - apmCustomLinkIndex: string; -} +type ISavedObjectsClient = Pick; export type ApmIndicesName = keyof ApmIndicesConfig; diff --git a/x-pack/plugins/apm/server/routes/observability_overview.ts b/x-pack/plugins/apm/server/routes/observability_overview.ts index d459570cf7337..c2e3d0e81ce0a 100644 --- a/x-pack/plugins/apm/server/routes/observability_overview.ts +++ b/x-pack/plugins/apm/server/routes/observability_overview.ts @@ -21,8 +21,7 @@ const observabilityOverviewHasDataRoute = createApmServerRoute({ options: { tags: ['access:apm'] }, handler: async (resources) => { const setup = await setupRequest(resources); - const res = await getHasData({ setup }); - return { hasData: res }; + return await getHasData({ setup }); }, }); diff --git a/x-pack/plugins/observability/common/typings.ts b/x-pack/plugins/observability/common/typings.ts index bd10543ef389b..305a18903fe7e 100644 --- a/x-pack/plugins/observability/common/typings.ts +++ b/x-pack/plugins/observability/common/typings.ts @@ -10,3 +10,16 @@ export type Maybe = T | null | undefined; export const alertStatusRt = t.union([t.literal('all'), t.literal('open'), t.literal('closed')]); export type AlertStatus = t.TypeOf; + +export interface ApmIndicesConfig { + /* eslint-disable @typescript-eslint/naming-convention */ + 'apm_oss.sourcemapIndices': string; + 'apm_oss.errorIndices': string; + 'apm_oss.onboardingIndices': string; + 'apm_oss.spanIndices': string; + 'apm_oss.transactionIndices': string; + 'apm_oss.metricsIndices': string; + /* eslint-enable @typescript-eslint/naming-convention */ + apmAgentConfigurationIndex: string; + apmCustomLinkIndex: string; +} diff --git a/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx b/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx index f7ce8675d8a45..47417a2bbb545 100644 --- a/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx +++ b/x-pack/plugins/observability/public/components/app/empty_sections/index.tsx @@ -13,26 +13,24 @@ import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useHasData } from '../../../hooks/use_has_data'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import { getEmptySections } from '../../../pages/overview/empty_section'; -import { UXHasDataResponse } from '../../../typings'; import { EmptySection } from './empty_section'; export function EmptySections() { const { core } = usePluginContext(); const theme = useContext(ThemeContext); - const { hasData } = useHasData(); + const { hasDataMap } = useHasData(); const appEmptySections = getEmptySections({ core }).filter(({ id }) => { if (id === 'alert') { - const { status, hasData: alerts } = hasData.alert || {}; + const { status, hasData: alerts } = hasDataMap.alert || {}; return ( status === FETCH_STATUS.FAILURE || (status === FETCH_STATUS.SUCCESS && (alerts as Alert[]).length === 0) ); } else { - const app = hasData[id]; + const app = hasDataMap[id]; if (app) { - const _hasData = id === 'ux' ? (app.hasData as UXHasDataResponse)?.hasData : app.hasData; - return app.status === FETCH_STATUS.FAILURE || !_hasData; + return app.status === FETCH_STATUS.FAILURE || !app.hasData; } } return false; diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx index ad3ecd2740802..16eb8dd24d3c2 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.test.tsx @@ -29,7 +29,7 @@ jest.mock('react-router-dom', () => ({ describe('APMSection', () => { beforeAll(() => { jest.spyOn(hasDataHook, 'useHasData').mockReturnValue({ - hasData: { + hasDataMap: { apm: { status: fetcherHook.FETCH_STATUS.SUCCESS, hasData: true, diff --git a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx index e71468d3b028c..7a42e96c3823d 100644 --- a/x-pack/plugins/observability/public/components/app/section/apm/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/apm/index.tsx @@ -48,7 +48,7 @@ export function APMSection({ bucketSize }: Props) { const theme = useContext(ThemeContext); const chartTheme = useChartTheme(); const history = useHistory(); - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const { data, status } = useFetcher( @@ -66,7 +66,7 @@ export function APMSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); - if (!hasData.apm?.hasData) { + if (!hasDataMap.apm?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx index cb4c831d25022..da5a8f25045a5 100644 --- a/x-pack/plugins/observability/public/components/app/section/logs/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/logs/index.tsx @@ -47,7 +47,7 @@ function getColorPerItem(series?: LogsFetchDataResponse['series']) { export function LogsSection({ bucketSize }: Props) { const history = useHistory(); const chartTheme = useChartTheme(); - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const { data, status } = useFetcher( @@ -65,7 +65,7 @@ export function LogsSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); - if (!hasData.infra_logs?.hasData) { + if (!hasDataMap.infra_logs?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx index 5a642084733c7..2f5bb9bac9348 100644 --- a/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/metrics/index.tsx @@ -50,7 +50,7 @@ const bytesPerSecondFormatter = (value: NumberOrNull) => value === null ? '' : numeral(value).format('0b') + '/s'; export function MetricsSection({ bucketSize }: Props) { - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const [sortDirection, setSortDirection] = useState('asc'); const [sortField, setSortField] = useState('uptime'); @@ -88,7 +88,7 @@ export function MetricsSection({ bucketSize }: Props) { [data, setSortField, setSortDirection] ); - if (!hasData.infra_metrics?.hasData) { + if (!hasDataMap.infra_metrics?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx index 1dbcdeaee800a..28cbd12663c1b 100644 --- a/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/uptime/index.tsx @@ -40,7 +40,7 @@ export function UptimeSection({ bucketSize }: Props) { const theme = useContext(ThemeContext); const chartTheme = useChartTheme(); const history = useHistory(); - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); const { data, status } = useFetcher( @@ -58,7 +58,7 @@ export function UptimeSection({ bucketSize }: Props) { [bucketSize, relativeStart, relativeEnd, forceUpdate] ); - if (!hasData.synthetics?.hasData) { + if (!hasDataMap.synthetics?.hasData) { return null; } diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx index fab461476e713..61bce8aaf845d 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.test.tsx @@ -28,10 +28,11 @@ jest.mock('react-router-dom', () => ({ describe('UXSection', () => { beforeAll(() => { jest.spyOn(hasDataHook, 'useHasData').mockReturnValue({ - hasData: { + hasDataMap: { ux: { status: fetcherHook.FETCH_STATUS.SUCCESS, - hasData: { hasData: true, serviceName: 'elastic-co-frontend' }, + hasData: true, + serviceName: 'elastic-co-frontend', }, }, } as HasDataContextValue); diff --git a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx index 0ac337e5ba0b1..5aa89eb2d3074 100644 --- a/x-pack/plugins/observability/public/components/app/section/ux/index.tsx +++ b/x-pack/plugins/observability/public/components/app/section/ux/index.tsx @@ -12,7 +12,6 @@ import { getDataHandler } from '../../../../data_handler'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useHasData } from '../../../../hooks/use_has_data'; import { useTimeRange } from '../../../../hooks/use_time_range'; -import { UXHasDataResponse } from '../../../../typings'; import CoreVitals from '../../../shared/core_web_vitals'; interface Props { @@ -20,10 +19,10 @@ interface Props { } export function UXSection({ bucketSize }: Props) { - const { forceUpdate, hasData } = useHasData(); + const { forceUpdate, hasDataMap } = useHasData(); const { relativeStart, relativeEnd, absoluteStart, absoluteEnd } = useTimeRange(); - const uxHasDataResponse = (hasData.ux?.hasData as UXHasDataResponse) || {}; - const serviceName = uxHasDataResponse.serviceName as string; + const uxHasDataResponse = hasDataMap.ux; + const serviceName = uxHasDataResponse?.serviceName as string; const { data, status } = useFetcher( () => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts index 8d33dfbab2c62..5c1afbca2a776 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/field_formats.ts @@ -6,7 +6,11 @@ */ import { FieldFormat } from '../../types'; -import { TRANSACTION_DURATION } from '../constants/elasticsearch_fieldnames'; +import { + METRIC_SYSTEM_CPU_USAGE, + METRIC_SYSTEM_MEMORY_USAGE, + TRANSACTION_DURATION, +} from '../constants/elasticsearch_fieldnames'; export const apmFieldFormats: FieldFormat[] = [ { @@ -18,7 +22,16 @@ export const apmFieldFormats: FieldFormat[] = [ outputFormat: 'asMilliseconds', outputPrecision: 0, showSuffix: true, + useShortSuffix: true, }, }, }, + { + field: METRIC_SYSTEM_MEMORY_USAGE, + format: { id: 'bytes', params: {} }, + }, + { + field: METRIC_SYSTEM_CPU_USAGE, + format: { id: 'percent', params: {} }, + }, ]; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index 26459e676de08..e119507860c5c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -13,12 +13,13 @@ import { BROWSER_VERSION_LABEL, CLS_LABEL, CORE_WEB_VITALS_LABEL, + DEVICE_DISTRIBUTION_LABEL, DEVICE_LABEL, ENVIRONMENT_LABEL, FCP_LABEL, FID_LABEL, HOST_NAME_LABEL, - KIP_OVER_TIME_LABEL, + KPI_OVER_TIME_LABEL, KPI_LABEL, LCP_LABEL, LOCATION_LABEL, @@ -31,6 +32,7 @@ import { OS_LABEL, PERF_DIST_LABEL, PORT_LABEL, + REQUEST_METHOD, SERVICE_NAME_LABEL, TAGS_LABEL, TBT_LABEL, @@ -72,14 +74,17 @@ export const FieldLabels: Record = { 'performance.metric': METRIC_LABEL, 'Business.KPI': KPI_LABEL, + 'http.request.method': REQUEST_METHOD, }; export const DataViewLabels: Record = { dist: PERF_DIST_LABEL, - kpi: KIP_OVER_TIME_LABEL, + kpi: KPI_OVER_TIME_LABEL, cwv: CORE_WEB_VITALS_LABEL, + mdd: DEVICE_DISTRIBUTION_LABEL, }; export const USE_BREAK_DOWN_COLUMN = 'USE_BREAK_DOWN_COLUMN'; export const FILTER_RECORDS = 'FILTER_RECORDS'; +export const TERMS_COLUMN = 'TERMS_COLUMN'; export const OPERATION_COLUMN = 'operation'; 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 5ecc5b758de84..01dd2a49b9be0 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 @@ -86,6 +86,8 @@ export const ERROR_PAGE_URL = 'error.page.url'; // METRICS export const METRIC_SYSTEM_FREE_MEMORY = 'system.memory.actual.free'; +export const METRIC_SYSTEM_MEMORY_USAGE = 'system.memory.usage'; +export const METRIC_SYSTEM_CPU_USAGE = 'system.cpu.usage'; export const METRIC_SYSTEM_TOTAL_MEMORY = 'system.memory.total'; export const METRIC_SYSTEM_CPU_PERCENT = 'system.cpu.total.norm.pct'; export const METRIC_PROCESS_CPU_PERCENT = 'system.process.cpu.total.norm.pct'; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts index b5816daa419df..73739b7db12ef 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/labels.ts @@ -165,7 +165,7 @@ export const KPI_LABEL = i18n.translate('xpack.observability.expView.fieldLabels export const PERF_DIST_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.performanceDistribution', { - defaultMessage: 'Performance Distribution', + defaultMessage: 'Performance distribution', } ); @@ -176,6 +176,20 @@ export const CORE_WEB_VITALS_LABEL = i18n.translate( } ); +export const DEVICE_DISTRIBUTION_LABEL = i18n.translate( + 'xpack.observability.expView.fieldLabels.deviceDistribution', + { + defaultMessage: 'Device distribution', + } +); + +export const MOBILE_RESPONSE_LABEL = i18n.translate( + 'xpack.observability.expView.fieldLabels.mobileResponse', + { + defaultMessage: 'Mobile response', + } +); + export const MEMORY_USAGE_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.memoryUsage', { @@ -183,7 +197,7 @@ export const MEMORY_USAGE_LABEL = i18n.translate( } ); -export const KIP_OVER_TIME_LABEL = i18n.translate( +export const KPI_OVER_TIME_LABEL = i18n.translate( 'xpack.observability.expView.fieldLabels.kpiOverTime', { defaultMessage: 'KPI over time', @@ -211,3 +225,82 @@ export const UP_LABEL = i18n.translate('xpack.observability.expView.fieldLabels. export const DOWN_LABEL = i18n.translate('xpack.observability.expView.fieldLabels.downPings', { defaultMessage: 'Down Pings', }); + +export const CARRIER_NAME = i18n.translate('xpack.observability.expView.fieldLabels.carrierName', { + defaultMessage: 'Carrier Name', +}); + +export const REQUEST_METHOD = i18n.translate( + 'xpack.observability.expView.fieldLabels.requestMethod', + { + defaultMessage: 'Request Method', + } +); + +export const CONNECTION_TYPE = i18n.translate( + 'xpack.observability.expView.fieldLabels.connectionType', + { + defaultMessage: 'Connection Type', + } +); +export const HOST_OS = i18n.translate('xpack.observability.expView.fieldLabels.hostOS', { + defaultMessage: 'Host OS', +}); + +export const SERVICE_VERSION = i18n.translate( + 'xpack.observability.expView.fieldLabels.serviceVersion', + { + defaultMessage: 'Service Version', + } +); + +export const OS_PLATFORM = i18n.translate('xpack.observability.expView.fieldLabels.osPlatform', { + defaultMessage: 'OS Platform', +}); + +export const DEVICE_MODEL = i18n.translate('xpack.observability.expView.fieldLabels.deviceModel', { + defaultMessage: 'Device Model', +}); + +export const CARRIER_LOCATION = i18n.translate( + 'xpack.observability.expView.fieldLabels.carrierLocation', + { + defaultMessage: 'Carrier Location', + } +); + +export const RESPONSE_LATENCY = i18n.translate( + 'xpack.observability.expView.fieldLabels.responseLatency', + { + defaultMessage: 'Response latency', + } +); + +export const MOBILE_APP = i18n.translate('xpack.observability.expView.fieldLabels.mobileApp', { + defaultMessage: 'Mobile App', +}); + +export const MEMORY_USAGE = i18n.translate( + 'xpack.observability.expView.fieldLabels.mobile.memoryUsage', + { + defaultMessage: 'Memory Usage', + } +); + +export const CPU_USAGE = i18n.translate('xpack.observability.expView.fieldLabels.cpuUsage', { + defaultMessage: 'CPU Usage', +}); + +export const TRANSACTIONS_PER_MINUTE = i18n.translate( + 'xpack.observability.expView.fieldLabels.transactionPerMinute', + { + defaultMessage: 'Transactions per minute', + } +); + +export const NUMBER_OF_DEVICES = i18n.translate( + 'xpack.observability.expView.fieldLabels.numberOfDevices', + { + defaultMessage: 'Number of Devices', + } +); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts index 13a7900ef5764..07342d976cbea 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/default_configs.ts @@ -12,6 +12,9 @@ import { getSyntheticsKPIConfig } from './synthetics/kpi_over_time_config'; import { getKPITrendsLensConfig } from './rum/kpi_over_time_config'; import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { getCoreWebVitalsConfig } from './rum/core_web_vitals_config'; +import { getMobileKPIConfig } from './mobile/kpi_over_time_config'; +import { getMobileKPIDistributionConfig } from './mobile/distribution_config'; +import { getMobileDeviceDistributionConfig } from './mobile/device_distribution_config'; interface Props { reportType: keyof typeof ReportViewTypes; @@ -34,7 +37,14 @@ export const getDefaultConfigs = ({ reportType, dataType, indexPattern }: Props) return getSyntheticsDistributionConfig({ indexPattern }); } return getSyntheticsKPIConfig({ indexPattern }); - + case 'mobile': + if (reportType === 'dist') { + return getMobileKPIDistributionConfig({ indexPattern }); + } + if (reportType === 'mdd') { + return getMobileDeviceDistributionConfig({ indexPattern }); + } + return getMobileKPIConfig({ indexPattern }); default: return getKPITrendsLensConfig({ indexPattern }); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index bc535e29ab435..22ad18c663b32 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -25,13 +25,14 @@ import { FieldBasedIndexPatternColumn, SumIndexPatternColumn, TermsIndexPatternColumn, + CardinalityIndexPatternColumn, } from '../../../../../../lens/public'; import { buildPhraseFilter, buildPhrasesFilter, IndexPattern, } from '../../../../../../../../src/plugins/data/common'; -import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN } from './constants'; +import { FieldLabels, FILTER_RECORDS, USE_BREAK_DOWN_COLUMN, TERMS_COLUMN } from './constants'; import { ColumnFilter, DataSeries, UrlFilter, URLReportDefinition } from '../types'; function getLayerReferenceName(layerId: string) { @@ -55,6 +56,7 @@ export const parseCustomFieldName = ( let fieldName = sourceField; let columnType; let columnFilters; + let timeScale; let columnLabel; const rdf = reportViewConfig.reportDefinitions ?? []; @@ -70,17 +72,19 @@ export const parseCustomFieldName = ( ); columnType = currField?.columnType; columnFilters = currField?.columnFilters; + timeScale = currField?.timeScale; columnLabel = currField?.label; } } else if (customField.options?.[0].field || customField.options?.[0].id) { fieldName = customField.options?.[0].field || customField.options?.[0].id; columnType = customField.options?.[0].columnType; columnFilters = customField.options?.[0].columnFilters; + timeScale = customField.options?.[0].timeScale; columnLabel = customField.options?.[0].label; } } - return { fieldName, columnType, columnFilters, columnLabel }; + return { fieldName, columnType, columnFilters, timeScale, columnLabel }; }; export class LensAttributes { @@ -167,10 +171,10 @@ export class LensAttributes { this.visualization.layers[0].splitAccessor = undefined; } - getNumberRangeColumn(sourceField: string): RangeIndexPatternColumn { + getNumberRangeColumn(sourceField: string, label?: string): RangeIndexPatternColumn { return { sourceField, - label: this.reportViewConfig.labels[sourceField], + label: this.reportViewConfig.labels[sourceField] ?? label, dataType: 'number', operationType: 'range', isBucketed: true, @@ -183,6 +187,10 @@ export class LensAttributes { }; } + getCardinalityColumn(sourceField: string, label?: string) { + return this.getNumberOperationColumn(sourceField, 'unique_count', label); + } + getNumberColumn( sourceField: string, columnType?: string, @@ -190,21 +198,30 @@ export class LensAttributes { label?: string ) { if (columnType === 'operation' || operationType) { - if (operationType === 'median' || operationType === 'average' || operationType === 'sum') { + if ( + operationType === 'median' || + operationType === 'average' || + operationType === 'sum' || + operationType === 'unique_count' + ) { return this.getNumberOperationColumn(sourceField, operationType, label); } if (operationType?.includes('th')) { return this.getPercentileNumberColumn(sourceField, operationType); } } - return this.getNumberRangeColumn(sourceField); + return this.getNumberRangeColumn(sourceField, label); } getNumberOperationColumn( sourceField: string, - operationType: 'average' | 'median' | 'sum', + operationType: 'average' | 'median' | 'sum' | 'unique_count', label?: string - ): AvgIndexPatternColumn | MedianIndexPatternColumn | SumIndexPatternColumn { + ): + | AvgIndexPatternColumn + | MedianIndexPatternColumn + | SumIndexPatternColumn + | CardinalityIndexPatternColumn { return { ...buildNumberColumn(sourceField), label: @@ -247,6 +264,25 @@ export class LensAttributes { }; } + getTermsColumn(sourceField: string, label?: string): TermsIndexPatternColumn { + return { + operationType: 'terms', + sourceField, + label: label || 'Top values of ' + sourceField, + dataType: 'string', + isBucketed: true, + scale: 'ordinal', + params: { + size: 10, + orderBy: { + type: 'alphabetical', + fallback: false, + }, + orderDirection: 'desc', + }, + }; + } + getXAxis() { const { xAxisColumn } = this.reportViewConfig; @@ -263,15 +299,25 @@ export class LensAttributes { label?: string, colIndex?: number ) { - const { fieldMeta, columnType, fieldName, columnFilters, columnLabel } = this.getFieldMeta( - sourceField - ); + const { + fieldMeta, + columnType, + fieldName, + columnFilters, + timeScale, + columnLabel, + } = this.getFieldMeta(sourceField); const { type: fieldType } = fieldMeta ?? {}; + if (columnType === TERMS_COLUMN) { + return this.getTermsColumn(fieldName, columnLabel || label); + } + if (fieldName === 'Records' || columnType === FILTER_RECORDS) { return this.getRecordsColumn( columnLabel || label, - colIndex !== undefined ? columnFilters?.[colIndex] : undefined + colIndex !== undefined ? columnFilters?.[colIndex] : undefined, + timeScale ); } @@ -281,6 +327,9 @@ export class LensAttributes { if (fieldType === 'number') { return this.getNumberColumn(fieldName, columnType, operationType, columnLabel || label); } + if (operationType === 'unique_count') { + return this.getCardinalityColumn(fieldName, columnLabel || label); + } // FIXME review my approach again return this.getDateHistogramColumn(fieldName); @@ -291,13 +340,17 @@ export class LensAttributes { } getFieldMeta(sourceField: string) { - const { fieldName, columnType, columnFilters, columnLabel } = this.getCustomFieldName( - sourceField - ); + const { + fieldName, + columnType, + columnFilters, + timeScale, + columnLabel, + } = this.getCustomFieldName(sourceField); const fieldMeta = this.indexPattern.getFieldByName(fieldName); - return { fieldMeta, fieldName, columnType, columnFilters, columnLabel }; + return { fieldMeta, fieldName, columnType, columnFilters, timeScale, columnLabel }; } getMainYAxis() { @@ -330,7 +383,11 @@ export class LensAttributes { return lensColumns; } - getRecordsColumn(label?: string, columnFilter?: ColumnFilter): CountIndexPatternColumn { + getRecordsColumn( + label?: string, + columnFilter?: ColumnFilter, + timeScale?: string + ): CountIndexPatternColumn { return { dataType: 'number', isBucketed: false, @@ -339,6 +396,7 @@ export class LensAttributes { scale: 'ratio', sourceField: 'Records', filter: columnFilter, + timeScale, } as CountIndexPatternColumn; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts new file mode 100644 index 0000000000000..6f9806660e489 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/device_distribution_config.ts @@ -0,0 +1,49 @@ +/* + * 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 { ConfigProps, DataSeries } from '../../types'; +import { FieldLabels, USE_BREAK_DOWN_COLUMN } from '../constants'; +import { buildPhraseFilter } from '../utils'; +import { SERVICE_NAME } from '../constants/elasticsearch_fieldnames'; +import { MOBILE_APP, NUMBER_OF_DEVICES } from '../constants/labels'; +import { MobileFields } from './mobile_fields'; + +export function getMobileDeviceDistributionConfig({ indexPattern }: ConfigProps): DataSeries { + return { + reportType: 'mobile-device-distribution', + defaultSeriesType: 'bar', + seriesTypes: ['bar', 'bar_horizontal'], + xAxisColumn: { + sourceField: USE_BREAK_DOWN_COLUMN, + }, + yAxisColumns: [ + { + sourceField: 'labels.device_id', + operationType: 'unique_count', + label: NUMBER_OF_DEVICES, + }, + ], + hasOperationType: false, + defaultFilters: Object.keys(MobileFields), + breakdowns: Object.keys(MobileFields), + filters: [ + ...buildPhraseFilter('agent.name', 'iOS/swift', indexPattern), + ...buildPhraseFilter('processor.event', 'transaction', indexPattern), + ], + labels: { + ...FieldLabels, + ...MobileFields, + [SERVICE_NAME]: MOBILE_APP, + }, + reportDefinitions: [ + { + field: SERVICE_NAME, + required: true, + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts new file mode 100644 index 0000000000000..62dd38e55a32a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/distribution_config.ts @@ -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 { ConfigProps, DataSeries } from '../../types'; +import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD } from '../constants'; +import { buildPhrasesFilter } from '../utils'; +import { + METRIC_SYSTEM_CPU_USAGE, + METRIC_SYSTEM_MEMORY_USAGE, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_DURATION, +} from '../constants/elasticsearch_fieldnames'; + +import { CPU_USAGE, MEMORY_USAGE, MOBILE_APP, RESPONSE_LATENCY } from '../constants/labels'; +import { MobileFields } from './mobile_fields'; + +export function getMobileKPIDistributionConfig({ indexPattern }: ConfigProps): DataSeries { + return { + reportType: 'data-distribution', + defaultSeriesType: 'bar', + seriesTypes: ['line', 'bar'], + xAxisColumn: { + sourceField: 'performance.metric', + }, + yAxisColumns: [ + { + sourceField: RECORDS_FIELD, + }, + ], + hasOperationType: false, + defaultFilters: Object.keys(MobileFields), + breakdowns: Object.keys(MobileFields), + filters: [ + ...buildPhrasesFilter('agent.name', ['iOS/swift', 'open-telemetry/swift'], indexPattern), + ], + labels: { + ...FieldLabels, + ...MobileFields, + [SERVICE_NAME]: MOBILE_APP, + }, + reportDefinitions: [ + { + field: SERVICE_NAME, + required: true, + }, + { + field: SERVICE_ENVIRONMENT, + required: true, + }, + { + field: 'performance.metric', + custom: true, + options: [ + { + label: RESPONSE_LATENCY, + field: TRANSACTION_DURATION, + id: TRANSACTION_DURATION, + columnType: OPERATION_COLUMN, + }, + { + label: MEMORY_USAGE, + field: METRIC_SYSTEM_MEMORY_USAGE, + id: METRIC_SYSTEM_MEMORY_USAGE, + columnType: OPERATION_COLUMN, + }, + { + label: CPU_USAGE, + field: METRIC_SYSTEM_CPU_USAGE, + id: METRIC_SYSTEM_CPU_USAGE, + columnType: OPERATION_COLUMN, + }, + ], + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts new file mode 100644 index 0000000000000..2ed4d95760db7 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/kpi_over_time_config.ts @@ -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 { ConfigProps, DataSeries } from '../../types'; +import { FieldLabels, OPERATION_COLUMN, RECORDS_FIELD } from '../constants'; +import { buildPhrasesFilter } from '../utils'; +import { + METRIC_SYSTEM_CPU_USAGE, + METRIC_SYSTEM_MEMORY_USAGE, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_DURATION, +} from '../constants/elasticsearch_fieldnames'; +import { + CPU_USAGE, + MEMORY_USAGE, + MOBILE_APP, + RESPONSE_LATENCY, + TRANSACTIONS_PER_MINUTE, +} from '../constants/labels'; +import { MobileFields } from './mobile_fields'; + +export function getMobileKPIConfig({ indexPattern }: ConfigProps): DataSeries { + return { + reportType: 'kpi-over-time', + defaultSeriesType: 'line', + seriesTypes: ['line', 'bar', 'area'], + xAxisColumn: { + sourceField: '@timestamp', + }, + yAxisColumns: [ + { + sourceField: 'business.kpi', + operationType: 'median', + }, + ], + hasOperationType: true, + defaultFilters: Object.keys(MobileFields), + breakdowns: Object.keys(MobileFields), + filters: [ + ...buildPhrasesFilter('agent.name', ['iOS/swift', 'open-telemetry/swift'], indexPattern), + ], + labels: { + ...FieldLabels, + ...MobileFields, + [TRANSACTION_DURATION]: RESPONSE_LATENCY, + [SERVICE_NAME]: MOBILE_APP, + [METRIC_SYSTEM_MEMORY_USAGE]: MEMORY_USAGE, + [METRIC_SYSTEM_CPU_USAGE]: CPU_USAGE, + }, + reportDefinitions: [ + { + field: SERVICE_NAME, + required: true, + }, + { + field: SERVICE_ENVIRONMENT, + required: true, + }, + { + field: 'business.kpi', + custom: true, + options: [ + { + label: RESPONSE_LATENCY, + field: TRANSACTION_DURATION, + id: TRANSACTION_DURATION, + columnType: OPERATION_COLUMN, + }, + { + label: MEMORY_USAGE, + field: METRIC_SYSTEM_MEMORY_USAGE, + id: METRIC_SYSTEM_MEMORY_USAGE, + columnType: OPERATION_COLUMN, + }, + { + label: CPU_USAGE, + field: METRIC_SYSTEM_CPU_USAGE, + id: METRIC_SYSTEM_CPU_USAGE, + columnType: OPERATION_COLUMN, + }, + { + field: RECORDS_FIELD, + id: RECORDS_FIELD, + label: TRANSACTIONS_PER_MINUTE, + columnFilters: [ + { + language: 'kuery', + query: `processor.event: transaction`, + }, + ], + timeScale: 'm', + }, + ], + }, + ], + }; +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts new file mode 100644 index 0000000000000..4ece4ff056a59 --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/mobile/mobile_fields.ts @@ -0,0 +1,26 @@ +/* + * 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 { + CARRIER_LOCATION, + CARRIER_NAME, + CONNECTION_TYPE, + DEVICE_MODEL, + HOST_OS, + OS_PLATFORM, + SERVICE_VERSION, +} from '../constants/labels'; + +export const MobileFields: Record = { + 'host.os.platform': OS_PLATFORM, + 'host.os.full': HOST_OS, + 'service.version': SERVICE_VERSION, + 'network.carrier.icc': CARRIER_LOCATION, + 'network.carrier.name': CARRIER_NAME, + 'network.connection_type': CONNECTION_TYPE, + 'labels.device_model': DEVICE_MODEL, +}; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx index 487ecdb2bafcc..779049601bd6d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.test.tsx @@ -63,7 +63,7 @@ describe('ExploratoryView', () => { render(, { initSeries }); expect(await screen.findByText(/open in lens/i)).toBeInTheDocument(); - expect(await screen.findByText('Performance Distribution')).toBeInTheDocument(); + expect((await screen.findAllByText('Performance distribution'))[0]).toBeInTheDocument(); expect(await screen.findByText(/Lens Embeddable Component/i)).toBeInTheDocument(); await waitFor(() => { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx index 4f13cf6a1f9ca..4259bb778e511 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_app_index_pattern.tsx @@ -12,7 +12,6 @@ import { useKibana } from '../../../../../../../../src/plugins/kibana_react/publ import { ObservabilityPublicPluginsStart } from '../../../../plugin'; import { ObservabilityIndexPatterns } from '../utils/observability_index_patterns'; import { getDataHandler } from '../../../../data_handler'; -import { HasDataResponse } from '../../../../typings/fetch_overview_data'; export interface IIndexPatternContext { loading: boolean; @@ -41,17 +40,13 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { synthetics: null, ux: null, apm: null, + mobile: null, } as HasAppDataState); const { services: { data }, } = useKibana(); - const checkIfAppHasData = async (dataType: AppDataType) => { - const handler = getDataHandler(dataType); - return handler?.hasData(); - }; - const loadIndexPattern: IIndexPatternContext['loadIndexPattern'] = useCallback( async ({ dataType }) => { setSelectedApp(dataType); @@ -59,15 +54,27 @@ export function IndexPatternContextProvider({ children }: ProviderProps) { if (hasAppData[dataType] === null) { setLoading(true); try { - const hasDataResponse = (await checkIfAppHasData(dataType)) as HasDataResponse; - - const hasDataT = hasDataResponse.hasData; - + let hasDataT = false; + let indices: string | undefined = ''; + switch (dataType) { + case 'ux': + case 'synthetics': + const resultUx = await getDataHandler(dataType)?.hasData(); + hasDataT = Boolean(resultUx?.hasData); + indices = resultUx?.indices; + break; + case 'apm': + case 'mobile': + const resultApm = await getDataHandler('apm')?.hasData(); + hasDataT = Boolean(resultApm?.hasData); + indices = resultApm?.indices['apm_oss.transactionIndices']; + break; + } setHasAppData((prevState) => ({ ...prevState, [dataType]: hasDataT })); - if (hasDataT || hasAppData?.[dataType]) { + if (hasDataT && indices) { const obsvIndexP = new ObservabilityIndexPatterns(data); - const indPattern = await obsvIndexP.getIndexPattern(dataType, hasDataResponse.indices); + const indPattern = await obsvIndexP.getIndexPattern(dataType, indices); setIndexPatterns((prevState) => ({ ...prevState, [dataType]: indPattern })); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index 3fe88de518f75..985afdf888868 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -15,6 +15,7 @@ import { useSeriesStorage } from '../../hooks/use_series_storage'; export const dataTypes: Array<{ id: AppDataType; label: string }> = [ { id: 'synthetics', label: 'Synthetic Monitoring' }, { id: 'ux', label: 'User Experience (RUM)' }, + { id: 'mobile', label: 'Mobile Experience' }, // { id: 'infra_logs', label: 'Logs' }, // { id: 'infra_metrics', label: 'Metrics' }, // { id: 'apm', label: 'APM' }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx index 9687f1bea4ec9..4571ecfe252e9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_filters.tsx @@ -20,8 +20,10 @@ export function ReportFilters({ ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index e24d246d60e58..9aef16931d7ec 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -18,16 +18,27 @@ import { ReportBreakdowns } from './columns/report_breakdowns'; import { NEW_SERIES_KEY, useSeriesStorage } from '../hooks/use_series_storage'; import { useAppIndexPatternContext } from '../hooks/use_app_index_pattern'; import { getDefaultConfigs } from '../configurations/default_configs'; +import { + CORE_WEB_VITALS_LABEL, + DEVICE_DISTRIBUTION_LABEL, + KPI_OVER_TIME_LABEL, + PERF_DIST_LABEL, +} from '../configurations/constants/labels'; export const ReportTypes: Record> = { synthetics: [ - { id: 'kpi', label: 'KPI over time' }, - { id: 'dist', label: 'Performance distribution' }, + { id: 'kpi', label: KPI_OVER_TIME_LABEL }, + { id: 'dist', label: PERF_DIST_LABEL }, ], ux: [ - { id: 'kpi', label: 'KPI over time' }, - { id: 'dist', label: 'Performance distribution' }, - { id: 'cwv', label: 'Core Web Vitals' }, + { id: 'kpi', label: KPI_OVER_TIME_LABEL }, + { id: 'dist', label: PERF_DIST_LABEL }, + { id: 'cwv', label: CORE_WEB_VITALS_LABEL }, + ], + mobile: [ + { id: 'kpi', label: KPI_OVER_TIME_LABEL }, + { id: 'dist', label: PERF_DIST_LABEL }, + { id: 'mdd', label: DEVICE_DISTRIBUTION_LABEL }, ], apm: [], infra_logs: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx index cfac838ba5aeb..2fadb0e56433e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.test.tsx @@ -22,6 +22,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={jest.fn()} + filters={[]} />, { initSeries } ); @@ -38,6 +39,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={goBack} + filters={[]} />, { initSeries } ); @@ -64,6 +66,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={goBack} + filters={[]} />, { initSeries } ); @@ -90,6 +93,7 @@ describe('FilterExpanded', function () { label={'Browser Family'} field={USER_AGENT_NAME} goBack={jest.fn()} + filters={[]} />, { initSeries } ); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx index 0d5b73f14671d..17d62b68c57e4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/filter_expanded.tsx @@ -6,17 +6,21 @@ */ import React, { useState, Fragment } from 'react'; -import { EuiFieldSearch, EuiSpacer, EuiButtonEmpty, EuiFilterGroup } from '@elastic/eui'; +import { EuiFieldSearch, EuiSpacer, EuiButtonEmpty, EuiFilterGroup, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { rgba } from 'polished'; import { i18n } from '@kbn/i18n'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/api/types'; import { map } from 'lodash'; import { useAppIndexPatternContext } from '../../hooks/use_app_index_pattern'; import { useSeriesStorage } from '../../hooks/use_series_storage'; -import { UrlFilter } from '../../types'; +import { DataSeries, UrlFilter } from '../../types'; import { FilterValueButton } from './filter_value_btn'; import { useValuesList } from '../../../../../hooks/use_values_list'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { ESFilter } from '../../../../../../../../../typings/elasticsearch'; +import { PersistableFilter } from '../../../../../../../lens/common'; +import { ExistsFilter } from '../../../../../../../../../src/plugins/data/common/es_query/filters'; interface Props { seriesId: string; @@ -25,9 +29,18 @@ interface Props { isNegated?: boolean; goBack: () => void; nestedField?: string; + filters: DataSeries['filters']; } -export function FilterExpanded({ seriesId, field, label, goBack, nestedField, isNegated }: Props) { +export function FilterExpanded({ + seriesId, + field, + label, + goBack, + nestedField, + isNegated, + filters: defaultFilters, +}: Props) { const { indexPattern } = useAppIndexPatternContext(); const [value, setValue] = useState(''); @@ -38,12 +51,25 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField, is const series = getSeries(seriesId); + const queryFilters: ESFilter[] = []; + + defaultFilters?.forEach((qFilter: PersistableFilter | ExistsFilter) => { + if (qFilter.query) { + queryFilters.push(qFilter.query); + } + const asExistFilter = qFilter as ExistsFilter; + if (asExistFilter?.exists) { + queryFilters.push(asExistFilter.exists as QueryDslQueryContainer); + } + }); + const { values, loading } = useValuesList({ query: value, indexPatternTitle: indexPattern?.title, sourceField: field, time: series.time, keepHistory: true, + filters: queryFilters, }); const filters = series?.filters ?? []; @@ -73,6 +99,13 @@ export function FilterExpanded({ seriesId, field, label, goBack, nestedField, is /> + {displayValues.length === 0 && !loading && ( + + {i18n.translate('xpack.observability.filters.expanded.noFilter', { + defaultMessage: 'No filters found.', + })} + + )} {displayValues.map((opt) => ( diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx index 9e5770c2de8f9..b7e20b341b572 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/series_filter.tsx @@ -24,8 +24,10 @@ import { useSeriesStorage } from '../../hooks/use_series_storage'; interface Props { seriesId: string; defaultFilters: DataSeries['defaultFilters']; + filters: DataSeries['filters']; series: DataSeries; isNew?: boolean; + labels?: Record; } export interface Field { @@ -35,21 +37,28 @@ export interface Field { isNegated?: boolean; } -export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: Props) { +export function SeriesFilter({ + series, + isNew, + seriesId, + defaultFilters = [], + filters, + labels, +}: Props) { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const [selectedField, setSelectedField] = useState(); const options: Field[] = defaultFilters.map((field) => { if (typeof field === 'string') { - return { label: FieldLabels[field], field }; + return { label: labels?.[field] ?? FieldLabels[field], field }; } return { field: field.field, nested: field.nested, isNegated: field.isNegated, - label: FieldLabels[field.field], + label: labels?.[field.field] ?? FieldLabels[field.field], }; }); @@ -102,6 +111,7 @@ export function SeriesFilter({ series, isNew, seriesId, defaultFilters = [] }: P goBack={() => { setSelectedField(undefined); }} + filters={filters} /> ) : null; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx index 79218aa111f16..17d4356dcf65b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/series_editor.tsx @@ -49,7 +49,12 @@ export function SeriesEditor() { field: 'defaultFilters', width: '15%', render: (defaultFilters: string[], { id, seriesConfig }: EditItem) => ( - + ), }, { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index 98605dfdb4ca3..73b4d7794dd51 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -23,6 +23,7 @@ export const ReportViewTypes = { dist: 'data-distribution', kpi: 'kpi-over-time', cwv: 'core-web-vitals', + mdd: 'mobile-device-distribution', } as const; type ValueOf = T[keyof T]; @@ -45,8 +46,9 @@ export interface ReportDefinition { field?: string; label: string; description?: string; - columnType?: 'range' | 'operation' | 'FILTER_RECORDS'; + columnType?: 'range' | 'operation' | 'FILTER_RECORDS' | 'TERMS_COLUMN'; columnFilters?: ColumnFilter[]; + timeScale?: string; }>; } @@ -94,15 +96,15 @@ export interface ConfigProps { indexPattern: IIndexPattern; } -export type AppDataType = 'synthetics' | 'ux' | 'infra_logs' | 'infra_metrics' | 'apm'; +export type AppDataType = 'synthetics' | 'ux' | 'infra_logs' | 'infra_metrics' | 'apm' | 'mobile'; -type FormatType = 'duration' | 'number'; +type FormatType = 'duration' | 'number' | 'bytes' | 'percent'; type InputFormat = 'microseconds' | 'milliseconds' | 'seconds'; type OutputFormat = 'asSeconds' | 'asMilliseconds' | 'humanize' | 'humanizePrecise'; export interface FieldFormatParams { - inputFormat: InputFormat; - outputFormat: OutputFormat; + inputFormat?: InputFormat; + outputFormat?: OutputFormat; outputPrecision?: number; showSuffix?: boolean; useShortSuffix?: boolean; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts index 858eb52555da6..634408dd614da 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts @@ -23,6 +23,7 @@ const appFieldFormats: Record = { ux: rumFieldFormats, apm: apmFieldFormats, synthetics: syntheticsFieldFormats, + mobile: apmFieldFormats, }; function getFieldFormatsForApp(app: AppDataType) { @@ -35,6 +36,7 @@ export const indexPatternList: Record = { ux: 'rum_static_index_pattern_id', infra_logs: 'infra_logs_static_index_pattern_id', infra_metrics: 'infra_metrics_static_index_pattern_id', + mobile: 'mobile_static_index_pattern_id', }; const appToPatternMap: Record = { @@ -43,6 +45,7 @@ const appToPatternMap: Record = { ux: '(rum-data-view)*', infra_logs: '', infra_metrics: '', + mobile: '(mobile-data-view)*', }; const getAppIndicesWithPattern = (app: AppDataType, indices: string) => { @@ -124,6 +127,7 @@ export class ObservabilityIndexPatterns { if (!this.data) { throw new Error('data is not defined'); } + try { const indexPatternId = getAppIndexPatternId(app, indices); const indexPatternTitle = getAppIndicesWithPattern(app, indices); diff --git a/x-pack/plugins/observability/public/context/has_data_context.test.tsx b/x-pack/plugins/observability/public/context/has_data_context.test.tsx index b5a0806306461..f2f550e35ac6b 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.test.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -// import { act, getByText } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; import { CoreStart } from 'kibana/public'; import React from 'react'; @@ -19,10 +18,17 @@ import * as pluginContext from '../hooks/use_plugin_context'; import { PluginContextValue } from './plugin_context'; import { Router } from 'react-router-dom'; import { createMemoryHistory } from 'history'; +import { ApmIndicesConfig } from '../../common/typings'; +import { act } from '@testing-library/react'; const relativeStart = '2020-10-08T06:00:00.000Z'; const relativeEnd = '2020-10-08T07:00:00.000Z'; +const sampleAPMIndices = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'apm_oss.transactionIndices': 'apm-*', +} as ApmIndicesConfig; + function wrapper({ children }: { children: React.ReactElement }) { const history = createMemoryHistory(); return ( @@ -76,17 +82,18 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return undefined', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toMatchObject({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'success' }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, @@ -105,16 +112,16 @@ describe('HasDataContextProvider', () => { describe('all apps return false', () => { beforeAll(() => { registerApps([ - { appName: 'apm', hasData: async () => false }, + { appName: 'apm', hasData: async () => ({ hasData: false }) }, { appName: 'infra_logs', hasData: async () => false }, { appName: 'infra_metrics', hasData: async () => false }, { appName: 'synthetics', - hasData: async () => ({ hasData: false, indices: 'heartbeat-*, synthetics-*' }), + hasData: async () => ({ hasData: false }), }, { appName: 'ux', - hasData: async () => ({ hasData: false, serviceName: undefined, indices: 'apm-*' }), + hasData: async () => ({ hasData: false }), }, ]); }); @@ -124,29 +131,28 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return false', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: false, status: 'success' }, synthetics: { - hasData: { - hasData: false, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: false, status: 'success', }, infra_logs: { hasData: false, status: 'success' }, infra_metrics: { hasData: false, status: 'success' }, ux: { - hasData: { hasData: false, serviceName: undefined, indices: 'apm-*' }, + hasData: false, status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -162,7 +168,7 @@ describe('HasDataContextProvider', () => { describe('at least one app returns true', () => { beforeAll(() => { registerApps([ - { appName: 'apm', hasData: async () => true }, + { appName: 'apm', hasData: async () => ({ hasData: true }) }, { appName: 'infra_logs', hasData: async () => false }, { appName: 'infra_metrics', hasData: async () => false }, { @@ -181,29 +187,30 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true apm returns true and all other apps return false', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: true, status: 'success' }, synthetics: { - hasData: { - hasData: false, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: false, + indices: 'heartbeat-*, synthetics-*', status: 'success', }, infra_logs: { hasData: false, status: 'success' }, infra_metrics: { hasData: false, status: 'success' }, ux: { - hasData: { hasData: false, serviceName: undefined, indices: 'apm-*' }, + hasData: false, + indices: 'apm-*', status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -219,7 +226,7 @@ describe('HasDataContextProvider', () => { describe('all apps return true', () => { beforeAll(() => { registerApps([ - { appName: 'apm', hasData: async () => true }, + { appName: 'apm', hasData: async () => ({ hasData: true }) }, { appName: 'infra_logs', hasData: async () => true }, { appName: 'infra_metrics', hasData: async () => true }, { @@ -238,32 +245,34 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true and all apps return true', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: true, status: 'success', }, synthetics: { - hasData: { - hasData: true, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: true, + indices: 'heartbeat-*, synthetics-*', status: 'success', }, infra_logs: { hasData: true, status: 'success' }, infra_metrics: { hasData: true, status: 'success' }, ux: { - hasData: { hasData: true, serviceName: 'ux', indices: 'apm-*' }, + hasData: true, + serviceName: 'ux', + indices: 'apm-*', status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -279,7 +288,9 @@ describe('HasDataContextProvider', () => { describe('only apm is registered', () => { describe('when apm returns true', () => { beforeAll(() => { - registerApps([{ appName: 'apm', hasData: async () => true }]); + registerApps([ + { appName: 'apm', hasData: async () => ({ hasData: true, indices: sampleAPMIndices }) }, + ]); }); afterAll(unregisterAll); @@ -289,18 +300,20 @@ describe('HasDataContextProvider', () => { wrapper, }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { - apm: { hasData: true, status: 'success' }, + hasDataMap: { + apm: { hasData: true, indices: sampleAPMIndices, status: 'success' }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, @@ -317,7 +330,12 @@ describe('HasDataContextProvider', () => { describe('when apm returns false', () => { beforeAll(() => { - registerApps([{ appName: 'apm', hasData: async () => false }]); + registerApps([ + { + appName: 'apm', + hasData: async () => ({ indices: sampleAPMIndices, hasData: false }), + }, + ]); }); afterAll(unregisterAll); @@ -327,18 +345,24 @@ describe('HasDataContextProvider', () => { wrapper, }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { - apm: { hasData: false, status: 'success' }, + hasDataMap: { + apm: { + hasData: false, + indices: sampleAPMIndices, + status: 'success', + }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, infra_metrics: { hasData: undefined, status: 'success' }, @@ -381,29 +405,31 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns true, apm is undefined and all other apps return true', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'failure' }, synthetics: { - hasData: { - hasData: true, - indices: 'heartbeat-*, synthetics-*', - }, + hasData: true, + indices: 'heartbeat-*, synthetics-*', status: 'success', }, infra_logs: { hasData: true, status: 'success' }, infra_metrics: { hasData: true, status: 'success' }, ux: { - hasData: { hasData: true, serviceName: 'ux', indices: 'apm-*' }, + hasData: true, + serviceName: 'ux', + indices: 'apm-*', status: 'success', }, alert: { hasData: [], status: 'success' }, @@ -457,17 +483,19 @@ describe('HasDataContextProvider', () => { it('hasAnyData returns false and all apps return undefined', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'failure' }, synthetics: { hasData: undefined, status: 'failure' }, infra_logs: { hasData: undefined, status: 'failure' }, @@ -505,17 +533,19 @@ describe('HasDataContextProvider', () => { it('returns all alerts available', async () => { const { result, waitForNextUpdate } = renderHook(() => useHasData(), { wrapper }); expect(result.current).toEqual({ - hasData: {}, + hasDataMap: {}, hasAnyData: false, isAllRequestsComplete: false, forceUpdate: expect.any(String), onRefreshTimeRange: expect.any(Function), }); - await waitForNextUpdate(); + await act(async () => { + await waitForNextUpdate(); + }); expect(result.current).toEqual({ - hasData: { + hasDataMap: { apm: { hasData: undefined, status: 'success' }, synthetics: { hasData: undefined, status: 'success' }, infra_logs: { hasData: undefined, status: 'success' }, diff --git a/x-pack/plugins/observability/public/context/has_data_context.tsx b/x-pack/plugins/observability/public/context/has_data_context.tsx index 97aa72f07b09c..047a596ea349e 100644 --- a/x-pack/plugins/observability/public/context/has_data_context.tsx +++ b/x-pack/plugins/observability/public/context/has_data_context.tsx @@ -14,17 +14,23 @@ import { FETCH_STATUS } from '../hooks/use_fetcher'; import { usePluginContext } from '../hooks/use_plugin_context'; import { useTimeRange } from '../hooks/use_time_range'; import { getObservabilityAlerts } from '../services/get_observability_alerts'; -import { ObservabilityFetchDataPlugins, UXHasDataResponse } from '../typings/fetch_overview_data'; +import { ObservabilityFetchDataPlugins } from '../typings/fetch_overview_data'; +import { ApmIndicesConfig } from '../../common/typings'; type DataContextApps = ObservabilityFetchDataPlugins | 'alert'; export type HasDataMap = Record< DataContextApps, - { status: FETCH_STATUS; hasData?: boolean | UXHasDataResponse | Alert[] } + { + status: FETCH_STATUS; + hasData?: boolean | Alert[]; + indices?: string | ApmIndicesConfig; + serviceName?: string; + } >; export interface HasDataContextValue { - hasData: Partial; + hasDataMap: Partial; hasAnyData: boolean; isAllRequestsComplete: boolean; onRefreshTimeRange: () => void; @@ -40,7 +46,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode const [forceUpdate, setForceUpdate] = useState(''); const { absoluteStart, absoluteEnd } = useTimeRange(); - const [hasData, setHasData] = useState({}); + const [hasDataMap, setHasDataMap] = useState({}); const isExploratoryView = useRouteMatch('/exploratory-view'); @@ -49,23 +55,53 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode if (!isExploratoryView) apps.forEach(async (app) => { try { - if (app !== 'alert') { - const params = - app === 'ux' - ? { absoluteTime: { start: absoluteStart, end: absoluteEnd } } - : undefined; - - const result = await getDataHandler(app)?.hasData(params); - setHasData((prevState) => ({ + const updateState = ({ + hasData, + indices, + serviceName, + }: { + hasData?: boolean; + serviceName?: string; + indices?: string | ApmIndicesConfig; + }) => { + setHasDataMap((prevState) => ({ ...prevState, [app]: { - hasData: result, + hasData, + ...(serviceName ? { serviceName } : {}), + ...(indices ? { indices } : {}), status: FETCH_STATUS.SUCCESS, }, })); + }; + switch (app) { + case 'ux': + const params = { absoluteTime: { start: absoluteStart, end: absoluteEnd } }; + const resultUx = await getDataHandler(app)?.hasData(params); + updateState({ + hasData: resultUx?.hasData, + indices: resultUx?.indices, + serviceName: resultUx?.serviceName as string, + }); + break; + case 'synthetics': + const resultSy = await getDataHandler(app)?.hasData(); + updateState({ hasData: resultSy?.hasData, indices: resultSy?.indices }); + + break; + case 'apm': + const resultApm = await getDataHandler(app)?.hasData(); + updateState({ hasData: resultApm?.hasData, indices: resultApm?.indices }); + + break; + case 'infra_logs': + case 'infra_metrics': + const resultInfra = await getDataHandler(app)?.hasData(); + updateState({ hasData: resultInfra }); + break; } } catch (e) { - setHasData((prevState) => ({ + setHasDataMap((prevState) => ({ ...prevState, [app]: { hasData: undefined, @@ -83,7 +119,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode async function fetchAlerts() { try { const alerts = await getObservabilityAlerts({ core }); - setHasData((prevState) => ({ + setHasDataMap((prevState) => ({ ...prevState, alert: { hasData: alerts, @@ -91,7 +127,7 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode }, })); } catch (e) { - setHasData((prevState) => ({ + setHasDataMap((prevState) => ({ ...prevState, alert: { hasData: undefined, @@ -105,18 +141,18 @@ export function HasDataContextProvider({ children }: { children: React.ReactNode }, [forceUpdate, core]); const isAllRequestsComplete = apps.every((app) => { - const appStatus = hasData[app]?.status; + const appStatus = hasDataMap[app]?.status; return appStatus !== undefined && appStatus !== FETCH_STATUS.LOADING; }); - const hasAnyData = (Object.keys(hasData) as ObservabilityFetchDataPlugins[]).some( - (app) => hasData[app]?.hasData === true + const hasAnyData = (Object.keys(hasDataMap) as ObservabilityFetchDataPlugins[]).some( + (app) => hasDataMap[app]?.hasData === true ); return ( { const originalConsole = global.console; beforeAll(() => { - // mocks console to avoid poluting the test output + // mocks console to avoid polluting the test output global.console = ({ error: jest.fn() } as unknown) as typeof console; }); @@ -58,7 +64,7 @@ describe('registerDataHandler', () => { }, }; }, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); it('registered data handler', () => { diff --git a/x-pack/plugins/observability/public/pages/home/index.test.tsx b/x-pack/plugins/observability/public/pages/home/index.test.tsx index a2c784cb4b2de..60b3e809e7de9 100644 --- a/x-pack/plugins/observability/public/pages/home/index.test.tsx +++ b/x-pack/plugins/observability/public/pages/home/index.test.tsx @@ -24,32 +24,38 @@ describe('Home page', () => { }); it('renders loading component while requests are not returned', () => { - jest - .spyOn(hasData, 'useHasData') - .mockImplementation( - () => - ({ hasData: {}, hasAnyData: false, isAllRequestsComplete: false } as HasDataContextValue) - ); + jest.spyOn(hasData, 'useHasData').mockImplementation( + () => + ({ + hasDataMap: {}, + hasAnyData: false, + isAllRequestsComplete: false, + } as HasDataContextValue) + ); const { getByText } = render(); expect(getByText('Loading Observability')).toBeInTheDocument(); }); it('renders landing page', () => { - jest - .spyOn(hasData, 'useHasData') - .mockImplementation( - () => - ({ hasData: {}, hasAnyData: false, isAllRequestsComplete: true } as HasDataContextValue) - ); + jest.spyOn(hasData, 'useHasData').mockImplementation( + () => + ({ + hasDataMap: {}, + hasAnyData: false, + isAllRequestsComplete: true, + } as HasDataContextValue) + ); render(); expect(mockHistoryPush).toHaveBeenCalledWith({ pathname: '/landing' }); }); it('renders overview page', () => { - jest - .spyOn(hasData, 'useHasData') - .mockImplementation( - () => - ({ hasData: {}, hasAnyData: true, isAllRequestsComplete: false } as HasDataContextValue) - ); + jest.spyOn(hasData, 'useHasData').mockImplementation( + () => + ({ + hasDataMap: {}, + hasAnyData: true, + isAllRequestsComplete: false, + } as HasDataContextValue) + ); render(); expect(mockHistoryPush).toHaveBeenCalledWith({ pathname: '/overview' }); }); diff --git a/x-pack/plugins/observability/public/pages/overview/index.tsx b/x-pack/plugins/observability/public/pages/overview/index.tsx index 89398ad16f198..fdb52270befed 100644 --- a/x-pack/plugins/observability/public/pages/overview/index.tsx +++ b/x-pack/plugins/observability/public/pages/overview/index.tsx @@ -57,13 +57,13 @@ export function OverviewPage({ routeParams }: Props) { const { data: newsFeed } = useFetcher(() => getNewsFeed({ core }), [core]); - const { hasData, hasAnyData } = useHasData(); + const { hasDataMap, hasAnyData } = useHasData(); if (hasAnyData === undefined) { return ; } - const alerts = (hasData.alert?.hasData as Alert[]) || []; + const alerts = (hasDataMap.alert?.hasData as Alert[]) || []; const { refreshInterval = 10000, refreshPaused = true } = routeParams.query; diff --git a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx index 2482ae7a8e7ab..dd424cf221d15 100644 --- a/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx +++ b/x-pack/plugins/observability/public/pages/overview/overview.stories.tsx @@ -25,6 +25,7 @@ import { newsFeedFetchData } from './mock/news_feed.mock'; import { emptyResponse as emptyUptimeResponse, fetchUptimeData } from './mock/uptime.mock'; import { createObservabilityRuleTypeRegistryMock } from '../../rules/observability_rule_type_registry_mock'; import { KibanaPageTemplate } from '../../../../../../src/plugins/kibana_react/public'; +import { ApmIndicesConfig } from '../../../common/typings'; function unregisterAll() { unregisterDataHandler({ appName: 'apm' }); @@ -33,6 +34,11 @@ function unregisterAll() { unregisterDataHandler({ appName: 'synthetics' }); } +const sampleAPMIndices = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'apm_oss.transactionIndices': 'apm-*', +} as ApmIndicesConfig; + const withCore = makeDecorator({ name: 'withCore', parameterName: 'core', @@ -177,7 +183,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => false, + hasData: async () => ({ hasData: false, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -272,7 +278,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); return ( @@ -289,7 +295,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -321,7 +327,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -355,7 +361,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: fetchApmData, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -386,7 +392,7 @@ storiesOf('app/Overview', module) registerDataHandler({ appName: 'apm', fetchData: async () => emptyAPMResponse, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', @@ -420,7 +426,7 @@ storiesOf('app/Overview', module) fetchData: async () => { throw new Error('Error fetching APM data'); }, - hasData: async () => true, + hasData: async () => ({ hasData: true, indices: sampleAPMIndices }), }); registerDataHandler({ appName: 'infra_logs', diff --git a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts index 6b69aa9888cf6..197a8c1060cdb 100644 --- a/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability/public/typings/fetch_overview_data/index.ts @@ -7,6 +7,8 @@ import { ObservabilityApp } from '../../../typings/common'; import { UXMetrics } from '../../components/shared/core_web_vitals'; +import { ApmIndicesConfig } from '../../../common/typings'; + export interface Stat { type: 'number' | 'percent' | 'bytesPerSecond'; value: number; @@ -34,11 +36,20 @@ export interface HasDataParams { export interface HasDataResponse { hasData: boolean; - indices: string; } export interface UXHasDataResponse extends HasDataResponse { - serviceName: string | number | undefined; + serviceName?: string | number; + indices?: string; +} + +export interface SyntheticsHasDataResponse extends HasDataResponse { + indices: string; +} + +export interface APMHasDataResponse { + hasData: boolean; + indices: ApmIndicesConfig; } export type FetchData = ( @@ -134,9 +145,9 @@ export interface ObservabilityFetchDataResponse { } export interface ObservabilityHasDataResponse { - apm: boolean; + apm: APMHasDataResponse; infra_metrics: boolean; infra_logs: boolean; - synthetics: HasDataResponse; + synthetics: SyntheticsHasDataResponse; ux: UXHasDataResponse; } From 61b6496476a30d4e287fe674639241b07aad1a1d Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 18 Jun 2021 17:37:51 +0200 Subject: [PATCH 18/35] [Discover] Update kibana.json adding owner and description (#102292) --- src/plugins/discover/kibana.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 6ea22001f5d80..04469e0ef4276 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -16,5 +16,10 @@ "indexPatternFieldEditor" ], "optionalPlugins": ["home", "share", "usageCollection"], - "requiredBundles": ["kibanaUtils", "home", "kibanaReact"] + "requiredBundles": ["kibanaUtils", "home", "kibanaReact"], + "owner": { + "name": "Kibana App", + "githubTeam": "kibana-app" + }, + "description": "This plugin contains the Discover application and the saved search embeddable." } From a7444835dd396e2eebeb6bd876c251866e01305c Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Fri, 18 Jun 2021 21:25:11 +0300 Subject: [PATCH 19/35] =?UTF-8?q?[TSVB]=20Replaces=20EuiCodeEditor=20?= =?UTF-8?q?=F0=9F=91=89=20Monaco=20editor=20=20(#100684)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Сhanged EuiCodeEditor to CodeEditor (monaco) at markdown_editor.js * Added css lang support for monaco-editor. * Added .d.ts for css lang import directly from monaco. * Moved handlebars_url language to the code_editor. Moved handlebars_url language registration to the code_editor. Changed the way of registration of languages. * Added merge for markdown_handlebars lang. * Changed to simple markdown syntax. Handlebars syntax breaks highlighting of special chars in markdown syntax. * Removed useless mergeConfig function. * Removed legacy import. * Refactor export from monaco-editor. * Fixed 'Could not find a declaration file for module' * Fixed tests. * Fixed typings errors. * Added comment to typings. * Fixed clearMarkdown for Monaco editor. * Made changes based on suggestions. * Fixed types errors. * Fixed function tests types errors. * Fixes, based on nits. * Fixes based on nits. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-monaco/src/esql/index.ts | 3 ++- packages/kbn-monaco/src/helpers.ts | 21 +++++++++++++++++ packages/kbn-monaco/src/index.ts | 10 ++++++-- packages/kbn-monaco/src/painless/index.ts | 3 ++- packages/kbn-monaco/src/register_globals.ts | 13 ++++------- packages/kbn-monaco/src/types.ts | 22 ++++++++++++++++++ packages/kbn-monaco/src/xjson/index.ts | 3 ++- .../kibana_react/public/code_editor/index.tsx | 3 +++ .../languages/css}/constants.ts | 2 +- .../public/code_editor/languages/css/index.ts | 12 ++++++++++ .../code_editor/languages/css/language.ts | 12 ++++++++++ .../languages/handlebars/constants.ts | 9 ++++++++ .../code_editor/languages/handlebars/index.ts | 13 +++++++++++ .../languages/handlebars}/language.ts | 4 ++-- .../public/code_editor/languages/index.ts | 13 +++++++++++ .../languages/markdown/constants.ts | 9 ++++++++ .../code_editor/languages/markdown/index.ts | 12 ++++++++++ .../languages/markdown/language.ts | 12 ++++++++++ .../public/code_editor/register_languages.ts | 13 +++++++++++ .../url_template_editor.tsx | 14 +++-------- .../application/components/markdown_editor.js | 20 ++++++++-------- .../components/panel_config/markdown.tsx | 12 ++++------ .../page_objects/visual_builder_page.ts | 23 +++++++++---------- typings/index.d.ts | 4 ++++ 24 files changed, 205 insertions(+), 57 deletions(-) create mode 100644 packages/kbn-monaco/src/helpers.ts create mode 100644 packages/kbn-monaco/src/types.ts rename src/plugins/kibana_react/public/{url_template_editor => code_editor/languages/css}/constants.ts (90%) create mode 100644 src/plugins/kibana_react/public/code_editor/languages/css/index.ts create mode 100644 src/plugins/kibana_react/public/code_editor/languages/css/language.ts create mode 100644 src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts create mode 100644 src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts rename src/plugins/kibana_react/public/{url_template_editor => code_editor/languages/handlebars}/language.ts (96%) create mode 100644 src/plugins/kibana_react/public/code_editor/languages/index.ts create mode 100644 src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts create mode 100644 src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts create mode 100644 src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts create mode 100644 src/plugins/kibana_react/public/code_editor/register_languages.ts diff --git a/packages/kbn-monaco/src/esql/index.ts b/packages/kbn-monaco/src/esql/index.ts index a3f9df00118b7..4b50a222ad2d6 100644 --- a/packages/kbn-monaco/src/esql/index.ts +++ b/packages/kbn-monaco/src/esql/index.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ +import { LangModule as LangModuleType } from '../types'; import { ID } from './constants'; import { lexerRules } from './lexer_rules'; -export const EsqlLang = { ID, lexerRules }; +export const EsqlLang: LangModuleType = { ID, lexerRules }; diff --git a/packages/kbn-monaco/src/helpers.ts b/packages/kbn-monaco/src/helpers.ts new file mode 100644 index 0000000000000..e525b8c132132 --- /dev/null +++ b/packages/kbn-monaco/src/helpers.ts @@ -0,0 +1,21 @@ +/* + * 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 { monaco } from './monaco_imports'; +import { LangModule as LangModuleType } from './types'; + +function registerLanguage(language: LangModuleType) { + const { ID, lexerRules, languageConfiguration } = language; + + monaco.languages.register({ id: ID }); + monaco.languages.setMonarchTokensProvider(ID, lexerRules); + if (languageConfiguration) { + monaco.languages.setLanguageConfiguration(ID, languageConfiguration); + } +} + +export { registerLanguage }; diff --git a/packages/kbn-monaco/src/index.ts b/packages/kbn-monaco/src/index.ts index ce35d7c3572e6..85d3518461a49 100644 --- a/packages/kbn-monaco/src/index.ts +++ b/packages/kbn-monaco/src/index.ts @@ -12,7 +12,13 @@ import './register_globals'; export { monaco } from './monaco_imports'; export { XJsonLang } from './xjson'; export { PainlessLang, PainlessContext, PainlessAutocompleteField } from './painless'; - /* eslint-disable-next-line @kbn/eslint/module_migration */ import * as BarePluginApi from 'monaco-editor/esm/vs/editor/editor.api'; -export { BarePluginApi }; + +import { registerLanguage } from './helpers'; +import { + LangModule as LangModuleType, + CompleteLangModule as CompleteLangModuleType, +} from './types'; + +export { BarePluginApi, registerLanguage, LangModuleType, CompleteLangModuleType }; diff --git a/packages/kbn-monaco/src/painless/index.ts b/packages/kbn-monaco/src/painless/index.ts index 6858209756430..9863204117b12 100644 --- a/packages/kbn-monaco/src/painless/index.ts +++ b/packages/kbn-monaco/src/painless/index.ts @@ -9,8 +9,9 @@ import { ID } from './constants'; import { lexerRules, languageConfiguration } from './lexer_rules'; import { getSuggestionProvider, getSyntaxErrors } from './language'; +import { CompleteLangModule as CompleteLangModuleType } from '../types'; -export const PainlessLang = { +export const PainlessLang: CompleteLangModuleType = { ID, getSuggestionProvider, lexerRules, diff --git a/packages/kbn-monaco/src/register_globals.ts b/packages/kbn-monaco/src/register_globals.ts index 4047ddedeca42..c6eb68b89e718 100644 --- a/packages/kbn-monaco/src/register_globals.ts +++ b/packages/kbn-monaco/src/register_globals.ts @@ -10,6 +10,8 @@ import { XJsonLang } from './xjson'; import { PainlessLang } from './painless'; import { EsqlLang } from './esql'; import { monaco } from './monaco_imports'; +import { registerLanguage } from './helpers'; + // @ts-ignore import xJsonWorkerSrc from '!!raw-loader!../../target_web/xjson.editor.worker.js'; // @ts-ignore @@ -20,14 +22,9 @@ import painlessWorkerSrc from '!!raw-loader!../../target_web/painless.editor.wor /** * Register languages and lexer rules */ -monaco.languages.register({ id: XJsonLang.ID }); -monaco.languages.setMonarchTokensProvider(XJsonLang.ID, XJsonLang.lexerRules); -monaco.languages.setLanguageConfiguration(XJsonLang.ID, XJsonLang.languageConfiguration); -monaco.languages.register({ id: PainlessLang.ID }); -monaco.languages.setMonarchTokensProvider(PainlessLang.ID, PainlessLang.lexerRules); -monaco.languages.setLanguageConfiguration(PainlessLang.ID, PainlessLang.languageConfiguration); -monaco.languages.register({ id: EsqlLang.ID }); -monaco.languages.setMonarchTokensProvider(EsqlLang.ID, EsqlLang.lexerRules); +registerLanguage(XJsonLang); +registerLanguage(PainlessLang); +registerLanguage(EsqlLang); /** * Create web workers by language ID diff --git a/packages/kbn-monaco/src/types.ts b/packages/kbn-monaco/src/types.ts new file mode 100644 index 0000000000000..f977ada5b624b --- /dev/null +++ b/packages/kbn-monaco/src/types.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { monaco } from './monaco_imports'; + +export interface LangModule { + ID: string; + lexerRules: monaco.languages.IMonarchLanguage; + languageConfiguration?: monaco.languages.LanguageConfiguration; + getSuggestionProvider?: Function; + getSyntaxErrors?: Function; +} + +export interface CompleteLangModule extends LangModule { + languageConfiguration: monaco.languages.LanguageConfiguration; + getSuggestionProvider: Function; + getSyntaxErrors: Function; +} diff --git a/packages/kbn-monaco/src/xjson/index.ts b/packages/kbn-monaco/src/xjson/index.ts index 5e278795fef12..e9ece97ac0023 100644 --- a/packages/kbn-monaco/src/xjson/index.ts +++ b/packages/kbn-monaco/src/xjson/index.ts @@ -12,5 +12,6 @@ import './language'; import { ID } from './constants'; import { lexerRules, languageConfiguration } from './lexer_rules'; +import { LangModule as LangModuleType } from '../types'; -export const XJsonLang = { ID, lexerRules, languageConfiguration }; +export const XJsonLang: LangModuleType = { ID, lexerRules, languageConfiguration }; diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx index 2440974c3b1d1..9e3824b784219 100644 --- a/src/plugins/kibana_react/public/code_editor/index.tsx +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -17,6 +17,9 @@ import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { useUiSetting } from '../ui_settings'; import { Props } from './code_editor'; +import './register_languages'; + +export * from './languages'; const LazyBaseEditor = React.lazy(() => import('./code_editor')); diff --git a/src/plugins/kibana_react/public/url_template_editor/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/css/constants.ts similarity index 90% rename from src/plugins/kibana_react/public/url_template_editor/constants.ts rename to src/plugins/kibana_react/public/code_editor/languages/css/constants.ts index 6c1a1dbce5d67..2f465775e2a1b 100644 --- a/src/plugins/kibana_react/public/url_template_editor/constants.ts +++ b/src/plugins/kibana_react/public/code_editor/languages/css/constants.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export const LANG = 'handlebars_url'; +export const LANG = 'css'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/css/index.ts b/src/plugins/kibana_react/public/code_editor/languages/css/index.ts new file mode 100644 index 0000000000000..fa1cbf4808a4e --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/css/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { LangModuleType } from '@kbn/monaco'; +import { lexerRules, languageConfiguration } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, lexerRules, languageConfiguration }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/css/language.ts b/src/plugins/kibana_react/public/code_editor/languages/css/language.ts new file mode 100644 index 0000000000000..5bdd6c8eb8b1f --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/css/language.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable @kbn/eslint/module_migration */ +import { conf, language } from 'monaco-editor/esm/vs/basic-languages/css/css'; + +export { conf as languageConfiguration, language as lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts new file mode 100644 index 0000000000000..1634c02429f59 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/handlebars/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const LANG = 'handlebars'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts b/src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts new file mode 100644 index 0000000000000..ff3c08267da9b --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/handlebars/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { LangModuleType } from '@kbn/monaco'; +import { languageConfiguration, lexerRules } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, languageConfiguration, lexerRules }; diff --git a/src/plugins/kibana_react/public/url_template_editor/language.ts b/src/plugins/kibana_react/public/code_editor/languages/handlebars/language.ts similarity index 96% rename from src/plugins/kibana_react/public/url_template_editor/language.ts rename to src/plugins/kibana_react/public/code_editor/languages/handlebars/language.ts index 278a7130ad1fa..7f760836088d6 100644 --- a/src/plugins/kibana_react/public/url_template_editor/language.ts +++ b/src/plugins/kibana_react/public/code_editor/languages/handlebars/language.ts @@ -13,7 +13,7 @@ import { monaco } from '@kbn/monaco'; -export const conf: monaco.languages.LanguageConfiguration = { +export const languageConfiguration: monaco.languages.LanguageConfiguration = { wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, comments: { @@ -42,7 +42,7 @@ export const conf: monaco.languages.LanguageConfiguration = { ], }; -export const language: monaco.languages.IMonarchLanguage = { +export const lexerRules: monaco.languages.IMonarchLanguage = { // Set defaultToken to invalid to see what you do not tokenize yet. defaultToken: 'invalid', tokenPostfix: '', diff --git a/src/plugins/kibana_react/public/code_editor/languages/index.ts b/src/plugins/kibana_react/public/code_editor/languages/index.ts new file mode 100644 index 0000000000000..ff7da1725fa7f --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { Lang as CssLang } from './css'; +import { Lang as HandlebarsLang } from './handlebars'; +import { Lang as MarkdownLang } from './markdown'; + +export { CssLang, HandlebarsLang, MarkdownLang }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts b/src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts new file mode 100644 index 0000000000000..bd8aa23256637 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/markdown/constants.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const LANG = 'markdown'; diff --git a/src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts b/src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts new file mode 100644 index 0000000000000..f501de74debec --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/markdown/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { LangModuleType } from '@kbn/monaco'; +import { languageConfiguration, lexerRules } from './language'; +import { LANG } from './constants'; + +export const Lang: LangModuleType = { ID: LANG, languageConfiguration, lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts b/src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts new file mode 100644 index 0000000000000..d8a1234fcf191 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/languages/markdown/language.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/* eslint-disable @kbn/eslint/module_migration */ +import { conf, language } from 'monaco-editor/esm/vs/basic-languages/markdown/markdown'; + +export { conf as languageConfiguration, language as lexerRules }; diff --git a/src/plugins/kibana_react/public/code_editor/register_languages.ts b/src/plugins/kibana_react/public/code_editor/register_languages.ts new file mode 100644 index 0000000000000..b4a0f4d53cdf4 --- /dev/null +++ b/src/plugins/kibana_react/public/code_editor/register_languages.ts @@ -0,0 +1,13 @@ +/* + * 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 { registerLanguage } from '@kbn/monaco'; +import { CssLang, HandlebarsLang, MarkdownLang } from './languages'; + +registerLanguage(CssLang); +registerLanguage(HandlebarsLang); +registerLanguage(MarkdownLang); diff --git a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx index f830b4012976a..0fed4d37e4f7f 100644 --- a/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx +++ b/src/plugins/kibana_react/public/url_template_editor/url_template_editor.tsx @@ -9,18 +9,10 @@ import * as React from 'react'; import { monaco } from '@kbn/monaco'; import { Props as CodeEditorProps } from '../code_editor/code_editor'; -import { CodeEditor } from '../code_editor'; -import { LANG } from './constants'; -import { language, conf } from './language'; +import { CodeEditor, HandlebarsLang } from '../code_editor'; import './styles.scss'; -monaco.languages.register({ - id: LANG, -}); -monaco.languages.setMonarchTokensProvider(LANG, language); -monaco.languages.setLanguageConfiguration(LANG, conf); - export interface UrlTemplateEditorVariable { label: string; title?: string; @@ -74,7 +66,7 @@ export const UrlTemplateEditor: React.FC = ({ return; } - const { dispose } = monaco.languages.registerCompletionItemProvider(LANG, { + const { dispose } = monaco.languages.registerCompletionItemProvider(HandlebarsLang.ID, { triggerCharacters: ['{', '/', '?', '&', '='], provideCompletionItems(model, position, context, token) { const { lineNumber } = position; @@ -132,7 +124,7 @@ export const UrlTemplateEditor: React.FC = ({ return (
- diff --git a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx index c33b4df914a81..7f82f95d250ea 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/panel_config/markdown.tsx @@ -20,7 +20,6 @@ import { EuiSpacer, EuiTitle, EuiHorizontalRule, - EuiCodeEditor, } from '@elastic/eui'; // @ts-expect-error import less from 'less/lib/less-browser'; @@ -43,6 +42,7 @@ import { getDefaultQueryLanguage } from '../lib/get_default_query_language'; import { VisDataContext } from '../../contexts/vis_data_context'; import { PanelConfigProps, PANEL_CONFIG_TABS } from './types'; import { TimeseriesVisParams } from '../../../types'; +import { CodeEditor, CssLang } from '../../../../../kibana_react/public'; const lessC = less(window, { env: 'production' }); @@ -281,12 +281,10 @@ export class MarkdownPanelConfig extends Component< - diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index d796067372fa8..6e263dd1cdbbf 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -132,19 +132,18 @@ export class VisualBuilderPageObject extends FtrService { } public async clearMarkdown() { - // Since we use ACE editor and that isn't really storing its value inside - // a textarea we must really select all text and remove it, and cannot use - // clearValue(). await this.retry.waitForWithTimeout('text area is cleared', 20000, async () => { - const editor = await this.testSubjects.find('codeEditorContainer'); - const $ = await editor.parseDomContent(); - const value = $('.ace_line').text(); - if (value.length > 0) { - this.log.debug('Clearing text area input'); - this.waitForMarkdownTextAreaCleaned(); - } - - return value.length === 0; + const input = await this.find.byCssSelector('.tvbMarkdownEditor__editor textarea'); + await input.clickMouseButton(); + await input.clearValueWithKeyboard(); + + const linesContainer = await this.find.byCssSelector( + '.tvbMarkdownEditor__editor .view-lines' + ); + // lines of code in monaco-editor + // text is not present in textarea + const lines = await linesContainer.findAllByClassName('mtk1'); + return lines.length === 0; }); } diff --git a/typings/index.d.ts b/typings/index.d.ts index c7186a0e5795b..2a5c5e3fa430f 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -33,3 +33,7 @@ declare module 'axios/lib/adapters/xhr'; // See https://github.com/storybookjs/storybook/issues/11684 declare module 'react-syntax-highlighter/dist/cjs/create-element'; declare module 'react-syntax-highlighter/dist/cjs/prism-light'; + +// Monaco languages support +declare module 'monaco-editor/esm/vs/basic-languages/markdown/markdown'; +declare module 'monaco-editor/esm/vs/basic-languages/css/css'; From 32f3d0fda7d094f4e0a5c048d0b3d57d663941e4 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 18 Jun 2021 21:31:04 +0200 Subject: [PATCH 20/35] Do not double register dashboard url generator (#102599) Co-authored-by: Vadim Kibana --- src/plugins/dashboard/public/plugin.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index b73fe5f2ba410..b5d6eda71ca4a 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -205,19 +205,6 @@ export class DashboardPlugin }; }; - if (share) { - this.dashboardUrlGenerator = share.urlGenerators.registerUrlGenerator( - createDashboardUrlGenerator(async () => { - const [coreStart, , selfStart] = await core.getStartServices(); - return { - appBasePath: coreStart.application.getUrlForApp('dashboards'), - useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'), - savedDashboardLoader: selfStart.getSavedDashboardLoader(), - }; - }) - ); - } - const { appMounted, appUnMounted, From 31aa1c8a59e00b32e2f82ead73edf7a01b600ef0 Mon Sep 17 00:00:00 2001 From: Kuldeep M Date: Fri, 18 Jun 2021 21:11:25 +0100 Subject: [PATCH 21/35] [Workplace Search] remove or replace xs props for text on source connect view (#102663) * remove xs props for text on source connect view * change more text sizes --- .../components/add_source/connect_instance.tsx | 8 +++----- .../components/add_source/source_features.tsx | 12 ++++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index fd45d779e6f2a..e3b34050593fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -161,13 +161,13 @@ export const ConnectInstance: React.FC = ({ const permissionField = ( <> - +

{CONNECT_DOC_PERMISSIONS_TITLE}

- + {!needsPermissions && ( = ({ )} - {!indexPermissionsValue && ( <> - - +

{CONNECT_NOT_SYNCED_TEXT} {needsPermissions && whichDocsLink} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx index 7a66efe4ba5f4..0f170be8ba076 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -63,20 +63,20 @@ export const SourceFeatures: React.FC = ({ features, objTy )} - + {title} - {children} + {children} ); }; const SyncFrequencyFeature = ( - +

= ({ features, objTy const SyncedItemsFeature = ( <> - +

{SOURCE_FEATURES_SEARCHABLE}

- +