From e0029ed19156e59184a2f0471ebb9b08864a44a9 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Mon, 3 Oct 2022 20:33:55 +0200 Subject: [PATCH 001/174] [Profiling] Fix calculation/formatting of frame info values (#141909) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joseph Crail --- .../flamegraph_information_window.tsx | 19 +++++------- .../flame_graphs_view/get_impact_rows.ts | 31 +++++++++++-------- .../public/components/flamegraph.tsx | 17 ++++------ .../profiling/public/components/subchart.tsx | 2 +- .../public/utils/formatters/as_cost.ts | 6 ++-- .../public/utils/formatters/as_duration.ts | 18 ++++++++++- .../public/utils/formatters/as_number.test.ts | 31 +++++++++++++++++++ .../public/utils/formatters/as_number.ts | 30 ++++++++++++++++++ .../public/utils/formatters/as_percentage.ts | 6 ++-- .../public/utils/formatters/as_weight.ts | 7 +++-- 10 files changed, 123 insertions(+), 44 deletions(-) create mode 100644 x-pack/plugins/profiling/public/utils/formatters/as_number.test.ts create mode 100644 x-pack/plugins/profiling/public/utils/formatters/as_number.ts diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx b/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx index 824e6d1476a14..39795474763be 100644 --- a/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/flamegraph_information_window.tsx @@ -25,11 +25,10 @@ interface Props { exeFileName: string; functionName: string; sourceFileName: string; - samples: number; - childSamples: number; + countInclusive: number; + countExclusive: number; }; - sampledTraces: number; - totalTraces: number; + totalSamples: number; totalSeconds: number; onClose: () => void; status: AsyncStatus; @@ -105,8 +104,7 @@ function FlamegraphFrameInformationPanel({ export function FlamegraphInformationWindow({ onClose, frame, - sampledTraces, - totalTraces, + totalSamples, totalSeconds, status, }: Props) { @@ -122,14 +120,13 @@ export function FlamegraphInformationWindow({ ); } - const { childSamples, exeFileName, samples, functionName, sourceFileName } = frame; + const { exeFileName, functionName, sourceFileName, countInclusive, countExclusive } = frame; const impactRows = getImpactRows({ - samples, - childSamples, - sampledTraces, + countInclusive, + countExclusive, + totalSamples, totalSeconds, - totalTraces, }); return ( diff --git a/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts b/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts index 8ca1347e4497f..40d3bfc02c1f2 100644 --- a/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts +++ b/x-pack/plugins/profiling/public/components/flame_graphs_view/get_impact_rows.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { asCost } from '../../utils/formatters/as_cost'; import { asDuration } from '../../utils/formatters/as_duration'; +import { asNumber } from '../../utils/formatters/as_number'; import { asPercentage } from '../../utils/formatters/as_percentage'; import { asWeight } from '../../utils/formatters/as_weight'; @@ -23,21 +24,19 @@ const CO2_PER_KWH = 0.92; const CORE_COST_PER_HOUR = 0.0425; export function getImpactRows({ - samples, - childSamples, - sampledTraces, - totalTraces, + countInclusive, + countExclusive, + totalSamples, totalSeconds, }: { - samples: number; - childSamples: number; - sampledTraces: number; - totalTraces: number; + countInclusive: number; + countExclusive: number; + totalSamples: number; totalSeconds: number; }) { - const percentage = samples / sampledTraces; - const percentageNoChildren = (samples - childSamples) / sampledTraces; - const totalCoreSeconds = totalTraces / 20; + const percentage = countInclusive / totalSamples; + const percentageNoChildren = countExclusive / totalSamples; + const totalCoreSeconds = totalSamples / 20; const coreSeconds = totalCoreSeconds * percentage; const coreSecondsNoChildren = totalCoreSeconds * percentageNoChildren; const coreHours = coreSeconds / (60 * 60); @@ -70,10 +69,16 @@ export function getImpactRows({ value: asPercentage(percentageNoChildren), }, { - label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesLabel', { + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesInclusiveLabel', { defaultMessage: 'Samples', }), - value: samples, + value: asNumber(countInclusive), + }, + { + label: i18n.translate('xpack.profiling.flameGraphInformationWindow.samplesExclusiveLabel', { + defaultMessage: 'Samples (excl. children)', + }), + value: asNumber(countExclusive), }, { label: i18n.translate( diff --git a/x-pack/plugins/profiling/public/components/flamegraph.tsx b/x-pack/plugins/profiling/public/components/flamegraph.tsx index 9abac27ef9fb2..5ffe72646f01c 100644 --- a/x-pack/plugins/profiling/public/components/flamegraph.tsx +++ b/x-pack/plugins/profiling/public/components/flamegraph.tsx @@ -31,11 +31,9 @@ function TooltipRow({ formatAsPercentage: boolean; showChange: boolean; }) { - const valueLabel = formatAsPercentage ? asPercentage(value, 2) : value.toString(); + const valueLabel = formatAsPercentage ? asPercentage(value) : value.toString(); const comparisonLabel = - formatAsPercentage && isNumber(comparison) - ? asPercentage(comparison, 2) - : comparison?.toString(); + formatAsPercentage && isNumber(comparison) ? asPercentage(comparison) : comparison?.toString(); const diff = showChange && isNumber(comparison) ? comparison - value : undefined; @@ -46,7 +44,7 @@ function TooltipRow({ defaultMessage: 'no change', }); } else if (formatAsPercentage && diff !== undefined) { - diffLabel = asPercentage(diff, 2); + diffLabel = asPercentage(diff); } return ( @@ -226,10 +224,8 @@ export const FlameGraph: React.FC = ({ exeFileName: highlightedFrame.ExeFileName, sourceFileName: highlightedFrame.SourceFilename, functionName: highlightedFrame.FunctionName, - samples: primaryFlamegraph.Samples[highlightedVmIndex], - childSamples: - primaryFlamegraph.Samples[highlightedVmIndex] - - primaryFlamegraph.CountExclusive[highlightedVmIndex], + countInclusive: primaryFlamegraph.Samples[highlightedVmIndex], + countExclusive: primaryFlamegraph.CountExclusive[highlightedVmIndex], } : undefined; @@ -315,8 +311,7 @@ export const FlameGraph: React.FC = ({ frame={selected} status={highlightedFrameStatus} totalSeconds={primaryFlamegraph?.TotalSeconds ?? 0} - totalTraces={primaryFlamegraph?.TotalTraces ?? 0} - sampledTraces={primaryFlamegraph?.SampledTraces ?? 0} + totalSamples={totalSamples} onClose={() => { setShowInformationWindow(false); }} diff --git a/x-pack/plugins/profiling/public/components/subchart.tsx b/x-pack/plugins/profiling/public/components/subchart.tsx index caafeb5e3d481..0dc017bbdf5f3 100644 --- a/x-pack/plugins/profiling/public/components/subchart.tsx +++ b/x-pack/plugins/profiling/public/components/subchart.tsx @@ -194,7 +194,7 @@ export const SubChart: React.FC = ({ )} - {asPercentage(percentage / 100, 2)} + {asPercentage(percentage / 100)} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts b/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts index 148eba4785263..ea2afc3f50f58 100644 --- a/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts +++ b/x-pack/plugins/profiling/public/utils/formatters/as_cost.ts @@ -5,6 +5,8 @@ * 2.0. */ -export function asCost(value: number, precision: number = 2, unit: string = '$') { - return `${value.toPrecision(precision)}${unit}`; +import { asNumber } from './as_number'; + +export function asCost(value: number, unit: string = '$') { + return `${asNumber(value)}${unit}`; } diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts b/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts index ba0839f06e779..833602cc38203 100644 --- a/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts +++ b/x-pack/plugins/profiling/public/utils/formatters/as_duration.ts @@ -4,9 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { i18n } from '@kbn/i18n'; import moment from 'moment'; +moment.relativeTimeRounding((t) => { + const DIGITS = 2; // like: 2.56 minutes + return Math.round(t * Math.pow(10, DIGITS)) / Math.pow(10, DIGITS); +}); +moment.relativeTimeThreshold('y', 365); +moment.relativeTimeThreshold('M', 12); +moment.relativeTimeThreshold('w', 4); +moment.relativeTimeThreshold('d', 31); +moment.relativeTimeThreshold('h', 24); +moment.relativeTimeThreshold('m', 60); +moment.relativeTimeThreshold('s', 60); +moment.relativeTimeThreshold('ss', 0); + export function asDuration(valueInSeconds: number) { + if (valueInSeconds === 0) { + return i18n.translate('xpack.profiling.zeroSeconds', { defaultMessage: '0 seconds' }); + } return moment.duration(valueInSeconds * 1000).humanize(); } diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_number.test.ts b/x-pack/plugins/profiling/public/utils/formatters/as_number.test.ts new file mode 100644 index 0000000000000..c30def19eb8e3 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/formatters/as_number.test.ts @@ -0,0 +1,31 @@ +/* + * 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 { asNumber } from './as_number'; + +describe('asNumber', () => { + it('rounds numbers appropriately', () => { + expect(asNumber(999)).toBe('999'); + + expect(asNumber(1.11)).toBe('1.11'); + + expect(asNumber(0.001)).toBe('~0.00'); + + expect(asNumber(0)).toBe('0'); + }); + + it('adds k/m/b where needed', () => { + expect(asNumber(999.999)).toBe('1k'); + + expect(asNumber(4.5e5)).toBe('450k'); + + expect(asNumber(4.5001e5)).toBe('450.01k'); + + expect(asNumber(2.4991e7)).toBe('24.99m'); + + expect(asNumber(9e9)).toBe('9b'); + }); +}); diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_number.ts b/x-pack/plugins/profiling/public/utils/formatters/as_number.ts new file mode 100644 index 0000000000000..f7b67bafbf7f7 --- /dev/null +++ b/x-pack/plugins/profiling/public/utils/formatters/as_number.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export function asNumber(value: number): string { + if (value === 0) { + return '0'; + } + + value = Math.round(value * 100) / 100; + if (value < 0.01) { + return '~0.00'; + } + if (value < 1e3) { + return value.toString(); + } + + if (value < 1e6) { + return `${asNumber(value / 1e3)}k`; + } + + if (value < 1e9) { + return `${asNumber(value / 1e6)}m`; + } + + return `${asNumber(value / 1e9)}b`; +} diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts b/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts index f4c3a84b6275f..6b3af016b44c1 100644 --- a/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts +++ b/x-pack/plugins/profiling/public/utils/formatters/as_percentage.ts @@ -5,6 +5,8 @@ * 2.0. */ -export function asPercentage(value: number, precision: number = 0) { - return `${Number(value * 100).toFixed(precision)}%`; +import { asNumber } from './as_number'; + +export function asPercentage(value: number) { + return `${asNumber(value * 100)}%`; } diff --git a/x-pack/plugins/profiling/public/utils/formatters/as_weight.ts b/x-pack/plugins/profiling/public/utils/formatters/as_weight.ts index 82a6cbd4f64b0..fa938a9351f3f 100644 --- a/x-pack/plugins/profiling/public/utils/formatters/as_weight.ts +++ b/x-pack/plugins/profiling/public/utils/formatters/as_weight.ts @@ -6,12 +6,13 @@ */ import { i18n } from '@kbn/i18n'; +import { asNumber } from './as_number'; const ONE_POUND_TO_A_KILO = 0.45359237; -export function asWeight(valueInPounds: number, precision: number = 2) { - const lbs = valueInPounds.toPrecision(precision); - const kgs = Number(valueInPounds * ONE_POUND_TO_A_KILO).toPrecision(precision); +export function asWeight(valueInPounds: number) { + const lbs = asNumber(valueInPounds); + const kgs = asNumber(Number(valueInPounds * ONE_POUND_TO_A_KILO)); return i18n.translate('xpack.profiling.formatters.weight', { defaultMessage: `{lbs} lbs / {kgs} kg`, From 8ad95df6fa731c72096bbb751cd38c0a7ecdf3f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 21:06:10 +0200 Subject: [PATCH 002/174] Update dependency react-hook-form to ^7.36.1 (#142420) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Patryk KopyciƄski --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4108e83b0e6c6..f122f532d5274 100644 --- a/package.json +++ b/package.json @@ -579,7 +579,7 @@ "react-fast-compare": "^2.0.4", "react-focus-on": "^3.6.0", "react-grid-layout": "^1.3.4", - "react-hook-form": "^7.35.0", + "react-hook-form": "^7.36.1", "react-intl": "^2.8.0", "react-is": "^17.0.2", "react-markdown": "^6.0.3", diff --git a/yarn.lock b/yarn.lock index b9db4636175d0..62d61b9fd93f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23386,10 +23386,10 @@ react-grid-layout@^1.3.4: react-draggable "^4.0.0" react-resizable "^3.0.4" -react-hook-form@^7.35.0: - version "7.35.0" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.35.0.tgz#b133de48fc84b1e62f9277ba79dfbacd9bb13dd3" - integrity sha512-9CYdOed+Itbiu5VMVxW0PK9mBR3f0gDGJcZEyUSm0eJbDymQ913TRs2gHcQZZmfTC+rtxyDFRuelMxx/+xwMcw== +react-hook-form@^7.36.1: + version "7.36.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.36.1.tgz#82a311fe8cbe75e689fd4529f083b7c983da6520" + integrity sha512-EbYYkCG2p8ywe7ikOH2l02lAFMrrrslZi1I8fqd8ifDGNAkhomHZQzQsP6ksvzrWBKntRe8b5L5L7Zsd+Gm02Q== react-input-autosize@^3.0.0: version "3.0.0" From 059fecd3115cf4dad81c969d34b642359d47e82f Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Mon, 3 Oct 2022 15:46:07 -0400 Subject: [PATCH 003/174] [Guided onboarding] State management improvements (#141278) --- .../public/components/app.tsx | 4 + .../public/components/main.tsx | 189 ++++++++---- .../public/components/step_three.tsx | 90 ++++++ .../public/components/step_two.tsx | 4 +- ...grations_state_action_machine.test.ts.snap | 30 ++ .../src/core/unused_types.ts | 2 + .../src/initial_state.test.ts | 5 + .../migrations/type_registrations.test.ts | 1 + .../common/{index.ts => constants.ts} | 0 src/plugins/guided_onboarding/common/types.ts | 44 +++ .../public/components/guide_panel.test.tsx | 196 ++++++++++-- .../public/components/guide_panel.tsx | 186 +++++++----- .../public/components/guide_panel_step.tsx | 26 +- .../index.ts} | 2 +- .../{ => guides_config}/observability.ts | 2 +- .../constants/{ => guides_config}/search.ts | 6 +- .../constants/{ => guides_config}/security.ts | 2 +- src/plugins/guided_onboarding/public/index.ts | 11 +- .../guided_onboarding/public/plugin.tsx | 2 +- .../public/services/api.test.ts | 280 +++++++++++++++--- .../guided_onboarding/public/services/api.ts | 280 +++++++++++++++--- .../public/services/helpers.test.ts | 20 +- .../public/services/helpers.ts | 13 +- src/plugins/guided_onboarding/public/types.ts | 20 +- .../guided_onboarding/server/routes/index.ts | 154 +++++++--- .../server/saved_objects/guided_setup.ts | 14 +- .../server/saved_objects/index.ts | 7 +- 27 files changed, 1216 insertions(+), 374 deletions(-) create mode 100644 examples/guided_onboarding_example/public/components/step_three.tsx rename src/plugins/guided_onboarding/common/{index.ts => constants.ts} (100%) create mode 100644 src/plugins/guided_onboarding/common/types.ts rename src/plugins/guided_onboarding/public/constants/{guides_config.ts => guides_config/index.ts} (92%) rename src/plugins/guided_onboarding/public/constants/{ => guides_config}/observability.ts (97%) rename src/plugins/guided_onboarding/public/constants/{ => guides_config}/search.ts (93%) rename src/plugins/guided_onboarding/public/constants/{ => guides_config}/security.ts (97%) diff --git a/examples/guided_onboarding_example/public/components/app.tsx b/examples/guided_onboarding_example/public/components/app.tsx index dc8cbbdcfac83..a5252920c27fa 100755 --- a/examples/guided_onboarding_example/public/components/app.tsx +++ b/examples/guided_onboarding_example/public/components/app.tsx @@ -23,6 +23,7 @@ import { CoreStart, ScopedHistory } from '@kbn/core/public'; import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types'; import { StepTwo } from './step_two'; import { StepOne } from './step_one'; +import { StepThree } from './step_three'; import { Main } from './main'; interface GuidedOnboardingExampleAppDeps { @@ -60,6 +61,9 @@ export const GuidedOnboardingExampleApp = (props: GuidedOnboardingExampleAppDeps + + + diff --git a/examples/guided_onboarding_example/public/components/main.tsx b/examples/guided_onboarding_example/public/components/main.tsx index 157b13f1276c0..59e6fa3192402 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -25,45 +25,50 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import { +import type { GuidedOnboardingPluginStart, - GuidedOnboardingState, - UseCase, + GuideState, + GuideStepIds, + GuideId, + GuideStep, } from '@kbn/guided-onboarding-plugin/public'; +import { guidesConfig } from '@kbn/guided-onboarding-plugin/public'; interface MainProps { guidedOnboarding: GuidedOnboardingPluginStart; notifications: CoreStart['notifications']; } + export const Main = (props: MainProps) => { const { guidedOnboarding: { guidedOnboardingApi }, notifications, } = props; const history = useHistory(); - const [guideState, setGuideState] = useState(undefined); + const [guidesState, setGuidesState] = useState(undefined); + const [activeGuide, setActiveGuide] = useState(undefined); - const [selectedGuide, setSelectedGuide] = useState< - GuidedOnboardingState['activeGuide'] | undefined - >(undefined); - const [selectedStep, setSelectedStep] = useState( - undefined - ); + const [selectedGuide, setSelectedGuide] = useState(undefined); + const [selectedStep, setSelectedStep] = useState(undefined); useEffect(() => { - const subscription = guidedOnboardingApi - ?.fetchGuideState$() - .subscribe((newState: GuidedOnboardingState) => { - setGuideState(newState); - }); - return () => subscription?.unsubscribe(); + const fetchGuidesState = async () => { + const newGuidesState = await guidedOnboardingApi?.fetchAllGuidesState(); + setGuidesState(newGuidesState ? newGuidesState.state : []); + }; + + fetchGuidesState(); }, [guidedOnboardingApi]); - const startGuide = async (guide: UseCase) => { - const response = await guidedOnboardingApi?.updateGuideState({ - activeGuide: guide, - activeStep: 'add_data', - }); + useEffect(() => { + const newActiveGuide = guidesState?.find((guide) => guide.isActive === true); + if (newActiveGuide) { + setActiveGuide(newActiveGuide); + } + }, [guidesState, setActiveGuide]); + + const activateGuide = async (guideId: GuideId, guideState?: GuideState) => { + const response = await guidedOnboardingApi?.activateGuide(guideId, guideState); if (response) { notifications.toasts.addSuccess( @@ -75,11 +80,45 @@ export const Main = (props: MainProps) => { }; const updateGuideState = async () => { - const response = await guidedOnboardingApi?.updateGuideState({ - activeGuide: selectedGuide!, - activeStep: selectedStep!, + const selectedGuideConfig = guidesConfig[selectedGuide!]; + const selectedStepIndex = selectedGuideConfig.steps.findIndex( + (step) => step.id === selectedStep! + ); + + // Noop if the selected step is invalid + if (selectedStepIndex === -1) { + return; + } + + const updatedSteps: GuideStep[] = selectedGuideConfig.steps.map((step, stepIndex) => { + if (selectedStepIndex > stepIndex) { + return { + id: step.id, + status: 'complete', + }; + } + + if (selectedStepIndex < stepIndex) { + return { + id: step.id, + status: 'inactive', + }; + } + + return { + id: step.id, + status: 'active', + }; }); + const updatedGuideState: GuideState = { + isActive: true, + status: 'in_progress', + steps: updatedSteps, + guideId: selectedGuide!, + }; + + const response = await guidedOnboardingApi?.updateGuideState(updatedGuideState, true); if (response) { notifications.toasts.addSuccess( i18n.translate('guidedOnboardingExample.updateGuideState.toastLabel', { @@ -116,7 +155,7 @@ export const Main = (props: MainProps) => { so there is no need to 'load' the state from the server." />

- {guideState ? ( + {activeGuide ? (
{ defaultMessage="Active guide" />
-
{guideState.activeGuide ?? 'undefined'}
+
{activeGuide.guideId}
-
{guideState.activeStep ?? 'undefined'}
+
+ {activeGuide.steps.map((step) => { + return ( + <> + {`Step "${step.id}": ${step.status}`}
+ + ); + })} +
- ) : undefined} + ) : ( +

+ +

+ )}

- - startGuide('search')} fill> - - - - - startGuide('observability')} fill> - - - - - startGuide('security')} fill> - - - + {(Object.keys(guidesConfig) as GuideId[]).map((guideId) => { + const guideState = guidesState?.find((guide) => guide.guideId === guideId); + return ( + + activateGuide(guideId, guideState)} + fill + disabled={guideState?.status === 'complete'} + > + {guideState === undefined && ( + + )} + {(guideState?.isActive === true || + guideState?.status === 'in_progress' || + guideState?.status === 'ready_to_complete') && ( + + )} + {guideState?.status === 'complete' && ( + + )} + + + ); + })} @@ -187,16 +259,15 @@ export const Main = (props: MainProps) => { { - const value = e.target.value as UseCase; + const value = e.target.value as GuideId; const shouldResetState = value.trim().length === 0; if (shouldResetState) { setSelectedGuide(undefined); @@ -209,10 +280,10 @@ export const Main = (props: MainProps) => { - + setSelectedStep(e.target.value)} + onChange={(e) => setSelectedStep(e.target.value as GuideStepIds)} /> diff --git a/examples/guided_onboarding_example/public/components/step_three.tsx b/examples/guided_onboarding_example/public/components/step_three.tsx new file mode 100644 index 0000000000000..ffe9d87993611 --- /dev/null +++ b/examples/guided_onboarding_example/public/components/step_three.tsx @@ -0,0 +1,90 @@ +/* + * 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, { useEffect, useState } from 'react'; + +import { EuiButton, EuiSpacer, EuiText, EuiTitle, EuiTourStep } from '@elastic/eui'; + +import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public/types'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { + EuiPageContentHeader_Deprecated as EuiPageContentHeader, + EuiPageContentBody_Deprecated as EuiPageContentBody, +} from '@elastic/eui'; + +interface StepThreeProps { + guidedOnboarding: GuidedOnboardingPluginStart; +} + +export const StepThree = (props: StepThreeProps) => { + const { + guidedOnboarding: { guidedOnboardingApi }, + } = props; + + const [isTourStepOpen, setIsTourStepOpen] = useState(false); + + useEffect(() => { + const subscription = guidedOnboardingApi + ?.isGuideStepActive$('search', 'search_experience') + .subscribe((isStepActive) => { + setIsTourStepOpen(isStepActive); + }); + return () => subscription?.unsubscribe(); + }, [guidedOnboardingApi]); + + return ( + <> + + +

+ +

+
+
+ + +

+ +

+
+ + +

Click this button to complete step 3.

+ + } + isStepOpen={isTourStepOpen} + minWidth={300} + onFinish={() => { + setIsTourStepOpen(false); + }} + step={1} + stepsTotal={1} + title="Step Build search experience" + anchorPosition="rightUp" + > + { + await guidedOnboardingApi?.completeGuideStep('search', 'search_experience'); + }} + > + Complete step 3 + +
+
+ + ); +}; diff --git a/examples/guided_onboarding_example/public/components/step_two.tsx b/examples/guided_onboarding_example/public/components/step_two.tsx index a79ce2329351e..07f4fd7e63e0c 100644 --- a/examples/guided_onboarding_example/public/components/step_two.tsx +++ b/examples/guided_onboarding_example/public/components/step_two.tsx @@ -55,7 +55,7 @@ export const StepTwo = (props: StepTwoProps) => {

@@ -73,7 +73,7 @@ export const StepTwo = (props: StepTwoProps) => { }} step={1} stepsTotal={1} - title="Step Search experience" + title="Step Browse documents" anchorPosition="rightUp" > { "type": "fleet-enrollment-api-keys", }, }, + Object { + "term": Object { + "type": "guided-setup-state", + }, + }, Object { "term": Object { "type": "ml-telemetry", diff --git a/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts b/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts index a1f7490168345..4fd5ca5cd2aea 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/type_registrations.test.ts @@ -60,6 +60,7 @@ const previouslyRegisteredTypes = [ 'fleet-preconfiguration-deletion-record', 'graph-workspace', 'guided-setup-state', + 'guided-onboarding-guide-state', 'index-pattern', 'infrastructure-monitoring-log-view', 'infrastructure-ui-source', diff --git a/src/plugins/guided_onboarding/common/index.ts b/src/plugins/guided_onboarding/common/constants.ts similarity index 100% rename from src/plugins/guided_onboarding/common/index.ts rename to src/plugins/guided_onboarding/common/constants.ts diff --git a/src/plugins/guided_onboarding/common/types.ts b/src/plugins/guided_onboarding/common/types.ts new file mode 100644 index 0000000000000..412154ede98b0 --- /dev/null +++ b/src/plugins/guided_onboarding/common/types.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type GuideId = 'observability' | 'security' | 'search'; + +export type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; +export type SecurityStepIds = 'add_data' | 'rules' | 'alerts' | 'cases'; +export type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience'; + +export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds; + +/** + * Allowed states for a guide: + * in_progress: Guide has been started + * ready_to_complete: All steps have been completed, but the "Continue using Elastic" button has not been clicked + * complete: All steps and the guide have been completed + */ +export type GuideStatus = 'in_progress' | 'ready_to_complete' | 'complete'; + +/** + * Allowed states for each step in a guide: + * inactive: Step has not started + * active: Step is ready to start (i.e., the guide has been started) + * in_progress: Step has been started and is in progress + * complete: Step has been completed + */ +export type StepStatus = 'inactive' | 'active' | 'in_progress' | 'complete'; + +export interface GuideStep { + id: GuideStepIds; + status: StepStatus; +} + +export interface GuideState { + guideId: GuideId; + status: GuideStatus; + isActive?: boolean; // Drives the current guide shown in the dropdown panel + steps: GuideStep[]; +} diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx index 5eaf24163d2ae..3506c15fcba35 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.test.tsx @@ -13,18 +13,39 @@ import { applicationServiceMock } from '@kbn/core-application-browser-mocks'; import { httpServiceMock } from '@kbn/core/public/mocks'; import { HttpSetup } from '@kbn/core/public'; -import { apiService } from '../services/api'; import { guidesConfig } from '../constants/guides_config'; +import type { GuideState } from '../../common/types'; +import { apiService } from '../services/api'; import { GuidePanel } from './guide_panel'; import { registerTestBed, TestBed } from '@kbn/test-jest-helpers'; const applicationMock = applicationServiceMock.createStartContract(); +const mockActiveSearchGuideState: GuideState = { + guideId: 'search', + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'active', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], +}; + const getGuidePanel = () => () => { return ; }; -describe('GuidePanel', () => { +describe('Guided setup', () => { let httpClient: jest.Mocked; let testBed: TestBed; @@ -32,7 +53,7 @@ describe('GuidePanel', () => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); // Set default state on initial request (no active guides) httpClient.get.mockResolvedValue({ - state: { activeGuide: 'unset', activeStep: 'unset' }, + state: [], }); apiService.setup(httpClient); @@ -48,29 +69,164 @@ describe('GuidePanel', () => { jest.restoreAllMocks(); }); - test('it should be disabled in there is no active guide', async () => { - const { exists } = testBed; - expect(exists('disabledGuideButton')).toBe(true); - expect(exists('guideButton')).toBe(false); - expect(exists('guidePanel')).toBe(false); + describe('Button component', () => { + test('should be disabled in there is no active guide', async () => { + const { exists } = testBed; + expect(exists('disabledGuideButton')).toBe(true); + expect(exists('guideButton')).toBe(false); + expect(exists('guidePanel')).toBe(false); + }); + + test('should be enabled if there is an active guide', async () => { + const { exists, component, find } = testBed; + + await act(async () => { + // Enable the "search" guide + await apiService.updateGuideState(mockActiveSearchGuideState, true); + }); + + component.update(); + + expect(exists('disabledGuideButton')).toBe(false); + expect(exists('guideButton')).toBe(true); + expect(find('guideButton').text()).toEqual('Setup guide'); + }); + + test('should show the step number in the button label if a step is active', async () => { + const { component, find } = testBed; + + const mockInProgressSearchGuideState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'in_progress', + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], + }; + + await act(async () => { + await apiService.updateGuideState(mockInProgressSearchGuideState, true); + }); + + component.update(); + + expect(find('guideButton').text()).toEqual('Setup guide: step 1'); + }); }); - test('it should be enabled if there is an active guide', async () => { - const { exists, component, find } = testBed; + describe('Panel component', () => { + test('should be enabled if a guide is activated', async () => { + const { exists, component, find } = testBed; - await act(async () => { - // Enable the "search" guide - await apiService.updateGuideState({ - activeGuide: 'search', - activeStep: guidesConfig.search.steps[0].id, + await act(async () => { + // Enable the "search" guide + await apiService.updateGuideState(mockActiveSearchGuideState, true); }); + + component.update(); + + expect(exists('guidePanel')).toBe(true); + expect(exists('guideProgress')).toBe(false); + expect(find('guidePanelStep').length).toEqual(guidesConfig.search.steps.length); }); - component.update(); + test('should show the progress bar if the first step has been completed', async () => { + const { component, exists } = testBed; - expect(exists('disabledGuideButton')).toBe(false); - expect(exists('guideButton')).toBe(true); - expect(exists('guidePanel')).toBe(true); - expect(find('guidePanelStep').length).toEqual(guidesConfig.search.steps.length); + const mockInProgressSearchGuideState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'complete', + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], + }; + + await act(async () => { + await apiService.updateGuideState(mockInProgressSearchGuideState, true); + }); + + component.update(); + + expect(exists('guidePanel')).toBe(true); + expect(exists('guideProgress')).toBe(true); + }); + + test('should show the "Continue using Elastic" button when all steps has been completed', async () => { + const { component, exists } = testBed; + + const readyToCompleteGuideState: GuideState = { + guideId: 'search', + status: 'ready_to_complete', + isActive: true, + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'browse_docs', + status: 'complete', + }, + { + id: 'search_experience', + status: 'complete', + }, + ], + }; + + await act(async () => { + await apiService.updateGuideState(readyToCompleteGuideState, true); + }); + + component.update(); + + expect(exists('useElasticButton')).toBe(true); + }); + + describe('Steps', () => { + test('should show "Start" button label if step has not been started', async () => { + const { component, find } = testBed; + + await act(async () => { + // Enable the "search" guide + await apiService.updateGuideState(mockActiveSearchGuideState, true); + }); + + component.update(); + + expect(find('activeStepButtonLabel').text()).toEqual('Start'); + }); + + test('should show "Continue" button label if step is in progress', async () => { + const { component, find } = testBed; + + const mockInProgressSearchGuideState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'in_progress', + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], + }; + + await act(async () => { + await apiService.updateGuideState(mockInProgressSearchGuideState, true); + }); + + component.update(); + + expect(find('activeStepButtonLabel').text()).toEqual('Continue'); + }); + }); }); }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel.tsx b/src/plugins/guided_onboarding/public/components/guide_panel.tsx index f32f55e42b340..bf57d502918d2 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect } from 'react'; import { EuiFlyout, EuiFlyoutBody, @@ -30,7 +30,9 @@ import { ApplicationStart } from '@kbn/core-application-browser'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { guidesConfig } from '../constants/guides_config'; -import type { GuideConfig, StepStatus, GuidedOnboardingState, StepConfig } from '../types'; +import type { GuideState, GuideStepIds } from '../../common/types'; +import type { GuideConfig, StepConfig } from '../types'; + import type { ApiService } from '../services/api'; import { GuideStep } from './guide_panel_step'; @@ -41,47 +43,48 @@ interface GuidePanelProps { application: ApplicationStart; } -const getConfig = (state?: GuidedOnboardingState): GuideConfig | undefined => { - if (state?.activeGuide && state.activeGuide !== 'unset') { - return guidesConfig[state.activeGuide]; +const getConfig = (state?: GuideState): GuideConfig | undefined => { + if (state) { + return guidesConfig[state.guideId]; } return undefined; }; -const getCurrentStep = ( - steps?: StepConfig[], - state?: GuidedOnboardingState -): number | undefined => { - if (steps && state?.activeStep) { - const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === state.activeStep); - if (activeStepIndex > -1) { - return activeStepIndex + 1; - } +const getStepNumber = (state?: GuideState): number | undefined => { + let stepNumber: number | undefined; - return undefined; - } -}; + state?.steps.forEach((step, stepIndex) => { + // If the step is in_progress, show that step number + if (step.status === 'in_progress') { + stepNumber = stepIndex + 1; + } -const getStepStatus = (steps: StepConfig[], stepIndex: number, activeStep?: string): StepStatus => { - const activeStepIndex = steps.findIndex((step: StepConfig) => step.id === activeStep); + // If the step is active, show the previous step number + if (step.status === 'active') { + stepNumber = stepIndex; + } + }); - if (activeStepIndex < stepIndex) { - return 'incomplete'; - } + return stepNumber; +}; - if (activeStepIndex === stepIndex) { - return 'in_progress'; +const getProgress = (state?: GuideState): number => { + if (state) { + return state.steps.reduce((acc, currentVal) => { + if (currentVal.status === 'complete') { + acc = acc + 1; + } + return acc; + }, 0); } - - return 'complete'; + return 0; }; export const GuidePanel = ({ api, application }: GuidePanelProps) => { const { euiTheme } = useEuiTheme(); const [isGuideOpen, setIsGuideOpen] = useState(false); - const [guideState, setGuideState] = useState(undefined); - const isFirstRender = useRef(true); + const [guideState, setGuideState] = useState(undefined); const styles = getGuidePanelStyles(euiTheme); @@ -89,10 +92,10 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { setIsGuideOpen((prevIsGuideOpen) => !prevIsGuideOpen); }; - const navigateToStep = (step: StepConfig) => { - setIsGuideOpen(false); - if (step.location) { - application.navigateToApp(step.location.appID, { path: step.location.path }); + const navigateToStep = async (stepId: GuideStepIds, stepLocation: StepConfig['location']) => { + await api.startGuideStep(guideState!.guideId, stepId); + if (stepLocation) { + application.navigateToApp(stepLocation.appID, { path: stepLocation.path }); } }; @@ -101,22 +104,25 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { application.navigateToApp('home', { path: '#getting_started' }); }; + const completeGuide = async () => { + await api.completeGuide(guideState!.guideId); + }; + useEffect(() => { - const subscription = api.fetchGuideState$().subscribe((newState) => { - if ( - guideState?.activeGuide !== newState.activeGuide || - guideState?.activeStep !== newState.activeStep - ) { - if (isFirstRender.current) { - isFirstRender.current = false; - } else { - setIsGuideOpen(true); - } + const subscription = api.fetchActiveGuideState$().subscribe((newGuideState) => { + if (newGuideState) { + setGuideState(newGuideState); } - setGuideState(newState); }); return () => subscription.unsubscribe(); - }, [api, guideState?.activeGuide, guideState?.activeStep]); + }, [api]); + + useEffect(() => { + const subscription = api.isGuidePanelOpen$.subscribe((isGuidePanelOpen) => { + setIsGuideOpen(isGuidePanelOpen); + }); + return () => subscription.unsubscribe(); + }, [api]); const guideConfig = getConfig(guideState); @@ -139,16 +145,17 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { ); } - const currentStep = getCurrentStep(guideConfig.steps, guideState); + const stepNumber = getStepNumber(guideState); + const stepsCompleted = getProgress(guideState); return ( <> - {currentStep + {Boolean(stepNumber) ? i18n.translate('guidedOnboarding.guidedSetupStepButtonLabel', { - defaultMessage: 'Setup guide: Step {currentStep}', + defaultMessage: 'Setup guide: step {stepNumber}', values: { - currentStep, + stepNumber, }, }) : i18n.translate('guidedOnboarding.guidedSetupButtonLabel', { @@ -203,46 +210,61 @@ export const GuidePanel = ({ api, application }: GuidePanelProps) => { )} - - - {/* - TODO: Progress bar should only show after the first step has been started - We need to make changes to the state itself in order to support this - */} - - - + {/* Progress bar should only show after the first step has been complete */} + {stepsCompleted > 0 && ( + <> + + + + + + )} {guideConfig?.steps.map((step, index, steps) => { const accordionId = htmlIdGenerator(`accordion${index}`)(); - const stepStatus = getStepStatus(steps, index, guideState?.activeStep); - - return ( - - ); + const stepState = guideState?.steps[index]; + + if (stepState) { + return ( + + ); + } })} + + {guideState?.status === 'ready_to_complete' && ( + + + + {i18n.translate('guidedOnboarding.dropdownPanel.elasticButtonLabel', { + defaultMessage: 'Continue using Elastic', + })} + + + + )} diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index e6a300b6b6742..8a98d87debf1a 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -20,7 +20,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import type { StepStatus, StepConfig } from '../types'; +import type { StepStatus, GuideStepIds } from '../../common/types'; +import type { StepConfig } from '../types'; import { getGuidePanelStepStyles } from './guide_panel_step.styles'; interface GuideStepProps { @@ -28,7 +29,7 @@ interface GuideStepProps { stepStatus: StepStatus; stepConfig: StepConfig; stepNumber: number; - navigateToStep: (step: StepConfig) => void; + navigateToStep: (stepId: GuideStepIds, stepLocation: StepConfig['location']) => void; } export const GuideStep = ({ @@ -64,7 +65,7 @@ export const GuideStep = ({ id={accordionId} buttonContent={buttonContent} arrowDisplay="right" - forceState={stepStatus === 'in_progress' ? 'open' : 'closed'} + forceState={stepStatus === 'in_progress' || stepStatus === 'active' ? 'open' : 'closed'} > <> @@ -78,14 +79,21 @@ export const GuideStep = ({ - {stepStatus === 'in_progress' && ( + {(stepStatus === 'in_progress' || stepStatus === 'active') && ( - navigateToStep(stepConfig)} fill> - {/* TODO: Support for conditional "Continue" button label if user revists a step - https://github.com/elastic/kibana/issues/139752 */} - {i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { - defaultMessage: 'Start', - })} + navigateToStep(stepConfig.id, stepConfig.location)} + fill + data-test-subj="activeStepButtonLabel" + > + {stepStatus === 'active' + ? i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { + defaultMessage: 'Start', + }) + : i18n.translate('guidedOnboarding.dropdownPanel.continueStepButtonLabel', { + defaultMessage: 'Continue', + })} diff --git a/src/plugins/guided_onboarding/public/constants/guides_config.ts b/src/plugins/guided_onboarding/public/constants/guides_config/index.ts similarity index 92% rename from src/plugins/guided_onboarding/public/constants/guides_config.ts rename to src/plugins/guided_onboarding/public/constants/guides_config/index.ts index 0cbee9d4b12b6..9ce81cf9d4698 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { GuidesConfig } from '../types'; +import type { GuidesConfig } from '../../types'; import { securityConfig } from './security'; import { observabilityConfig } from './observability'; import { searchConfig } from './search'; diff --git a/src/plugins/guided_onboarding/public/constants/observability.ts b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts similarity index 97% rename from src/plugins/guided_onboarding/public/constants/observability.ts rename to src/plugins/guided_onboarding/public/constants/guides_config/observability.ts index 3f96ad1268173..91b69490131b3 100644 --- a/src/plugins/guided_onboarding/public/constants/observability.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/observability.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { GuideConfig } from '../types'; +import type { GuideConfig } from '../../types'; export const observabilityConfig: GuideConfig = { title: 'Observe my infrastructure', diff --git a/src/plugins/guided_onboarding/public/constants/search.ts b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts similarity index 93% rename from src/plugins/guided_onboarding/public/constants/search.ts rename to src/plugins/guided_onboarding/public/constants/guides_config/search.ts index 1f2a26b5f0b93..57d81fdfe1301 100644 --- a/src/plugins/guided_onboarding/public/constants/search.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { GuideConfig } from '../types'; +import type { GuideConfig } from '../../types'; export const searchConfig: GuideConfig = { title: 'Search my data', @@ -50,6 +50,10 @@ export const searchConfig: GuideConfig = { 'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.', 'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.', ], + location: { + appID: 'guidedOnboardingExample', + path: 'stepThree', + }, }, ], }; diff --git a/src/plugins/guided_onboarding/public/constants/security.ts b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts similarity index 97% rename from src/plugins/guided_onboarding/public/constants/security.ts rename to src/plugins/guided_onboarding/public/constants/guides_config/security.ts index 2c19e7acc2bed..df17d00d7f2d4 100644 --- a/src/plugins/guided_onboarding/public/constants/security.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { GuideConfig } from '../types'; +import type { GuideConfig } from '../../types'; export const securityConfig: GuideConfig = { title: 'Get started with SIEM', diff --git a/src/plugins/guided_onboarding/public/index.ts b/src/plugins/guided_onboarding/public/index.ts index 5b950b190c375..08ae777bb360f 100755 --- a/src/plugins/guided_onboarding/public/index.ts +++ b/src/plugins/guided_onboarding/public/index.ts @@ -12,9 +12,8 @@ import { GuidedOnboardingPlugin } from './plugin'; export function plugin(ctx: PluginInitializerContext) { return new GuidedOnboardingPlugin(ctx); } -export type { - GuidedOnboardingPluginSetup, - GuidedOnboardingPluginStart, - GuidedOnboardingState, - UseCase, -} from './types'; +export type { GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart } from './types'; + +export type { GuideId, GuideStepIds, GuideState, GuideStep } from '../common/types'; + +export { guidesConfig } from './constants/guides_config'; diff --git a/src/plugins/guided_onboarding/public/plugin.tsx b/src/plugins/guided_onboarding/public/plugin.tsx index 902acaa899e3a..f74e19a03300f 100755 --- a/src/plugins/guided_onboarding/public/plugin.tsx +++ b/src/plugins/guided_onboarding/public/plugin.tsx @@ -20,7 +20,7 @@ import { } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import { +import type { ClientConfigType, GuidedOnboardingPluginSetup, GuidedOnboardingPluginStart, diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 9f5e20cb9f89d..ffe5596bd7e35 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -10,15 +10,33 @@ import { HttpSetup } from '@kbn/core/public'; import { httpServiceMock } from '@kbn/core/public/mocks'; import { firstValueFrom, Subscription } from 'rxjs'; -import { API_BASE_PATH } from '../../common'; -import { ApiService } from './api'; -import { GuidedOnboardingState } from '..'; +import { API_BASE_PATH } from '../../common/constants'; import { guidesConfig } from '../constants/guides_config'; +import type { GuideState } from '../../common/types'; +import { ApiService } from './api'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; -const secondStep = guidesConfig[searchGuide].steps[1].id; -const lastStep = guidesConfig[searchGuide].steps[2].id; + +const mockActiveSearchGuideState: GuideState = { + guideId: searchGuide, + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'active', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], +}; describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; @@ -41,40 +59,67 @@ describe('GuidedOnboarding ApiService', () => { jest.restoreAllMocks(); }); - describe('fetchGuideState$', () => { + describe('fetchActiveGuideState$', () => { it('sends a request to the get API', () => { - subscription = apiService.fetchGuideState$().subscribe(); + subscription = apiService.fetchActiveGuideState$().subscribe(); expect(httpClient.get).toHaveBeenCalledTimes(1); - expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`); + expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + query: { active: true }, + }); }); it('broadcasts the updated state', async () => { - await apiService.updateGuideState({ - activeGuide: searchGuide, - activeStep: secondStep, - }); + await apiService.activateGuide(searchGuide); + + const state = await firstValueFrom(apiService.fetchActiveGuideState$()); + expect(state).toEqual(mockActiveSearchGuideState); + }); + }); - const state = await firstValueFrom(apiService.fetchGuideState$()); - expect(state).toEqual({ activeGuide: searchGuide, activeStep: secondStep }); + describe('fetchAllGuidesState', () => { + it('sends a request to the get API', async () => { + await apiService.fetchAllGuidesState(); + expect(httpClient.get).toHaveBeenCalledTimes(1); + expect(httpClient.get).toHaveBeenCalledWith(`${API_BASE_PATH}/state`); }); }); describe('updateGuideState', () => { it('sends a request to the put API', async () => { - const state = { - activeGuide: searchGuide, - activeStep: secondStep, + const updatedState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'in_progress', // update the first step status + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], }; - await apiService.updateGuideState(state as GuidedOnboardingState); + await apiService.updateGuideState(updatedState, false); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify(state), + body: JSON.stringify(updatedState), }); }); }); describe('isGuideStepActive$', () => { - it('returns true if the step is active', async (done) => { + it('returns true if the step has been started', async (done) => { + const updatedState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'in_progress', + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], + }; + await apiService.updateGuideState(updatedState, false); + subscription = apiService .isGuideStepActive$(searchGuide, firstStep) .subscribe((isStepActive) => { @@ -84,9 +129,10 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if the step is not active', async (done) => { + it('returns false if the step is not been started', async (done) => { + await apiService.updateGuideState(mockActiveSearchGuideState, false); subscription = apiService - .isGuideStepActive$(searchGuide, secondStep) + .isGuideStepActive$(searchGuide, firstStep) .subscribe((isStepActive) => { if (!isStepActive) { done(); @@ -95,40 +141,192 @@ describe('GuidedOnboarding ApiService', () => { }); }); - describe('completeGuideStep', () => { - it(`completes the step when it's active`, async () => { - await apiService.completeGuideStep(searchGuide, firstStep); + describe('activateGuide', () => { + it('activates a new guide', async () => { + await apiService.activateGuide(searchGuide); + expect(httpClient.put).toHaveBeenCalledTimes(1); - // this assertion depends on the guides config, we are checking for the next step expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - activeGuide: searchGuide, - activeStep: secondStep, + isActive: true, + status: 'in_progress', + steps: [ + { + id: 'add_data', + status: 'active', + }, + { + id: 'browse_docs', + status: 'inactive', + }, + { + id: 'search_experience', + status: 'inactive', + }, + ], + guideId: searchGuide, }), }); }); - it(`completes the guide when the last step is active`, async () => { - httpClient.get.mockResolvedValue({ - // this state depends on the guides config - state: { activeGuide: searchGuide, activeStep: lastStep }, - }); - apiService.setup(httpClient); + it('reactivates a guide that has already been started', async () => { + await apiService.activateGuide(searchGuide, mockActiveSearchGuideState); - await apiService.completeGuideStep(searchGuide, lastStep); expect(httpClient.put).toHaveBeenCalledTimes(1); - // this assertion depends on the guides config, we are checking for the last step expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - activeGuide: searchGuide, - activeStep: 'completed', + ...mockActiveSearchGuideState, + isActive: true, + }), + }); + }); + }); + + describe('completeGuide', () => { + const readyToCompleteGuideState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'browse_docs', + status: 'complete', + }, + { + id: 'search_experience', + status: 'complete', + }, + ], + }; + + beforeEach(async () => { + await apiService.updateGuideState(readyToCompleteGuideState, false); + }); + + it('updates the selected guide and marks it as complete', async () => { + await apiService.completeGuide(searchGuide); + + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + ...readyToCompleteGuideState, + isActive: false, + status: 'complete', }), }); }); - it(`does nothing if the step is not active`, async () => { - await apiService.completeGuideStep(searchGuide, secondStep); - expect(httpClient.put).not.toHaveBeenCalled(); + it('returns undefined if the selected guide is not active', async () => { + const completedState = await apiService.completeGuide('observability'); // not active + expect(completedState).not.toBeDefined(); + }); + + it('returns undefined if the selected guide has uncompleted steps', async () => { + const incompleteGuideState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: 'add_data', + status: 'complete', + }, + { + id: 'browse_docs', + status: 'complete', + }, + { + id: 'search_experience', + status: 'in_progress', + }, + ], + }; + await apiService.updateGuideState(incompleteGuideState, false); + + const completedState = await apiService.completeGuide(searchGuide); + expect(completedState).not.toBeDefined(); + }); + }); + + describe('startGuideStep', () => { + beforeEach(async () => { + await apiService.updateGuideState(mockActiveSearchGuideState, false); + }); + + it('updates the selected step and marks it as in_progress', async () => { + await apiService.startGuideStep(searchGuide, firstStep); + + expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + ...mockActiveSearchGuideState, + isActive: true, + status: 'in_progress', + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'in_progress', + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], + }), + }); + }); + + it('returns undefined if the selected guide is not active', async () => { + const startState = await apiService.startGuideStep('observability', 'add_data'); // not active + expect(startState).not.toBeDefined(); + }); + }); + + describe('completeGuideStep', () => { + it(`completes the step when it's in progress`, async () => { + const updatedState: GuideState = { + ...mockActiveSearchGuideState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'in_progress', // Mark a step as in_progress in order to test the "completeGuideStep" behavior + }, + mockActiveSearchGuideState.steps[1], + mockActiveSearchGuideState.steps[2], + ], + }; + await apiService.updateGuideState(updatedState, false); + + await apiService.completeGuideStep(searchGuide, firstStep); + + // Once on update, once on complete + expect(httpClient.put).toHaveBeenCalledTimes(2); + // Verify the completed step now has a "complete" status, and the subsequent step is "active" + expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, { + body: JSON.stringify({ + ...updatedState, + steps: [ + { + id: mockActiveSearchGuideState.steps[0].id, + status: 'complete', + }, + { + id: mockActiveSearchGuideState.steps[1].id, + status: 'active', + }, + mockActiveSearchGuideState.steps[2], + ], + }), + }); + }); + + it('returns undefined if the selected guide is not active', async () => { + const startState = await apiService.completeGuideStep('observability', 'add_data'); // not active + expect(startState).not.toBeDefined(); + }); + + it('does nothing if the step is not in progress', async () => { + await apiService.updateGuideState(mockActiveSearchGuideState, false); + + await apiService.completeGuideStep(searchGuide, firstStep); + // Expect only 1 call from updateGuideState() + expect(httpClient.put).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index b99975c3a837a..1adfaa5d8cc23 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -9,31 +9,42 @@ import { HttpSetup } from '@kbn/core/public'; import { BehaviorSubject, map, from, concatMap, of, Observable, firstValueFrom } from 'rxjs'; -import { API_BASE_PATH } from '../../common'; -import { GuidedOnboardingState, UseCase } from '../types'; -import { getNextStep, isLastStep } from './helpers'; +import { API_BASE_PATH } from '../../common/constants'; +import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types'; +import { isLastStep, getGuideConfig } from './helpers'; export class ApiService { private client: HttpSetup | undefined; - private onboardingGuideState$!: BehaviorSubject; + private onboardingGuideState$!: BehaviorSubject; + public isGuidePanelOpen$: BehaviorSubject = new BehaviorSubject(false); public setup(httpClient: HttpSetup): void { this.client = httpClient; - this.onboardingGuideState$ = new BehaviorSubject(undefined); + this.onboardingGuideState$ = new BehaviorSubject(undefined); } /** - * An Observable with the guided onboarding state. + * An Observable with the active guide state. * Initially the state is fetched from the backend. * Subsequently, the observable is updated automatically, when the state changes. */ - public fetchGuideState$(): Observable { + public fetchActiveGuideState$(): Observable { // TODO add error handling if this.client has not been initialized or request fails return this.onboardingGuideState$.pipe( concatMap((state) => state === undefined - ? from(this.client!.get<{ state: GuidedOnboardingState }>(`${API_BASE_PATH}/state`)).pipe( - map((response) => response.state) + ? from( + this.client!.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`, { + query: { + active: true, + }, + }) + ).pipe( + map((response) => { + // There should only be 1 active guide + const hasState = response.state.length === 1; + return hasState ? response.state[0] : undefined; + }) ) : of(state) ) @@ -41,25 +52,45 @@ export class ApiService { } /** - * Updates the state of the guided onboarding - * @param {GuidedOnboardingState} newState the new state of the guided onboarding - * @return {Promise} a promise with the updated state or undefined if the update fails + * Async operation to fetch state for all guides + * This is useful for the onboarding landing page, + * where all guides are displayed with their corresponding status + */ + public async fetchAllGuidesState(): Promise<{ state: GuideState[] } | undefined> { + if (!this.client) { + throw new Error('ApiService has not be initialized.'); + } + + try { + return await this.client.get<{ state: GuideState[] }>(`${API_BASE_PATH}/state`); + } catch (error) { + // TODO handle error + // eslint-disable-next-line no-console + console.error(error); + } + } + + /** + * Updates the SO with the updated guide state and refreshes the observables + * This is largely used internally and for tests + * @param {GuideState} guideState the updated guide state + * @param {boolean} panelState boolean to determine whether the dropdown panel should open or not + * @return {Promise} a promise with the updated guide state */ public async updateGuideState( - newState: GuidedOnboardingState - ): Promise<{ state: GuidedOnboardingState } | undefined> { + newState: GuideState, + panelState: boolean + ): Promise<{ state: GuideState } | undefined> { if (!this.client) { throw new Error('ApiService has not be initialized.'); } try { - const response = await this.client.put<{ state: GuidedOnboardingState }>( - `${API_BASE_PATH}/state`, - { - body: JSON.stringify(newState), - } - ); + const response = await this.client.put<{ state: GuideState }>(`${API_BASE_PATH}/state`, { + body: JSON.stringify(newState), + }); this.onboardingGuideState$.next(newState); + this.isGuidePanelOpen$.next(panelState); return response; } catch (error) { // TODO handle error @@ -69,47 +100,204 @@ export class ApiService { } /** - * An observable with the boolean value if the step is active. - * Returns true, if the passed params identify the guide step that is currently active. + * Activates a guide by guideId + * This is useful for the onboarding landing page, when a user selects a guide to start or continue + * @param {GuideId} guideID the id of the guide (one of search, observability, security) + * @param {GuideState} guideState (optional) the selected guide state, if it exists (i.e., if a user is continuing a guide) + * @return {Promise} a promise with the updated guide state + */ + public async activateGuide( + guideId: GuideId, + guide?: GuideState + ): Promise<{ state: GuideState } | undefined> { + // If we already have the guide state (i.e., user has already started the guide at some point), + // simply pass it through so they can continue where they left off, and update the guide to active + if (guide) { + return await this.updateGuideState( + { + ...guide, + isActive: true, + }, + true + ); + } + + // If this is the 1st-time attempt, we need to create the default state + const guideConfig = getGuideConfig(guideId); + + if (guideConfig) { + const updatedSteps: GuideStep[] = guideConfig.steps.map((step, stepIndex) => { + const isFirstStep = stepIndex === 0; + return { + id: step.id, + // Only the first step should be activated when activating a new guide + status: isFirstStep ? 'active' : 'inactive', + }; + }); + + const updatedGuide: GuideState = { + isActive: true, + status: 'in_progress', + steps: updatedSteps, + guideId, + }; + + return await this.updateGuideState(updatedGuide, true); + } + } + + /** + * Completes a guide + * Updates the overall guide status to 'complete', and marks it as inactive + * This is useful for the dropdown panel, when the user clicks the "Continue using Elastic" button after completing all steps + * @param {GuideId} guideID the id of the guide (one of search, observability, security) + * @return {Promise} a promise with the updated guide state + */ + public async completeGuide(guideId: GuideId): Promise<{ state: GuideState } | undefined> { + const guideState = await firstValueFrom(this.fetchActiveGuideState$()); + + // For now, returning undefined if consumer attempts to complete a guide that is not active + if (guideState?.guideId !== guideId) { + return undefined; + } + + // All steps should be complete at this point + // However, we do a final check here as a safeguard + const allStepsComplete = + Boolean(guideState.steps.find((step) => step.status !== 'complete')) === false; + + if (allStepsComplete) { + const updatedGuide: GuideState = { + ...guideState, + isActive: false, + status: 'complete', + }; + + return await this.updateGuideState(updatedGuide, false); + } + } + + /** + * An observable with the boolean value if the step is in progress (i.e., user clicked "Start" on a step). + * Returns true, if the passed params identify the guide step that is currently in progress. * Returns false otherwise. - * @param {string} guideID the id of the guide (one of search, observability, security) - * @param {string} stepID the id of the step in the guide + * @param {GuideId} guideId the id of the guide (one of search, observability, security) + * @param {GuideStepIds} stepId the id of the step in the guide * @return {Observable} an observable with the boolean value */ - public isGuideStepActive$(guideID: string, stepID: string): Observable { - return this.fetchGuideState$().pipe( - map((state) => { - return state ? state.activeGuide === guideID && state.activeStep === stepID : false; + public isGuideStepActive$(guideId: GuideId, stepId: GuideStepIds): Observable { + return this.fetchActiveGuideState$().pipe( + map((activeGuideState) => { + // Return false right away if the guide itself is not active + if (activeGuideState?.guideId !== guideId) { + return false; + } + + // If the guide is active, next check the step + const selectedStep = activeGuideState.steps.find((step) => step.id === stepId); + return selectedStep ? selectedStep.status === 'in_progress' : false; }) ); } + /** + * Updates the selected step to 'in_progress' state + * This is useful for the dropdown panel, when the user clicks the "Start" button for the active step + * @param {GuideId} guideId the id of the guide (one of search, observability, security) + * @param {GuideStepIds} stepId the id of the step + * @return {Promise} a promise with the updated guide state + */ + public async startGuideStep( + guideId: GuideId, + stepId: GuideStepIds + ): Promise<{ state: GuideState } | undefined> { + const guideState = await firstValueFrom(this.fetchActiveGuideState$()); + + // For now, returning undefined if consumer attempts to start a step for a guide that isn't active + if (guideState?.guideId !== guideId) { + return undefined; + } + + const updatedSteps: GuideStep[] = guideState.steps.map((step) => { + // Mark the current step as in_progress + if (step.id === stepId) { + return { + id: step.id, + status: 'in_progress', + }; + } + + // All other steps return as-is + return step; + }); + + const currentGuide: GuideState = { + guideId, + isActive: true, + status: 'in_progress', + steps: updatedSteps, + }; + + return await this.updateGuideState(currentGuide, false); + } + /** * Completes the guide step identified by the passed params. * A noop if the passed step is not active. - * Completes the current guide, if the step is the last one in the guide. - * @param {string} guideID the id of the guide (one of search, observability, security) - * @param {string} stepID the id of the step in the guide + * @param {GuideId} guideId the id of the guide (one of search, observability, security) + * @param {GuideStepIds} stepId the id of the step in the guide * @return {Promise} a promise with the updated state or undefined if the operation fails */ public async completeGuideStep( - guideID: string, - stepID: string - ): Promise<{ state: GuidedOnboardingState } | undefined> { - const isStepActive = await firstValueFrom(this.isGuideStepActive$(guideID, stepID)); - if (isStepActive) { - if (isLastStep(guideID, stepID)) { - await this.updateGuideState({ activeGuide: guideID as UseCase, activeStep: 'completed' }); - } else { - const nextStepID = getNextStep(guideID, stepID); - if (nextStepID !== undefined) { - await this.updateGuideState({ - activeGuide: guideID as UseCase, - activeStep: nextStepID, - }); + guideId: GuideId, + stepId: GuideStepIds + ): Promise<{ state: GuideState } | undefined> { + const guideState = await firstValueFrom(this.fetchActiveGuideState$()); + + // For now, returning undefined if consumer attempts to complete a step for a guide that isn't active + if (guideState?.guideId !== guideId) { + return undefined; + } + + const currentStepIndex = guideState.steps.findIndex((step) => step.id === stepId); + const currentStep = guideState.steps[currentStepIndex]; + const isCurrentStepInProgress = currentStep ? currentStep.status === 'in_progress' : false; + + if (isCurrentStepInProgress) { + const updatedSteps: GuideStep[] = guideState.steps.map((step, stepIndex) => { + const isCurrentStep = step.id === currentStep!.id; + const isNextStep = stepIndex === currentStepIndex + 1; + + // Mark the current step as complete + if (isCurrentStep) { + return { + id: step.id, + status: 'complete', + }; } - } + + // Update the next step to active status + if (isNextStep) { + return { + id: step.id, + status: 'active', + }; + } + + // All other steps return as-is + return step; + }); + + const currentGuide: GuideState = { + guideId, + isActive: true, + status: isLastStep(guideId, stepId) ? 'ready_to_complete' : 'in_progress', + steps: updatedSteps, + }; + + return await this.updateGuideState(currentGuide, true); } + return undefined; } } diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts index 6e1a3cc3e0049..bc09a9185424c 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.test.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -7,11 +7,10 @@ */ import { guidesConfig } from '../constants/guides_config'; -import { getNextStep, isLastStep } from './helpers'; +import { isLastStep } from './helpers'; const searchGuide = 'search'; const firstStep = guidesConfig[searchGuide].steps[0].id; -const secondStep = guidesConfig[searchGuide].steps[1].id; const lastStep = guidesConfig[searchGuide].steps[2].id; describe('GuidedOnboarding ApiService helpers', () => { @@ -27,21 +26,4 @@ describe('GuidedOnboarding ApiService helpers', () => { expect(result).toBe(false); }); }); - - describe('getNextStep', () => { - it('returns id of the next step', () => { - const result = getNextStep(searchGuide, firstStep); - expect(result).toEqual(secondStep); - }); - - it('returns undefined if the params are not part of the config', () => { - const result = getNextStep('some_guide', 'some_step'); - expect(result).toBeUndefined(); - }); - - it(`returns undefined if it's the last step`, () => { - const result = getNextStep(searchGuide, lastStep); - expect(result).toBeUndefined(); - }); - }); }); diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts index 3eb0bfca9b751..ea4245be99150 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.ts @@ -6,12 +6,13 @@ * Side Public License, v 1. */ +import type { GuideId } from '../../common/types'; import { guidesConfig } from '../constants/guides_config'; -import { GuideConfig, StepConfig, UseCase } from '../types'; +import type { GuideConfig, StepConfig } from '../types'; export const getGuideConfig = (guideID?: string): GuideConfig | undefined => { if (guideID && Object.keys(guidesConfig).includes(guideID)) { - return guidesConfig[guideID as UseCase]; + return guidesConfig[guideID as GuideId]; } }; @@ -32,11 +33,3 @@ export const isLastStep = (guideID: string, stepID: string): boolean => { } return false; }; - -export const getNextStep = (guideID: string, stepID: string): string | undefined => { - const guide = getGuideConfig(guideID); - const activeStepIndex = getStepIndex(guideID, stepID); - if (activeStepIndex > -1 && guide?.steps[activeStepIndex + 1]) { - return guide?.steps[activeStepIndex + 1].id; - } -}; diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts index 7925fa8ae69d7..4a16c16336c6b 100755 --- a/src/plugins/guided_onboarding/public/types.ts +++ b/src/plugins/guided_onboarding/public/types.ts @@ -7,6 +7,7 @@ */ import { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; +import { GuideId, GuideStepIds, StepStatus } from '../common/types'; import { ApiService } from './services/api'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -20,11 +21,12 @@ export interface AppPluginStartDependencies { navigation: NavigationPublicPluginStart; } -export type UseCase = 'observability' | 'security' | 'search'; -export type StepStatus = 'incomplete' | 'complete' | 'in_progress'; +export interface ClientConfigType { + ui: boolean; +} export interface StepConfig { - id: string; + id: GuideStepIds; title: string; descriptionList: string[]; location?: { @@ -33,7 +35,6 @@ export interface StepConfig { }; status?: StepStatus; } - export interface GuideConfig { title: string; description: string; @@ -45,14 +46,5 @@ export interface GuideConfig { } export type GuidesConfig = { - [key in UseCase]: GuideConfig; + [key in GuideId]: GuideConfig; }; - -export interface GuidedOnboardingState { - activeGuide: UseCase | 'unset'; - activeStep: string | 'unset' | 'completed'; -} - -export interface ClientConfigType { - ui: boolean; -} diff --git a/src/plugins/guided_onboarding/server/routes/index.ts b/src/plugins/guided_onboarding/server/routes/index.ts index e4e4fcaae5054..cce5aad08b1e5 100755 --- a/src/plugins/guided_onboarding/server/routes/index.ts +++ b/src/plugins/guided_onboarding/server/routes/index.ts @@ -7,92 +7,154 @@ */ import { schema } from '@kbn/config-schema'; -import { IRouter, SavedObjectsClient } from '@kbn/core/server'; -import { - guidedSetupDefaultState, - guidedSetupSavedObjectsId, - guidedSetupSavedObjectsType, -} from '../saved_objects'; - -const doesGuidedSetupExist = async (savedObjectsClient: SavedObjectsClient): Promise => { - return savedObjectsClient - .find({ type: guidedSetupSavedObjectsType }) - .then((foundSavedObjects) => foundSavedObjects.total > 0); +import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; +import type { GuideState } from '../../common/types'; +import { guidedSetupSavedObjectsType } from '../saved_objects'; + +const findGuideById = async (savedObjectsClient: SavedObjectsClient, guideId: string) => { + return savedObjectsClient.find({ + type: guidedSetupSavedObjectsType, + search: `"${guideId}"`, + searchFields: ['guideId'], + }); +}; + +const findActiveGuide = async (savedObjectsClient: SavedObjectsClient) => { + return savedObjectsClient.find({ + type: guidedSetupSavedObjectsType, + search: 'true', + searchFields: ['isActive'], + }); +}; + +const findAllGuides = async (savedObjectsClient: SavedObjectsClient) => { + return savedObjectsClient.find({ type: guidedSetupSavedObjectsType }); }; export function defineRoutes(router: IRouter) { + // Fetch all guides state; optionally pass the query param ?active=true to only return the active guide router.get( { path: '/api/guided_onboarding/state', - validate: false, + validate: { + query: schema.object({ + active: schema.maybe(schema.boolean()), + }), + }, }, async (context, request, response) => { const coreContext = await context.core; const soClient = coreContext.savedObjects.client as SavedObjectsClient; - const stateExists = await doesGuidedSetupExist(soClient); - if (stateExists) { - const guidedSetupSO = await soClient.get( - guidedSetupSavedObjectsType, - guidedSetupSavedObjectsId - ); + const existingGuides = + request.query.active === true + ? await findActiveGuide(soClient) + : await findAllGuides(soClient); + + if (existingGuides.total > 0) { + const guidesState = existingGuides.saved_objects.map((guide) => guide.attributes); return response.ok({ - body: { state: guidedSetupSO.attributes }, + body: { state: guidesState }, }); } else { + // If no SO exists, we assume state hasn't been stored yet and return an empty array return response.ok({ - body: { state: guidedSetupDefaultState }, + body: { state: [] }, }); } } ); + // Update the guide state for the passed guideId; + // will also check any existing active guides and update them to an "inactive" state router.put( { path: '/api/guided_onboarding/state', validate: { body: schema.object({ - activeGuide: schema.maybe(schema.string()), - activeStep: schema.maybe(schema.string()), + status: schema.string(), + guideId: schema.string(), + isActive: schema.boolean(), + steps: schema.arrayOf( + schema.object({ + status: schema.string(), + id: schema.string(), + }) + ), }), }, }, async (context, request, response) => { - const activeGuide = request.body.activeGuide; - const activeStep = request.body.activeStep; - const attributes = { - activeGuide: activeGuide ?? 'unset', - activeStep: activeStep ?? 'unset', - }; + const updatedGuideState = request.body; + const coreContext = await context.core; - const soClient = coreContext.savedObjects.client as SavedObjectsClient; + const savedObjectsClient = coreContext.savedObjects.client as SavedObjectsClient; - const stateExists = await doesGuidedSetupExist(soClient); + const selectedGuideSO = await findGuideById(savedObjectsClient, updatedGuideState.guideId); + + // If the SO already exists, update it, else create a new SO + if (selectedGuideSO.total > 0) { + const updatedGuides = []; + const selectedGuide = selectedGuideSO.saved_objects[0]; + + updatedGuides.push({ + type: guidedSetupSavedObjectsType, + id: selectedGuide.id, + attributes: { + ...updatedGuideState, + }, + }); + + // If we are activating a new guide, we need to check if there is a different, existing active guide + // If yes, we need to mark it as inactive (only 1 guide can be active at a time) + if (updatedGuideState.isActive) { + const activeGuideSO = await findActiveGuide(savedObjectsClient); + + if (activeGuideSO.total > 0) { + const activeGuide = activeGuideSO.saved_objects[0]; + if (activeGuide.attributes.guideId !== updatedGuideState.guideId) { + updatedGuides.push({ + type: guidedSetupSavedObjectsType, + id: activeGuide.id, + attributes: { + ...activeGuide.attributes, + isActive: false, + }, + }); + } + } + } + + const updatedGuidesResponse = await savedObjectsClient.bulkUpdate(updatedGuides); - if (stateExists) { - const updatedGuidedSetupSO = await soClient.update( - guidedSetupSavedObjectsType, - guidedSetupSavedObjectsId, - attributes - ); return response.ok({ - body: { state: updatedGuidedSetupSO.attributes }, + body: { + state: updatedGuidesResponse, + }, }); } else { - const guidedSetupSO = await soClient.create( - guidedSetupSavedObjectsType, - { - ...guidedSetupDefaultState, - ...attributes, - }, - { - id: guidedSetupSavedObjectsId, + // If we are activating a new guide, we need to check if there is an existing active guide + // If yes, we need to mark it as inactive (only 1 guide can be active at a time) + if (updatedGuideState.isActive) { + const activeGuideSO = await findActiveGuide(savedObjectsClient); + + if (activeGuideSO.total > 0) { + const activeGuide = activeGuideSO.saved_objects[0]; + await savedObjectsClient.update(guidedSetupSavedObjectsType, activeGuide.id, { + ...activeGuide.attributes, + isActive: false, + }); } + } + + const createdGuideResponse = await savedObjectsClient.create( + guidedSetupSavedObjectsType, + updatedGuideState ); return response.ok({ body: { - state: guidedSetupSO.attributes, + state: createdGuideResponse, }, }); } diff --git a/src/plugins/guided_onboarding/server/saved_objects/guided_setup.ts b/src/plugins/guided_onboarding/server/saved_objects/guided_setup.ts index 2576148868597..6fe0a90339f69 100644 --- a/src/plugins/guided_onboarding/server/saved_objects/guided_setup.ts +++ b/src/plugins/guided_onboarding/server/saved_objects/guided_setup.ts @@ -8,12 +8,8 @@ import { SavedObjectsType } from '@kbn/core/server'; -export const guidedSetupSavedObjectsType = 'guided-setup-state'; -export const guidedSetupSavedObjectsId = 'guided-setup-state-id'; -export const guidedSetupDefaultState = { - activeGuide: 'unset', - activeStep: 'unset', -}; +export const guidedSetupSavedObjectsType = 'guided-onboarding-guide-state'; + export const guidedSetupSavedObjects: SavedObjectsType = { name: guidedSetupSavedObjectsType, hidden: false, @@ -22,11 +18,11 @@ export const guidedSetupSavedObjects: SavedObjectsType = { mappings: { dynamic: false, properties: { - activeGuide: { + guideId: { type: 'keyword', }, - activeStep: { - type: 'keyword', + isActive: { + type: 'boolean', }, }, }, diff --git a/src/plugins/guided_onboarding/server/saved_objects/index.ts b/src/plugins/guided_onboarding/server/saved_objects/index.ts index 2fa5366cc2b9e..58195618a0ec4 100644 --- a/src/plugins/guided_onboarding/server/saved_objects/index.ts +++ b/src/plugins/guided_onboarding/server/saved_objects/index.ts @@ -6,9 +6,4 @@ * Side Public License, v 1. */ -export { - guidedSetupSavedObjects, - guidedSetupSavedObjectsType, - guidedSetupSavedObjectsId, - guidedSetupDefaultState, -} from './guided_setup'; +export { guidedSetupSavedObjects, guidedSetupSavedObjectsType } from './guided_setup'; From 3bad88157a06e21753e8d3052f2fd81ca987d615 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Mon, 3 Oct 2022 12:53:10 -0700 Subject: [PATCH 004/174] [RAM] Storybook implementation for triggers actions UI shareable components (#139157) * Storybook implementation for triggers actions UI shareable components * Fix storybooks useUiSettings * Fix API renaming and add KPI Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau --- .../action_type_registry.tsx} | 11 +- .../.storybook/context/http.ts | 305 ++++++++++++++++++ .../.storybook/context/rule_type_registry.ts | 31 ++ .../.storybook/decorator.tsx | 124 +++++++ .../.storybook/{main.js => main.ts} | 4 +- .../triggers_actions_ui/.storybook/manager.ts | 20 ++ .../.storybook/preview.tsx | 31 ++ .../rule_event_log_list.stories.tsx | 82 +++++ .../components/rule_event_log_list.test.tsx | 80 +---- .../rule_details/components/test_helpers.ts | 26 ++ .../rule_status_dropdown.stories.tsx | 72 +++++ .../components/rule_tag_badge.stories.tsx | 70 ++++ .../components/rule_tag_filter.stories.tsx | 100 ++++++ .../components/rules_list.stories.tsx | 112 +++++++ .../plugins/triggers_actions_ui/tsconfig.json | 1 + 15 files changed, 986 insertions(+), 83 deletions(-) rename x-pack/plugins/triggers_actions_ui/.storybook/{preview.js => context/action_type_registry.tsx} (63%) create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/context/rule_type_registry.ts create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx rename x-pack/plugins/triggers_actions_ui/.storybook/{main.js => main.ts} (75%) create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/manager.ts create mode 100644 x-pack/plugins/triggers_actions_ui/.storybook/preview.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.stories.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.stories.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_badge.stories.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_filter.stories.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.stories.tsx diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/preview.js b/x-pack/plugins/triggers_actions_ui/.storybook/context/action_type_registry.tsx similarity index 63% rename from x-pack/plugins/triggers_actions_ui/.storybook/preview.js rename to x-pack/plugins/triggers_actions_ui/.storybook/context/action_type_registry.tsx index 3200746243d47..73a28b53d0a8b 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/preview.js +++ b/x-pack/plugins/triggers_actions_ui/.storybook/context/action_type_registry.tsx @@ -5,6 +5,11 @@ * 2.0. */ -import { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common'; - -export const decorators = [EuiThemeProviderDecorator]; +export const getActionTypeRegistry = () => { + return { + has: () => true, + register: () => {}, + get: () => {}, + list: () => [], + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts b/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts new file mode 100644 index 0000000000000..dc260578641f8 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/context/http.ts @@ -0,0 +1,305 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import uuid from 'uuid'; +import { DecoratorFn } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import type { HttpStart, HttpFetchOptions, HttpHandler } from '@kbn/core/public'; +import { + mockLogResponse, + getMockLogResponse, +} from '../../public/application/sections/rule_details/components/test_helpers'; + +const getMockRule = () => { + const id = uuid.v4(); + return { + id, + name: `test rule - ${id}`, + tags: ['tag1', 'tag2', 'tag3'], + enabled: true, + ruleTypeId: 'test_rule_type', + schedule: { interval: '1s' }, + actions: [], + consumer: 'alerts', + params: { name: 'test rule type name' }, + scheduledTaskId: null, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + executionStatus: { + status: 'active', + lastDuration: 500, + lastExecutionDate: new Date('2020-08-20T19:23:38Z'), + error: null, + }, + monitoring: { + execution: { + history: [ + { + success: true, + duration: 1000000, + }, + { + success: true, + duration: 200000, + }, + { + success: false, + duration: 300000, + }, + ], + calculated_metrics: { + success_ratio: 0.66, + p50: 200000, + p95: 300000, + p99: 300000, + }, + }, + }, + }; +}; + +const mockRuleTypes = [ + { + id: 'test_rule_type', + name: 'some rule type', + action_groups: [{ id: 'default', name: 'Default' }], + recovery_action_group: { id: 'recovered', name: 'Recovered' }, + action_variables: { context: [], state: [] }, + default_action_group_id: 'default', + producer: 'alerts', + minimum_license_required: 'basic', + enabled_in_license: true, + authorized_consumers: { + alerts: { read: true, all: true }, + }, + rule_task_timeout: '1m', + }, +]; + +const mockConfig = { + minimumScheduleInterval: { + value: '1m', + enforce: false, + }, + isUsingSecurity: true, +}; + +const mockConnectorTypes = [ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, +]; + +const mockHealth = { + isAlertsAvailable: true, +}; + +const mockAggregation = { + rule_execution_status: { ok: 0, active: 0, error: 0, pending: 0, unknown: 0, warning: 0 }, + rule_enabled_status: { enabled: 0, disabled: 0 }, + rule_muted_status: { muted: 0, unmuted: 0 }, + rule_snoozed_status: { snoozed: 0 }, + rule_tags: ['a', 'b'], +}; + +const mockConnectors: any[] = []; + +const mockRuleSummary = { + id: 'rule-id', + name: 'rule-name', + tags: ['tag-1', 'tag-2'], + rule_type_id: 'test-rule-type-id', + consumer: 'rule-consumer', + status: 'OK', + mute_all: false, + throttle: '', + enabled: true, + error_messages: [], + status_start_date: '2022-03-21T07:40:46-07:00', + status_end_date: '2022-03-25T07:40:46-07:00', + alerts: { + foo: { + status: 'OK', + muted: false, + actionGroupId: 'testActionGroup', + }, + }, + execution_duration: { + average: 100, + valuesWithTimestamp: {}, + }, +}; + +const getMockErrorLog = () => { + return { + id: '66b9c04a-d5d3-4ed4-aa7c-94ddaca3ac1d', + timestamp: '2022-03-31T18:03:33.133Z', + type: 'alerting', + message: + "rule execution failure: .es-query:d87fcbd0-b11b-11ec-88f6-293354dba871: 'Mine' - x_content_parse_exception: [parsing_exception] Reason: unknown query [match_allxxxx] did you mean [match_all]?", + }; +}; + +const baseRulesListGetResponse = (path: string) => { + if (path === '/internal/triggers_actions_ui/_config') { + return mockConfig; + } + if (path === '/internal/triggers_actions_ui/_health') { + return mockHealth; + } + if (path === '/api/actions/connectors') { + return mockConnectors; + } + if (path === '/api/alerting/rule_types') { + return mockRuleTypes; + } + if (path === '/api/actions/connector_types') { + return mockConnectorTypes; + } + if (path === '/internal/alerting/rules/_aggregate') { + return mockAggregation; + } +}; + +const emptyRulesListGetResponse = (path: string) => { + if (path === '/internal/alerting/rules/_find') { + return { + data: [], + page: 1, + per_page: 10, + total: 0, + }; + } + return baseRulesListGetResponse(path); +}; + +const rulesListGetResponse = (path: string) => { + if (path === '/internal/alerting/rules/_find') { + return { + data: [getMockRule(), getMockRule(), getMockRule(), getMockRule()], + page: 1, + per_page: 10, + total: 4, + }; + } + return baseRulesListGetResponse(path); +}; + +const rulesListGetPaginatedResponse = (path: string) => { + if (path === '/internal/alerting/rules/_find') { + return { + data: Array.from(Array(10), () => getMockRule()), + page: 1, + per_page: 10, + total: 50, + }; + } + return baseRulesListGetResponse(path); +}; + +const baseEventLogListGetResponse = (path: string) => { + if (path.endsWith('/_alert_summary')) { + return { + ...mockRuleSummary, + execution_duration: { + ...mockRuleSummary.execution_duration, + valuesWithTimestamp: { + '2022-08-18T23:07:28.662Z': 68, + '2022-08-18T23:07:29.662Z': 59, + '2022-08-18T23:07:30.662Z': 20, + '2022-08-18T23:07:31.662Z': 140, + }, + }, + }; + } + if (path.endsWith('/_action_error_log')) { + return { + errors: Array.from(Array(4), () => getMockErrorLog()), + totalErrors: 4, + }; + } + if (path.endsWith('/_execution_kpi')) { + return { + activeAlerts: 49, + erroredActions: 36, + failure: 30, + newAlerts: 1, + recoveredAlerts: 20, + success: 49, + triggeredActions: 49, + unknown: 10, + }; + } +}; + +const emptyEventLogListGetResponse = (path: string) => { + if (path.endsWith('/_alert_summary')) { + return mockRuleSummary; + } + if (path.endsWith('/_execution_log')) { + return { + data: [], + total: 0, + }; + } + return baseEventLogListGetResponse(path); +}; + +const eventLogListGetResponse = (path: string) => { + if (path.endsWith('/_execution_log')) { + return mockLogResponse; + } + return baseEventLogListGetResponse(path); +}; + +const paginatedEventLogListGetResponse = (path: string) => { + if (path.endsWith('/_execution_log')) { + return { + data: Array.from(Array(10), () => getMockLogResponse()), + total: 500, + }; + } + return baseEventLogListGetResponse(path); +}; + +export const getHttp = (context: Parameters[1]) => { + return { + get: (async (path: string, options: HttpFetchOptions) => { + const { id } = context; + if (id === 'app-ruleslist--empty') { + return emptyRulesListGetResponse(path); + } + if (id === 'app-ruleslist--with-rules') { + return rulesListGetResponse(path); + } + if (id === 'app-ruleslist--with-paginated-rules') { + return rulesListGetPaginatedResponse(path); + } + if (id === 'app-ruleeventloglist--empty') { + return emptyEventLogListGetResponse(path); + } + if (id === 'app-ruleeventloglist--with-events') { + return eventLogListGetResponse(path); + } + if (id === 'app-ruleeventloglist--with-paginated-events') { + return paginatedEventLogListGetResponse(path); + } + }) as HttpHandler, + post: (async (path: string, options: HttpFetchOptions) => { + action('POST')(path, options); + }) as HttpHandler, + } as unknown as HttpStart; +}; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/context/rule_type_registry.ts b/x-pack/plugins/triggers_actions_ui/.storybook/context/rule_type_registry.ts new file mode 100644 index 0000000000000..f8ddcf6f8def4 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/context/rule_type_registry.ts @@ -0,0 +1,31 @@ +/* + * 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 mockRuleType = { + id: 'test_rule_type', + iconClass: 'test', + description: 'Rule when testing', + documentationUrl: 'https://localhost.local/docs', + validate: () => { + return { errors: {} }; + }, + ruleParamsExpression: () => null, + requiresAppContext: false, +}; + +export const getRuleTypeRegistry = () => { + return { + has: () => true, + register: () => {}, + get: () => { + return mockRuleType; + }, + list: () => { + return [mockRuleType]; + }, + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx new file mode 100644 index 0000000000000..ed2a1d7b17e14 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/decorator.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import uuid from 'uuid'; +import { action } from '@storybook/addon-actions'; +import { DecoratorFn } from '@storybook/react'; +import { EMPTY, of } from 'rxjs'; +import { I18nProvider } from '@kbn/i18n-react'; +import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; +import type { NotificationsStart, ApplicationStart } from '@kbn/core/public'; +import { KibanaContextProvider } from '../public/common/lib/kibana'; +import { ExperimentalFeaturesService } from '../public/common/experimental_features_service'; +import { getHttp } from './context/http'; +import { getRuleTypeRegistry } from './context/rule_type_registry'; +import { getActionTypeRegistry } from './context/action_type_registry'; + +interface StorybookContextDecoratorProps { + context: Parameters[1]; +} + +const handler = (type: string, ...rest: any[]) => { + action(`${type} Toast`)(rest); + return { id: uuid() }; +}; + +const notifications: NotificationsStart = { + toasts: { + add: (params) => handler('add', params), + addDanger: (params) => handler('danger', params), + addError: (params) => handler('error', params), + addWarning: (params) => handler('warning', params), + addSuccess: (params) => handler('success', params), + addInfo: (params) => handler('info', params), + remove: () => {}, + get$: () => of([]), + }, +}; + +const applications = new Map(); + +const application: ApplicationStart = { + currentAppId$: of('fleet'), + navigateToUrl: async (url: string) => { + action(`Navigate to: ${url}`); + }, + navigateToApp: async (app: string) => { + action(`Navigate to: ${app}`); + }, + getUrlForApp: (url: string) => url, + capabilities: { + actions: { + show: true, + save: true, + execute: true, + delete: true, + }, + catalogue: {}, + management: {}, + navLinks: {}, + fleet: { + read: true, + all: true, + }, + fleetv2: { + read: true, + all: true, + }, + }, + applications$: of(applications), +}; + +export const StorybookContextDecorator: React.FC = (props) => { + const { children, context } = props; + const { globals } = context; + const { euiTheme } = globals; + + const darkMode = ['v8.dark', 'v7.dark'].includes(euiTheme); + ExperimentalFeaturesService.init({ + experimentalFeatures: { + rulesListDatagrid: true, + internalAlertsTable: true, + ruleTagFilter: true, + ruleStatusFilter: true, + rulesDetailLogs: true, + }, + }); + return ( + + + + { + if (context.componentId === 'app-ruleslist') { + return 'format:number:defaultPattern'; + } + }, + get$: () => { + if (context.componentId === 'app-ruleslist') { + return of('format:number:defaultPattern'); + } + }, + }, + application, + http: getHttp(context), + actionTypeRegistry: getActionTypeRegistry(), + ruleTypeRegistry: getRuleTypeRegistry(), + }} + > + {children} + + + + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/main.js b/x-pack/plugins/triggers_actions_ui/.storybook/main.ts similarity index 75% rename from x-pack/plugins/triggers_actions_ui/.storybook/main.js rename to x-pack/plugins/triggers_actions_ui/.storybook/main.ts index 86b48c32f103e..bf63e08d64c32 100644 --- a/x-pack/plugins/triggers_actions_ui/.storybook/main.js +++ b/x-pack/plugins/triggers_actions_ui/.storybook/main.ts @@ -5,4 +5,6 @@ * 2.0. */ -module.exports = require('@kbn/storybook').defaultConfig; +import { defaultConfig } from '@kbn/storybook'; + +module.exports = defaultConfig; diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/manager.ts b/x-pack/plugins/triggers_actions_ui/.storybook/manager.ts new file mode 100644 index 0000000000000..17fb8fc042000 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/manager.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { addons } from '@storybook/addons'; +import { create } from '@storybook/theming'; +import { PANEL_ID } from '@storybook/addon-actions'; + +addons.setConfig({ + theme: create({ + base: 'light', + brandTitle: 'Triggers Actions UI Storybook', + brandUrl: 'https://github.com/elastic/kibana/tree/main/x-pack/plugins/triggers_actions_ui', + }), + showPanel: true, + selectedPanel: PANEL_ID, +}); diff --git a/x-pack/plugins/triggers_actions_ui/.storybook/preview.tsx b/x-pack/plugins/triggers_actions_ui/.storybook/preview.tsx new file mode 100644 index 0000000000000..8f334c0dc921c --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/.storybook/preview.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { addDecorator, DecoratorFn } from '@storybook/react'; +import { Title, Subtitle, Description, Primary, Stories } from '@storybook/addon-docs'; +import { StorybookContextDecorator } from './decorator'; + +const decorator: DecoratorFn = (story, context) => { + return {story()}; +}; + +addDecorator(decorator); + +export const parameters = { + docs: { + page: () => { + <> + + <Subtitle /> + <Description /> + <Primary /> + <Stories /> + </>; + }, + }, +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.stories.tsx new file mode 100644 index 0000000000000..8bf88bed72359 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.stories.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentProps } from 'react'; +import { Meta } from '@storybook/react'; +import { RuleEventLogList, RuleEventLogListProps } from './rule_event_log_list'; +import { mockRule, mockRuleType } from './test_helpers'; + +type Args = ComponentProps<typeof RuleEventLogList>; + +const rule = mockRule({ ruleTypeId: 'test-rule-type-id' }); +const ruleType = mockRuleType(); + +export default { + title: 'app/RuleEventLogList', + component: RuleEventLogList, + argTypes: { + rule: { + control: { + type: 'object', + }, + }, + ruleType: { + control: { + type: 'object', + }, + }, + localStorageKey: { + defaultValue: 'xpack.triggersActionsUI.ruleEventLogList.initialColumns', + control: { + type: 'text', + }, + }, + refreshToken: { + control: { + type: 'number', + }, + }, + requestRefresh: {}, + fetchRuleSummary: { + defaultValue: true, + control: { + type: 'boolean', + }, + }, + ruleSummary: { + control: { + type: 'object', + }, + }, + onChangeDuration: {}, + numberOfExecutions: { + control: { + type: 'number', + }, + }, + isLoadingRuleSummary: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + }, + args: { + rule, + ruleType, + }, +} as Meta<Args>; + +const Template = (args: RuleEventLogListProps) => { + return <RuleEventLogList {...args} />; +}; + +export const Empty = Template.bind({}); + +export const WithEvents = Template.bind({}); + +export const WithPaginatedEvents = Template.bind({}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.test.tsx index 541cf94d1d539..96d9c0013fe4e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/rule_event_log_list.test.tsx @@ -19,7 +19,7 @@ import { RULE_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS, GLOBAL_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS, } from '../../../constants'; -import { mockRule, mockRuleType, mockRuleSummary } from './test_helpers'; +import { mockRule, mockRuleType, mockRuleSummary, mockLogResponse } from './test_helpers'; import { RuleType } from '../../../../types'; import { loadActionErrorLog } from '../../../lib/rule_api/load_action_error_log'; @@ -33,84 +33,6 @@ const loadActionErrorLogMock = loadActionErrorLog as unknown as jest.MockedFunct typeof loadActionErrorLog >; -const mockLogResponse: any = { - data: [ - { - id: uuid.v4(), - timestamp: '2022-03-20T07:40:44-07:00', - duration: 5000000, - status: 'success', - message: 'rule execution #1', - version: '8.2.0', - num_active_alerts: 2, - num_new_alerts: 4, - num_recovered_alerts: 3, - num_triggered_actions: 10, - num_succeeded_actions: 0, - num_errored_actions: 4, - total_search_duration: 1000000, - es_search_duration: 1400000, - schedule_delay: 2000000, - timed_out: false, - }, - { - id: uuid.v4(), - timestamp: '2022-03-20T07:40:45-07:00', - duration: 6000000, - status: 'success', - message: 'rule execution #2', - version: '8.2.0', - num_active_alerts: 4, - num_new_alerts: 2, - num_recovered_alerts: 4, - num_triggered_actions: 5, - num_succeeded_actions: 3, - num_errored_actions: 0, - total_search_duration: 300000, - es_search_duration: 300000, - schedule_delay: 300000, - timed_out: false, - }, - { - id: uuid.v4(), - timestamp: '2022-03-20T07:40:46-07:00', - duration: 340000, - status: 'failure', - message: 'rule execution #3', - version: '8.2.0', - num_active_alerts: 8, - num_new_alerts: 5, - num_recovered_alerts: 0, - num_triggered_actions: 1, - num_succeeded_actions: 1, - num_errored_actions: 4, - total_search_duration: 2300000, - es_search_duration: 2300000, - schedule_delay: 2300000, - timed_out: false, - }, - { - id: uuid.v4(), - timestamp: '2022-03-21T07:40:46-07:00', - duration: 3000000, - status: 'unknown', - message: 'rule execution #4', - version: '8.2.0', - num_active_alerts: 4, - num_new_alerts: 4, - num_recovered_alerts: 4, - num_triggered_actions: 4, - num_succeeded_actions: 4, - num_errored_actions: 4, - total_search_duration: 400000, - es_search_duration: 400000, - schedule_delay: 400000, - timed_out: false, - }, - ], - total: 4, -}; - const loadExecutionLogAggregationsMock = jest.fn(); const onChangeDurationMock = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts index 704410f6265fd..9e96487b167a4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_details/components/test_helpers.ts @@ -8,6 +8,32 @@ import uuid from 'uuid'; import { Rule, RuleSummary, RuleType } from '../../../../types'; +export const getMockLogResponse = () => { + return { + id: uuid.v4(), + timestamp: '2022-03-20T07:40:44-07:00', + duration: 5000000, + status: 'success', + message: 'rule execution #1', + version: '8.2.0', + num_active_alerts: 2, + num_new_alerts: 4, + num_recovered_alerts: 3, + num_triggered_actions: 10, + num_succeeded_actions: 0, + num_errored_actions: 4, + total_search_duration: 1000000, + es_search_duration: 1400000, + schedule_delay: 2000000, + timed_out: false, + }; +}; + +export const mockLogResponse: any = { + data: [getMockLogResponse(), getMockLogResponse(), getMockLogResponse(), getMockLogResponse()], + total: 4, +}; + export function mockRule(overloads: Partial<Rule> = {}): Rule { return { id: uuid.v4(), diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.stories.tsx new file mode 100644 index 0000000000000..401fcaf749fb1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.stories.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentProps } from 'react'; +import { Story } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { RuleStatusDropdown } from './rule_status_dropdown'; +import { mockRule } from '../../rule_details/components/test_helpers'; + +type Args = ComponentProps<typeof RuleStatusDropdown>; + +const rule = mockRule({ ruleTypeId: 'test-rule-type-id' }); + +export default { + title: 'app/RuleStatusDropdown', + component: RuleStatusDropdown, + argTypes: { + rule: { + defaultValue: rule, + control: { + type: 'object', + }, + }, + onRuleChanged: {}, + enableRule: {}, + disableRule: {}, + snoozeRule: {}, + unsnoozeRule: {}, + isEditable: { + defaultValue: true, + control: { + type: 'boolean', + }, + }, + direction: { + defaultValue: 'column', + control: { + type: 'text', + }, + }, + hideSnoozeOption: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + }, + args: { + rule, + onRuleChanged: (...args: any) => action('onRuleChanged')(args), + enableRule: (...args: any) => action('enableRule')(args), + disableRule: (...args: any) => action('disableRule')(args), + snoozeRule: (...args: any) => action('snoozeRule')(args), + unsnoozeRule: (...args: any) => action('unsnoozeRule')(args), + }, +}; + +const Template: Story<Args> = (args) => { + return <RuleStatusDropdown {...args} />; +}; + +export const EnabledRule = Template.bind({}); + +export const DisabledRule = Template.bind({}); + +DisabledRule.args = { + rule: mockRule({ enabled: false }), +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_badge.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_badge.stories.tsx new file mode 100644 index 0000000000000..4e5abf410afa1 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_badge.stories.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentProps } from 'react'; +import { Story } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { RuleTagBadge } from './rule_tag_badge'; + +type Args = ComponentProps<typeof RuleTagBadge>; + +export default { + title: 'app/RuleTagBadge', + component: RuleTagBadge, + argTypes: { + isOpen: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + onClick: {}, + onClose: {}, + tagsOutPopover: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + tags: { + defaultValue: ['tag1', 'tag2', 'tag3'], + control: { + type: 'object', + }, + }, + badgeDataTestSubj: { + control: { + type: 'text', + }, + }, + titleDataTestSubj: { + control: { + type: 'text', + }, + }, + tagItemDataTestSubj: { + control: { + type: 'text', + }, + }, + }, + args: { + onClick: () => action('onClick')(), + onClose: () => action('onClose')(), + }, +}; + +const Template: Story<Args> = (args) => { + return <RuleTagBadge {...args} />; +}; + +export const Default = Template.bind({}); + +export const OutPopover = Template.bind({}); +OutPopover.args = { + tagsOutPopover: true, +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_filter.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_filter.stories.tsx new file mode 100644 index 0000000000000..7e3f3f696969e --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_tag_filter.stories.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentProps } from 'react'; +import { Story } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { RuleTagFilter } from './rule_tag_filter'; + +type Args = ComponentProps<typeof RuleTagFilter>; + +export default { + title: 'app/RuleTagFilter', + component: RuleTagFilter, + argTypes: { + tags: { + defaultValue: ['tag1', 'tag2', 'tag3'], + control: { + type: 'object', + }, + }, + selectedTags: { + defaultValue: [], + control: { + type: 'object', + }, + }, + isGrouped: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + isLoading: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + loadingMessage: { + control: { + type: 'text', + }, + }, + noMatchesMessage: { + control: { + type: 'text', + }, + }, + emptyMessage: { + control: { + type: 'text', + }, + }, + errorMessage: { + control: { + type: 'text', + }, + }, + dataTestSubj: { + control: { + type: 'text', + }, + }, + selectableDataTestSubj: { + control: { + type: 'text', + }, + }, + optionDataTestSubj: { + control: { + type: 'text', + }, + }, + buttonDataTestSubj: { + control: { + type: 'text', + }, + }, + onChange: {}, + }, + args: { + onChange: (...args: any) => action('onChange')(args), + }, +}; + +const Template: Story<Args> = (args) => { + return <RuleTagFilter {...args} />; +}; + +export const Default = Template.bind({}); + +export const Selected = Template.bind({}); + +Selected.args = { + selectedTags: ['tag1'], +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.stories.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.stories.tsx new file mode 100644 index 0000000000000..487da3e973653 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.stories.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { ComponentProps, useEffect } from 'react'; +import { Meta } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import { MemoryRouter, useLocation } from 'react-router-dom'; +import { RulesList, RulesListProps } from './rules_list'; + +type Args = ComponentProps<typeof RulesList>; + +export default { + title: 'app/RulesList', + component: RulesList, + decorators: [ + (StoryComponent) => { + return ( + <MemoryRouter> + <StoryComponent /> + </MemoryRouter> + ); + }, + ], + argTypes: { + filteredRuleTypes: { + defaultValue: [], + control: { + type: 'object', + }, + }, + showActionFilter: { + defaultValue: true, + control: { + type: 'boolean', + }, + }, + showCreateRuleButton: { + defaultValue: true, + control: { + type: 'boolean', + }, + }, + ruleDetailsRoute: { + control: { + type: 'text', + }, + }, + statusFilter: { + defaultValue: [], + control: { + type: 'object', + }, + }, + lastResponseFilter: { + defaultValue: [], + control: { + type: 'object', + }, + }, + onStatusFilterChange: { + action: 'onStatusFilterChange', + }, + onLastResponseFilterChange: { + action: 'onLastResponseFilterChange', + }, + refresh: { + control: { + type: 'date', + }, + }, + rulesListKey: { + control: { + type: 'text', + }, + }, + visibleColumns: { + defaultValue: [ + 'ruleName', + 'ruleTags', + 'ruleExecutionStatusLastDate', + 'ruleSnoozeNotify', + 'ruleScheduleInterval', + 'ruleExecutionStatusLastDuration', + 'ruleExecutionPercentile', + 'ruleExecutionSuccessRatio', + 'ruleExecutionStatus', + 'ruleExecutionState', + ], + control: { + type: 'object', + }, + }, + }, +} as Meta<Args>; + +const Template = (args: RulesListProps) => { + const location = useLocation(); + useEffect(() => { + action('location')(location); + }, [location]); + return <RulesList {...args} />; +}; + +export const Empty = Template.bind({}); + +export const WithRules = Template.bind({}); + +export const WithPaginatedRules = Template.bind({}); diff --git a/x-pack/plugins/triggers_actions_ui/tsconfig.json b/x-pack/plugins/triggers_actions_ui/tsconfig.json index 8618be6c9c285..c98e5f1dfd511 100644 --- a/x-pack/plugins/triggers_actions_ui/tsconfig.json +++ b/x-pack/plugins/triggers_actions_ui/tsconfig.json @@ -7,6 +7,7 @@ "declarationMap": true }, "include": [ + ".storybook/**/*", "server/**/*", "public/**/*", "common/**/*", From 392f49020baa191a7ac5eb84ae2ba2d60d38385c Mon Sep 17 00:00:00 2001 From: Lola <omolola.akinleye@elastic.co> Date: Mon, 3 Oct 2022 16:07:49 -0400 Subject: [PATCH 005/174] fix: missing metadata info from text-ouput events (#142392) --- .../public/components/session_view_detail_panel/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx b/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx index 947af14db0d2f..7eb01bfb5223f 100644 --- a/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx +++ b/x-pack/plugins/session_view/public/components/session_view_detail_panel/index.tsx @@ -66,10 +66,10 @@ export const SessionViewDetailPanel = ({ }), content: ( <DetailPanelMetadataTab - processHost={selectedProcess?.events[0]?.host} - processContainer={selectedProcess?.events[0]?.container} - processOrchestrator={selectedProcess?.events[0]?.orchestrator} - processCloud={selectedProcess?.events[0]?.cloud} + processHost={selectedProcess?.getDetails()?.host} + processContainer={selectedProcess?.getDetails()?.container} + processOrchestrator={selectedProcess?.getDetails()?.orchestrator} + processCloud={selectedProcess?.getDetails()?.cloud} /> ), }, From fdba8d3a560fde90082c639f0e1f3ff27443c7ce Mon Sep 17 00:00:00 2001 From: "Quynh Nguyen (Quinn)" <43350163+qn895@users.noreply.github.com> Date: Mon, 3 Oct 2022 16:20:39 -0500 Subject: [PATCH 006/174] [ML] Fix Index data visualizer doc count when time field is not defined (#142409) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../requests/get_document_stats.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts index e6ef6f2d77831..7b2ef96ba2b72 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts @@ -121,7 +121,7 @@ export const getDocumentCountStats = async ( }, }); - const getSearchParams = (aggregations: unknown) => ({ + const getSearchParams = (aggregations: unknown, trackTotalHits = false) => ({ index, body: { query, @@ -133,13 +133,17 @@ export const getDocumentCountStats = async ( : {}), ...(isPopulatedObject(runtimeFieldMap) ? { runtime_mappings: runtimeFieldMap } : {}), }, - track_total_hits: false, + track_total_hits: trackTotalHits, size: 0, }); const firstResp = await search .search( { - params: getSearchParams(getAggsWithRandomSampling(initialDefaultProbability)), + params: getSearchParams( + getAggsWithRandomSampling(initialDefaultProbability), + // Track total hits if time field is not defined + timeFieldName === undefined + ), }, searchOptions ) @@ -152,6 +156,22 @@ export const getDocumentCountStats = async ( )}` ); } + + // If time field is not defined, no need to show the document count chart + // Just need to return the tracked total hits + if (timeFieldName === undefined) { + const trackedTotalHits = + typeof firstResp.rawResponse.hits.total === 'number' + ? firstResp.rawResponse.hits.total + : firstResp.rawResponse.hits.total?.value; + return { + ...result, + randomlySampled: false, + took: firstResp.rawResponse.took, + totalCount: trackedTotalHits ?? 0, + }; + } + if (isDefined(probability)) { return { ...result, From 44d028fdf871292e74948ac8cd6650d1418e0929 Mon Sep 17 00:00:00 2001 From: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Mon, 3 Oct 2022 17:53:07 -0400 Subject: [PATCH 007/174] [Security Solution] Trusted Apps about text updated to add new docs link (#142467) --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../artifact_list_page/artifact_list_page.tsx | 23 ++++++++++- .../components/no_data_empty_state.tsx | 16 +++++++- .../view/components/artifacts_docs_link.tsx | 40 +++++++++++++++++++ .../trusted_apps/view/components/form.tsx | 11 ++++- .../pages/trusted_apps/view/translations.ts | 2 +- .../trusted_apps/view/trusted_apps_list.tsx | 6 ++- 8 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/artifacts_docs_link.tsx diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 3a0b89c1f0d1b..8ef5a68a3f98c 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -363,6 +363,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { eventFilters: `${SECURITY_SOLUTION_DOCS}event-filters.html`, blocklist: `${SECURITY_SOLUTION_DOCS}blocklist.html`, threatIntelInt: `${SECURITY_SOLUTION_DOCS}es-threat-intel-integrations.html`, + endpointArtifacts: `${SECURITY_SOLUTION_DOCS}endpoint-artifacts.html`, policyResponseTroubleshooting: { full_disk_access: `${SECURITY_SOLUTION_DOCS}deploy-elastic-endpoint.html#enable-fda-endpoint`, macos_system_ext: `${SECURITY_SOLUTION_DOCS}deploy-elastic-endpoint.html#system-extension-endpoint`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index 7cd785ee194fa..aed1b552bdb30 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -265,6 +265,7 @@ export interface DocLinks { readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; + readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx index 0586034d15550..344dbd6cd8349 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/artifact_list_page.tsx @@ -74,6 +74,7 @@ export interface ArtifactListPageProps { allowCardEditAction?: boolean; allowCardDeleteAction?: boolean; allowCardCreateAction?: boolean; + secondaryPageInfo?: React.ReactNode; } export const ArtifactListPage = memo<ArtifactListPageProps>( @@ -82,6 +83,7 @@ export const ArtifactListPage = memo<ArtifactListPageProps>( ArtifactFormComponent, searchableFields = DEFAULT_EXCEPTION_LIST_ITEM_SEARCHABLE_FIELDS, labels: _labels = {}, + secondaryPageInfo, onFormSubmit, flyoutSize, 'data-test-subj': dataTestSubj, @@ -240,6 +242,24 @@ export const ArtifactListPage = memo<ArtifactListPageProps>( setSelectedItemForEdit(undefined); }, []); + const description = useMemo(() => { + const subtitleText = labels.pageAboutInfo ? ( + <span data-test-subj="header-panel-subtitle">{labels.pageAboutInfo}</span> + ) : undefined; + const detailedPageInfoElement = secondaryPageInfo ? ( + <> + <EuiSpacer size="m" /> + {secondaryPageInfo} + </> + ) : undefined; + return ( + <> + {subtitleText} + {detailedPageInfoElement} + </> + ); + }, [labels.pageAboutInfo, secondaryPageInfo]); + if (isPageInitializing) { return <ManagementPageLoader data-test-subj={getTestId('pageLoader')} />; } @@ -249,7 +269,7 @@ export const ArtifactListPage = memo<ArtifactListPageProps>( headerBackComponent={backButtonHeaderComponent} hideHeader={!doesDataExist} title={labels.pageTitle} - subtitle={labels.pageAboutInfo} + subtitle={description} actions={ allowCardCreateAction && ( <EuiButton @@ -300,6 +320,7 @@ export const ArtifactListPage = memo<ArtifactListPageProps>( primaryButtonLabel={labels.emptyStatePrimaryButtonLabel} backComponent={backButtonEmptyComponent} data-test-subj={getTestId('emptyState')} + secondaryAboutInfo={secondaryPageInfo} /> ) : ( <> diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/no_data_empty_state.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/no_data_empty_state.tsx index e2dfd992f0e80..87fb9414b894a 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/no_data_empty_state.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/no_data_empty_state.tsx @@ -7,7 +7,7 @@ import React, { memo } from 'react'; import styled, { css } from 'styled-components'; -import { EuiButton, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiButton, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import { ManagementEmptyStateWrapper } from '../../management_empty_state_wrapper'; import { useTestIdGenerator } from '../../../hooks/use_test_id_generator'; @@ -25,6 +25,7 @@ export const NoDataEmptyState = memo<{ /** Should the Add button be disabled */ isAddDisabled?: boolean; backComponent?: React.ReactNode; + secondaryAboutInfo?: React.ReactNode; 'data-test-subj'?: string; }>( ({ @@ -35,6 +36,7 @@ export const NoDataEmptyState = memo<{ titleLabel, aboutInfo, primaryButtonLabel, + secondaryAboutInfo, }) => { const getTestId = useTestIdGenerator(dataTestSubj); @@ -44,7 +46,17 @@ export const NoDataEmptyState = memo<{ data-test-subj={dataTestSubj} iconType="plusInCircle" title={<h2 data-test-subj={getTestId('title')}>{titleLabel}</h2>} - body={<div data-test-subj={getTestId('aboutInfo')}>{aboutInfo}</div>} + body={ + <div data-test-subj={getTestId('aboutInfo')}> + {aboutInfo} + {secondaryAboutInfo ? ( + <> + <EuiSpacer size="m" /> + {secondaryAboutInfo} + </> + ) : undefined} + </div> + } actions={[ <EuiButton fill diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/artifacts_docs_link.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/artifacts_docs_link.tsx new file mode 100644 index 0000000000000..aed23a9f1e30e --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/artifacts_docs_link.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { memo } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiLink } from '@elastic/eui'; +import { useKibana } from '../../../../../common/lib/kibana'; + +export const TrustedAppsArtifactsDocsLink = memo(() => { + const { + docLinks: { + links: { securitySolution }, + }, + } = useKibana().services; + + return ( + <> + <FormattedMessage + id="xpack.securitySolution.trustedApps.docsLinkInfoStart" + defaultMessage="Have too many alerts? Add an " + /> + <EuiLink target="_blank" href={`${securitySolution.endpointArtifacts}`}> + <FormattedMessage + id="xpack.securitySolution.trustedApps.docsLinkText" + defaultMessage="endpoint alert exception" + /> + </EuiLink> + <FormattedMessage + id="xpack.securitySolution.trustedApps.docsLinkInfoEnd" + defaultMessage=" instead." + /> + </> + ); +}); + +TrustedAppsArtifactsDocsLink.displayName = 'TrustedAppsArtifactsDocsLink'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx index 333e0da92cceb..90e1dcc1c0c89 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/form.tsx @@ -71,6 +71,7 @@ import { } from '../../../../../../common/endpoint/service/artifacts/constants'; import type { ArtifactFormComponentProps } from '../../../../components/artifact_list_page'; import { isGlobalPolicyEffected } from '../../../../components/effected_policy_select/utils'; +import { TrustedAppsArtifactsDocsLink } from './artifacts_docs_link'; interface FieldValidationState { /** If this fields state is invalid. Drives display of errors on the UI */ @@ -419,7 +420,15 @@ export const TrustedAppsForm = memo<ArtifactFormComponentProps>( <EuiSpacer size="xs" /> {mode === 'create' && ( <EuiText size="s" data-test-subj={getTestId('about')}> - <p>{DETAILS_HEADER_DESCRIPTION}</p> + <p> + {DETAILS_HEADER_DESCRIPTION} + { + <> + <EuiSpacer size="m" /> + <TrustedAppsArtifactsDocsLink /> + </> + } + </p> </EuiText> )} <EuiSpacer size="m" /> diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts index c19b3c78d0f8c..02ada2533f9b8 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/translations.ts @@ -25,7 +25,7 @@ export const DETAILS_HEADER_DESCRIPTION = i18n.translate( 'xpack.securitySolution.trustedApps.details.header.description', { defaultMessage: - 'Trusted applications improve performance or alleviate conflicts with other applications running on your hosts.', + 'Add a trusted application to improve performance or alleviate conflicts with other applications running on your hosts. Trusted applications may still generate alerts in some cases.', } ); diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx index acb4c4ae13bce..33912a5b795c4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_apps_list.tsx @@ -17,6 +17,7 @@ import { ArtifactListPage } from '../../../components/artifact_list_page'; import { TrustedAppsApiClient } from '../service'; import { TrustedAppsForm } from './components/form'; import { SEARCHABLE_FIELDS } from '../constants'; +import { TrustedAppsArtifactsDocsLink } from './components/artifacts_docs_link'; const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageProps['labels'] = { pageTitle: i18n.translate('xpack.securitySolution.trustedApps.pageTitle', { @@ -24,7 +25,7 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageProps['labels'] = { }), pageAboutInfo: i18n.translate('xpack.securitySolution.trustedApps.pageAboutInfo', { defaultMessage: - 'Trusted applications improve performance or alleviate conflicts with other applications running on your hosts.', + 'Add a trusted application to improve performance or alleviate conflicts with other applications running on your hosts. Trusted applications may still generate alerts in some cases.', }), pageAddButtonTitle: i18n.translate('xpack.securitySolution.trustedApps.pageAddButtonTitle', { defaultMessage: 'Add trusted application', @@ -92,7 +93,7 @@ const TRUSTED_APPS_PAGE_LABELS: ArtifactListPageProps['labels'] = { }), emptyStateInfo: i18n.translate('xpack.securitySolution.trustedApps.emptyStateInfo', { defaultMessage: - 'Add a trusted application to improve performance or alleviate conflicts with other applications running on your hosts.', + 'Add a trusted application to improve performance or alleviate conflicts with other applications running on your hosts. Trusted applications may still generate alerts in some cases.', }), emptyStatePrimaryButtonLabel: i18n.translate( 'xpack.securitySolution.trustedApps.emptyStatePrimaryButtonLabel', @@ -117,6 +118,7 @@ export const TrustedAppsList = memo(() => { labels={TRUSTED_APPS_PAGE_LABELS} data-test-subj="trustedAppsListPage" searchableFields={SEARCHABLE_FIELDS} + secondaryPageInfo={<TrustedAppsArtifactsDocsLink />} /> ); }); From 9a8008b00b5cd4f68c7a39a681a69e940094f8ad Mon Sep 17 00:00:00 2001 From: Rodney Norris <rodney.norris@elastic.co> Date: Mon, 3 Oct 2022 16:54:22 -0500 Subject: [PATCH 008/174] [Enterprise Search] pipelines copy tweaks (#142406) Updated copy on the pipelines tab and modal when using an API-based index to explicitly call-out required actions in API requests to run the ingest pipeline. --- .../pipelines/ingest_pipeline_modal.tsx | 53 ++++++++++++----- .../search_index/pipelines/pipelines.tsx | 59 ++++++++++++++----- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx index b60da157ebf1f..a245c5db97882 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipeline_modal.tsx @@ -25,6 +25,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; import { DEFAULT_PIPELINE_NAME } from '../../../../../../common/constants'; @@ -91,21 +92,43 @@ export const IngestPipelineModal: React.FC<IngestPipelineModalProps> = ({ <EuiFlexGroup direction="column" gutterSize="none"> <EuiFlexItem> <EuiText color="subdued" size="s"> - {displayOnly - ? i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyAPIText', - { - defaultMessage: - 'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search. To use this configuration on API-based indices you can use the sample cURL request below.', - } - ) - : i18n.translate( - 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyConnectorText', - { - defaultMessage: - 'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search.', - } - )} + {displayOnly ? ( + <> + <p> + <FormattedMessage + id="xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyAPIText" + defaultMessage="{apiIndex} Changes made to the settings below are for reference only. These settings will not be persisted to your index or pipeline." + values={{ + apiIndex: ( + <strong> + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.apiIndex', + { defaultMessage: 'This is an API-based index.' } + )} + </strong> + ), + }} + /> + </p> + <p> + {i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyAPITextCont', + { + defaultMessage: + "In order to use this pipeline on your API-based indices you'll need to explicitly reference it in your API requests.", + } + )} + </p> + </> + ) : ( + i18n.translate( + 'xpack.enterpriseSearch.content.index.pipelines.ingestModal.modalBodyConnectorText', + { + defaultMessage: + 'This pipeline runs automatically on all Crawler and Connector indices created through Enterprise Search.', + } + ) + )} </EuiText> </EuiFlexItem> <EuiSpacer /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx index 949e9610954c1..07be63b54f3b5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx @@ -15,6 +15,7 @@ import { i18n } from '@kbn/i18n'; import { DataPanel } from '../../../../shared/data_panel/data_panel'; import { docLinks } from '../../../../shared/doc_links'; +import { isApiIndex } from '../../../utils/indices'; import { IngestPipelinesCard } from './ingest_pipelines_card'; import { AddMLInferencePipelineButton } from './ml_inference/add_ml_inference_button'; @@ -23,9 +24,15 @@ import { MlInferencePipelineProcessorsCard } from './ml_inference_pipeline_proce import { PipelinesLogic } from './pipelines_logic'; export const SearchIndexPipelines: React.FC = () => { - const { showAddMlInferencePipelineModal } = useValues(PipelinesLogic); + const { + showAddMlInferencePipelineModal, + hasIndexIngestionPipeline, + index, + pipelineState: { name: pipelineName }, + } = useValues(PipelinesLogic); const { closeAddMlInferencePipelineModal, openAddMlInferencePipelineModal } = useActions(PipelinesLogic); + const apiIndex = isApiIndex(index); return ( <> @@ -54,12 +61,23 @@ export const SearchIndexPipelines: React.FC = () => { )} </h2> } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.ingestionPipeline.subtitle', - { - defaultMessage: 'Ingest pipelines optimize your index for search applications', - } - )} + subtitle={ + apiIndex + ? i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestionPipeline.apiIndexSubtitle', + { + defaultMessage: + "Ingest pipelines optimize your index for search applications. If you'd like to use these pipelines in your API-based index, you'll need to reference them explicitly in your API requests.", + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.ingestionPipeline.subtitle', + { + defaultMessage: + 'Ingest pipelines optimize your index for search applications', + } + ) + } iconType="logstashInput" > <IngestPipelinesCard /> @@ -88,13 +106,26 @@ export const SearchIndexPipelines: React.FC = () => { )} </h2> } - subtitle={i18n.translate( - 'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.subtitle', - { - defaultMessage: - 'Inference pipelines will be run as processors from the Enterprise Search Ingest Pipeline', - } - )} + subtitle={ + apiIndex && hasIndexIngestionPipeline + ? i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.subtitleAPIindex', + { + defaultMessage: + "Inference pipelines will be run as processors from the Enterprise Search Ingest Pipeline. In order to use these pipeline on API-based indices you'll need to reference the {pipelineName} pipeline in your API requests.", + values: { + pipelineName, + }, + } + ) + : i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.subtitle', + { + defaultMessage: + 'Inference pipelines will be run as processors from the Enterprise Search Ingest Pipeline', + } + ) + } iconType="compute" action={ <AddMLInferencePipelineButton onClick={() => openAddMlInferencePipelineModal()} /> From 01113b265bd1d90f086c0c2e1286201f201b1365 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski <tomasz.ciecierski@elastic.co> Date: Tue, 4 Oct 2022 02:47:29 +0200 Subject: [PATCH 009/174] [Osquery] Another batch of small fixes (#142193) Co-authored-by: Patryk Kopycinski <contact@patrykkopycinski.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../cypress/e2e/roles/alert_test.cy.ts | 104 ++++++++++++------ .../cypress/e2e/roles/t1_analyst.cy.ts | 2 +- .../fixtures/saved_objects/rule.ndjson | 3 +- x-pack/plugins/osquery/kibana.json | 1 + .../osquery/public/actions/actions_table.tsx | 41 ++++++- .../public/common/schemas/ecs/v8.4.0.json | 1 - .../public/common/schemas/ecs/v8.5.0.json | 1 + .../public/live_queries/form/index.tsx | 25 +++-- .../osquery/public/live_queries/index.tsx | 5 +- .../osquery/public/packs/packs_table.tsx | 17 ++- .../queries/ecs_mapping_editor_field.tsx | 55 ++++----- .../public/packs/queries/query_flyout.tsx | 4 +- .../public/routes/saved_queries/edit/form.tsx | 4 +- .../routes/saved_queries/list/index.tsx | 56 ++++++---- .../public/routes/saved_queries/new/form.tsx | 4 +- .../scripts/roles_users/t1_analyst/role.json | 10 ++ .../scripts/schema_formatter/ecs_formatter.ts | 2 +- .../lib/osquery_app_context_services.ts | 8 ++ x-pack/plugins/osquery/server/plugin.ts | 1 + .../live_query/create_live_query_route.ts | 42 ++++++- .../routes/live_query/osquery_parser.ts | 77 +++++++++++++ x-pack/plugins/osquery/server/types.ts | 2 + .../markdown_editor/plugins/osquery/index.tsx | 3 +- .../timeline_actions/alert_context_menu.tsx | 8 +- .../event_details/flyout/footer.tsx | 7 +- 25 files changed, 365 insertions(+), 118 deletions(-) delete mode 100644 x-pack/plugins/osquery/public/common/schemas/ecs/v8.4.0.json create mode 100644 x-pack/plugins/osquery/public/common/schemas/ecs/v8.5.0.json create mode 100644 x-pack/plugins/osquery/server/routes/live_query/osquery_parser.ts diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts index 5d25b6599b13c..3adffecd77848 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/alert_test.cy.ts @@ -8,7 +8,12 @@ import { ROLES } from '../../test'; import { ArchiverMethod, runKbnArchiverScript } from '../../tasks/archiver'; import { login } from '../../tasks/login'; -import { findAndClickButton, findFormFieldByRowsLabelAndType } from '../../tasks/live_query'; +import { + checkResults, + findAndClickButton, + findFormFieldByRowsLabelAndType, + submitQuery, +} from '../../tasks/live_query'; import { preparePack } from '../../tasks/packs'; import { closeModalIfVisible } from '../../tasks/integrations'; import { navigateTo } from '../../tasks/navigation'; @@ -18,43 +23,76 @@ describe('Alert_Test', () => { runKbnArchiverScript(ArchiverMethod.LOAD, 'pack'); runKbnArchiverScript(ArchiverMethod.LOAD, 'rule'); }); - beforeEach(() => { - login(ROLES.alert_test); - }); after(() => { runKbnArchiverScript(ArchiverMethod.UNLOAD, 'pack'); runKbnArchiverScript(ArchiverMethod.UNLOAD, 'rule'); }); - it('should be able to run live query', () => { - const PACK_NAME = 'testpack'; - const RULE_NAME = 'Test-rule'; - navigateTo('/app/osquery'); - preparePack(PACK_NAME); - findAndClickButton('Edit'); - cy.contains(`Edit ${PACK_NAME}`); - findFormFieldByRowsLabelAndType( - 'Scheduled agent policies (optional)', - 'fleet server {downArrow}{enter}' - ); - findAndClickButton('Update pack'); - closeModalIfVisible(); - cy.contains(PACK_NAME); - cy.visit('/app/security/rules'); - cy.contains(RULE_NAME).click(); - cy.wait(2000); - cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); - cy.getBySel('ruleSwitch').click(); - cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'false'); - cy.getBySel('ruleSwitch').click(); - cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); - cy.visit('/app/security/alerts'); - cy.getBySel('expand-event').first().click(); - cy.getBySel('take-action-dropdown-btn').click(); - cy.getBySel('osquery-action-item').click(); - - cy.contains('Run Osquery'); - cy.contains('Permission denied'); + describe('alert_test role', () => { + it('should not be able to run live query', () => { + login(ROLES.alert_test); + + const PACK_NAME = 'testpack'; + const RULE_NAME = 'Test-rule'; + navigateTo('/app/osquery'); + preparePack(PACK_NAME); + findAndClickButton('Edit'); + cy.contains(`Edit ${PACK_NAME}`); + findFormFieldByRowsLabelAndType( + 'Scheduled agent policies (optional)', + 'fleet server {downArrow}{enter}' + ); + findAndClickButton('Update pack'); + closeModalIfVisible(); + cy.contains(PACK_NAME); + cy.visit('/app/security/rules'); + cy.contains(RULE_NAME).click(); + cy.wait(2000); + cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); + cy.getBySel('ruleSwitch').click(); + cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'false'); + cy.getBySel('ruleSwitch').click(); + cy.getBySel('ruleSwitch').should('have.attr', 'aria-checked', 'true'); + cy.visit('/app/security/alerts'); + cy.getBySel('expand-event').first().click(); + cy.getBySel('take-action-dropdown-btn').click(); + cy.getBySel('osquery-action-item').click(); + + cy.contains('Run Osquery'); + cy.contains('Permission denied'); + }); + }); + + describe('t1_analyst role', () => { + it('should be able to run rule investigation guide query', () => { + login(ROLES.t1_analyst); + + navigateTo('/app/osquery'); + + cy.visit('/app/security/alerts'); + cy.getBySel('expand-event').first().click(); + + cy.contains('Get processes').click(); + submitQuery(); + checkResults(); + }); + + it('should not be able to run custom query', () => { + login(ROLES.t1_analyst); + + navigateTo('/app/osquery'); + + cy.visit('/app/security/alerts'); + cy.getBySel('expand-event').first().click(); + + cy.contains('Get processes').click(); + + cy.intercept('POST', '/api/osquery/live_queries', (req) => { + req.body.query = 'select * from processes limit 10'; + }); + submitQuery(); + cy.contains('Forbidden'); + }); }); }); diff --git a/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts b/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts index 8cd90d200bca7..2df197f5f63ce 100644 --- a/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts +++ b/x-pack/plugins/osquery/cypress/e2e/roles/t1_analyst.cy.ts @@ -50,7 +50,7 @@ describe('T1 Analyst - READ + runSavedQueries ', () => { cy.contains('New live query').should('not.be.disabled'); cy.contains('select * from uptime'); cy.wait(1000); - cy.react('EuiTableBody').first().react('DefaultItemAction').first().click(); + cy.react('EuiTableBody').first().react('CustomItemAction').first().click(); cy.contains(SAVED_QUERY_ID); submitQuery(); checkResults(); diff --git a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson index f688dc0731c7f..d1804c3aafec6 100644 --- a/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson +++ b/x-pack/plugins/osquery/cypress/fixtures/saved_objects/rule.ndjson @@ -47,7 +47,8 @@ "winlogbeat-*" ], "query": "_id:*", - "filters": [] + "filters": [], + "note": "!{osquery{\"query\":\"SELECT * FROM processes;\",\"label\":\"Get processes\",\"ecs_mapping\":{\"process.pid\":{\"field\":\"pid\"},\"process.name\":{\"field\":\"name\"},\"process.executable\":{\"field\":\"path\"},\"process.args\":{\"field\":\"cmdline\"},\"process.working_directory\":{\"field\":\"cwd\"},\"user.id\":{\"field\":\"uid\"},\"group.id\":{\"field\":\"gid\"},\"process.parent.pid\":{\"field\":\"parent\"},\"process.pgid\":{\"field\":\"pgroup\"}}}}\n\n!{osquery{\"query\":\"select * from users;\",\"label\":\"Get users\"}}" }, "schedule": { "interval": "5m" diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json index 63e7718368ce1..ec5443abd6fb1 100644 --- a/x-pack/plugins/osquery/kibana.json +++ b/x-pack/plugins/osquery/kibana.json @@ -19,6 +19,7 @@ "navigation", "taskManager", "triggersActionsUi", + "ruleRegistry", "security" ], "server": true, diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx index f34a775edf8f1..51eadd954cc4d 100644 --- a/x-pack/plugins/osquery/public/actions/actions_table.tsx +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -15,6 +15,7 @@ import { EuiIcon, EuiFlexItem, EuiFlexGroup, + EuiToolTip, } from '@elastic/eui'; import React, { useState, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; @@ -34,7 +35,18 @@ interface ActionTableResultsButtonProps { const ActionTableResultsButton: React.FC<ActionTableResultsButtonProps> = ({ actionId }) => { const navProps = useRouterNavigate(`live_queries/${actionId}`); - return <EuiButtonIcon iconType="visTable" {...navProps} />; + const detailsText = i18n.translate( + 'xpack.osquery.liveQueryActions.table.viewDetailsActionButton', + { + defaultMessage: 'Details', + } + ); + + return ( + <EuiToolTip position="top" content={detailsText}> + <EuiButtonIcon iconType="visTable" {...navProps} aria-label={detailsText} /> + </EuiToolTip> + ); }; ActionTableResultsButton.displayName = 'ActionTableResultsButton'; @@ -100,7 +112,7 @@ const ActionsTableComponent = () => { ); const handlePlayClick = useCallback( - (item) => { + (item) => () => { const packId = item._source.pack_id; if (packId) { @@ -139,6 +151,25 @@ const ActionsTableComponent = () => { }, [push] ); + const renderPlayButton = useCallback( + (item, enabled) => { + const playText = i18n.translate('xpack.osquery.liveQueryActions.table.runActionAriaLabel', { + defaultMessage: 'Run query', + }); + + return ( + <EuiToolTip position="top" content={playText}> + <EuiButtonIcon + iconType="play" + onClick={handlePlayClick(item)} + isDisabled={!enabled} + aria-label={playText} + /> + </EuiToolTip> + ); + }, + [handlePlayClick] + ); const existingPackIds = useMemo(() => map(packsData?.data ?? [], 'id'), [packsData]); @@ -197,10 +228,8 @@ const ActionsTableComponent = () => { }), actions: [ { - type: 'icon', - icon: 'play', - onClick: handlePlayClick, available: isPlayButtonAvailable, + render: renderPlayButton, }, { render: renderActionsColumn, @@ -209,11 +238,11 @@ const ActionsTableComponent = () => { }, ], [ - handlePlayClick, isPlayButtonAvailable, renderActionsColumn, renderAgentsColumn, renderCreatedByColumn, + renderPlayButton, renderQueryColumn, renderTimestampColumn, ] diff --git a/x-pack/plugins/osquery/public/common/schemas/ecs/v8.4.0.json b/x-pack/plugins/osquery/public/common/schemas/ecs/v8.4.0.json deleted file mode 100644 index 212a0f6b44b23..0000000000000 --- a/x-pack/plugins/osquery/public/common/schemas/ecs/v8.4.0.json +++ /dev/null @@ -1 +0,0 @@ -[{"field":"labels","type":"object","normalization":"","example":{"application":"foo-bar","env":"production"},"description":"Custom key/value pairs."},{"field":"message","type":"match_only_text","normalization":"","example":"Hello World","description":"Log message optimized for viewing in a log viewer."},{"field":"tags","type":"keyword","normalization":"array","example":["production","env2"],"description":"List of keywords used to tag each event."},{"field":"agent.build.original","type":"keyword","normalization":"","example":"metricbeat version 7.6.0 (amd64), libbeat 7.6.0 [6a23e8f8f30f5001ba344e4e54d8d9cb82cb107c built 2020-02-05 23:10:10 +0000 UTC]","description":"Extended build information for the agent."},{"field":"client.address","type":"keyword","normalization":"","example":"","description":"Client network address."},{"field":"client.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"client.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"client.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"client.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the client to the server."},{"field":"client.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the client."},{"field":"client.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"client.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"client.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"client.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"client.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"client.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"client.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"client.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"client.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"client.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"client.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"client.ip","type":"ip","normalization":"","example":"","description":"IP address of the client."},{"field":"client.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the client."},{"field":"client.nat.ip","type":"ip","normalization":"","example":"","description":"Client NAT ip address"},{"field":"client.nat.port","type":"long","normalization":"","example":"","description":"Client NAT port"},{"field":"client.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the client to the server."},{"field":"client.port","type":"long","normalization":"","example":"","description":"Port of the client."},{"field":"client.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered client domain, stripped of the subdomain."},{"field":"client.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"client.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"client.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"client.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"client.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"client.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"client.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"client.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"client.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"client.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"client.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"client.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"client.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"client.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"cloud.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.origin.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.origin.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.origin.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.origin.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.origin.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.origin.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.origin.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.origin.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.origin.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.origin.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.origin.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"cloud.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"cloud.target.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.target.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.target.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.target.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.target.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.target.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.target.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.target.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.target.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.target.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.target.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"container.cpu.usage","type":"scaled_float","normalization":"","example":"","description":"Percent CPU used, between 0 and 1."},{"field":"container.disk.read.bytes","type":"long","normalization":"","example":"","description":"The number of bytes read by all disks."},{"field":"container.disk.write.bytes","type":"long","normalization":"","example":"","description":"The number of bytes written on all disks."},{"field":"container.id","type":"keyword","normalization":"","example":"","description":"Unique container id."},{"field":"container.image.hash.all","type":"keyword","normalization":"array","example":"[sha256:f8fefc80e3273dc756f288a63945820d6476ad64883892c771b5e2ece6bf1b26]","description":"An array of digests of the image the container was built on."},{"field":"container.image.name","type":"keyword","normalization":"","example":"","description":"Name of the image the container was built on."},{"field":"container.image.tag","type":"keyword","normalization":"array","example":"","description":"Container image tags."},{"field":"container.labels","type":"object","normalization":"","example":"","description":"Image labels."},{"field":"container.memory.usage","type":"scaled_float","normalization":"","example":"","description":"Percent memory used, between 0 and 1."},{"field":"container.name","type":"keyword","normalization":"","example":"","description":"Container name."},{"field":"container.network.egress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes sent on all network interfaces."},{"field":"container.network.ingress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes received on all network interfaces."},{"field":"container.runtime","type":"keyword","normalization":"","example":"docker","description":"Runtime managing this container."},{"field":"data_stream.dataset","type":"constant_keyword","normalization":"","example":"nginx.access","description":"The field can contain anything that makes sense to signify the source of the data."},{"field":"data_stream.namespace","type":"constant_keyword","normalization":"","example":"production","description":"A user defined namespace. Namespaces are useful to allow grouping of data."},{"field":"data_stream.type","type":"constant_keyword","normalization":"","example":"logs","description":"An overarching type for the data stream."},{"field":"destination.address","type":"keyword","normalization":"","example":"","description":"Destination network address."},{"field":"destination.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"destination.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"destination.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"destination.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the destination to the source."},{"field":"destination.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the destination."},{"field":"destination.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"destination.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"destination.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"destination.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"destination.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"destination.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"destination.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"destination.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"destination.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"destination.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"destination.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"destination.ip","type":"ip","normalization":"","example":"","description":"IP address of the destination."},{"field":"destination.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the destination."},{"field":"destination.nat.ip","type":"ip","normalization":"","example":"","description":"Destination NAT ip"},{"field":"destination.nat.port","type":"long","normalization":"","example":"","description":"Destination NAT Port"},{"field":"destination.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the destination to the source."},{"field":"destination.port","type":"long","normalization":"","example":"","description":"Port of the destination."},{"field":"destination.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered destination domain, stripped of the subdomain."},{"field":"destination.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"destination.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"destination.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"destination.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"destination.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"destination.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"destination.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"destination.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"destination.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"destination.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"destination.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"destination.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"destination.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"destination.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"dll.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"dll.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"dll.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"dll.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"dll.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"dll.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"dll.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"dll.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"dll.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"dll.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"dll.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"dll.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"dll.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"dll.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"dll.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"dll.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"dll.name","type":"keyword","normalization":"","example":"kernel32.dll","description":"Name of the library."},{"field":"dll.path","type":"keyword","normalization":"","example":"C:\\Windows\\System32\\kernel32.dll","description":"Full file path of the library."},{"field":"dll.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"dll.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"dll.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"dll.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"dll.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"dll.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"dll.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"dll.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"dns.answers","type":"object","normalization":"array","example":"","description":"Array of DNS answers."},{"field":"dns.answers.class","type":"keyword","normalization":"","example":"IN","description":"The class of DNS data contained in this resource record."},{"field":"dns.answers.data","type":"keyword","normalization":"","example":"10.10.10.10","description":"The data describing the resource."},{"field":"dns.answers.name","type":"keyword","normalization":"","example":"www.example.com","description":"The domain name to which this resource record pertains."},{"field":"dns.answers.ttl","type":"long","normalization":"","example":180,"description":"The time interval in seconds that this resource record may be cached before it should be discarded."},{"field":"dns.answers.type","type":"keyword","normalization":"","example":"CNAME","description":"The type of data contained in this resource record."},{"field":"dns.header_flags","type":"keyword","normalization":"array","example":["RD","RA"],"description":"Array of DNS header flags."},{"field":"dns.id","type":"keyword","normalization":"","example":62111,"description":"The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response."},{"field":"dns.op_code","type":"keyword","normalization":"","example":"QUERY","description":"The DNS operation code that specifies the kind of query in the message."},{"field":"dns.question.class","type":"keyword","normalization":"","example":"IN","description":"The class of records being queried."},{"field":"dns.question.name","type":"keyword","normalization":"","example":"www.example.com","description":"The name being queried."},{"field":"dns.question.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered domain, stripped of the subdomain."},{"field":"dns.question.subdomain","type":"keyword","normalization":"","example":"www","description":"The subdomain of the domain."},{"field":"dns.question.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"dns.question.type","type":"keyword","normalization":"","example":"AAAA","description":"The type of record being queried."},{"field":"dns.resolved_ip","type":"ip","normalization":"array","example":["10.10.10.10","10.10.10.11"],"description":"Array containing all IPs seen in answers.data"},{"field":"dns.response_code","type":"keyword","normalization":"","example":"NOERROR","description":"The DNS response code."},{"field":"dns.type","type":"keyword","normalization":"","example":"answer","description":"The type of DNS event captured, query or answer."},{"field":"email.attachments","type":"nested","normalization":"array","example":"","description":"List of objects describing the attachments."},{"field":"email.attachments.file.extension","type":"keyword","normalization":"","example":"txt","description":"Attachment file extension."},{"field":"email.attachments.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"email.attachments.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"email.attachments.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"email.attachments.file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"email.attachments.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"email.attachments.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"email.attachments.file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"email.attachments.file.mime_type","type":"keyword","normalization":"","example":"text/plain","description":"MIME type of the attachment file."},{"field":"email.attachments.file.name","type":"keyword","normalization":"","example":"attachment.txt","description":"Name of the attachment file."},{"field":"email.attachments.file.size","type":"long","normalization":"","example":64329,"description":"Attachment file size."},{"field":"email.bcc.address","type":"keyword","normalization":"array","example":"bcc.user1@example.com","description":"Email address of BCC recipient"},{"field":"email.cc.address","type":"keyword","normalization":"array","example":"cc.user1@example.com","description":"Email address of CC recipient"},{"field":"email.content_type","type":"keyword","normalization":"","example":"text/plain","description":"MIME type of the email message."},{"field":"email.delivery_timestamp","type":"date","normalization":"","example":"2020-11-10T22:12:34.8196921Z","description":"Date and time when message was delivered."},{"field":"email.direction","type":"keyword","normalization":"","example":"inbound","description":"Direction of the message."},{"field":"email.from.address","type":"keyword","normalization":"array","example":"sender@example.com","description":"The sender's email address."},{"field":"email.local_id","type":"keyword","normalization":"","example":"c26dbea0-80d5-463b-b93c-4e8b708219ce","description":"Unique identifier given by the source."},{"field":"email.message_id","type":"wildcard","normalization":"","example":"81ce15$8r2j59@mail01.example.com","description":"Value from the Message-ID header."},{"field":"email.origination_timestamp","type":"date","normalization":"","example":"2020-11-10T22:12:34.8196921Z","description":"Date and time the email was composed."},{"field":"email.reply_to.address","type":"keyword","normalization":"array","example":"reply.here@example.com","description":"Address replies should be delivered to."},{"field":"email.sender.address","type":"keyword","normalization":"","example":"","description":"Address of the message sender."},{"field":"email.subject","type":"keyword","normalization":"","example":"Please see this important message.","description":"The subject of the email message."},{"field":"email.subject.text","type":"match_only_text","normalization":"","example":"Please see this important message.","description":"The subject of the email message."},{"field":"email.to.address","type":"keyword","normalization":"array","example":"user1@example.com","description":"Email address of recipient"},{"field":"email.x_mailer","type":"keyword","normalization":"","example":"Spambot v2.5","description":"Application that drafted email."},{"field":"error.code","type":"keyword","normalization":"","example":"","description":"Error code describing the error."},{"field":"error.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the error."},{"field":"error.message","type":"match_only_text","normalization":"","example":"","description":"Error message."},{"field":"error.stack_trace","type":"wildcard","normalization":"","example":"","description":"The stack trace of this error in plain text."},{"field":"error.stack_trace.text","type":"match_only_text","normalization":"","example":"","description":"The stack trace of this error in plain text."},{"field":"error.type","type":"keyword","normalization":"","example":"java.lang.NullPointerException","description":"The type of the error, for example the class name of the exception."},{"field":"event.action","type":"keyword","normalization":"","example":"user-password-change","description":"The action captured by the event."},{"field":"event.category","type":"keyword","normalization":"array","example":"authentication","description":"Event category. The second categorization field in the hierarchy."},{"field":"event.code","type":"keyword","normalization":"","example":4648,"description":"Identification code for this event."},{"field":"event.created","type":"date","normalization":"","example":"2016-05-23T08:05:34.857Z","description":"Time when the event was first read by an agent or by your pipeline."},{"field":"event.dataset","type":"keyword","normalization":"","example":"apache.access","description":"Name of the dataset."},{"field":"event.duration","type":"long","normalization":"","example":"","description":"Duration of the event in nanoseconds."},{"field":"event.end","type":"date","normalization":"","example":"","description":"event.end contains the date when the event ended or when the activity was last observed."},{"field":"event.hash","type":"keyword","normalization":"","example":"123456789012345678901234567890ABCD","description":"Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity."},{"field":"event.id","type":"keyword","normalization":"","example":"8a4f500d","description":"Unique ID to describe the event."},{"field":"event.kind","type":"keyword","normalization":"","example":"alert","description":"The kind of the event. The highest categorization field in the hierarchy."},{"field":"event.original","type":"keyword","normalization":"","example":"Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100| worm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232","description":"Raw text message of entire event."},{"field":"event.outcome","type":"keyword","normalization":"","example":"success","description":"The outcome of the event. The lowest level categorization field in the hierarchy."},{"field":"event.provider","type":"keyword","normalization":"","example":"kernel","description":"Source of the event."},{"field":"event.reason","type":"keyword","normalization":"","example":"Terminated an unexpected process","description":"Reason why this event happened, according to the source"},{"field":"event.reference","type":"keyword","normalization":"","example":"https://system.example.com/event/#0001234","description":"Event reference URL"},{"field":"event.risk_score","type":"float","normalization":"","example":"","description":"Risk score or priority of the event (e.g. security solutions). Use your system's original value here."},{"field":"event.risk_score_norm","type":"float","normalization":"","example":"","description":"Normalized risk score or priority of the event (0-100)."},{"field":"event.sequence","type":"long","normalization":"","example":"","description":"Sequence number of the event."},{"field":"event.severity","type":"long","normalization":"","example":7,"description":"Numeric severity of the event."},{"field":"event.start","type":"date","normalization":"","example":"","description":"event.start contains the date when the event started or when the activity was first observed."},{"field":"event.timezone","type":"keyword","normalization":"","example":"","description":"Event time zone."},{"field":"event.type","type":"keyword","normalization":"array","example":"","description":"Event type. The third categorization field in the hierarchy."},{"field":"event.url","type":"keyword","normalization":"","example":"https://mysystem.example.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe","description":"Event investigation URL"},{"field":"faas.coldstart","type":"boolean","normalization":"","example":"","description":"Boolean value indicating a cold start of a function."},{"field":"faas.execution","type":"keyword","normalization":"","example":"af9d5aa4-a685-4c5f-a22b-444f80b3cc28","description":"The execution ID of the current function execution."},{"field":"faas.id","type":"keyword","normalization":"","example":"arn:aws:lambda:us-west-2:123456789012:function:my-function","description":"The unique identifier of a serverless function."},{"field":"faas.name","type":"keyword","normalization":"","example":"my-function","description":"The name of a serverless function."},{"field":"faas.trigger","type":"nested","normalization":"","example":"","description":"Details about the function trigger."},{"field":"faas.trigger.request_id","type":"keyword","normalization":"","example":123456789,"description":"The ID of the trigger request , message, event, etc."},{"field":"faas.trigger.type","type":"keyword","normalization":"","example":"http","description":"The trigger for the function execution."},{"field":"faas.version","type":"keyword","normalization":"","example":123,"description":"The version of a serverless function."},{"field":"file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"file.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"file.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"file.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"host.boot.id","type":"keyword","normalization":"","example":"88a1f0ed-5ae5-41ee-af6b-41921c311872","description":"Linux boot uuid taken from /proc/sys/kernel/random/boot_id"},{"field":"host.cpu.usage","type":"scaled_float","normalization":"","example":"","description":"Percent CPU used, between 0 and 1."},{"field":"host.disk.read.bytes","type":"long","normalization":"","example":"","description":"The number of bytes read by all disks."},{"field":"host.disk.write.bytes","type":"long","normalization":"","example":"","description":"The number of bytes written on all disks."},{"field":"host.domain","type":"keyword","normalization":"","example":"CONTOSO","description":"Name of the directory the group is a member of."},{"field":"host.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"host.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"host.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"host.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"host.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"host.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"host.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"host.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"host.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"host.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"host.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"host.name","type":"keyword","normalization":"","example":"","description":"Name of the host."},{"field":"host.network.egress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes sent on all network interfaces."},{"field":"host.network.egress.packets","type":"long","normalization":"","example":"","description":"The number of packets sent on all network interfaces."},{"field":"host.network.ingress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes received on all network interfaces."},{"field":"host.network.ingress.packets","type":"long","normalization":"","example":"","description":"The number of packets received on all network interfaces."},{"field":"host.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"host.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"host.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"host.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"host.pid_ns_ino","type":"keyword","normalization":"","example":256383,"description":"Pid namespace inode"},{"field":"host.type","type":"keyword","normalization":"","example":"","description":"Type of host."},{"field":"host.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the host has been up."},{"field":"http.request.body.bytes","type":"long","normalization":"","example":887,"description":"Size in bytes of the request body."},{"field":"http.request.body.content","type":"wildcard","normalization":"","example":"Hello world","description":"The full HTTP request body."},{"field":"http.request.body.content.text","type":"match_only_text","normalization":"","example":"Hello world","description":"The full HTTP request body."},{"field":"http.request.bytes","type":"long","normalization":"","example":1437,"description":"Total size in bytes of the request (body and headers)."},{"field":"http.request.id","type":"keyword","normalization":"","example":"123e4567-e89b-12d3-a456-426614174000","description":"HTTP request ID."},{"field":"http.request.method","type":"keyword","normalization":"","example":"POST","description":"HTTP request method."},{"field":"http.request.mime_type","type":"keyword","normalization":"","example":"image/gif","description":"Mime type of the body of the request."},{"field":"http.request.referrer","type":"keyword","normalization":"","example":"https://blog.example.com/","description":"Referrer for this HTTP request."},{"field":"http.response.body.bytes","type":"long","normalization":"","example":887,"description":"Size in bytes of the response body."},{"field":"http.response.body.content","type":"wildcard","normalization":"","example":"Hello world","description":"The full HTTP response body."},{"field":"http.response.body.content.text","type":"match_only_text","normalization":"","example":"Hello world","description":"The full HTTP response body."},{"field":"http.response.bytes","type":"long","normalization":"","example":1437,"description":"Total size in bytes of the response (body and headers)."},{"field":"http.response.mime_type","type":"keyword","normalization":"","example":"image/gif","description":"Mime type of the body of the response."},{"field":"http.response.status_code","type":"long","normalization":"","example":404,"description":"HTTP response status code."},{"field":"http.version","type":"keyword","normalization":"","example":1.1,"description":"HTTP version."},{"field":"log.file.path","type":"keyword","normalization":"","example":"/var/log/fun-times.log","description":"Full path to the log file this event came from."},{"field":"log.level","type":"keyword","normalization":"","example":"error","description":"Log level of the log event."},{"field":"log.logger","type":"keyword","normalization":"","example":"org.elasticsearch.bootstrap.Bootstrap","description":"Name of the logger."},{"field":"log.origin.file.line","type":"long","normalization":"","example":42,"description":"The line number of the file which originated the log event."},{"field":"log.origin.file.name","type":"keyword","normalization":"","example":"Bootstrap.java","description":"The code file which originated the log event."},{"field":"log.origin.function","type":"keyword","normalization":"","example":"init","description":"The function which originated the log event."},{"field":"log.syslog","type":"object","normalization":"","example":"","description":"Syslog metadata"},{"field":"log.syslog.appname","type":"keyword","normalization":"","example":"sshd","description":"The device or application that originated the Syslog message."},{"field":"log.syslog.facility.code","type":"long","normalization":"","example":23,"description":"Syslog numeric facility of the event."},{"field":"log.syslog.facility.name","type":"keyword","normalization":"","example":"local7","description":"Syslog text-based facility of the event."},{"field":"log.syslog.hostname","type":"keyword","normalization":"","example":"example-host","description":"The host that originated the Syslog message."},{"field":"log.syslog.msgid","type":"keyword","normalization":"","example":"ID47","description":"An identifier for the type of Syslog message."},{"field":"log.syslog.priority","type":"long","normalization":"","example":135,"description":"Syslog priority of the event."},{"field":"log.syslog.procid","type":"keyword","normalization":"","example":12345,"description":"The process name or ID that originated the Syslog message."},{"field":"log.syslog.severity.code","type":"long","normalization":"","example":3,"description":"Syslog numeric severity of the event."},{"field":"log.syslog.severity.name","type":"keyword","normalization":"","example":"Error","description":"Syslog text-based severity of the event."},{"field":"log.syslog.structured_data","type":"flattened","normalization":"","example":"","description":"Structured data expressed in RFC 5424 messages."},{"field":"log.syslog.version","type":"keyword","normalization":"","example":1,"description":"Syslog protocol version."},{"field":"network.application","type":"keyword","normalization":"","example":"aim","description":"Application level protocol name."},{"field":"network.bytes","type":"long","normalization":"","example":368,"description":"Total bytes transferred in both directions."},{"field":"network.community_id","type":"keyword","normalization":"","example":"1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=","description":"A hash of source and destination IPs and ports."},{"field":"network.direction","type":"keyword","normalization":"","example":"inbound","description":"Direction of the network traffic."},{"field":"network.forwarded_ip","type":"ip","normalization":"","example":"192.1.1.2","description":"Host IP address when the source IP address is the proxy."},{"field":"network.iana_number","type":"keyword","normalization":"","example":6,"description":"IANA Protocol Number."},{"field":"network.inner","type":"object","normalization":"","example":"","description":"Inner VLAN tag information"},{"field":"network.inner.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"network.inner.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"network.name","type":"keyword","normalization":"","example":"Guest Wifi","description":"Name given by operators to sections of their network."},{"field":"network.packets","type":"long","normalization":"","example":24,"description":"Total packets transferred in both directions."},{"field":"network.protocol","type":"keyword","normalization":"","example":"http","description":"Application protocol name."},{"field":"network.transport","type":"keyword","normalization":"","example":"tcp","description":"Protocol Name corresponding to the field `iana_number`."},{"field":"network.type","type":"keyword","normalization":"","example":"ipv4","description":"In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc"},{"field":"network.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"network.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress","type":"object","normalization":"","example":"","description":"Object field for egress information"},{"field":"observer.egress.interface.alias","type":"keyword","normalization":"","example":"outside","description":"Interface alias"},{"field":"observer.egress.interface.id","type":"keyword","normalization":"","example":10,"description":"Interface ID"},{"field":"observer.egress.interface.name","type":"keyword","normalization":"","example":"eth0","description":"Interface name"},{"field":"observer.egress.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"observer.egress.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress.zone","type":"keyword","normalization":"","example":"Public_Internet","description":"Observer Egress zone"},{"field":"observer.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"observer.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"observer.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"observer.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"observer.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"observer.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"observer.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"observer.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"observer.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"observer.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"observer.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"observer.hostname","type":"keyword","normalization":"","example":"","description":"Hostname of the observer."},{"field":"observer.ingress","type":"object","normalization":"","example":"","description":"Object field for ingress information"},{"field":"observer.ingress.interface.alias","type":"keyword","normalization":"","example":"outside","description":"Interface alias"},{"field":"observer.ingress.interface.id","type":"keyword","normalization":"","example":10,"description":"Interface ID"},{"field":"observer.ingress.interface.name","type":"keyword","normalization":"","example":"eth0","description":"Interface name"},{"field":"observer.ingress.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"observer.ingress.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.ingress.zone","type":"keyword","normalization":"","example":"DMZ","description":"Observer ingress zone"},{"field":"observer.ip","type":"ip","normalization":"array","example":"","description":"IP addresses of the observer."},{"field":"observer.mac","type":"keyword","normalization":"array","example":["00-00-5E-00-53-23","00-00-5E-00-53-24"],"description":"MAC addresses of the observer."},{"field":"observer.name","type":"keyword","normalization":"","example":"1_proxySG","description":"Custom name of the observer."},{"field":"observer.os.family","type":"keyword","normalization":"","example":"debian","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"observer.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"observer.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"observer.os.kernel","type":"keyword","normalization":"","example":"4.4.0-112-generic","description":"Operating system kernel version as a raw string."},{"field":"observer.os.name","type":"keyword","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"observer.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"observer.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"observer.os.type","type":"keyword","normalization":"","example":"macos","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"observer.os.version","type":"keyword","normalization":"","example":"10.14.1","description":"Operating system version as a raw string."},{"field":"observer.product","type":"keyword","normalization":"","example":"s200","description":"The product name of the observer."},{"field":"observer.serial_number","type":"keyword","normalization":"","example":"","description":"Observer serial number."},{"field":"observer.type","type":"keyword","normalization":"","example":"firewall","description":"The type of the observer the data is coming from."},{"field":"observer.vendor","type":"keyword","normalization":"","example":"Symantec","description":"Vendor name of the observer."},{"field":"observer.version","type":"keyword","normalization":"","example":"","description":"Observer version."},{"field":"orchestrator.api_version","type":"keyword","normalization":"","example":"v1beta1","description":"API version being used to carry out the action"},{"field":"orchestrator.cluster.id","type":"keyword","normalization":"","example":"","description":"Unique ID of the cluster."},{"field":"orchestrator.cluster.name","type":"keyword","normalization":"","example":"","description":"Name of the cluster."},{"field":"orchestrator.cluster.url","type":"keyword","normalization":"","example":"","description":"URL of the API used to manage the cluster."},{"field":"orchestrator.cluster.version","type":"keyword","normalization":"","example":"","description":"The version of the cluster."},{"field":"orchestrator.namespace","type":"keyword","normalization":"","example":"kube-system","description":"Namespace in which the action is taking place."},{"field":"orchestrator.organization","type":"keyword","normalization":"","example":"elastic","description":"Organization affected by the event (for multi-tenant orchestrator setups)."},{"field":"orchestrator.resource.id","type":"keyword","normalization":"","example":"","description":"Unique ID of the resource being acted upon."},{"field":"orchestrator.resource.ip","type":"ip","normalization":"array","example":"","description":"IP address assigned to the resource associated with the event being observed."},{"field":"orchestrator.resource.name","type":"keyword","normalization":"","example":"test-pod-cdcws","description":"Name of the resource being acted upon."},{"field":"orchestrator.resource.parent.type","type":"keyword","normalization":"","example":"DaemonSet","description":"Type or kind of the parent resource associated with the event being observed."},{"field":"orchestrator.resource.type","type":"keyword","normalization":"","example":"service","description":"Type of resource being acted upon."},{"field":"orchestrator.type","type":"keyword","normalization":"","example":"kubernetes","description":"Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry)."},{"field":"organization.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the organization."},{"field":"organization.name","type":"keyword","normalization":"","example":"","description":"Organization name."},{"field":"organization.name.text","type":"match_only_text","normalization":"","example":"","description":"Organization name."},{"field":"package.architecture","type":"keyword","normalization":"","example":"x86_64","description":"Package architecture."},{"field":"package.build_version","type":"keyword","normalization":"","example":"36f4f7e89dd61b0988b12ee000b98966867710cd","description":"Build version information"},{"field":"package.checksum","type":"keyword","normalization":"","example":"68b329da9893e34099c7d8ad5cb9c940","description":"Checksum of the installed package for verification."},{"field":"package.description","type":"keyword","normalization":"","example":"Open source programming language to build simple/reliable/efficient software.","description":"Description of the package."},{"field":"package.install_scope","type":"keyword","normalization":"","example":"global","description":"Indicating how the package was installed, e.g. user-local, global."},{"field":"package.installed","type":"date","normalization":"","example":"","description":"Time when package was installed."},{"field":"package.license","type":"keyword","normalization":"","example":"Apache License 2.0","description":"Package license"},{"field":"package.name","type":"keyword","normalization":"","example":"go","description":"Package name"},{"field":"package.path","type":"keyword","normalization":"","example":"/usr/local/Cellar/go/1.12.9/","description":"Path where the package is installed."},{"field":"package.reference","type":"keyword","normalization":"","example":"https://golang.org","description":"Package home page or reference URL"},{"field":"package.size","type":"long","normalization":"","example":62231,"description":"Package size in bytes."},{"field":"package.type","type":"keyword","normalization":"","example":"rpm","description":"Package type"},{"field":"package.version","type":"keyword","normalization":"","example":"1.12.9","description":"Package version"},{"field":"process.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"process.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"process.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"process.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"process.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"process.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"process.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"process.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"process.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"process.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"process.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"process.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"process.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"process.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"process.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"process.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"process.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"process.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"process.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"process.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"process.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"process.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"process.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"process.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"process.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"process.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"process.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"process.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"process.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"process.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"process.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"process.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"process.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"process.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"process.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"process.end","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process ended."},{"field":"process.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.entry_leader.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.entry_leader.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.entry_leader.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.entry_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.entry_meta.source.ip","type":"ip","normalization":"","example":"","description":"IP address of the source."},{"field":"process.entry_leader.entry_meta.type","type":"keyword","normalization":"","example":"","description":"The entry type for the entry session leader."},{"field":"process.entry_leader.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.entry_leader.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.entry_leader.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.entry_leader.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.entry_leader.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.entry_leader.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.entry_leader.parent.session_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.parent.session_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.entry_leader.parent.session_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.entry_leader.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.entry_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.entry_leader.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.same_as_process","type":"boolean","normalization":"","example":"True","description":"This boolean is used to identify if a leader process is the same as the top level process."},{"field":"process.entry_leader.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.entry_leader.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.entry_leader.tty.char_device.major","type":"long","normalization":"","example":1,"description":"The TTY character device's major number."},{"field":"process.entry_leader.tty.char_device.minor","type":"long","normalization":"","example":128,"description":"The TTY character device's minor number."},{"field":"process.entry_leader.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.entry_leader.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.env_vars","type":"object","normalization":"","example":{"USER":"elastic","LANG":"en_US.UTF-8","HOME":"/home/elastic"},"description":"Environment variables set at the time of the event."},{"field":"process.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.exit_code","type":"long","normalization":"","example":137,"description":"The exit code of the process."},{"field":"process.group_leader.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.group_leader.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.group_leader.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.group_leader.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.group_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.group_leader.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.group_leader.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.group_leader.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.group_leader.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.group_leader.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.group_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.group_leader.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.group_leader.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.same_as_process","type":"boolean","normalization":"","example":"True","description":"This boolean is used to identify if a leader process is the same as the top level process."},{"field":"process.group_leader.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.group_leader.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.group_leader.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.group_leader.tty.char_device.major","type":"long","normalization":"","example":1,"description":"The TTY character device's major number."},{"field":"process.group_leader.tty.char_device.minor","type":"long","normalization":"","example":128,"description":"The TTY character device's minor number."},{"field":"process.group_leader.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.group_leader.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.group_leader.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"process.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"process.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"process.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"process.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"process.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"process.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"process.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.parent.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.parent.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"process.parent.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"process.parent.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"process.parent.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"process.parent.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"process.parent.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"process.parent.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"process.parent.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"process.parent.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.parent.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.parent.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.parent.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"process.parent.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"process.parent.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"process.parent.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"process.parent.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"process.parent.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.parent.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"process.parent.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"process.parent.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"process.parent.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"process.parent.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.parent.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"process.parent.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"process.parent.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"process.parent.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"process.parent.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"process.parent.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"process.parent.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"process.parent.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"process.parent.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"process.parent.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"process.parent.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"process.parent.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"process.parent.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"process.parent.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"process.parent.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"process.parent.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"process.parent.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"process.parent.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"process.parent.end","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process ended."},{"field":"process.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.parent.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.parent.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.parent.exit_code","type":"long","normalization":"","example":137,"description":"The exit code of the process."},{"field":"process.parent.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.group_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.parent.group_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.parent.group_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.parent.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"process.parent.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"process.parent.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"process.parent.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"process.parent.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"process.parent.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"process.parent.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"process.parent.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.parent.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"process.parent.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"process.parent.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"process.parent.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"process.parent.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"process.parent.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"process.parent.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"process.parent.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"process.parent.pgid","type":"long","normalization":"","example":"","description":"Deprecated identifier of the group of processes the process belongs to."},{"field":"process.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.parent.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.parent.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.parent.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.parent.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.thread.id","type":"long","normalization":"","example":4242,"description":"Thread ID."},{"field":"process.parent.thread.name","type":"keyword","normalization":"","example":"thread-0","description":"Thread name."},{"field":"process.parent.title","type":"keyword","normalization":"","example":"","description":"Process title."},{"field":"process.parent.title.text","type":"match_only_text","normalization":"","example":"","description":"Process title."},{"field":"process.parent.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.parent.tty.char_device.major","type":"long","normalization":"","example":1,"description":"The TTY character device's major number."},{"field":"process.parent.tty.char_device.minor","type":"long","normalization":"","example":128,"description":"The TTY character device's minor number."},{"field":"process.parent.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the process has been up."},{"field":"process.parent.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.parent.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.parent.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"process.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"process.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"process.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"process.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"process.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"process.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"process.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"process.pgid","type":"long","normalization":"","example":"","description":"Deprecated identifier of the group of processes the process belongs to."},{"field":"process.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.previous.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.previous.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.previous.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.previous.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.session_leader.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.session_leader.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.session_leader.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.session_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.session_leader.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.session_leader.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.session_leader.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.session_leader.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.session_leader.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.session_leader.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.session_leader.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.session_leader.parent.session_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.session_leader.parent.session_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.session_leader.parent.session_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.session_leader.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.session_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.session_leader.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.session_leader.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.same_as_process","type":"boolean","normalization":"","example":"True","description":"This boolean is used to identify if a leader process is the same as the top level process."},{"field":"process.session_leader.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.session_leader.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.session_leader.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.session_leader.tty.char_device.major","type":"long","normalization":"","example":1,"description":"The TTY character device's major number."},{"field":"process.session_leader.tty.char_device.minor","type":"long","normalization":"","example":128,"description":"The TTY character device's minor number."},{"field":"process.session_leader.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.session_leader.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.session_leader.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.thread.id","type":"long","normalization":"","example":4242,"description":"Thread ID."},{"field":"process.thread.name","type":"keyword","normalization":"","example":"thread-0","description":"Thread name."},{"field":"process.title","type":"keyword","normalization":"","example":"","description":"Process title."},{"field":"process.title.text","type":"match_only_text","normalization":"","example":"","description":"Process title."},{"field":"process.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.tty.char_device.major","type":"long","normalization":"","example":1,"description":"The TTY character device's major number."},{"field":"process.tty.char_device.minor","type":"long","normalization":"","example":128,"description":"The TTY character device's minor number."},{"field":"process.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the process has been up."},{"field":"process.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"related.hash","type":"keyword","normalization":"array","example":"","description":"All the hashes seen on your event."},{"field":"related.hosts","type":"keyword","normalization":"array","example":"","description":"All the host identifiers seen on your event."},{"field":"related.ip","type":"ip","normalization":"array","example":"","description":"All of the IPs seen on your event."},{"field":"related.user","type":"keyword","normalization":"array","example":"","description":"All the user names or other user identifiers seen on the event."},{"field":"rule.author","type":"keyword","normalization":"array","example":["Star-Lord"],"description":"Rule author"},{"field":"rule.category","type":"keyword","normalization":"","example":"Attempted Information Leak","description":"Rule category"},{"field":"rule.description","type":"keyword","normalization":"","example":"Block requests to public DNS over HTTPS / TLS protocols","description":"Rule description"},{"field":"rule.id","type":"keyword","normalization":"","example":101,"description":"Rule ID"},{"field":"rule.license","type":"keyword","normalization":"","example":"Apache 2.0","description":"Rule license"},{"field":"rule.name","type":"keyword","normalization":"","example":"BLOCK_DNS_over_TLS","description":"Rule name"},{"field":"rule.reference","type":"keyword","normalization":"","example":"https://en.wikipedia.org/wiki/DNS_over_TLS","description":"Rule reference URL"},{"field":"rule.ruleset","type":"keyword","normalization":"","example":"Standard_Protocol_Filters","description":"Rule ruleset"},{"field":"rule.uuid","type":"keyword","normalization":"","example":1100110011,"description":"Rule UUID"},{"field":"rule.version","type":"keyword","normalization":"","example":1.1,"description":"Rule version"},{"field":"server.address","type":"keyword","normalization":"","example":"","description":"Server network address."},{"field":"server.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"server.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"server.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"server.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the server to the client."},{"field":"server.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the server."},{"field":"server.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"server.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"server.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"server.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"server.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"server.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"server.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"server.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"server.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"server.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"server.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"server.ip","type":"ip","normalization":"","example":"","description":"IP address of the server."},{"field":"server.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the server."},{"field":"server.nat.ip","type":"ip","normalization":"","example":"","description":"Server NAT ip"},{"field":"server.nat.port","type":"long","normalization":"","example":"","description":"Server NAT port"},{"field":"server.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the server to the client."},{"field":"server.port","type":"long","normalization":"","example":"","description":"Port of the server."},{"field":"server.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered server domain, stripped of the subdomain."},{"field":"server.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"server.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"server.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"server.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"server.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"server.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"server.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"server.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"server.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"server.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"server.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"server.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"server.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"server.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"service.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.node.role","type":"keyword","normalization":"","example":"background_tasks","description":"Deprecated role (singular) of the service node."},{"field":"service.node.roles","type":"keyword","normalization":"array","example":["ui","background_tasks"],"description":"Roles of the service node."},{"field":"service.origin.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.origin.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.origin.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.origin.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.origin.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.origin.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.origin.node.role","type":"keyword","normalization":"","example":"background_tasks","description":"Deprecated role (singular) of the service node."},{"field":"service.origin.node.roles","type":"keyword","normalization":"array","example":["ui","background_tasks"],"description":"Roles of the service node."},{"field":"service.origin.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.origin.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.origin.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"service.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.target.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.target.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.target.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.target.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.target.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.target.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.target.node.role","type":"keyword","normalization":"","example":"background_tasks","description":"Deprecated role (singular) of the service node."},{"field":"service.target.node.roles","type":"keyword","normalization":"array","example":["ui","background_tasks"],"description":"Roles of the service node."},{"field":"service.target.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.target.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.target.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"service.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"source.address","type":"keyword","normalization":"","example":"","description":"Source network address."},{"field":"source.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"source.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"source.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"source.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the source to the destination."},{"field":"source.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the source."},{"field":"source.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"source.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"source.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"source.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"source.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"source.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"source.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"source.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"source.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"source.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"source.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"source.ip","type":"ip","normalization":"","example":"","description":"IP address of the source."},{"field":"source.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the source."},{"field":"source.nat.ip","type":"ip","normalization":"","example":"","description":"Source NAT ip"},{"field":"source.nat.port","type":"long","normalization":"","example":"","description":"Source NAT port"},{"field":"source.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the source to the destination."},{"field":"source.port","type":"long","normalization":"","example":"","description":"Port of the source."},{"field":"source.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered source domain, stripped of the subdomain."},{"field":"source.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"source.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"source.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"source.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"source.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"source.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"source.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"source.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"source.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"source.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"source.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"source.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"source.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"source.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"span.id","type":"keyword","normalization":"","example":"3ff9a8981b7ccd5a","description":"Unique identifier of the span within the scope of its trace."},{"field":"threat.enrichments","type":"nested","normalization":"array","example":"","description":"List of objects containing indicators enriching the event."},{"field":"threat.enrichments.indicator","type":"object","normalization":"","example":"","description":"Object containing indicators enriching the event."},{"field":"threat.enrichments.indicator.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"threat.enrichments.indicator.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.enrichments.indicator.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.enrichments.indicator.confidence","type":"keyword","normalization":"","example":"Medium","description":"Indicator confidence rating"},{"field":"threat.enrichments.indicator.description","type":"keyword","normalization":"","example":"IP x.x.x.x was observed delivering the Angler EK.","description":"Indicator description"},{"field":"threat.enrichments.indicator.email.address","type":"keyword","normalization":"","example":"phish@example.com","description":"Indicator email address"},{"field":"threat.enrichments.indicator.file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"threat.enrichments.indicator.file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"threat.enrichments.indicator.file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"threat.enrichments.indicator.file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"threat.enrichments.indicator.file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"threat.enrichments.indicator.file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"threat.enrichments.indicator.file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"threat.enrichments.indicator.file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.enrichments.indicator.file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"threat.enrichments.indicator.file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"threat.enrichments.indicator.file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"threat.enrichments.indicator.file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"threat.enrichments.indicator.file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"threat.enrichments.indicator.file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"threat.enrichments.indicator.file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"threat.enrichments.indicator.file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"threat.enrichments.indicator.file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.enrichments.indicator.file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"threat.enrichments.indicator.file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.enrichments.indicator.file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"threat.enrichments.indicator.file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"threat.enrichments.indicator.file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"threat.enrichments.indicator.file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"threat.enrichments.indicator.file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"threat.enrichments.indicator.file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"threat.enrichments.indicator.file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"threat.enrichments.indicator.file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"threat.enrichments.indicator.file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"threat.enrichments.indicator.file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"threat.enrichments.indicator.file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"threat.enrichments.indicator.file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"threat.enrichments.indicator.file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.enrichments.indicator.file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"threat.enrichments.indicator.file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"threat.enrichments.indicator.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"threat.enrichments.indicator.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"threat.enrichments.indicator.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"threat.enrichments.indicator.file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"threat.enrichments.indicator.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"threat.enrichments.indicator.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"threat.enrichments.indicator.file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"threat.enrichments.indicator.file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"threat.enrichments.indicator.file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.enrichments.indicator.file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"threat.enrichments.indicator.file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"threat.enrichments.indicator.file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"threat.enrichments.indicator.file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"threat.enrichments.indicator.file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"threat.enrichments.indicator.file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"threat.enrichments.indicator.file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"threat.enrichments.indicator.file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"threat.enrichments.indicator.file.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"threat.enrichments.indicator.file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"threat.enrichments.indicator.file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.enrichments.indicator.file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.enrichments.indicator.file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.file.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.enrichments.indicator.file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.enrichments.indicator.first_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was first reported."},{"field":"threat.enrichments.indicator.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"threat.enrichments.indicator.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"threat.enrichments.indicator.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"threat.enrichments.indicator.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"threat.enrichments.indicator.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"threat.enrichments.indicator.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"threat.enrichments.indicator.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"threat.enrichments.indicator.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"threat.enrichments.indicator.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"threat.enrichments.indicator.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"threat.enrichments.indicator.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"threat.enrichments.indicator.ip","type":"ip","normalization":"","example":"1.2.3.4","description":"Indicator IP address"},{"field":"threat.enrichments.indicator.last_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last reported."},{"field":"threat.enrichments.indicator.marking.tlp","type":"keyword","normalization":"","example":"WHITE","description":"Indicator TLP marking"},{"field":"threat.enrichments.indicator.modified_at","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last updated."},{"field":"threat.enrichments.indicator.port","type":"long","normalization":"","example":443,"description":"Indicator port"},{"field":"threat.enrichments.indicator.provider","type":"keyword","normalization":"","example":"lrz_urlhaus","description":"Indicator provider"},{"field":"threat.enrichments.indicator.reference","type":"keyword","normalization":"","example":"https://system.example.com/indicator/0001234","description":"Indicator reference URL"},{"field":"threat.enrichments.indicator.registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"threat.enrichments.indicator.registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"threat.enrichments.indicator.registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"threat.enrichments.indicator.registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"threat.enrichments.indicator.registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"threat.enrichments.indicator.registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"threat.enrichments.indicator.registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"threat.enrichments.indicator.scanner_stats","type":"long","normalization":"","example":4,"description":"Scanner statistics"},{"field":"threat.enrichments.indicator.sightings","type":"long","normalization":"","example":20,"description":"Number of times indicator observed"},{"field":"threat.enrichments.indicator.type","type":"keyword","normalization":"","example":"ipv4-addr","description":"Type of indicator"},{"field":"threat.enrichments.indicator.url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"threat.enrichments.indicator.url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.enrichments.indicator.url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"threat.enrichments.indicator.url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"threat.enrichments.indicator.url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"threat.enrichments.indicator.url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"threat.enrichments.indicator.url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"threat.enrichments.indicator.url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.enrichments.indicator.url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"threat.enrichments.indicator.url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"threat.enrichments.indicator.url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.enrichments.indicator.url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"threat.enrichments.indicator.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.enrichments.indicator.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.enrichments.indicator.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.enrichments.matched.atomic","type":"keyword","normalization":"","example":"bad-domain.com","description":"Matched indicator value"},{"field":"threat.enrichments.matched.field","type":"keyword","normalization":"","example":"file.hash.sha256","description":"Matched indicator field"},{"field":"threat.enrichments.matched.id","type":"keyword","normalization":"","example":"ff93aee5-86a1-4a61-b0e6-0cdc313d01b5","description":"Matched indicator identifier"},{"field":"threat.enrichments.matched.index","type":"keyword","normalization":"","example":"filebeat-8.0.0-2021.05.23-000011","description":"Matched indicator index"},{"field":"threat.enrichments.matched.occurred","type":"date","normalization":"","example":"2021-10-05T17:00:58.326Z","description":"Date of match"},{"field":"threat.enrichments.matched.type","type":"keyword","normalization":"","example":"indicator_match_rule","description":"Type of indicator match"},{"field":"threat.feed.dashboard_id","type":"keyword","normalization":"","example":"5ba16340-72e6-11eb-a3e3-b3cc7c78a70f","description":"Feed dashboard ID."},{"field":"threat.feed.description","type":"keyword","normalization":"","example":"Threat feed from the AlienVault Open Threat eXchange network.","description":"Description of the threat feed."},{"field":"threat.feed.name","type":"keyword","normalization":"","example":"AlienVault OTX","description":"Name of the threat feed."},{"field":"threat.feed.reference","type":"keyword","normalization":"","example":"https://otx.alienvault.com","description":"Reference for the threat feed."},{"field":"threat.framework","type":"keyword","normalization":"","example":"MITRE ATT&CK","description":"Threat classification framework."},{"field":"threat.group.alias","type":"keyword","normalization":"array","example":["Magecart Group 6"],"description":"Alias of the group."},{"field":"threat.group.id","type":"keyword","normalization":"","example":"G0037","description":"ID of the group."},{"field":"threat.group.name","type":"keyword","normalization":"","example":"FIN6","description":"Name of the group."},{"field":"threat.group.reference","type":"keyword","normalization":"","example":"https://attack.mitre.org/groups/G0037/","description":"Reference URL of the group."},{"field":"threat.indicator.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"threat.indicator.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.indicator.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.indicator.confidence","type":"keyword","normalization":"","example":"Medium","description":"Indicator confidence rating"},{"field":"threat.indicator.description","type":"keyword","normalization":"","example":"IP x.x.x.x was observed delivering the Angler EK.","description":"Indicator description"},{"field":"threat.indicator.email.address","type":"keyword","normalization":"","example":"phish@example.com","description":"Indicator email address"},{"field":"threat.indicator.file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"threat.indicator.file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"threat.indicator.file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"threat.indicator.file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"threat.indicator.file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"threat.indicator.file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"threat.indicator.file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"threat.indicator.file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"threat.indicator.file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.indicator.file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"threat.indicator.file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"threat.indicator.file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"threat.indicator.file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"threat.indicator.file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"threat.indicator.file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"threat.indicator.file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"threat.indicator.file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"threat.indicator.file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"threat.indicator.file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"threat.indicator.file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.indicator.file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"threat.indicator.file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"threat.indicator.file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"threat.indicator.file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"threat.indicator.file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.indicator.file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"threat.indicator.file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"threat.indicator.file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"threat.indicator.file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"threat.indicator.file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"threat.indicator.file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"threat.indicator.file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"threat.indicator.file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"threat.indicator.file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"threat.indicator.file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"threat.indicator.file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"threat.indicator.file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"threat.indicator.file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"threat.indicator.file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"threat.indicator.file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"threat.indicator.file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"threat.indicator.file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"threat.indicator.file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"threat.indicator.file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"threat.indicator.file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.indicator.file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"threat.indicator.file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"threat.indicator.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"threat.indicator.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"threat.indicator.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"threat.indicator.file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"threat.indicator.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"threat.indicator.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"threat.indicator.file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"threat.indicator.file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"threat.indicator.file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.indicator.file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"threat.indicator.file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"threat.indicator.file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"threat.indicator.file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"threat.indicator.file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"threat.indicator.file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"threat.indicator.file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"threat.indicator.file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"threat.indicator.file.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.indicator.file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"threat.indicator.file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.indicator.file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.indicator.file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"threat.indicator.file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.indicator.file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.indicator.file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.file.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.indicator.file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.indicator.file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.indicator.file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.indicator.file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.indicator.first_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was first reported."},{"field":"threat.indicator.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"threat.indicator.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"threat.indicator.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"threat.indicator.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"threat.indicator.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"threat.indicator.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"threat.indicator.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"threat.indicator.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"threat.indicator.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"threat.indicator.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"threat.indicator.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"threat.indicator.ip","type":"ip","normalization":"","example":"1.2.3.4","description":"Indicator IP address"},{"field":"threat.indicator.last_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last reported."},{"field":"threat.indicator.marking.tlp","type":"keyword","normalization":"","example":"WHITE","description":"Indicator TLP marking"},{"field":"threat.indicator.modified_at","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last updated."},{"field":"threat.indicator.port","type":"long","normalization":"","example":443,"description":"Indicator port"},{"field":"threat.indicator.provider","type":"keyword","normalization":"","example":"lrz_urlhaus","description":"Indicator provider"},{"field":"threat.indicator.reference","type":"keyword","normalization":"","example":"https://system.example.com/indicator/0001234","description":"Indicator reference URL"},{"field":"threat.indicator.registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"threat.indicator.registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"threat.indicator.registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"threat.indicator.registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"threat.indicator.registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"threat.indicator.registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"threat.indicator.registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"threat.indicator.scanner_stats","type":"long","normalization":"","example":4,"description":"Scanner statistics"},{"field":"threat.indicator.sightings","type":"long","normalization":"","example":20,"description":"Number of times indicator observed"},{"field":"threat.indicator.type","type":"keyword","normalization":"","example":"ipv4-addr","description":"Type of indicator"},{"field":"threat.indicator.url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"threat.indicator.url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.indicator.url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"threat.indicator.url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.indicator.url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.indicator.url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"threat.indicator.url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"threat.indicator.url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"threat.indicator.url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"threat.indicator.url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.indicator.url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"threat.indicator.url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"threat.indicator.url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.indicator.url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"threat.indicator.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.indicator.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.indicator.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.indicator.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.indicator.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.indicator.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.indicator.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.indicator.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.software.alias","type":"keyword","normalization":"array","example":["X-Agent"],"description":"Alias of the software"},{"field":"threat.software.id","type":"keyword","normalization":"","example":"S0552","description":"ID of the software"},{"field":"threat.software.name","type":"keyword","normalization":"","example":"AdFind","description":"Name of the software."},{"field":"threat.software.platforms","type":"keyword","normalization":"array","example":["Windows"],"description":"Platforms of the software."},{"field":"threat.software.reference","type":"keyword","normalization":"","example":"https://attack.mitre.org/software/S0552/","description":"Software reference URL."},{"field":"threat.software.type","type":"keyword","normalization":"","example":"Tool","description":"Software type."},{"field":"threat.tactic.id","type":"keyword","normalization":"array","example":"TA0002","description":"Threat tactic id."},{"field":"threat.tactic.name","type":"keyword","normalization":"array","example":"Execution","description":"Threat tactic."},{"field":"threat.tactic.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/tactics/TA0002/","description":"Threat tactic URL reference."},{"field":"threat.technique.id","type":"keyword","normalization":"array","example":"T1059","description":"Threat technique id."},{"field":"threat.technique.name","type":"keyword","normalization":"array","example":"Command and Scripting Interpreter","description":"Threat technique name."},{"field":"threat.technique.name.text","type":"match_only_text","normalization":"","example":"Command and Scripting Interpreter","description":"Threat technique name."},{"field":"threat.technique.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/techniques/T1059/","description":"Threat technique URL reference."},{"field":"threat.technique.subtechnique.id","type":"keyword","normalization":"array","example":"T1059.001","description":"Threat subtechnique id."},{"field":"threat.technique.subtechnique.name","type":"keyword","normalization":"array","example":"PowerShell","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.name.text","type":"match_only_text","normalization":"","example":"PowerShell","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/techniques/T1059/001/","description":"Threat subtechnique URL reference."},{"field":"tls.cipher","type":"keyword","normalization":"","example":"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","description":"String indicating the cipher used during the current connection."},{"field":"tls.client.certificate","type":"keyword","normalization":"","example":"MII...","description":"PEM-encoded stand-alone certificate offered by the client."},{"field":"tls.client.certificate_chain","type":"keyword","normalization":"array","example":["MII...","MII..."],"description":"Array of PEM-encoded certificates that make up the certificate chain offered by the client."},{"field":"tls.client.hash.md5","type":"keyword","normalization":"","example":"0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha1","type":"keyword","normalization":"","example":"9E393D93138888D288266C2D915214D1D1CCEB2A","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha256","type":"keyword","normalization":"","example":"0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.issuer","type":"keyword","normalization":"","example":"CN=Example Root CA, OU=Infrastructure Team, DC=example, DC=com","description":"Distinguished name of subject of the issuer of the x.509 certificate presented by the client."},{"field":"tls.client.ja3","type":"keyword","normalization":"","example":"d4e5b18d6b55c71272893221c96ba240","description":"A hash that identifies clients based on how they perform an SSL/TLS handshake."},{"field":"tls.client.not_after","type":"date","normalization":"","example":"2021-01-01T00:00:00.000Z","description":"Date/Time indicating when client certificate is no longer considered valid."},{"field":"tls.client.not_before","type":"date","normalization":"","example":"1970-01-01T00:00:00.000Z","description":"Date/Time indicating when client certificate is first considered valid."},{"field":"tls.client.server_name","type":"keyword","normalization":"","example":"www.elastic.co","description":"Hostname the client is trying to connect to. Also called the SNI."},{"field":"tls.client.subject","type":"keyword","normalization":"","example":"CN=myclient, OU=Documentation Team, DC=example, DC=com","description":"Distinguished name of subject of the x.509 certificate presented by the client."},{"field":"tls.client.supported_ciphers","type":"keyword","normalization":"array","example":["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","..."],"description":"Array of ciphers offered by the client during the client hello."},{"field":"tls.client.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"tls.client.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"tls.client.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"tls.client.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.client.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.client.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.client.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"tls.client.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"tls.client.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.client.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.client.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"tls.client.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"tls.client.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"tls.client.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"tls.client.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"tls.client.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.client.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"tls.client.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"tls.client.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"tls.client.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"tls.curve","type":"keyword","normalization":"","example":"secp256r1","description":"String indicating the curve used for the given cipher, when applicable."},{"field":"tls.established","type":"boolean","normalization":"","example":"","description":"Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel."},{"field":"tls.next_protocol","type":"keyword","normalization":"","example":"http/1.1","description":"String indicating the protocol being tunneled."},{"field":"tls.resumed","type":"boolean","normalization":"","example":"","description":"Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation."},{"field":"tls.server.certificate","type":"keyword","normalization":"","example":"MII...","description":"PEM-encoded stand-alone certificate offered by the server."},{"field":"tls.server.certificate_chain","type":"keyword","normalization":"array","example":["MII...","MII..."],"description":"Array of PEM-encoded certificates that make up the certificate chain offered by the server."},{"field":"tls.server.hash.md5","type":"keyword","normalization":"","example":"0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha1","type":"keyword","normalization":"","example":"9E393D93138888D288266C2D915214D1D1CCEB2A","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha256","type":"keyword","normalization":"","example":"0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.issuer","type":"keyword","normalization":"","example":"CN=Example Root CA, OU=Infrastructure Team, DC=example, DC=com","description":"Subject of the issuer of the x.509 certificate presented by the server."},{"field":"tls.server.ja3s","type":"keyword","normalization":"","example":"394441ab65754e2207b1e1b457b3641d","description":"A hash that identifies servers based on how they perform an SSL/TLS handshake."},{"field":"tls.server.not_after","type":"date","normalization":"","example":"2021-01-01T00:00:00.000Z","description":"Timestamp indicating when server certificate is no longer considered valid."},{"field":"tls.server.not_before","type":"date","normalization":"","example":"1970-01-01T00:00:00.000Z","description":"Timestamp indicating when server certificate is first considered valid."},{"field":"tls.server.subject","type":"keyword","normalization":"","example":"CN=www.example.com, OU=Infrastructure Team, DC=example, DC=com","description":"Subject of the x.509 certificate presented by the server."},{"field":"tls.server.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"tls.server.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"tls.server.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"tls.server.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.server.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.server.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.server.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"tls.server.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"tls.server.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.server.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.server.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"tls.server.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"tls.server.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"tls.server.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"tls.server.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"tls.server.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.server.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"tls.server.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"tls.server.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"tls.server.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"tls.version","type":"keyword","normalization":"","example":1.2,"description":"Numeric part of the version parsed from the original string."},{"field":"tls.version_protocol","type":"keyword","normalization":"","example":"tls","description":"Normalized lowercase protocol name parsed from original string."},{"field":"trace.id","type":"keyword","normalization":"","example":"4bf92f3577b34da6a3ce929d0e0e4736","description":"Unique identifier of the trace."},{"field":"transaction.id","type":"keyword","normalization":"","example":"00f067aa0ba902b7","description":"Unique identifier of the transaction within the scope of its trace."},{"field":"url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"user.changes.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.changes.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.changes.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.changes.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.changes.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.changes.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.changes.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.changes.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.changes.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.changes.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.changes.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.changes.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.effective.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.effective.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.effective.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.effective.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.effective.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.effective.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.effective.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.effective.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.effective.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.effective.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.effective.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.effective.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.target.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.target.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.target.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.target.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.target.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.target.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.target.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.target.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.target.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.target.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.target.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.target.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user_agent.device.name","type":"keyword","normalization":"","example":"iPhone","description":"Name of the device."},{"field":"user_agent.name","type":"keyword","normalization":"","example":"Safari","description":"Name of the user agent."},{"field":"user_agent.original","type":"keyword","normalization":"","example":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1","description":"Unparsed user_agent string."},{"field":"user_agent.original.text","type":"match_only_text","normalization":"","example":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1","description":"Unparsed user_agent string."},{"field":"user_agent.os.family","type":"keyword","normalization":"","example":"debian","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"user_agent.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.kernel","type":"keyword","normalization":"","example":"4.4.0-112-generic","description":"Operating system kernel version as a raw string."},{"field":"user_agent.os.name","type":"keyword","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"user_agent.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"user_agent.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"user_agent.os.type","type":"keyword","normalization":"","example":"macos","description":"Which commercial OS family (one of: linux, macos, unix or windows)."},{"field":"user_agent.os.version","type":"keyword","normalization":"","example":"10.14.1","description":"Operating system version as a raw string."},{"field":"user_agent.version","type":"keyword","normalization":"","example":12,"description":"Version of the user agent."},{"field":"vulnerability.category","type":"keyword","normalization":"array","example":["Firewall"],"description":"Category of a vulnerability."},{"field":"vulnerability.classification","type":"keyword","normalization":"","example":"CVSS","description":"Classification of the vulnerability."},{"field":"vulnerability.description","type":"keyword","normalization":"","example":"In macOS before 2.12.6, there is a vulnerability in the RPC...","description":"Description of the vulnerability."},{"field":"vulnerability.description.text","type":"match_only_text","normalization":"","example":"In macOS before 2.12.6, there is a vulnerability in the RPC...","description":"Description of the vulnerability."},{"field":"vulnerability.enumeration","type":"keyword","normalization":"","example":"CVE","description":"Identifier of the vulnerability."},{"field":"vulnerability.id","type":"keyword","normalization":"","example":"CVE-2019-00001","description":"ID of the vulnerability."},{"field":"vulnerability.reference","type":"keyword","normalization":"","example":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111","description":"Reference of the vulnerability."},{"field":"vulnerability.report_id","type":"keyword","normalization":"","example":20191018.0001,"description":"Scan identification number."},{"field":"vulnerability.scanner.vendor","type":"keyword","normalization":"","example":"Tenable","description":"Name of the scanner vendor."},{"field":"vulnerability.score.base","type":"float","normalization":"","example":5.5,"description":"Vulnerability Base score."},{"field":"vulnerability.score.environmental","type":"float","normalization":"","example":5.5,"description":"Vulnerability Environmental score."},{"field":"vulnerability.score.temporal","type":"float","normalization":"","example":"","description":"Vulnerability Temporal score."},{"field":"vulnerability.score.version","type":"keyword","normalization":"","example":2,"description":"CVSS version."},{"field":"vulnerability.severity","type":"keyword","normalization":"","example":"Critical","description":"Severity of the vulnerability."}] \ No newline at end of file diff --git a/x-pack/plugins/osquery/public/common/schemas/ecs/v8.5.0.json b/x-pack/plugins/osquery/public/common/schemas/ecs/v8.5.0.json new file mode 100644 index 0000000000000..5fe03a8130fd0 --- /dev/null +++ b/x-pack/plugins/osquery/public/common/schemas/ecs/v8.5.0.json @@ -0,0 +1 @@ +[{"field":"labels","type":"object","normalization":"","example":{"application":"foo-bar","env":"production"},"description":"Custom key/value pairs."},{"field":"message","type":"match_only_text","normalization":"","example":"Hello World","description":"Log message optimized for viewing in a log viewer."},{"field":"tags","type":"keyword","normalization":"array","example":["production","env2"],"description":"List of keywords used to tag each event."},{"field":"agent.build.original","type":"keyword","normalization":"","example":"metricbeat version 7.6.0 (amd64), libbeat 7.6.0 [6a23e8f8f30f5001ba344e4e54d8d9cb82cb107c built 2020-02-05 23:10:10 +0000 UTC]","description":"Extended build information for the agent."},{"field":"client.address","type":"keyword","normalization":"","example":"","description":"Client network address."},{"field":"client.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"client.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"client.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"client.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the client to the server."},{"field":"client.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the client."},{"field":"client.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"client.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"client.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"client.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"client.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"client.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"client.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"client.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"client.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"client.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"client.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"client.ip","type":"ip","normalization":"","example":"","description":"IP address of the client."},{"field":"client.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the client."},{"field":"client.nat.ip","type":"ip","normalization":"","example":"","description":"Client NAT ip address"},{"field":"client.nat.port","type":"long","normalization":"","example":"","description":"Client NAT port"},{"field":"client.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the client to the server."},{"field":"client.port","type":"long","normalization":"","example":"","description":"Port of the client."},{"field":"client.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered client domain, stripped of the subdomain."},{"field":"client.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"client.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"client.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"client.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"client.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"client.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"client.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"client.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"client.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"client.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"client.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"client.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"client.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"client.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"cloud.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.origin.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.origin.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.origin.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.origin.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.origin.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.origin.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.origin.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.origin.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.origin.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.origin.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.origin.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"cloud.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"cloud.target.account.id","type":"keyword","normalization":"","example":666777888999,"description":"The cloud account or organization id."},{"field":"cloud.target.account.name","type":"keyword","normalization":"","example":"elastic-dev","description":"The cloud account name."},{"field":"cloud.target.availability_zone","type":"keyword","normalization":"","example":"us-east-1c","description":"Availability zone in which this host, resource, or service is located."},{"field":"cloud.target.instance.id","type":"keyword","normalization":"","example":"i-1234567890abcdef0","description":"Instance ID of the host machine."},{"field":"cloud.target.instance.name","type":"keyword","normalization":"","example":"","description":"Instance name of the host machine."},{"field":"cloud.target.machine.type","type":"keyword","normalization":"","example":"t2.medium","description":"Machine type of the host machine."},{"field":"cloud.target.project.id","type":"keyword","normalization":"","example":"my-project","description":"The cloud project id."},{"field":"cloud.target.project.name","type":"keyword","normalization":"","example":"my project","description":"The cloud project name."},{"field":"cloud.target.provider","type":"keyword","normalization":"","example":"aws","description":"Name of the cloud provider."},{"field":"cloud.target.region","type":"keyword","normalization":"","example":"us-east-1","description":"Region in which this host, resource, or service is located."},{"field":"cloud.target.service.name","type":"keyword","normalization":"","example":"lambda","description":"The cloud service name."},{"field":"container.cpu.usage","type":"scaled_float","normalization":"","example":"","description":"Percent CPU used, between 0 and 1."},{"field":"container.disk.read.bytes","type":"long","normalization":"","example":"","description":"The number of bytes read by all disks."},{"field":"container.disk.write.bytes","type":"long","normalization":"","example":"","description":"The number of bytes written on all disks."},{"field":"container.id","type":"keyword","normalization":"","example":"","description":"Unique container id."},{"field":"container.image.hash.all","type":"keyword","normalization":"array","example":"[sha256:f8fefc80e3273dc756f288a63945820d6476ad64883892c771b5e2ece6bf1b26]","description":"An array of digests of the image the container was built on."},{"field":"container.image.name","type":"keyword","normalization":"","example":"","description":"Name of the image the container was built on."},{"field":"container.image.tag","type":"keyword","normalization":"array","example":"","description":"Container image tags."},{"field":"container.labels","type":"object","normalization":"","example":"","description":"Image labels."},{"field":"container.memory.usage","type":"scaled_float","normalization":"","example":"","description":"Percent memory used, between 0 and 1."},{"field":"container.name","type":"keyword","normalization":"","example":"","description":"Container name."},{"field":"container.network.egress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes sent on all network interfaces."},{"field":"container.network.ingress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes received on all network interfaces."},{"field":"container.runtime","type":"keyword","normalization":"","example":"docker","description":"Runtime managing this container."},{"field":"data_stream.dataset","type":"constant_keyword","normalization":"","example":"nginx.access","description":"The field can contain anything that makes sense to signify the source of the data."},{"field":"data_stream.namespace","type":"constant_keyword","normalization":"","example":"production","description":"A user defined namespace. Namespaces are useful to allow grouping of data."},{"field":"data_stream.type","type":"constant_keyword","normalization":"","example":"logs","description":"An overarching type for the data stream."},{"field":"destination.address","type":"keyword","normalization":"","example":"","description":"Destination network address."},{"field":"destination.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"destination.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"destination.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"destination.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the destination to the source."},{"field":"destination.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the destination."},{"field":"destination.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"destination.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"destination.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"destination.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"destination.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"destination.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"destination.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"destination.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"destination.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"destination.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"destination.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"destination.ip","type":"ip","normalization":"","example":"","description":"IP address of the destination."},{"field":"destination.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the destination."},{"field":"destination.nat.ip","type":"ip","normalization":"","example":"","description":"Destination NAT ip"},{"field":"destination.nat.port","type":"long","normalization":"","example":"","description":"Destination NAT Port"},{"field":"destination.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the destination to the source."},{"field":"destination.port","type":"long","normalization":"","example":"","description":"Port of the destination."},{"field":"destination.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered destination domain, stripped of the subdomain."},{"field":"destination.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"destination.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"destination.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"destination.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"destination.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"destination.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"destination.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"destination.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"destination.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"destination.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"destination.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"destination.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"destination.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"destination.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"dll.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"dll.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"dll.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"dll.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"dll.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"dll.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"dll.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"dll.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"dll.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"dll.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"dll.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"dll.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"dll.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"dll.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"dll.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"dll.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"dll.name","type":"keyword","normalization":"","example":"kernel32.dll","description":"Name of the library."},{"field":"dll.path","type":"keyword","normalization":"","example":"C:\\Windows\\System32\\kernel32.dll","description":"Full file path of the library."},{"field":"dll.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"dll.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"dll.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"dll.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"dll.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"dll.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"dll.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"dll.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"dns.answers","type":"object","normalization":"array","example":"","description":"Array of DNS answers."},{"field":"dns.answers.class","type":"keyword","normalization":"","example":"IN","description":"The class of DNS data contained in this resource record."},{"field":"dns.answers.data","type":"keyword","normalization":"","example":"10.10.10.10","description":"The data describing the resource."},{"field":"dns.answers.name","type":"keyword","normalization":"","example":"www.example.com","description":"The domain name to which this resource record pertains."},{"field":"dns.answers.ttl","type":"long","normalization":"","example":180,"description":"The time interval in seconds that this resource record may be cached before it should be discarded."},{"field":"dns.answers.type","type":"keyword","normalization":"","example":"CNAME","description":"The type of data contained in this resource record."},{"field":"dns.header_flags","type":"keyword","normalization":"array","example":["RD","RA"],"description":"Array of DNS header flags."},{"field":"dns.id","type":"keyword","normalization":"","example":62111,"description":"The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response."},{"field":"dns.op_code","type":"keyword","normalization":"","example":"QUERY","description":"The DNS operation code that specifies the kind of query in the message."},{"field":"dns.question.class","type":"keyword","normalization":"","example":"IN","description":"The class of records being queried."},{"field":"dns.question.name","type":"keyword","normalization":"","example":"www.example.com","description":"The name being queried."},{"field":"dns.question.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered domain, stripped of the subdomain."},{"field":"dns.question.subdomain","type":"keyword","normalization":"","example":"www","description":"The subdomain of the domain."},{"field":"dns.question.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"dns.question.type","type":"keyword","normalization":"","example":"AAAA","description":"The type of record being queried."},{"field":"dns.resolved_ip","type":"ip","normalization":"array","example":["10.10.10.10","10.10.10.11"],"description":"Array containing all IPs seen in answers.data"},{"field":"dns.response_code","type":"keyword","normalization":"","example":"NOERROR","description":"The DNS response code."},{"field":"dns.type","type":"keyword","normalization":"","example":"answer","description":"The type of DNS event captured, query or answer."},{"field":"email.attachments","type":"nested","normalization":"array","example":"","description":"List of objects describing the attachments."},{"field":"email.attachments.file.extension","type":"keyword","normalization":"","example":"txt","description":"Attachment file extension."},{"field":"email.attachments.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"email.attachments.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"email.attachments.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"email.attachments.file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"email.attachments.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"email.attachments.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"email.attachments.file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"email.attachments.file.mime_type","type":"keyword","normalization":"","example":"text/plain","description":"MIME type of the attachment file."},{"field":"email.attachments.file.name","type":"keyword","normalization":"","example":"attachment.txt","description":"Name of the attachment file."},{"field":"email.attachments.file.size","type":"long","normalization":"","example":64329,"description":"Attachment file size."},{"field":"email.bcc.address","type":"keyword","normalization":"array","example":"bcc.user1@example.com","description":"Email address of BCC recipient"},{"field":"email.cc.address","type":"keyword","normalization":"array","example":"cc.user1@example.com","description":"Email address of CC recipient"},{"field":"email.content_type","type":"keyword","normalization":"","example":"text/plain","description":"MIME type of the email message."},{"field":"email.delivery_timestamp","type":"date","normalization":"","example":"2020-11-10T22:12:34.8196921Z","description":"Date and time when message was delivered."},{"field":"email.direction","type":"keyword","normalization":"","example":"inbound","description":"Direction of the message."},{"field":"email.from.address","type":"keyword","normalization":"array","example":"sender@example.com","description":"The sender's email address."},{"field":"email.local_id","type":"keyword","normalization":"","example":"c26dbea0-80d5-463b-b93c-4e8b708219ce","description":"Unique identifier given by the source."},{"field":"email.message_id","type":"wildcard","normalization":"","example":"81ce15$8r2j59@mail01.example.com","description":"Value from the Message-ID header."},{"field":"email.origination_timestamp","type":"date","normalization":"","example":"2020-11-10T22:12:34.8196921Z","description":"Date and time the email was composed."},{"field":"email.reply_to.address","type":"keyword","normalization":"array","example":"reply.here@example.com","description":"Address replies should be delivered to."},{"field":"email.sender.address","type":"keyword","normalization":"","example":"","description":"Address of the message sender."},{"field":"email.subject","type":"keyword","normalization":"","example":"Please see this important message.","description":"The subject of the email message."},{"field":"email.subject.text","type":"match_only_text","normalization":"","example":"Please see this important message.","description":"The subject of the email message."},{"field":"email.to.address","type":"keyword","normalization":"array","example":"user1@example.com","description":"Email address of recipient"},{"field":"email.x_mailer","type":"keyword","normalization":"","example":"Spambot v2.5","description":"Application that drafted email."},{"field":"error.code","type":"keyword","normalization":"","example":"","description":"Error code describing the error."},{"field":"error.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the error."},{"field":"error.message","type":"match_only_text","normalization":"","example":"","description":"Error message."},{"field":"error.stack_trace","type":"wildcard","normalization":"","example":"","description":"The stack trace of this error in plain text."},{"field":"error.stack_trace.text","type":"match_only_text","normalization":"","example":"","description":"The stack trace of this error in plain text."},{"field":"error.type","type":"keyword","normalization":"","example":"java.lang.NullPointerException","description":"The type of the error, for example the class name of the exception."},{"field":"event.action","type":"keyword","normalization":"","example":"user-password-change","description":"The action captured by the event."},{"field":"event.category","type":"keyword","normalization":"array","example":"authentication","description":"Event category. The second categorization field in the hierarchy."},{"field":"event.code","type":"keyword","normalization":"","example":4648,"description":"Identification code for this event."},{"field":"event.created","type":"date","normalization":"","example":"2016-05-23T08:05:34.857Z","description":"Time when the event was first read by an agent or by your pipeline."},{"field":"event.dataset","type":"keyword","normalization":"","example":"apache.access","description":"Name of the dataset."},{"field":"event.duration","type":"long","normalization":"","example":"","description":"Duration of the event in nanoseconds."},{"field":"event.end","type":"date","normalization":"","example":"","description":"event.end contains the date when the event ended or when the activity was last observed."},{"field":"event.hash","type":"keyword","normalization":"","example":"123456789012345678901234567890ABCD","description":"Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity."},{"field":"event.id","type":"keyword","normalization":"","example":"8a4f500d","description":"Unique ID to describe the event."},{"field":"event.kind","type":"keyword","normalization":"","example":"alert","description":"The kind of the event. The highest categorization field in the hierarchy."},{"field":"event.original","type":"keyword","normalization":"","example":"Sep 19 08:26:10 host CEF:0|Security| threatmanager|1.0|100| worm successfully stopped|10|src=10.0.0.1 dst=2.1.2.2spt=1232","description":"Raw text message of entire event."},{"field":"event.outcome","type":"keyword","normalization":"","example":"success","description":"The outcome of the event. The lowest level categorization field in the hierarchy."},{"field":"event.provider","type":"keyword","normalization":"","example":"kernel","description":"Source of the event."},{"field":"event.reason","type":"keyword","normalization":"","example":"Terminated an unexpected process","description":"Reason why this event happened, according to the source"},{"field":"event.reference","type":"keyword","normalization":"","example":"https://system.example.com/event/#0001234","description":"Event reference URL"},{"field":"event.risk_score","type":"float","normalization":"","example":"","description":"Risk score or priority of the event (e.g. security solutions). Use your system's original value here."},{"field":"event.risk_score_norm","type":"float","normalization":"","example":"","description":"Normalized risk score or priority of the event (0-100)."},{"field":"event.sequence","type":"long","normalization":"","example":"","description":"Sequence number of the event."},{"field":"event.severity","type":"long","normalization":"","example":7,"description":"Numeric severity of the event."},{"field":"event.start","type":"date","normalization":"","example":"","description":"event.start contains the date when the event started or when the activity was first observed."},{"field":"event.timezone","type":"keyword","normalization":"","example":"","description":"Event time zone."},{"field":"event.type","type":"keyword","normalization":"array","example":"","description":"Event type. The third categorization field in the hierarchy."},{"field":"event.url","type":"keyword","normalization":"","example":"https://mysystem.example.com/alert/5271dedb-f5b0-4218-87f0-4ac4870a38fe","description":"Event investigation URL"},{"field":"faas.coldstart","type":"boolean","normalization":"","example":"","description":"Boolean value indicating a cold start of a function."},{"field":"faas.execution","type":"keyword","normalization":"","example":"af9d5aa4-a685-4c5f-a22b-444f80b3cc28","description":"The execution ID of the current function execution."},{"field":"faas.id","type":"keyword","normalization":"","example":"arn:aws:lambda:us-west-2:123456789012:function:my-function","description":"The unique identifier of a serverless function."},{"field":"faas.name","type":"keyword","normalization":"","example":"my-function","description":"The name of a serverless function."},{"field":"faas.trigger","type":"nested","normalization":"","example":"","description":"Details about the function trigger."},{"field":"faas.trigger.request_id","type":"keyword","normalization":"","example":123456789,"description":"The ID of the trigger request , message, event, etc."},{"field":"faas.trigger.type","type":"keyword","normalization":"","example":"http","description":"The trigger for the function execution."},{"field":"faas.version","type":"keyword","normalization":"","example":123,"description":"The version of a serverless function."},{"field":"file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"file.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"file.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"file.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"host.boot.id","type":"keyword","normalization":"","example":"88a1f0ed-5ae5-41ee-af6b-41921c311872","description":"Linux boot uuid taken from /proc/sys/kernel/random/boot_id"},{"field":"host.cpu.usage","type":"scaled_float","normalization":"","example":"","description":"Percent CPU used, between 0 and 1."},{"field":"host.disk.read.bytes","type":"long","normalization":"","example":"","description":"The number of bytes read by all disks."},{"field":"host.disk.write.bytes","type":"long","normalization":"","example":"","description":"The number of bytes written on all disks."},{"field":"host.domain","type":"keyword","normalization":"","example":"CONTOSO","description":"Name of the directory the group is a member of."},{"field":"host.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"host.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"host.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"host.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"host.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"host.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"host.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"host.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"host.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"host.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"host.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"host.name","type":"keyword","normalization":"","example":"","description":"Name of the host."},{"field":"host.network.egress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes sent on all network interfaces."},{"field":"host.network.egress.packets","type":"long","normalization":"","example":"","description":"The number of packets sent on all network interfaces."},{"field":"host.network.ingress.bytes","type":"long","normalization":"","example":"","description":"The number of bytes received on all network interfaces."},{"field":"host.network.ingress.packets","type":"long","normalization":"","example":"","description":"The number of packets received on all network interfaces."},{"field":"host.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"host.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"host.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"host.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"host.pid_ns_ino","type":"keyword","normalization":"","example":256383,"description":"Pid namespace inode"},{"field":"host.risk.calculated_level","type":"keyword","normalization":"","example":"High","description":"A risk classification level calculated by an internal system as part of entity analytics and entity risk scoring."},{"field":"host.risk.calculated_score","type":"float","normalization":"","example":880.73,"description":"A risk classification score calculated by an internal system as part of entity analytics and entity risk scoring."},{"field":"host.risk.calculated_score_norm","type":"float","normalization":"","example":88.73,"description":"A normalized risk score calculated by an internal system."},{"field":"host.risk.static_level","type":"keyword","normalization":"","example":"High","description":"A risk classification level obtained from outside the system, such as from some external Threat Intelligence Platform."},{"field":"host.risk.static_score","type":"float","normalization":"","example":830,"description":"A risk classification score obtained from outside the system, such as from some external Threat Intelligence Platform."},{"field":"host.risk.static_score_norm","type":"float","normalization":"","example":83,"description":"A normalized risk score calculated by an external system."},{"field":"host.type","type":"keyword","normalization":"","example":"","description":"Type of host."},{"field":"host.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the host has been up."},{"field":"http.request.body.bytes","type":"long","normalization":"","example":887,"description":"Size in bytes of the request body."},{"field":"http.request.body.content","type":"wildcard","normalization":"","example":"Hello world","description":"The full HTTP request body."},{"field":"http.request.body.content.text","type":"match_only_text","normalization":"","example":"Hello world","description":"The full HTTP request body."},{"field":"http.request.bytes","type":"long","normalization":"","example":1437,"description":"Total size in bytes of the request (body and headers)."},{"field":"http.request.id","type":"keyword","normalization":"","example":"123e4567-e89b-12d3-a456-426614174000","description":"HTTP request ID."},{"field":"http.request.method","type":"keyword","normalization":"","example":"POST","description":"HTTP request method."},{"field":"http.request.mime_type","type":"keyword","normalization":"","example":"image/gif","description":"Mime type of the body of the request."},{"field":"http.request.referrer","type":"keyword","normalization":"","example":"https://blog.example.com/","description":"Referrer for this HTTP request."},{"field":"http.response.body.bytes","type":"long","normalization":"","example":887,"description":"Size in bytes of the response body."},{"field":"http.response.body.content","type":"wildcard","normalization":"","example":"Hello world","description":"The full HTTP response body."},{"field":"http.response.body.content.text","type":"match_only_text","normalization":"","example":"Hello world","description":"The full HTTP response body."},{"field":"http.response.bytes","type":"long","normalization":"","example":1437,"description":"Total size in bytes of the response (body and headers)."},{"field":"http.response.mime_type","type":"keyword","normalization":"","example":"image/gif","description":"Mime type of the body of the response."},{"field":"http.response.status_code","type":"long","normalization":"","example":404,"description":"HTTP response status code."},{"field":"http.version","type":"keyword","normalization":"","example":1.1,"description":"HTTP version."},{"field":"log.file.path","type":"keyword","normalization":"","example":"/var/log/fun-times.log","description":"Full path to the log file this event came from."},{"field":"log.level","type":"keyword","normalization":"","example":"error","description":"Log level of the log event."},{"field":"log.logger","type":"keyword","normalization":"","example":"org.elasticsearch.bootstrap.Bootstrap","description":"Name of the logger."},{"field":"log.origin.file.line","type":"long","normalization":"","example":42,"description":"The line number of the file which originated the log event."},{"field":"log.origin.file.name","type":"keyword","normalization":"","example":"Bootstrap.java","description":"The code file which originated the log event."},{"field":"log.origin.function","type":"keyword","normalization":"","example":"init","description":"The function which originated the log event."},{"field":"log.syslog","type":"object","normalization":"","example":"","description":"Syslog metadata"},{"field":"log.syslog.appname","type":"keyword","normalization":"","example":"sshd","description":"The device or application that originated the Syslog message."},{"field":"log.syslog.facility.code","type":"long","normalization":"","example":23,"description":"Syslog numeric facility of the event."},{"field":"log.syslog.facility.name","type":"keyword","normalization":"","example":"local7","description":"Syslog text-based facility of the event."},{"field":"log.syslog.hostname","type":"keyword","normalization":"","example":"example-host","description":"The host that originated the Syslog message."},{"field":"log.syslog.msgid","type":"keyword","normalization":"","example":"ID47","description":"An identifier for the type of Syslog message."},{"field":"log.syslog.priority","type":"long","normalization":"","example":135,"description":"Syslog priority of the event."},{"field":"log.syslog.procid","type":"keyword","normalization":"","example":12345,"description":"The process name or ID that originated the Syslog message."},{"field":"log.syslog.severity.code","type":"long","normalization":"","example":3,"description":"Syslog numeric severity of the event."},{"field":"log.syslog.severity.name","type":"keyword","normalization":"","example":"Error","description":"Syslog text-based severity of the event."},{"field":"log.syslog.structured_data","type":"flattened","normalization":"","example":"","description":"Structured data expressed in RFC 5424 messages."},{"field":"log.syslog.version","type":"keyword","normalization":"","example":1,"description":"Syslog protocol version."},{"field":"network.application","type":"keyword","normalization":"","example":"aim","description":"Application level protocol name."},{"field":"network.bytes","type":"long","normalization":"","example":368,"description":"Total bytes transferred in both directions."},{"field":"network.community_id","type":"keyword","normalization":"","example":"1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=","description":"A hash of source and destination IPs and ports."},{"field":"network.direction","type":"keyword","normalization":"","example":"inbound","description":"Direction of the network traffic."},{"field":"network.forwarded_ip","type":"ip","normalization":"","example":"192.1.1.2","description":"Host IP address when the source IP address is the proxy."},{"field":"network.iana_number","type":"keyword","normalization":"","example":6,"description":"IANA Protocol Number."},{"field":"network.inner","type":"object","normalization":"","example":"","description":"Inner VLAN tag information"},{"field":"network.inner.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"network.inner.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"network.name","type":"keyword","normalization":"","example":"Guest Wifi","description":"Name given by operators to sections of their network."},{"field":"network.packets","type":"long","normalization":"","example":24,"description":"Total packets transferred in both directions."},{"field":"network.protocol","type":"keyword","normalization":"","example":"http","description":"Application protocol name."},{"field":"network.transport","type":"keyword","normalization":"","example":"tcp","description":"Protocol Name corresponding to the field `iana_number`."},{"field":"network.type","type":"keyword","normalization":"","example":"ipv4","description":"In the OSI Model this would be the Network Layer. ipv4, ipv6, ipsec, pim, etc"},{"field":"network.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"network.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress","type":"object","normalization":"","example":"","description":"Object field for egress information"},{"field":"observer.egress.interface.alias","type":"keyword","normalization":"","example":"outside","description":"Interface alias"},{"field":"observer.egress.interface.id","type":"keyword","normalization":"","example":10,"description":"Interface ID"},{"field":"observer.egress.interface.name","type":"keyword","normalization":"","example":"eth0","description":"Interface name"},{"field":"observer.egress.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"observer.egress.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.egress.zone","type":"keyword","normalization":"","example":"Public_Internet","description":"Observer Egress zone"},{"field":"observer.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"observer.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"observer.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"observer.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"observer.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"observer.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"observer.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"observer.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"observer.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"observer.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"observer.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"observer.hostname","type":"keyword","normalization":"","example":"","description":"Hostname of the observer."},{"field":"observer.ingress","type":"object","normalization":"","example":"","description":"Object field for ingress information"},{"field":"observer.ingress.interface.alias","type":"keyword","normalization":"","example":"outside","description":"Interface alias"},{"field":"observer.ingress.interface.id","type":"keyword","normalization":"","example":10,"description":"Interface ID"},{"field":"observer.ingress.interface.name","type":"keyword","normalization":"","example":"eth0","description":"Interface name"},{"field":"observer.ingress.vlan.id","type":"keyword","normalization":"","example":10,"description":"VLAN ID as reported by the observer."},{"field":"observer.ingress.vlan.name","type":"keyword","normalization":"","example":"outside","description":"Optional VLAN name as reported by the observer."},{"field":"observer.ingress.zone","type":"keyword","normalization":"","example":"DMZ","description":"Observer ingress zone"},{"field":"observer.ip","type":"ip","normalization":"array","example":"","description":"IP addresses of the observer."},{"field":"observer.mac","type":"keyword","normalization":"array","example":["00-00-5E-00-53-23","00-00-5E-00-53-24"],"description":"MAC addresses of the observer."},{"field":"observer.name","type":"keyword","normalization":"","example":"1_proxySG","description":"Custom name of the observer."},{"field":"observer.os.family","type":"keyword","normalization":"","example":"debian","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"observer.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"observer.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"observer.os.kernel","type":"keyword","normalization":"","example":"4.4.0-112-generic","description":"Operating system kernel version as a raw string."},{"field":"observer.os.name","type":"keyword","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"observer.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"observer.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"observer.os.type","type":"keyword","normalization":"","example":"macos","description":"Which commercial OS family (one of: linux, macos, unix, windows, ios or android)."},{"field":"observer.os.version","type":"keyword","normalization":"","example":"10.14.1","description":"Operating system version as a raw string."},{"field":"observer.product","type":"keyword","normalization":"","example":"s200","description":"The product name of the observer."},{"field":"observer.serial_number","type":"keyword","normalization":"","example":"","description":"Observer serial number."},{"field":"observer.type","type":"keyword","normalization":"","example":"firewall","description":"The type of the observer the data is coming from."},{"field":"observer.vendor","type":"keyword","normalization":"","example":"Symantec","description":"Vendor name of the observer."},{"field":"observer.version","type":"keyword","normalization":"","example":"","description":"Observer version."},{"field":"orchestrator.api_version","type":"keyword","normalization":"","example":"v1beta1","description":"API version being used to carry out the action"},{"field":"orchestrator.cluster.id","type":"keyword","normalization":"","example":"","description":"Unique ID of the cluster."},{"field":"orchestrator.cluster.name","type":"keyword","normalization":"","example":"","description":"Name of the cluster."},{"field":"orchestrator.cluster.url","type":"keyword","normalization":"","example":"","description":"URL of the API used to manage the cluster."},{"field":"orchestrator.cluster.version","type":"keyword","normalization":"","example":"","description":"The version of the cluster."},{"field":"orchestrator.namespace","type":"keyword","normalization":"","example":"kube-system","description":"Namespace in which the action is taking place."},{"field":"orchestrator.organization","type":"keyword","normalization":"","example":"elastic","description":"Organization affected by the event (for multi-tenant orchestrator setups)."},{"field":"orchestrator.resource.id","type":"keyword","normalization":"","example":"","description":"Unique ID of the resource being acted upon."},{"field":"orchestrator.resource.ip","type":"ip","normalization":"array","example":"","description":"IP address assigned to the resource associated with the event being observed."},{"field":"orchestrator.resource.name","type":"keyword","normalization":"","example":"test-pod-cdcws","description":"Name of the resource being acted upon."},{"field":"orchestrator.resource.parent.type","type":"keyword","normalization":"","example":"DaemonSet","description":"Type or kind of the parent resource associated with the event being observed."},{"field":"orchestrator.resource.type","type":"keyword","normalization":"","example":"service","description":"Type of resource being acted upon."},{"field":"orchestrator.type","type":"keyword","normalization":"","example":"kubernetes","description":"Orchestrator cluster type (e.g. kubernetes, nomad or cloudfoundry)."},{"field":"organization.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the organization."},{"field":"organization.name","type":"keyword","normalization":"","example":"","description":"Organization name."},{"field":"organization.name.text","type":"match_only_text","normalization":"","example":"","description":"Organization name."},{"field":"package.architecture","type":"keyword","normalization":"","example":"x86_64","description":"Package architecture."},{"field":"package.build_version","type":"keyword","normalization":"","example":"36f4f7e89dd61b0988b12ee000b98966867710cd","description":"Build version information"},{"field":"package.checksum","type":"keyword","normalization":"","example":"68b329da9893e34099c7d8ad5cb9c940","description":"Checksum of the installed package for verification."},{"field":"package.description","type":"keyword","normalization":"","example":"Open source programming language to build simple/reliable/efficient software.","description":"Description of the package."},{"field":"package.install_scope","type":"keyword","normalization":"","example":"global","description":"Indicating how the package was installed, e.g. user-local, global."},{"field":"package.installed","type":"date","normalization":"","example":"","description":"Time when package was installed."},{"field":"package.license","type":"keyword","normalization":"","example":"Apache License 2.0","description":"Package license"},{"field":"package.name","type":"keyword","normalization":"","example":"go","description":"Package name"},{"field":"package.path","type":"keyword","normalization":"","example":"/usr/local/Cellar/go/1.12.9/","description":"Path where the package is installed."},{"field":"package.reference","type":"keyword","normalization":"","example":"https://golang.org","description":"Package home page or reference URL"},{"field":"package.size","type":"long","normalization":"","example":62231,"description":"Package size in bytes."},{"field":"package.type","type":"keyword","normalization":"","example":"rpm","description":"Package type"},{"field":"package.version","type":"keyword","normalization":"","example":"1.12.9","description":"Package version"},{"field":"process.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"process.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"process.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"process.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"process.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"process.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"process.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"process.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"process.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"process.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"process.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"process.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"process.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"process.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"process.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"process.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"process.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"process.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"process.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"process.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"process.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"process.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"process.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"process.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"process.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"process.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"process.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"process.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"process.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"process.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"process.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"process.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"process.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"process.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"process.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"process.end","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process ended."},{"field":"process.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.entry_leader.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.entry_leader.attested_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.attested_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.attested_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.attested_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.entry_leader.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.entry_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.entry_meta.source.ip","type":"ip","normalization":"","example":"","description":"IP address of the source."},{"field":"process.entry_leader.entry_meta.type","type":"keyword","normalization":"","example":"","description":"The entry type for the entry session leader."},{"field":"process.entry_leader.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.entry_leader.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.entry_leader.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.entry_leader.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.entry_leader.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.entry_leader.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.entry_leader.parent.session_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.entry_leader.parent.session_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.entry_leader.parent.session_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.entry_leader.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.entry_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.entry_leader.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.same_as_process","type":"boolean","normalization":"","example":"True","description":"This boolean is used to identify if a leader process is the same as the top level process."},{"field":"process.entry_leader.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.entry_leader.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.entry_leader.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.entry_leader.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.entry_leader.tty.char_device.major","type":"long","normalization":"","example":4,"description":"The TTY character device's major number."},{"field":"process.entry_leader.tty.char_device.minor","type":"long","normalization":"","example":1,"description":"The TTY character device's minor number."},{"field":"process.entry_leader.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.entry_leader.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.entry_leader.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.entry_leader.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.env_vars","type":"keyword","normalization":"array","example":["PATH=/usr/local/bin:/usr/bin","USER=ubuntu"],"description":"Array of environment variable bindings."},{"field":"process.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.exit_code","type":"long","normalization":"","example":137,"description":"The exit code of the process."},{"field":"process.group_leader.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.group_leader.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.group_leader.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.group_leader.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.group_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.group_leader.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.group_leader.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.group_leader.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.group_leader.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.group_leader.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.group_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.group_leader.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.group_leader.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.same_as_process","type":"boolean","normalization":"","example":"True","description":"This boolean is used to identify if a leader process is the same as the top level process."},{"field":"process.group_leader.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.group_leader.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.group_leader.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.group_leader.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.group_leader.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.group_leader.tty.char_device.major","type":"long","normalization":"","example":4,"description":"The TTY character device's major number."},{"field":"process.group_leader.tty.char_device.minor","type":"long","normalization":"","example":1,"description":"The TTY character device's minor number."},{"field":"process.group_leader.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.group_leader.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.group_leader.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.group_leader.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"process.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"process.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"process.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"process.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"process.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"process.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"process.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.parent.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.parent.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"process.parent.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"process.parent.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"process.parent.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"process.parent.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"process.parent.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"process.parent.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"process.parent.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"process.parent.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"process.parent.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.parent.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.parent.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"process.parent.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"process.parent.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"process.parent.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"process.parent.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"process.parent.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"process.parent.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"process.parent.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"process.parent.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"process.parent.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"process.parent.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"process.parent.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"process.parent.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"process.parent.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"process.parent.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"process.parent.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"process.parent.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"process.parent.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"process.parent.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"process.parent.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"process.parent.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"process.parent.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"process.parent.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"process.parent.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"process.parent.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"process.parent.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"process.parent.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"process.parent.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"process.parent.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"process.parent.end","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process ended."},{"field":"process.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.parent.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.parent.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.parent.exit_code","type":"long","normalization":"","example":137,"description":"The exit code of the process."},{"field":"process.parent.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.group_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.parent.group_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.parent.group_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.parent.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"process.parent.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"process.parent.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"process.parent.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"process.parent.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"process.parent.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"process.parent.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"process.parent.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.parent.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.parent.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"process.parent.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"process.parent.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"process.parent.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"process.parent.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"process.parent.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"process.parent.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"process.parent.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"process.parent.pgid","type":"long","normalization":"","example":"","description":"Deprecated identifier of the group of processes the process belongs to."},{"field":"process.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.parent.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.parent.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.parent.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.parent.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.parent.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.parent.thread.id","type":"long","normalization":"","example":4242,"description":"Thread ID."},{"field":"process.parent.thread.name","type":"keyword","normalization":"","example":"thread-0","description":"Thread name."},{"field":"process.parent.title","type":"keyword","normalization":"","example":"","description":"Process title."},{"field":"process.parent.title.text","type":"match_only_text","normalization":"","example":"","description":"Process title."},{"field":"process.parent.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.parent.tty.char_device.major","type":"long","normalization":"","example":4,"description":"The TTY character device's major number."},{"field":"process.parent.tty.char_device.minor","type":"long","normalization":"","example":1,"description":"The TTY character device's minor number."},{"field":"process.parent.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the process has been up."},{"field":"process.parent.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.parent.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.parent.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.parent.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"process.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"process.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"process.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"process.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"process.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"process.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"process.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"process.pgid","type":"long","normalization":"","example":"","description":"Deprecated identifier of the group of processes the process belongs to."},{"field":"process.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.previous.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.previous.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.previous.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.previous.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.args","type":"keyword","normalization":"array","example":["/usr/bin/ssh","-l","user","10.0.0.16"],"description":"Array of process arguments."},{"field":"process.session_leader.args_count","type":"long","normalization":"","example":4,"description":"Length of the process.args array."},{"field":"process.session_leader.command_line","type":"wildcard","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.session_leader.command_line.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh -l user 10.0.0.16","description":"Full command line that started the process."},{"field":"process.session_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.session_leader.executable","type":"keyword","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.session_leader.executable.text","type":"match_only_text","normalization":"","example":"/usr/bin/ssh","description":"Absolute path to the process executable."},{"field":"process.session_leader.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.interactive","type":"boolean","normalization":"","example":"True","description":"Whether the process is connected to an interactive shell."},{"field":"process.session_leader.name","type":"keyword","normalization":"","example":"ssh","description":"Process name."},{"field":"process.session_leader.name.text","type":"match_only_text","normalization":"","example":"ssh","description":"Process name."},{"field":"process.session_leader.parent.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.session_leader.parent.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.session_leader.parent.session_leader.entity_id","type":"keyword","normalization":"","example":"c2c455d9f99375d","description":"Unique identifier for the process."},{"field":"process.session_leader.parent.session_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.session_leader.parent.session_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.session_leader.parent.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.session_leader.pid","type":"long","normalization":"","example":4242,"description":"Process id."},{"field":"process.session_leader.real_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.real_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.real_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.session_leader.real_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.real_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.same_as_process","type":"boolean","normalization":"","example":"True","description":"This boolean is used to identify if a leader process is the same as the top level process."},{"field":"process.session_leader.saved_group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.saved_group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.saved_user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.session_leader.saved_user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.saved_user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.session_leader.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.session_leader.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.session_leader.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.session_leader.tty.char_device.major","type":"long","normalization":"","example":4,"description":"The TTY character device's major number."},{"field":"process.session_leader.tty.char_device.minor","type":"long","normalization":"","example":1,"description":"The TTY character device's minor number."},{"field":"process.session_leader.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.session_leader.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.session_leader.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.session_leader.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.start","type":"date","normalization":"","example":"2016-05-23T08:05:34.853Z","description":"The time the process started."},{"field":"process.supplemental_groups.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"process.supplemental_groups.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"process.thread.id","type":"long","normalization":"","example":4242,"description":"Thread ID."},{"field":"process.thread.name","type":"keyword","normalization":"","example":"thread-0","description":"Thread name."},{"field":"process.title","type":"keyword","normalization":"","example":"","description":"Process title."},{"field":"process.title.text","type":"match_only_text","normalization":"","example":"","description":"Process title."},{"field":"process.tty","type":"object","normalization":"","example":"","description":"Information about the controlling TTY device."},{"field":"process.tty.char_device.major","type":"long","normalization":"","example":4,"description":"The TTY character device's major number."},{"field":"process.tty.char_device.minor","type":"long","normalization":"","example":1,"description":"The TTY character device's minor number."},{"field":"process.tty.columns","type":"long","normalization":"","example":80,"description":"The number of character columns per line. e.g terminal width"},{"field":"process.tty.rows","type":"long","normalization":"","example":24,"description":"The number of character rows in the terminal. e.g terminal height"},{"field":"process.uptime","type":"long","normalization":"","example":1325,"description":"Seconds the process has been up."},{"field":"process.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"process.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"process.working_directory","type":"keyword","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"process.working_directory.text","type":"match_only_text","normalization":"","example":"/home/alice","description":"The working directory of the process."},{"field":"registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"related.hash","type":"keyword","normalization":"array","example":"","description":"All the hashes seen on your event."},{"field":"related.hosts","type":"keyword","normalization":"array","example":"","description":"All the host identifiers seen on your event."},{"field":"related.ip","type":"ip","normalization":"array","example":"","description":"All of the IPs seen on your event."},{"field":"related.user","type":"keyword","normalization":"array","example":"","description":"All the user names or other user identifiers seen on the event."},{"field":"rule.author","type":"keyword","normalization":"array","example":["Star-Lord"],"description":"Rule author"},{"field":"rule.category","type":"keyword","normalization":"","example":"Attempted Information Leak","description":"Rule category"},{"field":"rule.description","type":"keyword","normalization":"","example":"Block requests to public DNS over HTTPS / TLS protocols","description":"Rule description"},{"field":"rule.id","type":"keyword","normalization":"","example":101,"description":"Rule ID"},{"field":"rule.license","type":"keyword","normalization":"","example":"Apache 2.0","description":"Rule license"},{"field":"rule.name","type":"keyword","normalization":"","example":"BLOCK_DNS_over_TLS","description":"Rule name"},{"field":"rule.reference","type":"keyword","normalization":"","example":"https://en.wikipedia.org/wiki/DNS_over_TLS","description":"Rule reference URL"},{"field":"rule.ruleset","type":"keyword","normalization":"","example":"Standard_Protocol_Filters","description":"Rule ruleset"},{"field":"rule.uuid","type":"keyword","normalization":"","example":1100110011,"description":"Rule UUID"},{"field":"rule.version","type":"keyword","normalization":"","example":1.1,"description":"Rule version"},{"field":"server.address","type":"keyword","normalization":"","example":"","description":"Server network address."},{"field":"server.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"server.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"server.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"server.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the server to the client."},{"field":"server.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the server."},{"field":"server.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"server.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"server.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"server.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"server.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"server.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"server.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"server.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"server.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"server.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"server.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"server.ip","type":"ip","normalization":"","example":"","description":"IP address of the server."},{"field":"server.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the server."},{"field":"server.nat.ip","type":"ip","normalization":"","example":"","description":"Server NAT ip"},{"field":"server.nat.port","type":"long","normalization":"","example":"","description":"Server NAT port"},{"field":"server.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the server to the client."},{"field":"server.port","type":"long","normalization":"","example":"","description":"Port of the server."},{"field":"server.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered server domain, stripped of the subdomain."},{"field":"server.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"server.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"server.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"server.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"server.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"server.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"server.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"server.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"server.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"server.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"server.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"server.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"server.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"server.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"service.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.node.role","type":"keyword","normalization":"","example":"background_tasks","description":"Deprecated role (singular) of the service node."},{"field":"service.node.roles","type":"keyword","normalization":"array","example":["ui","background_tasks"],"description":"Roles of the service node."},{"field":"service.origin.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.origin.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.origin.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.origin.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.origin.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.origin.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.origin.node.role","type":"keyword","normalization":"","example":"background_tasks","description":"Deprecated role (singular) of the service node."},{"field":"service.origin.node.roles","type":"keyword","normalization":"array","example":["ui","background_tasks"],"description":"Roles of the service node."},{"field":"service.origin.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.origin.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.origin.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"service.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.target.address","type":"keyword","normalization":"","example":"172.26.0.2:5432","description":"Address of this service."},{"field":"service.target.environment","type":"keyword","normalization":"","example":"production","description":"Environment of the service."},{"field":"service.target.ephemeral_id","type":"keyword","normalization":"","example":"8a4f500f","description":"Ephemeral identifier of this service."},{"field":"service.target.id","type":"keyword","normalization":"","example":"d37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6","description":"Unique identifier of the running service."},{"field":"service.target.name","type":"keyword","normalization":"","example":"elasticsearch-metrics","description":"Name of the service."},{"field":"service.target.node.name","type":"keyword","normalization":"","example":"instance-0000000016","description":"Name of the service node."},{"field":"service.target.node.role","type":"keyword","normalization":"","example":"background_tasks","description":"Deprecated role (singular) of the service node."},{"field":"service.target.node.roles","type":"keyword","normalization":"array","example":["ui","background_tasks"],"description":"Roles of the service node."},{"field":"service.target.state","type":"keyword","normalization":"","example":"","description":"Current state of the service."},{"field":"service.target.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.target.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"service.type","type":"keyword","normalization":"","example":"elasticsearch","description":"The type of the service."},{"field":"service.version","type":"keyword","normalization":"","example":"3.2.4","description":"Version of the service."},{"field":"source.address","type":"keyword","normalization":"","example":"","description":"Source network address."},{"field":"source.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"source.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"source.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"source.bytes","type":"long","normalization":"","example":184,"description":"Bytes sent from the source to the destination."},{"field":"source.domain","type":"keyword","normalization":"","example":"foo.example.com","description":"The domain name of the source."},{"field":"source.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"source.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"source.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"source.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"source.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"source.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"source.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"source.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"source.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"source.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"source.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"source.ip","type":"ip","normalization":"","example":"","description":"IP address of the source."},{"field":"source.mac","type":"keyword","normalization":"","example":"00-00-5E-00-53-23","description":"MAC address of the source."},{"field":"source.nat.ip","type":"ip","normalization":"","example":"","description":"Source NAT ip"},{"field":"source.nat.port","type":"long","normalization":"","example":"","description":"Source NAT port"},{"field":"source.packets","type":"long","normalization":"","example":12,"description":"Packets sent from the source to the destination."},{"field":"source.port","type":"long","normalization":"","example":"","description":"Port of the source."},{"field":"source.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered source domain, stripped of the subdomain."},{"field":"source.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"source.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"source.user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"source.user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"source.user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"source.user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"source.user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"source.user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"source.user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"source.user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"source.user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"source.user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"source.user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"source.user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"span.id","type":"keyword","normalization":"","example":"3ff9a8981b7ccd5a","description":"Unique identifier of the span within the scope of its trace."},{"field":"threat.enrichments","type":"nested","normalization":"array","example":"","description":"List of objects containing indicators enriching the event."},{"field":"threat.enrichments.indicator","type":"object","normalization":"","example":"","description":"Object containing indicators enriching the event."},{"field":"threat.enrichments.indicator.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"threat.enrichments.indicator.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.enrichments.indicator.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.enrichments.indicator.confidence","type":"keyword","normalization":"","example":"Medium","description":"Indicator confidence rating"},{"field":"threat.enrichments.indicator.description","type":"keyword","normalization":"","example":"IP x.x.x.x was observed delivering the Angler EK.","description":"Indicator description"},{"field":"threat.enrichments.indicator.email.address","type":"keyword","normalization":"","example":"phish@example.com","description":"Indicator email address"},{"field":"threat.enrichments.indicator.file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"threat.enrichments.indicator.file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"threat.enrichments.indicator.file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"threat.enrichments.indicator.file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"threat.enrichments.indicator.file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"threat.enrichments.indicator.file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"threat.enrichments.indicator.file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"threat.enrichments.indicator.file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"threat.enrichments.indicator.file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.enrichments.indicator.file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"threat.enrichments.indicator.file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"threat.enrichments.indicator.file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"threat.enrichments.indicator.file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"threat.enrichments.indicator.file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"threat.enrichments.indicator.file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"threat.enrichments.indicator.file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"threat.enrichments.indicator.file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"threat.enrichments.indicator.file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.enrichments.indicator.file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"threat.enrichments.indicator.file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.enrichments.indicator.file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"threat.enrichments.indicator.file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"threat.enrichments.indicator.file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"threat.enrichments.indicator.file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"threat.enrichments.indicator.file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"threat.enrichments.indicator.file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"threat.enrichments.indicator.file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"threat.enrichments.indicator.file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"threat.enrichments.indicator.file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"threat.enrichments.indicator.file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"threat.enrichments.indicator.file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"threat.enrichments.indicator.file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"threat.enrichments.indicator.file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"threat.enrichments.indicator.file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"threat.enrichments.indicator.file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"threat.enrichments.indicator.file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"threat.enrichments.indicator.file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.enrichments.indicator.file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"threat.enrichments.indicator.file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"threat.enrichments.indicator.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"threat.enrichments.indicator.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"threat.enrichments.indicator.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"threat.enrichments.indicator.file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"threat.enrichments.indicator.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"threat.enrichments.indicator.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"threat.enrichments.indicator.file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"threat.enrichments.indicator.file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"threat.enrichments.indicator.file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.enrichments.indicator.file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"threat.enrichments.indicator.file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"threat.enrichments.indicator.file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"threat.enrichments.indicator.file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"threat.enrichments.indicator.file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.enrichments.indicator.file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"threat.enrichments.indicator.file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"threat.enrichments.indicator.file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"threat.enrichments.indicator.file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"threat.enrichments.indicator.file.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.enrichments.indicator.file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"threat.enrichments.indicator.file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.enrichments.indicator.file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"threat.enrichments.indicator.file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.enrichments.indicator.file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.enrichments.indicator.file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.file.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.enrichments.indicator.file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.enrichments.indicator.first_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was first reported."},{"field":"threat.enrichments.indicator.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"threat.enrichments.indicator.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"threat.enrichments.indicator.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"threat.enrichments.indicator.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"threat.enrichments.indicator.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"threat.enrichments.indicator.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"threat.enrichments.indicator.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"threat.enrichments.indicator.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"threat.enrichments.indicator.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"threat.enrichments.indicator.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"threat.enrichments.indicator.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"threat.enrichments.indicator.ip","type":"ip","normalization":"","example":"1.2.3.4","description":"Indicator IP address"},{"field":"threat.enrichments.indicator.last_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last reported."},{"field":"threat.enrichments.indicator.marking.tlp","type":"keyword","normalization":"","example":"WHITE","description":"Indicator TLP marking"},{"field":"threat.enrichments.indicator.modified_at","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last updated."},{"field":"threat.enrichments.indicator.port","type":"long","normalization":"","example":443,"description":"Indicator port"},{"field":"threat.enrichments.indicator.provider","type":"keyword","normalization":"","example":"lrz_urlhaus","description":"Indicator provider"},{"field":"threat.enrichments.indicator.reference","type":"keyword","normalization":"","example":"https://system.example.com/indicator/0001234","description":"Indicator reference URL"},{"field":"threat.enrichments.indicator.registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"threat.enrichments.indicator.registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"threat.enrichments.indicator.registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"threat.enrichments.indicator.registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"threat.enrichments.indicator.registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"threat.enrichments.indicator.registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"threat.enrichments.indicator.registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"threat.enrichments.indicator.scanner_stats","type":"long","normalization":"","example":4,"description":"Scanner statistics"},{"field":"threat.enrichments.indicator.sightings","type":"long","normalization":"","example":20,"description":"Number of times indicator observed"},{"field":"threat.enrichments.indicator.type","type":"keyword","normalization":"","example":"ipv4-addr","description":"Type of indicator"},{"field":"threat.enrichments.indicator.url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"threat.enrichments.indicator.url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.enrichments.indicator.url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"threat.enrichments.indicator.url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.enrichments.indicator.url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.enrichments.indicator.url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"threat.enrichments.indicator.url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"threat.enrichments.indicator.url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"threat.enrichments.indicator.url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"threat.enrichments.indicator.url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.enrichments.indicator.url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"threat.enrichments.indicator.url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"threat.enrichments.indicator.url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.enrichments.indicator.url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"threat.enrichments.indicator.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.enrichments.indicator.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.enrichments.indicator.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.enrichments.indicator.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.enrichments.indicator.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.enrichments.indicator.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.enrichments.indicator.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.enrichments.indicator.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.enrichments.indicator.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.enrichments.indicator.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.enrichments.indicator.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.enrichments.indicator.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.enrichments.indicator.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.enrichments.indicator.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.enrichments.indicator.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.enrichments.indicator.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.enrichments.indicator.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.enrichments.indicator.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.enrichments.matched.atomic","type":"keyword","normalization":"","example":"bad-domain.com","description":"Matched indicator value"},{"field":"threat.enrichments.matched.field","type":"keyword","normalization":"","example":"file.hash.sha256","description":"Matched indicator field"},{"field":"threat.enrichments.matched.id","type":"keyword","normalization":"","example":"ff93aee5-86a1-4a61-b0e6-0cdc313d01b5","description":"Matched indicator identifier"},{"field":"threat.enrichments.matched.index","type":"keyword","normalization":"","example":"filebeat-8.0.0-2021.05.23-000011","description":"Matched indicator index"},{"field":"threat.enrichments.matched.occurred","type":"date","normalization":"","example":"2021-10-05T17:00:58.326Z","description":"Date of match"},{"field":"threat.enrichments.matched.type","type":"keyword","normalization":"","example":"indicator_match_rule","description":"Type of indicator match"},{"field":"threat.feed.dashboard_id","type":"keyword","normalization":"","example":"5ba16340-72e6-11eb-a3e3-b3cc7c78a70f","description":"Feed dashboard ID."},{"field":"threat.feed.description","type":"keyword","normalization":"","example":"Threat feed from the AlienVault Open Threat eXchange network.","description":"Description of the threat feed."},{"field":"threat.feed.name","type":"keyword","normalization":"","example":"AlienVault OTX","description":"Name of the threat feed."},{"field":"threat.feed.reference","type":"keyword","normalization":"","example":"https://otx.alienvault.com","description":"Reference for the threat feed."},{"field":"threat.framework","type":"keyword","normalization":"","example":"MITRE ATT&CK","description":"Threat classification framework."},{"field":"threat.group.alias","type":"keyword","normalization":"array","example":["Magecart Group 6"],"description":"Alias of the group."},{"field":"threat.group.id","type":"keyword","normalization":"","example":"G0037","description":"ID of the group."},{"field":"threat.group.name","type":"keyword","normalization":"","example":"FIN6","description":"Name of the group."},{"field":"threat.group.reference","type":"keyword","normalization":"","example":"https://attack.mitre.org/groups/G0037/","description":"Reference URL of the group."},{"field":"threat.indicator.as.number","type":"long","normalization":"","example":15169,"description":"Unique number allocated to the autonomous system."},{"field":"threat.indicator.as.organization.name","type":"keyword","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.indicator.as.organization.name.text","type":"match_only_text","normalization":"","example":"Google LLC","description":"Organization name."},{"field":"threat.indicator.confidence","type":"keyword","normalization":"","example":"Medium","description":"Indicator confidence rating"},{"field":"threat.indicator.description","type":"keyword","normalization":"","example":"IP x.x.x.x was observed delivering the Angler EK.","description":"Indicator description"},{"field":"threat.indicator.email.address","type":"keyword","normalization":"","example":"phish@example.com","description":"Indicator email address"},{"field":"threat.indicator.file.accessed","type":"date","normalization":"","example":"","description":"Last time the file was accessed."},{"field":"threat.indicator.file.attributes","type":"keyword","normalization":"array","example":["readonly","system"],"description":"Array of file attributes."},{"field":"threat.indicator.file.code_signature.digest_algorithm","type":"keyword","normalization":"","example":"sha256","description":"Hashing algorithm used to sign the process."},{"field":"threat.indicator.file.code_signature.exists","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if a signature is present."},{"field":"threat.indicator.file.code_signature.signing_id","type":"keyword","normalization":"","example":"com.apple.xpc.proxy","description":"The identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.status","type":"keyword","normalization":"","example":"ERROR_UNTRUSTED_ROOT","description":"Additional information about the certificate status."},{"field":"threat.indicator.file.code_signature.subject_name","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Subject name of the code signer"},{"field":"threat.indicator.file.code_signature.team_id","type":"keyword","normalization":"","example":"EQHXZ8M8AV","description":"The team identifier used to sign the process."},{"field":"threat.indicator.file.code_signature.timestamp","type":"date","normalization":"","example":"2021-01-01T12:10:30Z","description":"When the signature was generated and signed."},{"field":"threat.indicator.file.code_signature.trusted","type":"boolean","normalization":"","example":true,"description":"Stores the trust status of the certificate chain."},{"field":"threat.indicator.file.code_signature.valid","type":"boolean","normalization":"","example":true,"description":"Boolean to capture if the digital signature is verified against the binary content."},{"field":"threat.indicator.file.created","type":"date","normalization":"","example":"","description":"File creation time."},{"field":"threat.indicator.file.ctime","type":"date","normalization":"","example":"","description":"Last time the file attributes or metadata changed."},{"field":"threat.indicator.file.device","type":"keyword","normalization":"","example":"sda","description":"Device that is the source of the file."},{"field":"threat.indicator.file.directory","type":"keyword","normalization":"","example":"/home/alice","description":"Directory where the file is located."},{"field":"threat.indicator.file.drive_letter","type":"keyword","normalization":"","example":"C","description":"Drive letter where the file is located."},{"field":"threat.indicator.file.elf.architecture","type":"keyword","normalization":"","example":"x86-64","description":"Machine architecture of the ELF file."},{"field":"threat.indicator.file.elf.byte_order","type":"keyword","normalization":"","example":"Little Endian","description":"Byte sequence of ELF file."},{"field":"threat.indicator.file.elf.cpu_type","type":"keyword","normalization":"","example":"Intel","description":"CPU type of the ELF file."},{"field":"threat.indicator.file.elf.creation_date","type":"date","normalization":"","example":"","description":"Build or compile date."},{"field":"threat.indicator.file.elf.exports","type":"flattened","normalization":"array","example":"","description":"List of exported element names and types."},{"field":"threat.indicator.file.elf.header.abi_version","type":"keyword","normalization":"","example":"","description":"Version of the ELF Application Binary Interface (ABI)."},{"field":"threat.indicator.file.elf.header.class","type":"keyword","normalization":"","example":"","description":"Header class of the ELF file."},{"field":"threat.indicator.file.elf.header.data","type":"keyword","normalization":"","example":"","description":"Data table of the ELF header."},{"field":"threat.indicator.file.elf.header.entrypoint","type":"long","normalization":"","example":"","description":"Header entrypoint of the ELF file."},{"field":"threat.indicator.file.elf.header.object_version","type":"keyword","normalization":"","example":"","description":"0x1\" for original ELF files."},{"field":"threat.indicator.file.elf.header.os_abi","type":"keyword","normalization":"","example":"","description":"Application Binary Interface (ABI) of the Linux OS."},{"field":"threat.indicator.file.elf.header.type","type":"keyword","normalization":"","example":"","description":"Header type of the ELF file."},{"field":"threat.indicator.file.elf.header.version","type":"keyword","normalization":"","example":"","description":"Version of the ELF header."},{"field":"threat.indicator.file.elf.imports","type":"flattened","normalization":"array","example":"","description":"List of imported element names and types."},{"field":"threat.indicator.file.elf.sections","type":"nested","normalization":"array","example":"","description":"Section information of the ELF file."},{"field":"threat.indicator.file.elf.sections.chi2","type":"long","normalization":"","example":"","description":"Chi-square probability distribution of the section."},{"field":"threat.indicator.file.elf.sections.entropy","type":"long","normalization":"","example":"","description":"Shannon entropy calculation from the section."},{"field":"threat.indicator.file.elf.sections.flags","type":"keyword","normalization":"","example":"","description":"ELF Section List flags."},{"field":"threat.indicator.file.elf.sections.name","type":"keyword","normalization":"","example":"","description":"ELF Section List name."},{"field":"threat.indicator.file.elf.sections.physical_offset","type":"keyword","normalization":"","example":"","description":"ELF Section List offset."},{"field":"threat.indicator.file.elf.sections.physical_size","type":"long","normalization":"","example":"","description":"ELF Section List physical size."},{"field":"threat.indicator.file.elf.sections.type","type":"keyword","normalization":"","example":"","description":"ELF Section List type."},{"field":"threat.indicator.file.elf.sections.virtual_address","type":"long","normalization":"","example":"","description":"ELF Section List virtual address."},{"field":"threat.indicator.file.elf.sections.virtual_size","type":"long","normalization":"","example":"","description":"ELF Section List virtual size."},{"field":"threat.indicator.file.elf.segments","type":"nested","normalization":"array","example":"","description":"ELF object segment list."},{"field":"threat.indicator.file.elf.segments.sections","type":"keyword","normalization":"","example":"","description":"ELF object segment sections."},{"field":"threat.indicator.file.elf.segments.type","type":"keyword","normalization":"","example":"","description":"ELF object segment type."},{"field":"threat.indicator.file.elf.shared_libraries","type":"keyword","normalization":"array","example":"","description":"List of shared libraries used by this ELF object."},{"field":"threat.indicator.file.elf.telfhash","type":"keyword","normalization":"","example":"","description":"telfhash hash for ELF file."},{"field":"threat.indicator.file.extension","type":"keyword","normalization":"","example":"png","description":"File extension, excluding the leading dot."},{"field":"threat.indicator.file.fork_name","type":"keyword","normalization":"","example":"Zone.Identifer","description":"A fork is additional data associated with a filesystem object."},{"field":"threat.indicator.file.gid","type":"keyword","normalization":"","example":1001,"description":"Primary group ID (GID) of the file."},{"field":"threat.indicator.file.group","type":"keyword","normalization":"","example":"alice","description":"Primary group name of the file."},{"field":"threat.indicator.file.hash.md5","type":"keyword","normalization":"","example":"","description":"MD5 hash."},{"field":"threat.indicator.file.hash.sha1","type":"keyword","normalization":"","example":"","description":"SHA1 hash."},{"field":"threat.indicator.file.hash.sha256","type":"keyword","normalization":"","example":"","description":"SHA256 hash."},{"field":"threat.indicator.file.hash.sha384","type":"keyword","normalization":"","example":"","description":"SHA384 hash."},{"field":"threat.indicator.file.hash.sha512","type":"keyword","normalization":"","example":"","description":"SHA512 hash."},{"field":"threat.indicator.file.hash.ssdeep","type":"keyword","normalization":"","example":"","description":"SSDEEP hash."},{"field":"threat.indicator.file.hash.tlsh","type":"keyword","normalization":"","example":"","description":"TLSH hash."},{"field":"threat.indicator.file.inode","type":"keyword","normalization":"","example":256383,"description":"Inode representing the file in the filesystem."},{"field":"threat.indicator.file.mime_type","type":"keyword","normalization":"","example":"","description":"Media type of file, document, or arrangement of bytes."},{"field":"threat.indicator.file.mode","type":"keyword","normalization":"","example":"0640","description":"Mode of the file in octal representation."},{"field":"threat.indicator.file.mtime","type":"date","normalization":"","example":"","description":"Last time the file content was modified."},{"field":"threat.indicator.file.name","type":"keyword","normalization":"","example":"example.png","description":"Name of the file including the extension, without the directory."},{"field":"threat.indicator.file.owner","type":"keyword","normalization":"","example":"alice","description":"File owner's username."},{"field":"threat.indicator.file.path","type":"keyword","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.path.text","type":"match_only_text","normalization":"","example":"/home/alice/example.png","description":"Full path to the file, including the file name."},{"field":"threat.indicator.file.pe.architecture","type":"keyword","normalization":"","example":"x64","description":"CPU architecture target for the file."},{"field":"threat.indicator.file.pe.company","type":"keyword","normalization":"","example":"Microsoft Corporation","description":"Internal company name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.description","type":"keyword","normalization":"","example":"Paint","description":"Internal description of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.file_version","type":"keyword","normalization":"","example":"6.3.9600.17415","description":"Process name."},{"field":"threat.indicator.file.pe.imphash","type":"keyword","normalization":"","example":"0c6803c4e922103c4dca5963aad36ddf","description":"A hash of the imports in a PE file."},{"field":"threat.indicator.file.pe.original_file_name","type":"keyword","normalization":"","example":"MSPAINT.EXE","description":"Internal name of the file, provided at compile-time."},{"field":"threat.indicator.file.pe.pehash","type":"keyword","normalization":"","example":"73ff189b63cd6be375a7ff25179a38d347651975","description":"A hash of the PE header and data from one or more PE sections."},{"field":"threat.indicator.file.pe.product","type":"keyword","normalization":"","example":"MicrosoftÂź WindowsÂź Operating System","description":"Internal product name of the file, provided at compile-time."},{"field":"threat.indicator.file.size","type":"long","normalization":"","example":16384,"description":"File size in bytes."},{"field":"threat.indicator.file.target_path","type":"keyword","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.indicator.file.target_path.text","type":"match_only_text","normalization":"","example":"","description":"Target path for symlinks."},{"field":"threat.indicator.file.type","type":"keyword","normalization":"","example":"file","description":"File type (file, dir, or symlink)."},{"field":"threat.indicator.file.uid","type":"keyword","normalization":"","example":1001,"description":"The user ID (UID) or security identifier (SID) of the file owner."},{"field":"threat.indicator.file.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.file.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.indicator.file.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.file.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.file.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.file.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.file.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.file.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.indicator.file.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.file.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.file.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.indicator.file.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.indicator.file.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.file.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.indicator.file.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.indicator.file.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.file.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.file.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.indicator.first_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was first reported."},{"field":"threat.indicator.geo.city_name","type":"keyword","normalization":"","example":"Montreal","description":"City name."},{"field":"threat.indicator.geo.continent_code","type":"keyword","normalization":"","example":"NA","description":"Continent code."},{"field":"threat.indicator.geo.continent_name","type":"keyword","normalization":"","example":"North America","description":"Name of the continent."},{"field":"threat.indicator.geo.country_iso_code","type":"keyword","normalization":"","example":"CA","description":"Country ISO code."},{"field":"threat.indicator.geo.country_name","type":"keyword","normalization":"","example":"Canada","description":"Country name."},{"field":"threat.indicator.geo.location","type":"geo_point","normalization":"","example":{"lon":-73.61483,"lat":45.505918},"description":"Longitude and latitude."},{"field":"threat.indicator.geo.name","type":"keyword","normalization":"","example":"boston-dc","description":"User-defined description of a location."},{"field":"threat.indicator.geo.postal_code","type":"keyword","normalization":"","example":94040,"description":"Postal code."},{"field":"threat.indicator.geo.region_iso_code","type":"keyword","normalization":"","example":"CA-QC","description":"Region ISO code."},{"field":"threat.indicator.geo.region_name","type":"keyword","normalization":"","example":"Quebec","description":"Region name."},{"field":"threat.indicator.geo.timezone","type":"keyword","normalization":"","example":"America/Argentina/Buenos_Aires","description":"Time zone."},{"field":"threat.indicator.ip","type":"ip","normalization":"","example":"1.2.3.4","description":"Indicator IP address"},{"field":"threat.indicator.last_seen","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last reported."},{"field":"threat.indicator.marking.tlp","type":"keyword","normalization":"","example":"WHITE","description":"Indicator TLP marking"},{"field":"threat.indicator.modified_at","type":"date","normalization":"","example":"2020-11-05T17:25:47.000Z","description":"Date/time indicator was last updated."},{"field":"threat.indicator.port","type":"long","normalization":"","example":443,"description":"Indicator port"},{"field":"threat.indicator.provider","type":"keyword","normalization":"","example":"lrz_urlhaus","description":"Indicator provider"},{"field":"threat.indicator.reference","type":"keyword","normalization":"","example":"https://system.example.com/indicator/0001234","description":"Indicator reference URL"},{"field":"threat.indicator.registry.data.bytes","type":"keyword","normalization":"","example":"ZQBuAC0AVQBTAAAAZQBuAAAAAAA=","description":"Original bytes written with base64 encoding."},{"field":"threat.indicator.registry.data.strings","type":"wildcard","normalization":"array","example":"[\"C:\\rta\\red_ttp\\bin\\myapp.exe\"]","description":"List of strings representing what was written to the registry."},{"field":"threat.indicator.registry.data.type","type":"keyword","normalization":"","example":"REG_SZ","description":"Standard registry type for encoding contents"},{"field":"threat.indicator.registry.hive","type":"keyword","normalization":"","example":"HKLM","description":"Abbreviated name for the hive."},{"field":"threat.indicator.registry.key","type":"keyword","normalization":"","example":"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe","description":"Hive-relative path of keys."},{"field":"threat.indicator.registry.path","type":"keyword","normalization":"","example":"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\winword.exe\\Debugger","description":"Full path, including hive, key and value"},{"field":"threat.indicator.registry.value","type":"keyword","normalization":"","example":"Debugger","description":"Name of the value written."},{"field":"threat.indicator.scanner_stats","type":"long","normalization":"","example":4,"description":"Scanner statistics"},{"field":"threat.indicator.sightings","type":"long","normalization":"","example":20,"description":"Number of times indicator observed"},{"field":"threat.indicator.type","type":"keyword","normalization":"","example":"ipv4-addr","description":"Type of indicator"},{"field":"threat.indicator.url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"threat.indicator.url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"threat.indicator.url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"threat.indicator.url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.indicator.url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"threat.indicator.url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"threat.indicator.url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"threat.indicator.url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"threat.indicator.url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"threat.indicator.url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"threat.indicator.url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"threat.indicator.url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"threat.indicator.url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"threat.indicator.url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"threat.indicator.url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"threat.indicator.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"threat.indicator.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"threat.indicator.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"threat.indicator.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"threat.indicator.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"threat.indicator.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"threat.indicator.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"threat.indicator.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"threat.indicator.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"threat.indicator.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"threat.indicator.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"threat.indicator.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"threat.indicator.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"threat.indicator.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"threat.indicator.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"threat.indicator.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"threat.indicator.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"threat.indicator.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"threat.indicator.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"threat.software.alias","type":"keyword","normalization":"array","example":["X-Agent"],"description":"Alias of the software"},{"field":"threat.software.id","type":"keyword","normalization":"","example":"S0552","description":"ID of the software"},{"field":"threat.software.name","type":"keyword","normalization":"","example":"AdFind","description":"Name of the software."},{"field":"threat.software.platforms","type":"keyword","normalization":"array","example":["Windows"],"description":"Platforms of the software."},{"field":"threat.software.reference","type":"keyword","normalization":"","example":"https://attack.mitre.org/software/S0552/","description":"Software reference URL."},{"field":"threat.software.type","type":"keyword","normalization":"","example":"Tool","description":"Software type."},{"field":"threat.tactic.id","type":"keyword","normalization":"array","example":"TA0002","description":"Threat tactic id."},{"field":"threat.tactic.name","type":"keyword","normalization":"array","example":"Execution","description":"Threat tactic."},{"field":"threat.tactic.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/tactics/TA0002/","description":"Threat tactic URL reference."},{"field":"threat.technique.id","type":"keyword","normalization":"array","example":"T1059","description":"Threat technique id."},{"field":"threat.technique.name","type":"keyword","normalization":"array","example":"Command and Scripting Interpreter","description":"Threat technique name."},{"field":"threat.technique.name.text","type":"match_only_text","normalization":"","example":"Command and Scripting Interpreter","description":"Threat technique name."},{"field":"threat.technique.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/techniques/T1059/","description":"Threat technique URL reference."},{"field":"threat.technique.subtechnique.id","type":"keyword","normalization":"array","example":"T1059.001","description":"Threat subtechnique id."},{"field":"threat.technique.subtechnique.name","type":"keyword","normalization":"array","example":"PowerShell","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.name.text","type":"match_only_text","normalization":"","example":"PowerShell","description":"Threat subtechnique name."},{"field":"threat.technique.subtechnique.reference","type":"keyword","normalization":"array","example":"https://attack.mitre.org/techniques/T1059/001/","description":"Threat subtechnique URL reference."},{"field":"tls.cipher","type":"keyword","normalization":"","example":"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256","description":"String indicating the cipher used during the current connection."},{"field":"tls.client.certificate","type":"keyword","normalization":"","example":"MII...","description":"PEM-encoded stand-alone certificate offered by the client."},{"field":"tls.client.certificate_chain","type":"keyword","normalization":"array","example":["MII...","MII..."],"description":"Array of PEM-encoded certificates that make up the certificate chain offered by the client."},{"field":"tls.client.hash.md5","type":"keyword","normalization":"","example":"0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha1","type":"keyword","normalization":"","example":"9E393D93138888D288266C2D915214D1D1CCEB2A","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.hash.sha256","type":"keyword","normalization":"","example":"0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the client."},{"field":"tls.client.issuer","type":"keyword","normalization":"","example":"CN=Example Root CA, OU=Infrastructure Team, DC=example, DC=com","description":"Distinguished name of subject of the issuer of the x.509 certificate presented by the client."},{"field":"tls.client.ja3","type":"keyword","normalization":"","example":"d4e5b18d6b55c71272893221c96ba240","description":"A hash that identifies clients based on how they perform an SSL/TLS handshake."},{"field":"tls.client.not_after","type":"date","normalization":"","example":"2021-01-01T00:00:00.000Z","description":"Date/Time indicating when client certificate is no longer considered valid."},{"field":"tls.client.not_before","type":"date","normalization":"","example":"1970-01-01T00:00:00.000Z","description":"Date/Time indicating when client certificate is first considered valid."},{"field":"tls.client.server_name","type":"keyword","normalization":"","example":"www.elastic.co","description":"Hostname the client is trying to connect to. Also called the SNI."},{"field":"tls.client.subject","type":"keyword","normalization":"","example":"CN=myclient, OU=Documentation Team, DC=example, DC=com","description":"Distinguished name of subject of the x.509 certificate presented by the client."},{"field":"tls.client.supported_ciphers","type":"keyword","normalization":"array","example":["TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384","TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384","..."],"description":"Array of ciphers offered by the client during the client hello."},{"field":"tls.client.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"tls.client.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"tls.client.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.client.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"tls.client.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.client.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.client.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.client.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"tls.client.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"tls.client.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.client.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.client.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"tls.client.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"tls.client.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"tls.client.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"tls.client.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"tls.client.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.client.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"tls.client.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"tls.client.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"tls.client.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.client.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"tls.curve","type":"keyword","normalization":"","example":"secp256r1","description":"String indicating the curve used for the given cipher, when applicable."},{"field":"tls.established","type":"boolean","normalization":"","example":"","description":"Boolean flag indicating if the TLS negotiation was successful and transitioned to an encrypted tunnel."},{"field":"tls.next_protocol","type":"keyword","normalization":"","example":"http/1.1","description":"String indicating the protocol being tunneled."},{"field":"tls.resumed","type":"boolean","normalization":"","example":"","description":"Boolean flag indicating if this TLS connection was resumed from an existing TLS negotiation."},{"field":"tls.server.certificate","type":"keyword","normalization":"","example":"MII...","description":"PEM-encoded stand-alone certificate offered by the server."},{"field":"tls.server.certificate_chain","type":"keyword","normalization":"array","example":["MII...","MII..."],"description":"Array of PEM-encoded certificates that make up the certificate chain offered by the server."},{"field":"tls.server.hash.md5","type":"keyword","normalization":"","example":"0F76C7F2C55BFD7D8E8B8F4BFBF0C9EC","description":"Certificate fingerprint using the MD5 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha1","type":"keyword","normalization":"","example":"9E393D93138888D288266C2D915214D1D1CCEB2A","description":"Certificate fingerprint using the SHA1 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.hash.sha256","type":"keyword","normalization":"","example":"0687F666A054EF17A08E2F2162EAB4CBC0D265E1D7875BE74BF3C712CA92DAF0","description":"Certificate fingerprint using the SHA256 digest of DER-encoded version of certificate offered by the server."},{"field":"tls.server.issuer","type":"keyword","normalization":"","example":"CN=Example Root CA, OU=Infrastructure Team, DC=example, DC=com","description":"Subject of the issuer of the x.509 certificate presented by the server."},{"field":"tls.server.ja3s","type":"keyword","normalization":"","example":"394441ab65754e2207b1e1b457b3641d","description":"A hash that identifies servers based on how they perform an SSL/TLS handshake."},{"field":"tls.server.not_after","type":"date","normalization":"","example":"2021-01-01T00:00:00.000Z","description":"Timestamp indicating when server certificate is no longer considered valid."},{"field":"tls.server.not_before","type":"date","normalization":"","example":"1970-01-01T00:00:00.000Z","description":"Timestamp indicating when server certificate is first considered valid."},{"field":"tls.server.subject","type":"keyword","normalization":"","example":"CN=www.example.com, OU=Infrastructure Team, DC=example, DC=com","description":"Subject of the x.509 certificate presented by the server."},{"field":"tls.server.x509.alternative_names","type":"keyword","normalization":"array","example":"*.elastic.co","description":"List of subject alternative names (SAN)."},{"field":"tls.server.x509.issuer.common_name","type":"keyword","normalization":"array","example":"Example SHA2 High Assurance Server CA","description":"List of common name (CN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) codes"},{"field":"tls.server.x509.issuer.distinguished_name","type":"keyword","normalization":"","example":"C=US, O=Example Inc, OU=www.example.com, CN=Example SHA2 High Assurance Server CA","description":"Distinguished name (DN) of issuing certificate authority."},{"field":"tls.server.x509.issuer.locality","type":"keyword","normalization":"array","example":"Mountain View","description":"List of locality names (L)"},{"field":"tls.server.x509.issuer.organization","type":"keyword","normalization":"array","example":"Example Inc","description":"List of organizations (O) of issuing certificate authority."},{"field":"tls.server.x509.issuer.organizational_unit","type":"keyword","normalization":"array","example":"www.example.com","description":"List of organizational units (OU) of issuing certificate authority."},{"field":"tls.server.x509.issuer.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.not_after","type":"date","normalization":"","example":"2020-07-16T03:15:39Z","description":"Time at which the certificate is no longer considered valid."},{"field":"tls.server.x509.not_before","type":"date","normalization":"","example":"2019-08-16T01:40:25Z","description":"Time at which the certificate is first considered valid."},{"field":"tls.server.x509.public_key_algorithm","type":"keyword","normalization":"","example":"RSA","description":"Algorithm used to generate the public key."},{"field":"tls.server.x509.public_key_curve","type":"keyword","normalization":"","example":"nistp521","description":"The curve used by the elliptic curve public key algorithm. This is algorithm specific."},{"field":"tls.server.x509.public_key_exponent","type":"long","normalization":"","example":65537,"description":"Exponent used to derive the public key. This is algorithm specific."},{"field":"tls.server.x509.public_key_size","type":"long","normalization":"","example":2048,"description":"The size of the public key space in bits."},{"field":"tls.server.x509.serial_number","type":"keyword","normalization":"","example":"55FBB9C7DEBF09809D12CCAA","description":"Unique serial number issued by the certificate authority."},{"field":"tls.server.x509.signature_algorithm","type":"keyword","normalization":"","example":"SHA256-RSA","description":"Identifier for certificate signature algorithm."},{"field":"tls.server.x509.subject.common_name","type":"keyword","normalization":"array","example":"shared.global.example.net","description":"List of common names (CN) of subject."},{"field":"tls.server.x509.subject.country","type":"keyword","normalization":"array","example":"US","description":"List of country \\(C) code"},{"field":"tls.server.x509.subject.distinguished_name","type":"keyword","normalization":"","example":"C=US, ST=California, L=San Francisco, O=Example, Inc., CN=shared.global.example.net","description":"Distinguished name (DN) of the certificate subject entity."},{"field":"tls.server.x509.subject.locality","type":"keyword","normalization":"array","example":"San Francisco","description":"List of locality names (L)"},{"field":"tls.server.x509.subject.organization","type":"keyword","normalization":"array","example":"Example, Inc.","description":"List of organizations (O) of subject."},{"field":"tls.server.x509.subject.organizational_unit","type":"keyword","normalization":"array","example":"","description":"List of organizational units (OU) of subject."},{"field":"tls.server.x509.subject.state_or_province","type":"keyword","normalization":"array","example":"California","description":"List of state or province names (ST, S, or P)"},{"field":"tls.server.x509.version_number","type":"keyword","normalization":"","example":3,"description":"Version of x509 format."},{"field":"tls.version","type":"keyword","normalization":"","example":1.2,"description":"Numeric part of the version parsed from the original string."},{"field":"tls.version_protocol","type":"keyword","normalization":"","example":"tls","description":"Normalized lowercase protocol name parsed from original string."},{"field":"trace.id","type":"keyword","normalization":"","example":"4bf92f3577b34da6a3ce929d0e0e4736","description":"Unique identifier of the trace."},{"field":"transaction.id","type":"keyword","normalization":"","example":"00f067aa0ba902b7","description":"Unique identifier of the transaction within the scope of its trace."},{"field":"url.domain","type":"keyword","normalization":"","example":"www.elastic.co","description":"Domain of the url."},{"field":"url.extension","type":"keyword","normalization":"","example":"png","description":"File extension from the request url, excluding the leading dot."},{"field":"url.fragment","type":"keyword","normalization":"","example":"","description":"Portion of the url after the `#`."},{"field":"url.full","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"url.full.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top","description":"Full unparsed URL."},{"field":"url.original","type":"wildcard","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"url.original.text","type":"match_only_text","normalization":"","example":"https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch","description":"Unmodified original url as seen in the event source."},{"field":"url.password","type":"keyword","normalization":"","example":"","description":"Password of the request."},{"field":"url.path","type":"wildcard","normalization":"","example":"","description":"Path of the request, such as \"/search\"."},{"field":"url.port","type":"long","normalization":"","example":443,"description":"Port of the request, such as 443."},{"field":"url.query","type":"keyword","normalization":"","example":"","description":"Query string of the request."},{"field":"url.registered_domain","type":"keyword","normalization":"","example":"example.com","description":"The highest registered url domain, stripped of the subdomain."},{"field":"url.scheme","type":"keyword","normalization":"","example":"https","description":"Scheme of the url."},{"field":"url.subdomain","type":"keyword","normalization":"","example":"east","description":"The subdomain of the domain."},{"field":"url.top_level_domain","type":"keyword","normalization":"","example":"co.uk","description":"The effective top level domain (com, org, net, co.uk)."},{"field":"url.username","type":"keyword","normalization":"","example":"","description":"Username of the request."},{"field":"user.changes.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.changes.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.changes.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.changes.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.changes.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.changes.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.changes.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.changes.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.changes.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.changes.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.changes.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.changes.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.effective.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.effective.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.effective.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.effective.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.effective.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.effective.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.effective.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.effective.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.effective.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.effective.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.effective.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.effective.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.risk.calculated_level","type":"keyword","normalization":"","example":"High","description":"A risk classification level calculated by an internal system as part of entity analytics and entity risk scoring."},{"field":"user.risk.calculated_score","type":"float","normalization":"","example":880.73,"description":"A risk classification score calculated by an internal system as part of entity analytics and entity risk scoring."},{"field":"user.risk.calculated_score_norm","type":"float","normalization":"","example":88.73,"description":"A normalized risk score calculated by an internal system."},{"field":"user.risk.static_level","type":"keyword","normalization":"","example":"High","description":"A risk classification level obtained from outside the system, such as from some external Threat Intelligence Platform."},{"field":"user.risk.static_score","type":"float","normalization":"","example":830,"description":"A risk classification score obtained from outside the system, such as from some external Threat Intelligence Platform."},{"field":"user.risk.static_score_norm","type":"float","normalization":"","example":83,"description":"A normalized risk score calculated by an external system."},{"field":"user.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user.target.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the user is a member of."},{"field":"user.target.email","type":"keyword","normalization":"","example":"","description":"User email address."},{"field":"user.target.full_name","type":"keyword","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.target.full_name.text","type":"match_only_text","normalization":"","example":"Albert Einstein","description":"User's full name, if available."},{"field":"user.target.group.domain","type":"keyword","normalization":"","example":"","description":"Name of the directory the group is a member of."},{"field":"user.target.group.id","type":"keyword","normalization":"","example":"","description":"Unique identifier for the group on the system/platform."},{"field":"user.target.group.name","type":"keyword","normalization":"","example":"","description":"Name of the group."},{"field":"user.target.hash","type":"keyword","normalization":"","example":"","description":"Unique user hash to correlate information for a user in anonymized form."},{"field":"user.target.id","type":"keyword","normalization":"","example":"S-1-5-21-202424912787-2692429404-2351956786-1000","description":"Unique identifier of the user."},{"field":"user.target.name","type":"keyword","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.target.name.text","type":"match_only_text","normalization":"","example":"a.einstein","description":"Short name or login of the user."},{"field":"user.target.roles","type":"keyword","normalization":"array","example":["kibana_admin","reporting_user"],"description":"Array of user roles at the time of the event."},{"field":"user_agent.device.name","type":"keyword","normalization":"","example":"iPhone","description":"Name of the device."},{"field":"user_agent.name","type":"keyword","normalization":"","example":"Safari","description":"Name of the user agent."},{"field":"user_agent.original","type":"keyword","normalization":"","example":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1","description":"Unparsed user_agent string."},{"field":"user_agent.original.text","type":"match_only_text","normalization":"","example":"Mozilla/5.0 (iPhone; CPU iPhone OS 12_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1","description":"Unparsed user_agent string."},{"field":"user_agent.os.family","type":"keyword","normalization":"","example":"debian","description":"OS family (such as redhat, debian, freebsd, windows)."},{"field":"user_agent.os.full","type":"keyword","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.full.text","type":"match_only_text","normalization":"","example":"Mac OS Mojave","description":"Operating system name, including the version or code name."},{"field":"user_agent.os.kernel","type":"keyword","normalization":"","example":"4.4.0-112-generic","description":"Operating system kernel version as a raw string."},{"field":"user_agent.os.name","type":"keyword","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"user_agent.os.name.text","type":"match_only_text","normalization":"","example":"Mac OS X","description":"Operating system name, without the version."},{"field":"user_agent.os.platform","type":"keyword","normalization":"","example":"darwin","description":"Operating system platform (such centos, ubuntu, windows)."},{"field":"user_agent.os.type","type":"keyword","normalization":"","example":"macos","description":"Which commercial OS family (one of: linux, macos, unix, windows, ios or android)."},{"field":"user_agent.os.version","type":"keyword","normalization":"","example":"10.14.1","description":"Operating system version as a raw string."},{"field":"user_agent.version","type":"keyword","normalization":"","example":12,"description":"Version of the user agent."},{"field":"vulnerability.category","type":"keyword","normalization":"array","example":["Firewall"],"description":"Category of a vulnerability."},{"field":"vulnerability.classification","type":"keyword","normalization":"","example":"CVSS","description":"Classification of the vulnerability."},{"field":"vulnerability.description","type":"keyword","normalization":"","example":"In macOS before 2.12.6, there is a vulnerability in the RPC...","description":"Description of the vulnerability."},{"field":"vulnerability.description.text","type":"match_only_text","normalization":"","example":"In macOS before 2.12.6, there is a vulnerability in the RPC...","description":"Description of the vulnerability."},{"field":"vulnerability.enumeration","type":"keyword","normalization":"","example":"CVE","description":"Identifier of the vulnerability."},{"field":"vulnerability.id","type":"keyword","normalization":"","example":"CVE-2019-00001","description":"ID of the vulnerability."},{"field":"vulnerability.reference","type":"keyword","normalization":"","example":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-6111","description":"Reference of the vulnerability."},{"field":"vulnerability.report_id","type":"keyword","normalization":"","example":20191018.0001,"description":"Scan identification number."},{"field":"vulnerability.scanner.vendor","type":"keyword","normalization":"","example":"Tenable","description":"Name of the scanner vendor."},{"field":"vulnerability.score.base","type":"float","normalization":"","example":5.5,"description":"Vulnerability Base score."},{"field":"vulnerability.score.environmental","type":"float","normalization":"","example":5.5,"description":"Vulnerability Environmental score."},{"field":"vulnerability.score.temporal","type":"float","normalization":"","example":"","description":"Vulnerability Temporal score."},{"field":"vulnerability.score.version","type":"keyword","normalization":"","example":2,"description":"CVSS version."},{"field":"vulnerability.severity","type":"keyword","normalization":"","example":"Critical","description":"Severity of the vulnerability."}] \ No newline at end of file diff --git a/x-pack/plugins/osquery/public/live_queries/form/index.tsx b/x-pack/plugins/osquery/public/live_queries/form/index.tsx index b870d1385752f..aa3a1bd336607 100644 --- a/x-pack/plugins/osquery/public/live_queries/form/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/form/index.tsx @@ -29,6 +29,7 @@ import { savedQueryDataSerializer } from '../../saved_queries/form/use_saved_que import { PackFieldWrapper } from '../../shared_components/osquery_response_action_type/pack_field_wrapper'; export interface LiveQueryFormFields { + alertIds?: string[]; query?: string; agentSelection: AgentSelection; savedQueryId?: string | null; @@ -39,6 +40,7 @@ export interface LiveQueryFormFields { interface DefaultLiveQueryFormFields { query?: string; agentSelection?: AgentSelection; + alertIds?: string[]; savedQueryId?: string | null; ecs_mapping?: ECSMapping; packId?: string; @@ -119,6 +121,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ useEffect(() => { register('savedQueryId'); + register('alertIds'); }, [register]); const queryStatus = useMemo(() => { @@ -135,19 +138,20 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ ); const onSubmit = useCallback( - (values: LiveQueryFormFields) => { + async (values: LiveQueryFormFields) => { const serializedData = pickBy( { agentSelection: values.agentSelection, saved_query_id: values.savedQueryId, query: values.query, + alert_ids: values.alertIds, pack_id: values?.packId?.length ? values?.packId[0] : undefined, ecs_mapping: values.ecs_mapping, }, (value) => !isEmpty(value) ) as unknown as LiveQueryFormFields; - mutateAsync(serializedData); + await mutateAsync(serializedData); }, [mutateAsync] ); @@ -159,8 +163,6 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ const { data: packsData, isFetched: isPackDataFetched } = usePacks({}); - const handleSubmitForm = useMemo(() => handleSubmit(onSubmit), [handleSubmit, onSubmit]); - const submitButtonContent = useMemo( () => ( <EuiFlexItem> @@ -181,8 +183,9 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ <EuiFlexItem grow={false}> <EuiButton id="submit-button" - disabled={!enabled || isSubmitting} - onClick={handleSubmitForm} + disabled={!enabled} + isLoading={isSubmitting} + onClick={handleSubmit(onSubmit)} > <FormattedMessage id="xpack.osquery.liveQueryForm.form.submitButtonLabel" @@ -201,7 +204,8 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ handleShowSaveQueryFlyout, enabled, isSubmitting, - handleSubmitForm, + handleSubmit, + onSubmit, ] ); @@ -256,6 +260,10 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ setValue('agentSelection', defaultValue.agentSelection); } + if (defaultValue?.alertIds?.length) { + setValue('alertIds', defaultValue.alertIds); + } + if (defaultValue?.packId && canRunPacks) { setQueryType('pack'); @@ -297,6 +305,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ resetField('query'); resetField('ecs_mapping'); resetField('savedQueryId'); + resetField('alertIds'); clearErrors(); } }, [queryType, cleanupLiveQuery, resetField, setValue, clearErrors, defaultValue]); @@ -329,7 +338,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({ ) : ( <> <EuiFlexItem> - <LiveQueryQueryField handleSubmitForm={handleSubmitForm} /> + <LiveQueryQueryField handleSubmitForm={handleSubmit(onSubmit)} /> </EuiFlexItem> {submitButtonContent} <EuiFlexItem>{resultsStepContent}</EuiFlexItem> diff --git a/x-pack/plugins/osquery/public/live_queries/index.tsx b/x-pack/plugins/osquery/public/live_queries/index.tsx index cdb0dccd1a2eb..67b6194065c81 100644 --- a/x-pack/plugins/osquery/public/live_queries/index.tsx +++ b/x-pack/plugins/osquery/public/live_queries/index.tsx @@ -21,6 +21,7 @@ import type { AgentSelection } from '../agents/types'; interface LiveQueryProps { agentId?: string; agentIds?: string[]; + alertIds?: string[]; agentPolicyIds?: string[]; onSuccess?: () => void; query?: string; @@ -40,6 +41,7 @@ interface LiveQueryProps { const LiveQueryComponent: React.FC<LiveQueryProps> = ({ agentId, agentIds, + alertIds, agentPolicyIds, onSuccess, query, @@ -77,6 +79,7 @@ const LiveQueryComponent: React.FC<LiveQueryProps> = ({ const defaultValue = useMemo(() => { const initialValue = { ...(initialAgentSelection ? { agentSelection: initialAgentSelection } : {}), + alertIds, query, savedQueryId, ecs_mapping, @@ -84,7 +87,7 @@ const LiveQueryComponent: React.FC<LiveQueryProps> = ({ }; return !isEmpty(pickBy(initialValue, (value) => !isEmpty(value))) ? initialValue : undefined; - }, [ecs_mapping, initialAgentSelection, packId, query, savedQueryId]); + }, [alertIds, ecs_mapping, initialAgentSelection, packId, query, savedQueryId]); if (isLoading) { return <EuiLoadingContent lines={10} />; diff --git a/x-pack/plugins/osquery/public/packs/packs_table.tsx b/x-pack/plugins/osquery/public/packs/packs_table.tsx index 5e3e58dc7b4a4..69cfb3e40ef2e 100644 --- a/x-pack/plugins/osquery/public/packs/packs_table.tsx +++ b/x-pack/plugins/osquery/public/packs/packs_table.tsx @@ -126,9 +126,20 @@ const PacksTableComponent = () => { ); const renderPlayAction = useCallback( - (item, enabled) => ( - <EuiButtonIcon iconType="play" onClick={handlePlayClick(item)} isDisabled={!enabled} /> - ), + (item, enabled) => { + const playText = i18n.translate('xpack.osquery.packs.table.runActionAriaLabel', { + defaultMessage: 'Run {packName}', + values: { + packName: item.attributes.name, + }, + }); + + return ( + <EuiToolTip position="top" content={playText}> + <EuiButtonIcon iconType="play" onClick={handlePlayClick(item)} isDisabled={!enabled} /> + </EuiToolTip> + ); + }, [handlePlayClick] ); diff --git a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx index 259c131e48ca1..d8cc8f93e56ed 100644 --- a/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/ecs_mapping_editor_field.tsx @@ -49,7 +49,7 @@ import { convertECSMappingToArray, convertECSMappingToObject, } from '../../../common/schemas/common/utils'; -import ECSSchema from '../../common/schemas/ecs/v8.4.0.json'; +import ECSSchema from '../../common/schemas/ecs/v8.5.0.json'; import osquerySchema from '../../common/schemas/osquery/v5.4.0.json'; import { FieldIcon } from '../../common/lib/kibana'; @@ -728,19 +728,13 @@ interface OsqueryColumn { export const ECSMappingEditorField = React.memo(({ euiFieldProps }: ECSMappingEditorFieldProps) => { const { - setError, - clearErrors, watch: watchRoot, register: registerRoot, setValue: setValueRoot, formState: { errors: errorsRoot }, } = useFormContext<{ query: string; ecs_mapping: ECSMapping }>(); - useEffect(() => { - registerRoot('ecs_mapping'); - }, [registerRoot]); - - const [query, ecsMapping] = watchRoot(['query', 'ecs_mapping'], { ecs_mapping: {} }); + const [query, ecsMapping] = watchRoot(['query', 'ecs_mapping']); const { control, trigger, watch, formState, resetField, getFieldState } = useForm<{ ecsMappingArray: ECSMappingArray; }>({ @@ -761,6 +755,16 @@ export const ECSMappingEditorField = React.memo(({ euiFieldProps }: ECSMappingEd const ecsMappingArrayState = getFieldState('ecsMappingArray', formState); const [osquerySchemaOptions, setOsquerySchemaOptions] = useState<OsquerySchemaOption[]>([]); + useEffect(() => { + registerRoot('ecs_mapping', { + validate: () => { + const nonEmptyErrors = reject(ecsMappingArrayState.error, isEmpty) as InternalFieldErrors[]; + + return !nonEmptyErrors.length; + }, + }); + }, [ecsMappingArrayState.error, errorsRoot, registerRoot]); + useEffect(() => { const subscription = watchRoot((data, payload) => { if (payload.name === 'ecs_mapping') { @@ -1019,10 +1023,16 @@ export const ECSMappingEditorField = React.memo(({ euiFieldProps }: ECSMappingEd orderBy(suggestions, ['value.suggestion_label', 'value.tableOrder'], ['asc', 'desc']), 'label' ); - setOsquerySchemaOptions((prevValue) => - !deepEqual(prevValue, newOptions) ? newOptions : prevValue - ); - }, [query]); + setOsquerySchemaOptions((prevValue) => { + if (!deepEqual(prevValue, newOptions)) { + trigger(); + + return newOptions; + } + + return prevValue; + }); + }, [query, trigger]); useEffect(() => { const parsedMapping = convertECSMappingToObject(formValue.ecsMappingArray); @@ -1033,27 +1043,6 @@ export const ECSMappingEditorField = React.memo(({ euiFieldProps }: ECSMappingEd } }, [setValueRoot, formValue, ecsMappingArrayState.isDirty, ecsMapping]); - useEffect(() => { - if (!formState.isValid) { - const nonEmptyErrors = reject(ecsMappingArrayState.error, isEmpty) as InternalFieldErrors[]; - if (nonEmptyErrors.length) { - setError('ecs_mapping', { - type: nonEmptyErrors[0].key?.type ?? 'custom', - message: nonEmptyErrors[0].key?.message ?? '', - }); - } - } else { - clearErrors('ecs_mapping'); - } - }, [ - errorsRoot, - clearErrors, - formState.isValid, - formState.errors, - setError, - ecsMappingArrayState.error, - ]); - return ( <> <EuiFlexGroup> diff --git a/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx b/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx index 65d829e7b7e82..1d6b52fcf2802 100644 --- a/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx +++ b/x-pack/plugins/osquery/public/packs/queries/query_flyout.tsx @@ -62,9 +62,9 @@ const QueryFlyoutComponent: React.FC<QueryFlyoutProps> = ({ formState: { isSubmitting }, resetField, } = hooksForm; - const onSubmit = (payload: PackQueryFormData) => { + const onSubmit = async (payload: PackQueryFormData) => { const serializedData: PackSOQueryFormData = serializer(payload); - onSave(serializedData); + await onSave(serializedData); onClose(); }; diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx index 1b24b4a71eeb5..ef945b92a0af2 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/edit/form.tsx @@ -49,10 +49,10 @@ const EditSavedQueryFormComponent: React.FC<EditSavedQueryFormProps> = ({ formState: { isSubmitting }, } = hooksForm; - const onSubmit = (payload: SavedQueryFormData) => { + const onSubmit = async (payload: SavedQueryFormData) => { const serializedData = serializer(payload); try { - handleSubmit(serializedData); + await handleSubmit(serializedData); // eslint-disable-next-line no-empty } catch (e) {} }; diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx index f142c653656aa..276f2f2598d1e 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/list/index.tsx @@ -61,19 +61,27 @@ const PlayButtonComponent: React.FC<PlayButtonProps> = ({ disabled = false, save [push, savedQuery] ); - return ( - <EuiButtonIcon - color="primary" - iconType="play" - isDisabled={disabled} - onClick={handlePlayClick} - aria-label={i18n.translate('xpack.osquery.savedQueryList.queriesTable.runActionAriaLabel', { + const playText = useMemo( + () => + i18n.translate('xpack.osquery.savedQueryList.queriesTable.runActionAriaLabel', { defaultMessage: 'Run {savedQueryName}', values: { - savedQueryName: savedQuery.attributes.name, + savedQueryName: savedQuery.attributes.id, }, - })} - /> + }), + [savedQuery] + ); + + return ( + <EuiToolTip position="top" content={playText}> + <EuiButtonIcon + color="primary" + iconType="play" + isDisabled={disabled} + onClick={handlePlayClick} + aria-label={playText} + /> + </EuiToolTip> ); }; @@ -92,19 +100,27 @@ const EditButtonComponent: React.FC<EditButtonProps> = ({ }) => { const buttonProps = useRouterNavigate(`saved_queries/${savedQueryId}`); - return ( - <EuiButtonIcon - color="primary" - {...buttonProps} - iconType="pencil" - isDisabled={disabled} - aria-label={i18n.translate('xpack.osquery.savedQueryList.queriesTable.editActionAriaLabel', { + const editText = useMemo( + () => + i18n.translate('xpack.osquery.savedQueryList.queriesTable.editActionAriaLabel', { defaultMessage: 'Edit {savedQueryName}', values: { savedQueryName, }, - })} - /> + }), + [savedQueryName] + ); + + return ( + <EuiToolTip position="top" content={editText}> + <EuiButtonIcon + color="primary" + {...buttonProps} + iconType="pencil" + isDisabled={disabled} + aria-label={editText} + /> + </EuiToolTip> ); }; @@ -124,7 +140,7 @@ const SavedQueriesPageComponent = () => { const renderEditAction = useCallback( (item: SavedQuerySO) => ( - <EditButton savedQueryId={item.id} savedQueryName={item.attributes.name} /> + <EditButton savedQueryId={item.id} savedQueryName={item.attributes.id} /> ), [] ); diff --git a/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx b/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx index 350c35b2b3fa5..6763b5a1c73c4 100644 --- a/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx +++ b/x-pack/plugins/osquery/public/routes/saved_queries/new/form.tsx @@ -47,9 +47,9 @@ const NewSavedQueryFormComponent: React.FC<NewSavedQueryFormProps> = ({ formState: { isSubmitting, errors }, } = hooksForm; - const onSubmit = (payload: SavedQueryFormData) => { + const onSubmit = async (payload: SavedQueryFormData) => { const serializedData = serializer(payload); - handleSubmit(serializedData); + await handleSubmit(serializedData); }; return ( diff --git a/x-pack/plugins/osquery/scripts/roles_users/t1_analyst/role.json b/x-pack/plugins/osquery/scripts/roles_users/t1_analyst/role.json index 12d5c2607f9ab..5087ba9005a3c 100644 --- a/x-pack/plugins/osquery/scripts/roles_users/t1_analyst/role.json +++ b/x-pack/plugins/osquery/scripts/roles_users/t1_analyst/role.json @@ -1,6 +1,15 @@ { "elasticsearch": { + "cluster": ["manage"], "indices": [ + { + "names": [".items-*", ".lists-*", ".alerts-security.alerts-*", ".siem-signals-*"], + "privileges": ["manage", "read", "write", "view_index_metadata", "maintenance"] + }, + { + "names": ["*"], + "privileges": ["read"] + }, { "names": ["logs-osquery_manager*"], "privileges": ["read"] @@ -10,6 +19,7 @@ "kibana": [ { "feature": { + "siem": ["all"], "osquery": ["read", "run_saved_queries" ] }, "spaces": ["*"] diff --git a/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts b/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts index 27d8a0eecd17c..553c4e9de10fd 100644 --- a/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts +++ b/x-pack/plugins/osquery/scripts/schema_formatter/ecs_formatter.ts @@ -40,7 +40,7 @@ const RESTRICTED_FIELDS = [ run( async ({ flags }) => { - const schemaPath = path.resolve(`../../public/common/schemas/ecs/`); + const schemaPath = path.resolve(`./public/common/schemas/ecs/`); const schemaFile = path.join(schemaPath, flags.schema_version as string); const schemaData = await require(schemaFile); diff --git a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts index 8350355816459..19b5b13495718 100644 --- a/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts +++ b/x-pack/plugins/osquery/server/lib/osquery_app_context_services.ts @@ -14,6 +14,7 @@ import type { AgentPolicyServiceInterface, PackagePolicyClient, } from '@kbn/fleet-plugin/server'; +import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server'; import type { ConfigType } from '../../common/config'; import type { TelemetryEventsSender } from './telemetry/sender'; @@ -26,6 +27,7 @@ export type OsqueryAppContextServiceStartContract = Partial< logger: Logger; config: ConfigType; registerIngestCallback?: FleetStartContract['registerExternalCallback']; + ruleRegistryService?: RuleRegistryPluginStartContract; }; /** @@ -37,12 +39,14 @@ export class OsqueryAppContextService { private packageService: PackageService | undefined; private packagePolicyService: PackagePolicyClient | undefined; private agentPolicyService: AgentPolicyServiceInterface | undefined; + private ruleRegistryService: RuleRegistryPluginStartContract | undefined; public start(dependencies: OsqueryAppContextServiceStartContract) { this.agentService = dependencies.agentService; this.packageService = dependencies.packageService; this.packagePolicyService = dependencies.packagePolicyService; this.agentPolicyService = dependencies.agentPolicyService; + this.ruleRegistryService = dependencies.ruleRegistryService; } // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -63,6 +67,10 @@ export class OsqueryAppContextService { public getAgentPolicyService(): AgentPolicyServiceInterface | undefined { return this.agentPolicyService; } + + public getRuleRegistryService(): RuleRegistryPluginStartContract | undefined { + return this.ruleRegistryService; + } } /** diff --git a/x-pack/plugins/osquery/server/plugin.ts b/x-pack/plugins/osquery/server/plugin.ts index 655de66243416..601e0e29a3a83 100644 --- a/x-pack/plugins/osquery/server/plugin.ts +++ b/x-pack/plugins/osquery/server/plugin.ts @@ -100,6 +100,7 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt this.osqueryAppContextService.start({ ...plugins.fleet, + ruleRegistryService: plugins.ruleRegistry, // @ts-expect-error update types // eslint-disable-next-line @typescript-eslint/no-non-null-assertion config: this.config!, diff --git a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts index 53d13a8274033..061eb6f7d2928 100644 --- a/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/plugins/osquery/server/routes/live_query/create_live_query_route.ts @@ -6,12 +6,18 @@ */ import type { IRouter } from '@kbn/core/server'; +import unified from 'unified'; +import markdown from 'remark-parse'; +import { some, filter } from 'lodash'; +import deepEqual from 'fast-deep-equal'; +import type { ECSMappingOrUndefined } from '@kbn/osquery-io-ts-types'; import { createLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; import type { CreateLiveQueryRequestBodySchema } from '../../../common/schemas/routes/live_query'; import { buildRouteValidation } from '../../utils/build_validation/route_validation'; import type { OsqueryAppContext } from '../../lib/osquery_app_context_services'; import { createActionHandler } from '../../handlers'; +import { parser as OsqueryParser } from './osquery_parser'; export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.post( @@ -37,7 +43,41 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp ); if (isInvalid) { - return response.forbidden(); + if (request.body.alert_ids?.length) { + try { + const client = await osqueryContext.service + .getRuleRegistryService() + ?.getRacClientWithRequest(request); + + const alertData = await client?.get({ id: request.body.alert_ids[0] }); + + if (alertData?.['kibana.alert.rule.note']) { + const parsedAlertInvestigationGuide = unified() + .use([[markdown, {}], OsqueryParser]) + .parse(alertData?.['kibana.alert.rule.note']); + + const osqueryQueries = filter(parsedAlertInvestigationGuide?.children as object, [ + 'type', + 'osquery', + ]); + + const requestQueryExistsInTheInvestigationGuide = some( + osqueryQueries, + (payload: { + configuration: { query: string; ecs_mapping: ECSMappingOrUndefined }; + }) => + payload?.configuration?.query === request.body.query && + deepEqual(payload?.configuration?.ecs_mapping, request.body.ecs_mapping) + ); + + if (!requestQueryExistsInTheInvestigationGuide) throw new Error(); + } + } catch (error) { + return response.forbidden(); + } + } else { + return response.forbidden(); + } } try { diff --git a/x-pack/plugins/osquery/server/routes/live_query/osquery_parser.ts b/x-pack/plugins/osquery/server/routes/live_query/osquery_parser.ts new file mode 100644 index 0000000000000..afc51949f3c70 --- /dev/null +++ b/x-pack/plugins/osquery/server/routes/live_query/osquery_parser.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RemarkTokenizer } from '@elastic/eui'; +import type { Plugin } from 'unified'; + +export const parser: Plugin = function () { + const Parser = this.Parser; + const tokenizers = Parser.prototype.blockTokenizers; + const methods = Parser.prototype.blockMethods; + + const tokenizeOsquery: RemarkTokenizer = function (eat, value, silent) { + if (value.startsWith('!{osquery') === false) return false; + + const nextChar = value[9]; + + if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a osquery + + if (silent) { + return true; + } + + // is there a configuration? + const hasConfiguration = nextChar === '{'; + + let match = '!{osquery'; + let configuration = {}; + + if (hasConfiguration) { + let configurationString = ''; + + let openObjects = 0; + + for (let i = 9; i < value.length; i++) { + const char = value[i]; + if (char === '{') { + openObjects++; + configurationString += char; + } else if (char === '}') { + openObjects--; + if (openObjects === -1) { + break; + } + + configurationString += char; + } else { + configurationString += char; + } + } + + match += configurationString; + try { + configuration = JSON.parse(configurationString); + } catch (e) { + const now = eat.now(); + this.file.fail(`Unable to parse osquery JSON configuration: ${e}`, { + line: now.line, + column: now.column + 9, + }); + } + } + + match += '}'; + + return eat(match)({ + type: 'osquery', + configuration, + }); + }; + + tokenizers.osquery = tokenizeOsquery; + methods.splice(methods.indexOf('text'), 0, 'osquery'); +}; diff --git a/x-pack/plugins/osquery/server/types.ts b/x-pack/plugins/osquery/server/types.ts index 162ce9e7095d9..ef0bdacf0dfd2 100644 --- a/x-pack/plugins/osquery/server/types.ts +++ b/x-pack/plugins/osquery/server/types.ts @@ -20,6 +20,7 @@ import type { TaskManagerStartContract as TaskManagerPluginStart, } from '@kbn/task-manager-plugin/server'; import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; +import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server'; import type { CreateLiveQueryRequestBodySchema } from '../common/schemas/routes/live_query'; export interface OsqueryPluginSetup { @@ -46,4 +47,5 @@ export interface StartPlugins { fleet?: FleetStartContract; taskManager?: TaskManagerPluginStart; telemetry?: TelemetryPluginStart; + ruleRegistry?: RuleRegistryPluginStartContract; } diff --git a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx index 0d0143eab2e32..3d046e349de31 100644 --- a/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/markdown_editor/plugins/osquery/index.tsx @@ -261,7 +261,7 @@ const RunOsqueryButtonRenderer = ({ }; }) => { const [showFlyout, setShowFlyout] = useState(false); - const { agentId } = useContext(BasicAlertDataContext); + const { agentId, alertId } = useContext(BasicAlertDataContext); const handleOpen = useCallback(() => setShowFlyout(true), [setShowFlyout]); @@ -278,6 +278,7 @@ const RunOsqueryButtonRenderer = ({ {showFlyout && ( <OsqueryFlyout defaultValues={{ + ...(alertId ? { alertIds: [alertId] } : {}), query: configuration.query, ecs_mapping: configuration.ecs_mapping, queryField: false, diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 0127fe63efea7..3759178c163b3 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -72,6 +72,8 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps & PropsFromRedux const onMenuItemClick = useCallback(() => { setPopover(false); }, []); + + const alertId = ecsRowData?.kibana?.alert ? ecsRowData?._id : null; const ruleId = get(0, ecsRowData?.kibana?.alert?.rule?.uuid); const ruleName = get(0, ecsRowData?.kibana?.alert?.rule?.name); @@ -264,7 +266,11 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps & PropsFromRedux <EventFiltersFlyout data={ecsRowData} onCancel={closeAddEventFilterModal} /> )} {isOsqueryFlyoutOpen && agentId && ecsRowData != null && ( - <OsqueryFlyout agentId={agentId} onClose={handleOnOsqueryClick} /> + <OsqueryFlyout + agentId={agentId} + defaultValues={alertId ? { alertIds: [alertId] } : undefined} + onClose={handleOnOsqueryClick} + /> )} </> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx index d768f8aa94645..5c0e1cd813ed1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.tsx @@ -63,6 +63,7 @@ export const FlyoutFooterComponent = React.memo( timelineQuery, refetchFlyoutData, }: FlyoutFooterProps & PropsFromRedux) => { + const alertId = detailsEcsData?.kibana?.alert ? detailsEcsData?._id : null; const ruleIndex = useMemo( () => find({ category: 'signal', field: 'signal.rule.index' }, detailsData)?.values ?? @@ -173,7 +174,11 @@ export const FlyoutFooterComponent = React.memo( /> )} {isOsqueryFlyoutOpenWithAgentId && detailsEcsData != null && ( - <OsqueryFlyout agentId={isOsqueryFlyoutOpenWithAgentId} onClose={closeOsqueryFlyout} /> + <OsqueryFlyout + agentId={isOsqueryFlyoutOpenWithAgentId} + defaultValues={alertId ? { alertIds: [alertId] } : undefined} + onClose={closeOsqueryFlyout} + /> )} </> ); From b3a749e55a55f5ab1df4d236916dc270209e83fe Mon Sep 17 00:00:00 2001 From: Justin Kambic <jk@elastic.co> Date: Mon, 3 Oct 2022 21:10:38 -0400 Subject: [PATCH 010/174] [Synthetics UI] Serialize errors before sending to redux store to prevent warnings (#142259) * Serialize errors before sending to redux store to prevent warnings. * Serialize response errors in monitor list effect. --- .../public/apps/synthetics/state/index_status/actions.ts | 5 +++-- .../public/apps/synthetics/state/index_status/index.ts | 4 ++-- .../apps/synthetics/state/monitor_details/index.ts | 7 +++---- .../public/apps/synthetics/state/monitor_list/actions.ts | 9 +++++---- .../public/apps/synthetics/state/monitor_list/effects.ts | 4 ++-- .../public/apps/synthetics/state/monitor_list/index.ts | 4 ++-- .../public/apps/synthetics/state/overview/index.ts | 6 +++--- .../apps/synthetics/state/service_locations/actions.ts | 5 ++++- .../apps/synthetics/state/service_locations/index.ts | 3 ++- .../synthetics/state/synthetics_enablement/actions.ts | 7 ++++--- .../apps/synthetics/state/synthetics_enablement/index.ts | 3 ++- .../public/apps/synthetics/state/utils/actions.ts | 4 ++-- .../public/apps/synthetics/state/utils/fetch_effect.ts | 7 ++++--- .../legacy_uptime/state/private_locations/index.ts | 6 +++--- 14 files changed, 41 insertions(+), 33 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts index 36e2e2514910e..e522af3bfed7c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { IHttpFetchError } from '@kbn/core-http-browser'; import { createAction } from '@reduxjs/toolkit'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export const getIndexStatus = createAction<void>('[INDEX STATUS] GET'); export const getIndexStatusSuccess = createAction<StatesIndexStatus>('[INDEX STATUS] GET SUCCESS'); -export const getIndexStatusFail = createAction<IHttpFetchError>('[INDEX STATUS] GET FAIL'); +export const getIndexStatusFail = + createAction<IHttpSerializedFetchError>('[INDEX STATUS] GET FAIL'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts index f5351c65d0d6b..19ef8f94938a3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts @@ -6,7 +6,7 @@ */ import { createReducer } from '@reduxjs/toolkit'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './actions'; @@ -33,7 +33,7 @@ export const indexStatusReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getIndexStatusFail, (state, action) => { - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; state.loading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts index a2d9379df778e..b1fb95d5d5ee4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { createReducer } from '@reduxjs/toolkit'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { getMonitorRecentPingsAction, setMonitorDetailsLocationAction, @@ -47,7 +46,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getMonitorRecentPingsAction.fail, (state, action) => { - state.error = serializeHttpFetchError(action.payload as IHttpFetchError<ResponseErrorBody>); + state.error = action.payload; state.loading = false; }) @@ -59,7 +58,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { state.syntheticsMonitorLoading = false; }) .addCase(getMonitorAction.fail, (state, action) => { - state.error = serializeHttpFetchError(action.payload as IHttpFetchError<ResponseErrorBody>); + state.error = action.payload; state.syntheticsMonitorLoading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index fcfc3d4f22cf7..5a8c38284e034 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { IHttpFetchError } from '@kbn/core-http-browser'; import { createAction } from '@reduxjs/toolkit'; import { EncryptedSyntheticsMonitor, MonitorManagementListResult, } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; @@ -29,7 +29,8 @@ export const fetchUpsertSuccessAction = createAction<{ id: string; attributes: { enabled: boolean }; }>('fetchUpsertMonitorSuccess'); -export const fetchUpsertFailureAction = createAction<{ id: string; error: IHttpFetchError }>( - 'fetchUpsertMonitorFailure' -); +export const fetchUpsertFailureAction = createAction<{ + id: string; + error: IHttpSerializedFetchError; +}>('fetchUpsertMonitorFailure'); export const clearMonitorUpsertStatus = createAction<string>('clearMonitorUpsertStatus'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts index 0dee2edfd7903..67aaa4ec982ed 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { IHttpFetchError } from '@kbn/core-http-browser'; import { PayloadAction } from '@reduxjs/toolkit'; import { call, put, takeEvery, takeLeading } from 'redux-saga/effects'; import { fetchEffectFactory } from '../utils/fetch_effect'; +import { serializeHttpFetchError } from '../utils/http_error'; import { fetchMonitorListAction, fetchUpsertFailureAction, @@ -40,7 +40,7 @@ export function* upsertMonitorEffect() { ); } catch (error) { yield put( - fetchUpsertFailureAction({ id: action.payload.id, error: error as IHttpFetchError }) + fetchUpsertFailureAction({ id: action.payload.id, error: serializeHttpFetchError(error) }) ); } } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index e1f564c0d0a3f..997f853c9bfc5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -10,7 +10,7 @@ import { FETCH_STATUS } from '@kbn/observability-plugin/public'; import { ConfigKey, MonitorManagementListResult } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; import { @@ -58,7 +58,7 @@ export const monitorListReducer = createReducer(initialState, (builder) => { }) .addCase(fetchMonitorListAction.fail, (state, action) => { state.loading = false; - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; }) .addCase(fetchUpsertMonitorAction, (state, action) => { state.monitorUpsertStatuses[action.payload.id] = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts index 49159b29ef461..aa4a8db73b98c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts @@ -9,7 +9,7 @@ import { createReducer } from '@reduxjs/toolkit'; import { MonitorOverviewResult, OverviewStatus } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorOverviewPageState } from './models'; import { @@ -60,13 +60,13 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => { }) .addCase(fetchMonitorOverviewAction.fail, (state, action) => { state.loading = false; - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; }) .addCase(quietFetchOverviewAction.success, (state, action) => { state.data = action.payload; }) .addCase(quietFetchOverviewAction.fail, (state, action) => { - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; }) .addCase(setOverviewPerPageAction, (state, action) => { state.pageState = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts index 794e16d0292c5..dbdd53d4cbcb7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts @@ -7,10 +7,13 @@ import { createAction } from '@reduxjs/toolkit'; import { ServiceLocations, ThrottlingOptions } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export const getServiceLocations = createAction('[SERVICE LOCATIONS] GET'); export const getServiceLocationsSuccess = createAction<{ throttling: ThrottlingOptions | undefined; locations: ServiceLocations; }>('[SERVICE LOCATIONS] GET SUCCESS'); -export const getServiceLocationsFailure = createAction<Error>('[SERVICE LOCATIONS] GET FAILURE'); +export const getServiceLocationsFailure = createAction<IHttpSerializedFetchError>( + '[SERVICE LOCATIONS] GET FAILURE' +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts index e13fe756ec7fd..9a338458e603f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts @@ -11,6 +11,7 @@ import { ServiceLocations, ThrottlingOptions, } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { getServiceLocations, @@ -22,7 +23,7 @@ export interface ServiceLocationsState { locations: ServiceLocations; throttling: ThrottlingOptions | null; loading: boolean; - error: Error | null; + error: IHttpSerializedFetchError | null; locationsLoaded?: boolean; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts index c38fadc0952a6..0c7abffd1b289 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts @@ -7,23 +7,24 @@ import { createAction } from '@reduxjs/toolkit'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export const getSyntheticsEnablement = createAction('[SYNTHETICS_ENABLEMENT] GET'); export const getSyntheticsEnablementSuccess = createAction<MonitorManagementEnablementResult>( '[SYNTHETICS_ENABLEMENT] GET SUCCESS' ); -export const getSyntheticsEnablementFailure = createAction<Error>( +export const getSyntheticsEnablementFailure = createAction<IHttpSerializedFetchError>( '[SYNTHETICS_ENABLEMENT] GET FAILURE' ); export const disableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] DISABLE'); export const disableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] DISABLE SUCCESS'); -export const disableSyntheticsFailure = createAction<Error>( +export const disableSyntheticsFailure = createAction<IHttpSerializedFetchError>( '[SYNTHETICS_ENABLEMENT] DISABLE FAILURE' ); export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE'); export const enableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS'); -export const enableSyntheticsFailure = createAction<Error>( +export const enableSyntheticsFailure = createAction<IHttpSerializedFetchError>( '[SYNTHETICS_ENABLEMENT] ENABLE FAILURE' ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts index 62ed85ad17e86..3bf9ff69bf005 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts @@ -18,10 +18,11 @@ import { getSyntheticsEnablementFailure, } from './actions'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export interface SyntheticsEnablementState { loading: boolean; - error: Error | null; + error: IHttpSerializedFetchError | null; enablement: MonitorManagementEnablementResult | null; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts index 416c3134d6034..35e93fd91484e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts @@ -6,13 +6,13 @@ */ import { createAction } from '@reduxjs/toolkit'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { IHttpSerializedFetchError } from './http_error'; export function createAsyncAction<Payload, SuccessPayload>(actionStr: string) { return { get: createAction<Payload>(actionStr), success: createAction<SuccessPayload>(`${actionStr}_SUCCESS`), - fail: createAction<IHttpFetchError>(`${actionStr}_FAIL`), + fail: createAction<IHttpSerializedFetchError>(`${actionStr}_FAIL`), }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts index b07f1fa542633..294da718a6fd3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts @@ -8,6 +8,7 @@ import { call, put } from 'redux-saga/effects'; import { PayloadAction } from '@reduxjs/toolkit'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from './http_error'; /** * Factory function for a fetch effect. It expects three action creators, @@ -23,7 +24,7 @@ import type { IHttpFetchError } from '@kbn/core-http-browser'; export function fetchEffectFactory<T, R, S, F>( fetch: (request: T) => Promise<R>, success: (response: R) => PayloadAction<S>, - fail: (error: IHttpFetchError) => PayloadAction<F> + fail: (error: IHttpSerializedFetchError) => PayloadAction<F> ) { return function* (action: PayloadAction<T>): Generator { try { @@ -32,14 +33,14 @@ export function fetchEffectFactory<T, R, S, F>( // eslint-disable-next-line no-console console.error(response); - yield put(fail(response as IHttpFetchError)); + yield put(fail(serializeHttpFetchError(response as IHttpFetchError))); } else { yield put(success(response as R)); } } catch (error) { // eslint-disable-next-line no-console console.error(error); - yield put(fail(error as IHttpFetchError)); + yield put(fail(serializeHttpFetchError(error))); } }; } diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts index 0ff45023143ec..831f8a9cbf6bb 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { createReducer } from '@reduxjs/toolkit'; import { AgentPolicy } from '@kbn/fleet-plugin/common'; +import { IHttpSerializedFetchError } from '../../../apps/synthetics/state'; import { getAgentPoliciesAction, setAddingNewPrivateLocation, @@ -24,7 +24,7 @@ export interface AgentPoliciesList { export interface AgentPoliciesState { data: AgentPoliciesList | null; loading: boolean; - error: IHttpFetchError<ResponseErrorBody> | null; + error: IHttpSerializedFetchError | null; isManageFlyoutOpen?: boolean; isAddingNewPrivateLocation?: boolean; } @@ -47,7 +47,7 @@ export const agentPoliciesReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getAgentPoliciesAction.fail, (state, action) => { - state.error = action.payload as IHttpFetchError<ResponseErrorBody>; + state.error = action.payload; state.loading = false; }) .addCase(setManageFlyoutOpen, (state, action) => { From 6de00911781c5340536cf64e51b3e1bce3aa0137 Mon Sep 17 00:00:00 2001 From: Karl Godard <karl.godard@elastic.co> Date: Mon, 3 Oct 2022 18:38:00 -0700 Subject: [PATCH 011/174] [Terminal Output] bug fixes to text sizer and missed lines rendered issue. (#142524) * removed complex lines per frame logic. caused too many edge cases. tests added to prevent future regressions * fix fit to screen option (when changing from fullscreen to not. also button state). increased playback speed to make up for removal of multi line per frame rendering * fixed tests * removing tty loading technique due to problems with unique char_device in multi container sessions on the same pod Co-authored-by: Karl Godard <karlgodard@elastic.co> --- .../plugins/session_view/common/constants.ts | 3 +- .../components/session_view/index.test.tsx | 2 + .../components/tty_player/hooks.test.tsx | 47 ++++++++--- .../public/components/tty_player/hooks.ts | 10 +-- .../components/tty_player/index.test.tsx | 2 + .../public/components/tty_player/index.tsx | 5 +- .../components/tty_text_sizer/index.test.tsx | 8 +- .../components/tty_text_sizer/index.tsx | 11 +-- .../server/routes/get_total_io_bytes_route.ts | 25 +----- .../server/routes/io_events_route.ts | 78 +------------------ 10 files changed, 64 insertions(+), 127 deletions(-) diff --git a/x-pack/plugins/session_view/common/constants.ts b/x-pack/plugins/session_view/common/constants.ts index 85e714cd27cb3..e7efb0b1f11f6 100644 --- a/x-pack/plugins/session_view/common/constants.ts +++ b/x-pack/plugins/session_view/common/constants.ts @@ -48,8 +48,7 @@ export const ALERT_STATUS = { export const LOCAL_STORAGE_DISPLAY_OPTIONS_KEY = 'sessionView:displayOptions'; export const MOUSE_EVENT_PLACEHOLDER = { stopPropagation: () => undefined } as React.MouseEvent; export const DEBOUNCE_TIMEOUT = 500; -export const DEFAULT_TTY_PLAYSPEED_MS = 50; // milliseconds per render loop -export const TTY_LINES_PER_FRAME = 5; // number of lines to print to xterm on each render loop +export const DEFAULT_TTY_PLAYSPEED_MS = 30; // milliseconds per render loop export const TTY_LINES_PRE_SEEK = 200; // number of lines to redraw before the point we are seeking to. export const DEFAULT_TTY_FONT_SIZE = 11; export const DEFAULT_TTY_ROWS = 66; diff --git a/x-pack/plugins/session_view/public/components/session_view/index.test.tsx b/x-pack/plugins/session_view/public/components/session_view/index.test.tsx index e4650ca2eb4f1..8e970b8f50cc6 100644 --- a/x-pack/plugins/session_view/public/components/session_view/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/session_view/index.test.tsx @@ -39,6 +39,8 @@ describe('SessionView component', () => { dispatchEvent: jest.fn(), })), }); + + global.ResizeObserver = require('resize-observer-polyfill'); }); beforeEach(() => { diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx index 8b2161c3b1216..9f7201492520c 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.test.tsx @@ -8,11 +8,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock'; import { useIOLines, useXtermPlayer, XtermPlayerDeps } from './hooks'; import { ProcessEventsPage } from '../../../common/types/process_tree'; -import { - DEFAULT_TTY_FONT_SIZE, - DEFAULT_TTY_PLAYSPEED_MS, - TTY_LINES_PER_FRAME, -} from '../../../common/constants'; +import { DEFAULT_TTY_FONT_SIZE, DEFAULT_TTY_PLAYSPEED_MS } from '../../../common/constants'; const VIM_LINE_START = 22; @@ -132,9 +128,7 @@ describe('TTYPlayer/hooks', () => { jest.advanceTimersByTime(DEFAULT_TTY_PLAYSPEED_MS * 10); }); - const expectedLineNumber = Math.min(initialProps.lines.length - 1, TTY_LINES_PER_FRAME * 10); - - expect(result.current.currentLine).toBe(expectedLineNumber); + expect(result.current.currentLine).toBe(10); }); it('allows the user to stop', async () => { @@ -150,9 +144,7 @@ describe('TTYPlayer/hooks', () => { act(() => { jest.advanceTimersByTime(DEFAULT_TTY_PLAYSPEED_MS * 10); }); - const expectedLineNumber = Math.min(initialProps.lines.length - 1, TTY_LINES_PER_FRAME * 10); - - expect(result.current.currentLine).toBe(expectedLineNumber); // should not have advanced + expect(result.current.currentLine).toBe(10); // should not have advanced }); it('should stop when it reaches the end of the array of lines', async () => { @@ -182,6 +174,39 @@ describe('TTYPlayer/hooks', () => { expect(result.current.terminal.buffer.active.getLine(0)?.translateToString(true)).toBe('256'); }); + it('ensure the first few render loops have printed the right lines', async () => { + const { result, rerender } = renderHook((props) => useXtermPlayer(props), { + initialProps, + }); + + const LOOPS = 6; + + rerender({ ...initialProps, isPlaying: true }); + + act(() => { + // advance render loop + jest.advanceTimersByTime(DEFAULT_TTY_PLAYSPEED_MS * LOOPS); + }); + + rerender({ ...initialProps, isPlaying: false }); + + expect(result.current.terminal.buffer.active.getLine(0)?.translateToString(true)).toBe('256'); + expect(result.current.terminal.buffer.active.getLine(1)?.translateToString(true)).toBe(','); + expect(result.current.terminal.buffer.active.getLine(2)?.translateToString(true)).toBe( + ' Some Companies Puppet instance' + ); + expect(result.current.terminal.buffer.active.getLine(3)?.translateToString(true)).toBe( + ' | | | CentOS Stream release 8 on x86_64' + ); + expect(result.current.terminal.buffer.active.getLine(4)?.translateToString(true)).toBe( + ' *********************** Load average: 1.23, 1.01, 0.63' + ); + expect(result.current.terminal.buffer.active.getLine(5)?.translateToString(true)).toBe( + ' ************************ ' + ); + expect(result.current.currentLine).toBe(LOOPS); + }); + it('will allow a plain text search highlight on the last line printed', async () => { const { result: xTermResult } = renderHook((props) => useXtermPlayer(props), { initialProps, diff --git a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts index 680d50283d5f1..b6891f1dd1d49 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/hooks.ts +++ b/x-pack/plugins/session_view/public/components/tty_player/hooks.ts @@ -29,7 +29,6 @@ import { DEFAULT_TTY_ROWS, DEFAULT_TTY_COLS, TTY_LINE_SPLITTER_REGEX, - TTY_LINES_PER_FRAME, TTY_LINES_PRE_SEEK, } from '../../../common/constants'; @@ -226,6 +225,7 @@ export const useXtermPlayer = ({ if (clear) { linesToPrint = lines.slice(Math.max(0, lineNumber - TTY_LINES_PRE_SEEK), lineNumber + 1); + try { terminal.reset(); terminal.clear(); @@ -234,7 +234,7 @@ export const useXtermPlayer = ({ // there is some random race condition with the jump to feature that causes these calls to error out. } } else { - linesToPrint = lines.slice(lineNumber, lineNumber + TTY_LINES_PER_FRAME); + linesToPrint = lines.slice(lineNumber, lineNumber + 1); } linesToPrint.forEach((line, index) => { @@ -243,7 +243,7 @@ export const useXtermPlayer = ({ } }); }, - [terminal, lines] + [lines, terminal] ); useEffect(() => { @@ -284,9 +284,9 @@ export const useXtermPlayer = ({ if (!hasNextPage && currentLine === lines.length - 1) { setIsPlaying(false); } else { - const nextLine = Math.min(lines.length - 1, currentLine + TTY_LINES_PER_FRAME); - setCurrentLine(nextLine); + const nextLine = Math.min(lines.length - 1, currentLine + 1); render(nextLine, false); + setCurrentLine(nextLine); } }, playSpeed); diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx index a3b5518347ac6..f3332ae5bb7f8 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.test.tsx @@ -28,6 +28,8 @@ describe('TTYPlayer component', () => { dispatchEvent: jest.fn(), })), }); + + global.ResizeObserver = require('resize-observer-polyfill'); }); let render: () => ReturnType<AppContextTestRender['render']>; diff --git a/x-pack/plugins/session_view/public/components/tty_player/index.tsx b/x-pack/plugins/session_view/public/components/tty_player/index.tsx index cb2746736c02f..c77efc9d8c152 100644 --- a/x-pack/plugins/session_view/public/components/tty_player/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_player/index.tsx @@ -13,6 +13,7 @@ import { EuiButton, EuiBetaBadge, } from '@elastic/eui'; +import useResizeObserver from 'use-resize-observer'; import { throttle } from 'lodash'; import { ProcessEvent } from '../../../common/types/process_tree'; import { TTYSearchBar } from '../tty_search_bar'; @@ -45,7 +46,7 @@ export const TTYPlayer = ({ autoSeekToEntityId, }: TTYPlayerDeps) => { const ref = useRef<HTMLDivElement>(null); - const scrollRef = useRef<HTMLDivElement>(null); + const { ref: scrollRef, height: containerHeight = 1 } = useResizeObserver<HTMLDivElement>({}); const { data, fetchNextPage, hasNextPage, isFetching, refetch } = useFetchIOEvents(sessionEntityId); @@ -188,7 +189,7 @@ export const TTYPlayer = ({ textSizer={ <TTYTextSizer tty={tty} - containerHeight={scrollRef?.current?.offsetHeight || 0} + containerHeight={containerHeight} fontSize={fontSize} onFontSizeChanged={setFontSize} isFullscreen={isFullscreen} diff --git a/x-pack/plugins/session_view/public/components/tty_text_sizer/index.test.tsx b/x-pack/plugins/session_view/public/components/tty_text_sizer/index.test.tsx index 489b9da9a7d52..a534cb151a95f 100644 --- a/x-pack/plugins/session_view/public/components/tty_text_sizer/index.test.tsx +++ b/x-pack/plugins/session_view/public/components/tty_text_sizer/index.test.tsx @@ -79,9 +79,15 @@ describe('TTYTextSizer component', () => { it('emits a font size to fit to full screen, when isFullscreen = true', async () => { renderResult = mockedContext.render( - <TTYTextSizer {...props} isFullscreen={true} containerHeight={400} /> + <TTYTextSizer {...props} isFullscreen containerHeight={400} /> ); + const zoomFitBtn = renderResult.queryByTestId('sessionView:TTYZoomFit'); + + if (zoomFitBtn) { + userEvent.click(zoomFitBtn); + } + expect(props.onFontSizeChanged).toHaveBeenCalledTimes(1); expect(props.onFontSizeChanged).toHaveBeenCalledWith(FULL_SCREEN_FONT_SIZE); }); diff --git a/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx b/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx index 42531fc7f5e6c..a2454f8cac28b 100644 --- a/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx +++ b/x-pack/plugins/session_view/public/components/tty_text_sizer/index.tsx @@ -65,13 +65,7 @@ export const TTYTextSizer = ({ onFontSizeChanged(newSize); } } - }, [containerHeight, fit, fontSize, onFontSizeChanged, tty?.rows]); - - useEffect(() => { - if (isFullscreen) { - setFit(true); - } - }, [isFullscreen]); + }, [isFullscreen, containerHeight, fit, fontSize, onFontSizeChanged, tty?.rows]); const onToggleFit = useCallback(() => { const newValue = !fit; @@ -100,7 +94,8 @@ export const TTYTextSizer = ({ display={fit ? 'fill' : 'empty'} iconType={fit ? 'expand' : 'minimize'} onClick={onToggleFit} - {...commonButtonProps} + size="s" + color="ghost" /> </EuiToolTip> </EuiFlexItem> diff --git a/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts b/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts index 4987c284b6339..081969b66ca43 100644 --- a/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts +++ b/x-pack/plugins/session_view/server/routes/get_total_io_bytes_route.ts @@ -4,16 +4,13 @@ */ import { schema } from '@kbn/config-schema'; import { IRouter } from '@kbn/core/server'; -import { EVENT_ACTION, TIMESTAMP } from '@kbn/rule-data-utils'; +import { EVENT_ACTION } from '@kbn/rule-data-utils'; import { GET_TOTAL_IO_BYTES_ROUTE, PROCESS_EVENTS_INDEX, TOTAL_BYTES_CAPTURED_PROPERTY, - TTY_CHAR_DEVICE_MAJOR_PROPERTY, - TTY_CHAR_DEVICE_MINOR_PROPERTY, - HOST_ID_PROPERTY, + ENTRY_SESSION_ENTITY_ID_PROPERTY, } from '../../common/constants'; -import { getTTYQueryPredicates } from './io_events_route'; export const registerGetTotalIOBytesRoute = (router: IRouter) => { router.get( @@ -30,30 +27,14 @@ export const registerGetTotalIOBytesRoute = (router: IRouter) => { const { sessionEntityId } = request.query; try { - const ttyPredicates = await getTTYQueryPredicates(client, sessionEntityId); - - if (!ttyPredicates) { - return response.ok({ body: { total: 0 } }); - } - const search = await client.search({ index: [PROCESS_EVENTS_INDEX], body: { query: { bool: { must: [ - { term: { [TTY_CHAR_DEVICE_MAJOR_PROPERTY]: ttyPredicates.ttyMajor } }, - { term: { [TTY_CHAR_DEVICE_MINOR_PROPERTY]: ttyPredicates.ttyMinor } }, - { term: { [HOST_ID_PROPERTY]: ttyPredicates.hostId } }, + { term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } }, { term: { [EVENT_ACTION]: 'text_output' } }, - { - range: { - [TIMESTAMP]: { - gte: ttyPredicates.range[0], - lte: ttyPredicates.range[1], - }, - }, - }, ], }, }, diff --git a/x-pack/plugins/session_view/server/routes/io_events_route.ts b/x-pack/plugins/session_view/server/routes/io_events_route.ts index 52a24708126a5..7a88eacdeed7e 100644 --- a/x-pack/plugins/session_view/server/routes/io_events_route.ts +++ b/x-pack/plugins/session_view/server/routes/io_events_route.ts @@ -8,75 +8,17 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '@kbn/core/server'; import { EVENT_ACTION, TIMESTAMP } from '@kbn/rule-data-utils'; import type { ElasticsearchClient } from '@kbn/core/server'; -import { parse } from '@kbn/datemath'; import { Aggregate } from '../../common/types/aggregate'; -import { EventAction, EventKind, ProcessEvent } from '../../common/types/process_tree'; +import { EventAction, EventKind } from '../../common/types/process_tree'; import { IO_EVENTS_ROUTE, IO_EVENTS_PER_PAGE, PROCESS_EVENTS_INDEX, ENTRY_SESSION_ENTITY_ID_PROPERTY, - TTY_CHAR_DEVICE_MAJOR_PROPERTY, - TTY_CHAR_DEVICE_MINOR_PROPERTY, - HOST_ID_PROPERTY, PROCESS_ENTITY_ID_PROPERTY, PROCESS_EVENTS_PER_PAGE, } from '../../common/constants'; -/** - * Grabs the most recent event for the session and extracts the TTY char_device - * major/minor numbers, boot id, and session date range to use in querying for tty IO events. - * This is done so that any process from any session that writes to this TTY at the time of - * this session will be shown in the TTY Player. e.g. wall - */ -export const getTTYQueryPredicates = async ( - client: ElasticsearchClient, - sessionEntityId: string -) => { - const lastEventQuery = await client.search({ - index: [PROCESS_EVENTS_INDEX], - body: { - query: { - bool: { - minimum_should_match: 1, - should: [ - { term: { [EVENT_ACTION]: 'fork' } }, - { term: { [EVENT_ACTION]: 'exec' } }, - { term: { [EVENT_ACTION]: 'end' } }, - { term: { [EVENT_ACTION]: 'text_output' } }, - ], - must: [{ term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } }], - }, - }, - size: 1, - sort: [{ [TIMESTAMP]: 'desc' }], - }, - }); - - const lastEventHits = lastEventQuery.hits.hits; - - if (lastEventHits.length > 0) { - const lastEvent: ProcessEvent = lastEventHits[0]._source as ProcessEvent; - const lastEventTime = lastEvent['@timestamp']; - const rangeEnd = - (lastEventTime && parse(lastEventTime)?.toISOString()) || new Date().toISOString(); - const range = [lastEvent?.process?.entry_leader?.start, rangeEnd]; - const tty = lastEvent?.process?.entry_leader?.tty; - const hostId = lastEvent?.host?.id; - - if (tty?.char_device?.major !== undefined && tty?.char_device?.minor !== undefined && hostId) { - return { - ttyMajor: tty.char_device.major, - ttyMinor: tty.char_device.minor, - hostId, - range, - }; - } - } - - return null; -}; - export const registerIOEventsRoute = (router: IRouter) => { router.get( { @@ -94,30 +36,14 @@ export const registerIOEventsRoute = (router: IRouter) => { const { sessionEntityId, cursor, pageSize = IO_EVENTS_PER_PAGE } = request.query; try { - const ttyPredicates = await getTTYQueryPredicates(client, sessionEntityId); - - if (!ttyPredicates) { - return response.ok({ body: { total: 0, events: [] } }); - } - const search = await client.search({ index: [PROCESS_EVENTS_INDEX], body: { query: { bool: { must: [ - { term: { [TTY_CHAR_DEVICE_MAJOR_PROPERTY]: ttyPredicates.ttyMajor } }, - { term: { [TTY_CHAR_DEVICE_MINOR_PROPERTY]: ttyPredicates.ttyMinor } }, - { term: { [HOST_ID_PROPERTY]: ttyPredicates.hostId } }, + { term: { [ENTRY_SESSION_ENTITY_ID_PROPERTY]: sessionEntityId } }, { term: { [EVENT_ACTION]: 'text_output' } }, - { - range: { - [TIMESTAMP]: { - gte: ttyPredicates.range[0]?.toString(), - lte: ttyPredicates.range[1]?.toString(), - }, - }, - }, ], }, }, From aa12bea33c13b9a9b1f7f35e3270b9c193510114 Mon Sep 17 00:00:00 2001 From: Paulo Henrique <paulo.henrique@elastic.co> Date: Mon, 3 Oct 2022 19:01:01 -0700 Subject: [PATCH 012/174] [8.5][Elastic Defend onboarding] Updates to text for Endpoint presets (#142138) --- .../endpoint_policy_create_extension.tsx | 81 ++++++++++++++++--- .../translations.ts | 27 ++++--- 2 files changed, 89 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx index 0617707505e52..78da8134807f2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/endpoint_policy_create_extension.tsx @@ -14,10 +14,14 @@ import { EuiTitle, EuiSpacer, EuiFormRow, + EuiCallOut, + EuiLink, + EuiCode, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import styled from 'styled-components'; import type { PackagePolicyCreateExtensionComponentProps } from '@kbn/fleet-plugin/public'; +import { useLicense } from '../../../../../../common/hooks/use_license'; import { ALL_EVENTS, CLOUD_SECURITY, @@ -26,6 +30,8 @@ import { EDR_ESSENTIAL, ENDPOINT, INTERACTIVE_ONLY, + NGAV_NOTE, + EDR_NOTE, } from './translations'; const PREFIX = 'endpoint_policy_create_extension'; @@ -38,9 +44,18 @@ const environmentMapping = { }; const endpointPresetsMapping = { - NGAV, - EDREssential: EDR_ESSENTIAL, - EDRComplete: EDR_COMPLETE, + NGAV: { + label: NGAV, + note: NGAV_NOTE, + }, + EDREssential: { + label: EDR_ESSENTIAL, + note: EDR_NOTE, + }, + EDRComplete: { + label: EDR_COMPLETE, + note: EDR_NOTE, + }, }; const cloudEventMapping = { @@ -67,12 +82,21 @@ const HelpTextWithPadding = styled.div` */ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionComponentProps>( ({ newPolicy, onChange }) => { + const isPlatinumPlus = useLicense().isPlatinumPlus(); + const isEnterprise = useLicense().isEnterprise(); + // / Endpoint Radio Options (NGAV and EDRs) const [endpointPreset, setEndpointPreset] = useState<EndpointPreset>('NGAV'); - const [selectedCloudEvent, setSelectedCloudEvent] = useState<CloudEvent>('ALL_EVENTS'); + const [selectedCloudEvent, setSelectedCloudEvent] = useState<CloudEvent>('INTERACTIVE_ONLY'); const [selectedEnvironment, setSelectedEnvironment] = useState<Environment>('endpoint'); const initialRender = useRef(true); + // Show NGAV license note when Gold and below + // Show other licenses note when Platinum and Below + const showNote = + (endpointPreset === 'NGAV' && !isPlatinumPlus) || + (endpointPreset !== 'NGAV' && !isEnterprise); + // Fleet will initialize the create form with a default name for the integrating policy, however, // for endpoint security, we want the user to explicitly type in a name, so we blank it out // only during 1st component render (thus why the eslint disabled rule below). @@ -156,7 +180,7 @@ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionCo const getEndpointPresetsProps = useCallback( (preset: EndpointPreset) => ({ id: `${PREFIX}_endpoint_preset_${preset}`, - label: endpointPresetsMapping[preset], + label: endpointPresetsMapping[preset].label, value: preset, checked: endpointPreset === preset, onChange: onChangeEndpointPreset, @@ -231,7 +255,7 @@ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionCo <HelpTextWithPadding> <FormattedMessage id="xpack.securitySolution.createPackagePolicy.stepConfigure.packagePolicyTypeEndpointNGAV" - defaultMessage="Prevents Malware, Ransomware and Memory Threats and provides process telemetry" + defaultMessage="Machine learning malware, ransomware, memory threat, malicious behavior, and credential theft preventions, plus process telemetry" /> </HelpTextWithPadding> } @@ -245,7 +269,7 @@ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionCo <HelpTextWithPadding> <FormattedMessage id="xpack.securitySolution.createPackagePolicy.stepConfigure.packagePolicyTypeEndpointEDREssential" - defaultMessage="Endpoint Alerts, Process Events, Network Events, File Events" + defaultMessage="Everything in NGAV, plus file and network telemetry" /> </HelpTextWithPadding> } @@ -259,13 +283,42 @@ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionCo <HelpTextWithPadding> <FormattedMessage id="xpack.securitySolution.createPackagePolicy.stepConfigure.packagePolicyTypeEndpointEDRComplete" - defaultMessage="Endpoint Alerts, Full Event capture" + defaultMessage="Everything in Essential EDR, plus full telemetry" /> </HelpTextWithPadding> } > <EuiRadio {...getEndpointPresetsProps('EDRComplete')} /> </EuiFormRow> + {showNote && ( + <> + <EuiSpacer size="m" /> + <EuiCallOut iconType="iInCircle"> + <EuiText size="s"> + <p> + {endpointPresetsMapping[endpointPreset].note}{' '} + <FormattedMessage + id="xpack.securitySolution.createPackagePolicy.stepConfigure.seeDocumentation" + defaultMessage="See {documentation} for more information." + values={{ + documentation: ( + <EuiLink + href="https://www.elastic.co/guide/en/security/current/configure-endpoint-integration-policy.html" + target="_blank" + > + <FormattedMessage + id="xpack.securitySolution.endpoint.ingestManager.createPackagePolicy.seeDocumentationLink" + defaultMessage="documentation" + /> + </EuiLink> + ), + }} + /> + </p> + </EuiText> + </EuiCallOut> + </> + )} </> ) : ( <> @@ -285,7 +338,11 @@ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionCo <HelpTextWithPadding> <FormattedMessage id="xpack.securitySolution.createPackagePolicy.stepConfigure.packagePolicyTypeComprehensiveInfo" - defaultMessage="Monitors and collects session data from all process executions. " + defaultMessage="Monitors and collects process data from all executions, including those launched by daemon processes, like {nginx} or {postgres}" + values={{ + nginx: <EuiCode>{'nginx'}</EuiCode>, + postgres: <EuiCode>{'postgres'}</EuiCode>, + }} /> </HelpTextWithPadding> } @@ -299,7 +356,11 @@ export const EndpointPolicyCreateExtension = memo<PackagePolicyCreateExtensionCo <HelpTextWithPadding> <FormattedMessage id="xpack.securitySolution.createPackagePolicy.stepConfigure.packagePolicyTypeInteractiveOnlyInfo" - defaultMessage="Monitors and collects session data from interactive sessions only. " + defaultMessage="Monitors and collects session data from interactive sessions, like {ssh} or {telnet}" + values={{ + ssh: <EuiCode>{'ssh'}</EuiCode>, + telnet: <EuiCode>{'telnet'}</EuiCode>, + }} /> </HelpTextWithPadding> } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts index 66688371b68de..46246176119ae 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension/translations.ts @@ -10,20 +10,35 @@ import { i18n } from '@kbn/i18n'; export const NGAV = i18n.translate( 'xpack.securitySolution.createPackagePolicy.stepConfigure.endpointDropdownOptionNGAV', { - defaultMessage: 'NGAV', + defaultMessage: 'Next-Generation Antivirus (NGAV)', + } +); + +export const NGAV_NOTE = i18n.translate( + 'xpack.securitySolution.createPackagePolicy.stepConfigure.endpointDropdownOptionNGAVNote', + { + defaultMessage: 'Note: advanced protections require a platinum license level.', } ); export const EDR_ESSENTIAL = i18n.translate( 'xpack.securitySolution.createPackagePolicy.stepConfigure.endpointDropdownOptionEDREssential', { - defaultMessage: 'EDR Essential', + defaultMessage: 'Essential EDR (Endpoint Detection & Response)', } ); export const EDR_COMPLETE = i18n.translate( 'xpack.securitySolution.createPackagePolicy.stepConfigure.endpointDropdownOptionEDRComplete', { - defaultMessage: 'EDR Complete', + defaultMessage: 'Complete EDR (Endpoint Detection & Response)', + } +); + +export const EDR_NOTE = i18n.translate( + 'xpack.securitySolution.createPackagePolicy.stepConfigure.endpointDropdownOptionEDRNote', + { + defaultMessage: + 'Note: advanced protections require a platinum license, and full response capabilities require an enterprise license.', } ); @@ -51,9 +66,3 @@ export const ALL_EVENTS = i18n.translate( defaultMessage: 'All events', } ); -export const PREVENT_MALICIOUS_BEHAVIOR = i18n.translate( - 'xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersPreventionMaliciousBehavior', - { - defaultMessage: 'Prevent Malicious Behavior', - } -); From 999bc84c81f7413dc6a4f6af3174900a56df4aaa Mon Sep 17 00:00:00 2001 From: Nathan Reese <reese.nathan@elastic.co> Date: Mon, 3 Oct 2022 20:06:05 -0600 Subject: [PATCH 013/174] [Sample data] replace legacy control visualizations with dashboard controls (#141824) * [Sample data] replace legacy control visualizations with dashboard controls * i18n wrappings for title and description * update screen shots * fix functional tests * update functional test expects * more functional test expect updates Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../ecommerce/dashboard.webp | Bin 23900 -> 32920 bytes .../ecommerce/dashboard_dark.webp | Bin 32348 -> 31946 bytes .../flights/dashboard.webp | Bin 21616 -> 31426 bytes .../flights/dashboard_dark.webp | Bin 28074 -> 31330 bytes .../sample_data_resources/logs/dashboard.webp | Bin 23410 -> 38660 bytes .../logs/dashboard_dark.webp | Bin 29896 -> 39936 bytes .../data_sets/ecommerce/saved_objects.ts | 147 ++++++++---------- .../data_sets/flights/saved_objects.ts | 128 ++++++++------- .../data_sets/logs/saved_objects.ts | 112 +++++++------ .../from_the_browser/loaded_dashboard.ts | 2 +- test/functional/apps/home/_sample_data.ts | 8 +- .../services/dashboard/expectations.ts | 9 ++ .../journeys/ecommerce_dashboard.ts | 2 +- .../performance/journeys/flight_dashboard.ts | 2 +- .../journeys/web_logs_dashboard.ts | 2 +- .../apps/dashboard/group2/_async_dashboard.ts | 2 +- .../save_search_session_relative_time.ts | 2 +- .../apps/dashboard/dashboard_smoke_tests.ts | 2 +- 18 files changed, 214 insertions(+), 204 deletions(-) diff --git a/src/plugins/home/public/assets/sample_data_resources/ecommerce/dashboard.webp b/src/plugins/home/public/assets/sample_data_resources/ecommerce/dashboard.webp index f6720bf0e3e51127645416f0f551c95a459f0757..a48a4a05318bbe83f6f670ab58e4b460c80836f1 100644 GIT binary patch literal 32920 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 LZNB@5rQiSnR%R+d literal 23900 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 RYcB+E5)z3?3h+XJ002xXpMn4Y diff --git a/src/plugins/home/public/assets/sample_data_resources/ecommerce/dashboard_dark.webp b/src/plugins/home/public/assets/sample_data_resources/ecommerce/dashboard_dark.webp index b9263441d1f223dae0beae97aef84a46f7374139..cccee38594e9c9f2620b498bb152ff629a723097 100644 GIT binary patch literal 31946 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 literal 32348 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%0APqJ#4Fr( zUEn7B17w2;eZbe7xElFA4ysrkzwmSvVv3pQ-UcR7-r~(feu7w~9-723#r)^w%WSlD z7-@*hto$3mz~7<b&~4*MEc7-CZh8P7y9s20%+H1Mp-6F2a8N;<t^W<d1mkC+{N}+# z4vmDS&rYGPT6%?06Q!Zy*`&vh70oM4tsD0aX-ir=*$+hq>Af0I5UkDU+~1zjzX3c$ zJs{0Kf(jA}Q7v`UvVQ+ucu#~aXtF%<AQDgp)q^^GGL^u)WbG1>U5TN&Gli<JvAq%t zPcd}bv2PBp@Q#3mJB-7>%~F46970j(kHOq~mcz!y)FwHlE0>$Qr@4McN*iZ)H%yE1 z<_Z-?5zGO1cw)3#VPqY)xP=<}@=8UnRuoD4XKf8uuF&3%RfDp@fHTAD-@62gTC(pX z4>g&?6Ki%Q#3ovK6Fv=`Q`Tk0Ci*Q?ANPw1E;Q0qm*&5LT7Jqvc@fPdW;9lpXIe~3 z=btUqUT|uSLh3LRc=VPQF>_?$r&@|>m*KyZn+F`o0kn0T%#*9~-PrFQnX!!lm9A6| z61vs(jcG_w>Ht|;e?Mo%OzUJFs-hCPg6VeW|Mh}ESk9$TF43aEpAJ%uT8eJ~(f7)d zAJfjWVQApG1Y--pzl4T5gW)DU*Md>)mfcF4dMD{^U!P(8LW}bzDa<n_mDDlSFQUlI z(ijXr`J51<5t-2Qb;IrB-@2KmgfauSSbuQwa*jY_og65W4EC%(sn}157wOOQLC@l{ z_89urD|5Kewd0SBpxUIPCo%o6gR+7P{nOCE<-_`1?=%sb7OlJ$o?XxPL>u0U@fp~X z=kh(vAx;HIi_QsXU3vmhxuZd=gvRg-eZ$W|&!<JTnb8Lm6GoUp={eeQZ46#wHc#1o z^gOd#&=km_P9N&TigoO=;N(1e*1ZI%f!DoQ*x|N<@%*)vLUT?JZYiPsY8MsdwXBG) zgaUqE@Bn!CakZDIYC#UTPjJ10kK^hc8m<s^&m*|ggJCgDfJej@@AnkE+zWic6}*3D zHZFNM@4w!DfbB^=ditm`iwPCHmhfJk*2UqPZ7RZoC^vsS+KZ&7k7n3S(8|qH%uXR* zuxfx`@sOGx1|eQCSp|jEAzOK2vAC9w?qHa6G&x(^r%f*m^`tP?OO3o^iX6Q_eFcXi zkmos_zgr0lKN^$^+P0K|jcg7CU>BUPXil#@5qby}cPCR0_N^+S*sI-wF|%N?vsm_^ zDRR*{S&67q;tc9GhzzU1&qimPAEyxz__S0YD>z{Y{z+?LG&;)|d;|yn*MnF(m87x* z`~j7@Z87CW4I_}p4GNo%-=>R?zfeL@kk{S%&if#4f<<BSE`%zq>a2T}fe+5{%N2>| zkr<c&2RIv^e#4(pm#Izx{IDM%bP8@RtWs3>`bo26*0}1(4i`@{hTp9p)h;I8h4o#d zZdDek`eKjSFppF}!D(>tI1)TZIw!we@L+9ecxUhK%>7O5>QJ-Be=5bWcz{P%G0gr1 zb4XPh6^D|!UXloI_Bymy)Q{5GlIl~xcXOLkEy1iCfFVA%gYgEQ`md}<vT`)uVLm%+ zKO59ZT-5?vJM#S0>ZZGhV7&Ux?%BQrP?S7?X?_MHWn<U5*JLP}^B|WVk1>)${+x4j zwh{^5k6J}wxe<b!#}gwnlu{NGKR`?$Com?_E7ntvAsZ}{oEN;x_4EXdjo^rBdVfHt z%Gurlh~_BLf8C;+ApV^A&%hO?E+sPd;iEl|WT}6!*QIt?kk9_=ltqM(I4qkTBn>kX z`DmIg9%vWKuMK@90`+yj+-f&?u$5xtZJ#+o_818Mx6Ubqw+;s9og7yUKk>~(BuI;7 zRd-puZa_Z<a|<Yo!3rV>Sk*uo)PvY=Lpt_pg)EbP1|Dg~S;O)I)B>?`d?S!$J2wQh z_wUL|v91pB^gUw(xdYv`DCqTn=K7WrtcHWI=<R?QX(a5{Q4PluY7>htOBh`3Ujw5w zHb?fVt?98smOUWCLy?4-GnheGCi-#d6~GN^WwR3Pv!79#2_03j^A|_J6KlgHOsqXb z50v-id35D8#%3HA>?`aqq#slCG2nF5gk7iah~*G-XYqt^__I8NLi?u1l~s_jInnQ} zgeb`^D^vKVQ&Z*qA0Af0Tl;4$0AR0?L$y=Dqy#O|%L>_{76t!<{(c4NJF%@y;}p>e zPxfR)D?89>Ia`g<7zcC$wYFc|xLydF@n#Df8Tp4I(m7Mm1@5SFIvyd4U!f<H3<$Hp zG<mz+OZ-v&vJ6|Sm)ljW`1O$BvaJ8(1@rJINCOcz=#13Deu0j2o3=SwMfe#WyivHb z7RllQsmAbw6fq2R${qjBcS#^29e?4LZ?(TVmnWmnk*ZTM`o>Hfwx;h*3O`%Z3$-BO z50B_bvPN1-K3TMiH$wuK(;RP+*wBuPZBJT3HhwrIk=*!kjR{biLE9a93faeitUK+N zm9Cc=UOwOYCSu;K<Be+-sTO#0+TA(1^$-+M`<rO5wP;g`lO%jv%zb@x4@porXouvE zgseMv^M0;QdO+wp4>0D2mg#vu-l{^)xl%2PsZzh(d_A-V9eeP7@5(Cp6V1s)t1|{T zse5zPX+M{V1X35RtvSAKQ8c=430&^|Oy|h}i4RE!@njahJv4@e=B&}vLtDEH3lYN1 z>azzGz^=DDR9TN9H3ktP+%rZaSuq<)p|h#~2!HCUX9|C4wz4wJE~0eWh3SivHdOOI z5=Qm^8)E@Jv4!jvF6=LLa^Nr*^z{t~y>1^s_f`R?nyEU+*;Ot{eAwLARRf+(b6a0} z$6a%5LL2+)7lIprXyiLMQ_u($6ce|XX;OINxmE?xA92sp<QJLxzTXZCtoq)aLO3t| zfS9RF1{!m+ZH1=KTjbm0m;e9(T&JT%OqF_{i5;9ht|UMs_1D-2H5)x0iE(-P>j<F= z$c(4}2=*>}YHgamz@&70&k88I*5B~Om5wA5EnH>WL(NFuWLrz-pUg{7+35ojwfyu5 z3uT~B1zLK`|D_La`U~4ecnphXT?#_;rkY&kQ=Z8dUG6wD^QPB!UtsU7aTSkGEO78W z3%M<QN83~%q9|(ISNz2<hixEP)3Du`!?0-HQ@YbB+&j?v&oShwF~GKDLZso$Qw&kR zVstC5smZk#W%l5NJkfR9O|#7*$*)I$M_tX6J~>SCM_(^QKG2+~GQiA;S`z^c{wIkB zVsuu4Qn{~5$;JHL7($j!!bH}8U%=9~c*luR7zMFx;OPrSX|waiAOX6U8aKv|dn~y+ ze=H63P9vO!5xMZ*qjAk@Mo-!R7-YA=EV8X@mw^6|ylrHK%})rex3bTx2s}^GG2;z% zJBcxyMV2=4vMn*UbN>4ZJfJV1^$OE-rSdt1Tgr998<*-zr%-(FzkILU`cTRGiVLee zHNe+#8PbVESgA3z2B0+7RPepjIn$}o+?|hG;(|yq$vyp3GzQ0b_T-vh0>|~81Z1v& zEl39<0)ama`9xAU&)2Tb^^Nr`>B!3NgQd;ygHEx|(px`w%g8riz3P~~K@#PVjl*<~ z3W+v}RDm67&wdcR@=U2_=^%&(a_Kbk(t{cnF$aK){o|96{{VYU04XIx3mng-2ULSV z=TTLSqg<ToJf(O;J^a`FUY!}oVi<_hl0cEjZpOWLIL@E=UsRc>Rk_NqeU~=C3Uo%q zwUX42_U-#p<Yo%;IA(Tj>o%tCv#3s;i%__p=P36?u2Uukx>S(#WH`gGbdF@);RNxf z49OH752>N)jlKk4AJFY>!PcU3-=6YWNNnlB)Oic(vrLdj?>U!*ewGAYSkKNv`Ymxf z3Br@2z<_<;R=4+}?jD9DBK`p^yVm1_(T!0<g=BRIy1?TG1{vckciU*M?%70m<{9LS zka*|5guee%SBnC><ulXeF(V2ofVwFNVX<13i8<VIL`n>GeNb`e9Deg5UvG*RCCW|> z{e(Vc*8;Uedr(IUK-;r<ca=lIV)hS3JaQ(I)|+E$h)^-6*gZ3DYbL4~kTryH{XH%^ zpc=XA{_@pkMd|izgrP-S=55Uwt)(nc2|@YPaH8gTlMV|+jlyes1OZ=jb<&m3s}dr* z6;%aVN$J<=F7a~9+V62=%ruZhTn_(M0kXqdrGkKHw9y~ZzTM{1{oFrJitu@zBBh{$ zS=>zEE_NOhA4<gy!1G~Seo!FM<M=8M>T>5Zd5MlmX5-+mQYry?IQMT!GFI$J-AC== z7xrrtz^TS=F;E{(BHwu!P7DcPHULVB7V=j$iISDF9t(|Jq461Qy`qbn)fuZ$X`PTr z2!$Y3_*Hxs*WN>e?zBl&q;?vNY&1;8X#~aii83dD?xdONvK(Bla%Ie$-9dEavcg+r zjuD(i%hJ}MBdcZt%=X76(1a5y!nk}Oxu3WB<Y~%2Ns8a>-D7J$DHGvb06LT=<QSxR zo3S9Fj>Yq-J()AAL?e1glikTgNiPP;hLAUue|d?)I%@0sf5o{6LaIkmbiPI&uQXng zGRr}rn%w)<ASjrrY-Q<98}fYcW<65Wp1U<$lwf~p?HExwcZbyXf<E>ru2417r;1a< z>v(VcnoXmWZEv6oGZhg#J8<m|<L_;f;-_}<m8))=6j7U;<v--EcQQV)ZC60EZ?y>> zITKGkXM_b8*bf(0-F#aBN@_-r<j-?J5^|H#VELW$4QsD3)GC#{qc@Mx{NfcT*dwP| zoI$`n06+<Dn0L|=VaPYsIw_9t>$$S{{x^4w+>ciGU8M-)=iQ>9?Jj-FCzkOEo&0Mr zGLV~Djbu2nMvKDCG8hAzNa{M_-pwR)#5xxq){9iVX^OMeQta+RhGF-R^nw9@ymp31 z5832*u`3I!?aYEkFPe1U#!b=Q%=BUWG*gObT$&Zhw%oT!kB`!}poZPJF`x$f85JiB z8+o{S(6h$)<6hqs>?c!M3of{{O7$AThD4z+(f7l{^Ys9@8ggX_@6P>db5*K|Q`-&J zZ51(|jUL0)Cc&;wZnuqJAU-A#G7x8ju${RF+2Fa~e+ZnbN^*;fFo)G@`O2jlgH@|n zz{is#KFh@66^gNCh`&4lh5ds1|9{LTI+;wK8>Zr=SJlTI9b1pE8(|)Wm+vc~2JA{D ze$c|ar{)8<(2+_hL#s{>RB&<7WS}>-2z%MW@e6&Z84A1_@#^eV42B_r)_&U2fP*V; zbY5aCV<{nS`^o(0QlhVqI~})q&%|hR_5|u|XQDb8UDWf1^2BfI2Y{F-nLGw>TEdrS z80`rOMWft}jD&U8D)ph(MtvK24w>kVV*o;8^8znPPC99gyz4l2kLl#I%>gHXk|Wlo z+bH5T?9%npili2k#q^S||2X#)6{z%7VpsiX5g^662%2Z+ORGnzjxw}R?6%Y2B?-0A zFql!XplBXM@VXd>Qs<)D+vc4gvzW(~JUfsq5Talw=Dp$GT74tsa%Re3mCjdcfQEVz z#sxG(Y0J_6BD4((-y`f_gOFhj{}u;C5p`qF@gSR#!)g05YBqCYo&H8wjS{L^JZ_+d zb02prN(36ylOlwepAUh#p!M7bWQq@1SAuSKsE@y4k=shY5|K<khB%n+Z30vXNR|F_ z`**o2yQ!YNpqwl8gZE-B;5{)_c1+#auT9)|_E@(KG7vkauBdV{pz9W5*{;W(FX*TN z8gg8ry&L+xy&XLc`17H<ItqP~D&szPK~U=<RA1kW9B)5cf5|Qoa+;5kLuKnq?i!{` z{MY8Sz!tnQ1u)HVmBCl3Bh>XCxwzw3;a%QHJx;1Hoc2)cAU*%=@4ZibXvoeMMf>@u zB8)Es!X!w_V~0NMX?Sr7qVpAAk+%%rPRBr{M+m6h8%ctNn`a@Q##9jxADL(hEM>AR zKErVK$iRGu%p_JbT37jSIV^4dEQl*0hHG9IhgTqIs1e`gyzNG;5^J7JzR0dMh4rE~ z$=^Rlevf(Fimu@;+k6i14LKX)E~{-%E?Ik4cA?S<^^@rF9rlW`@L7in27P=2W%2NJ zsoEMv0)F}I`lxJ^CsiUkX*+^%h6YtWn)Rr#;7y&GIS6)h6*gsot7+EoMgv0VHOP!j zKT$n@Jci0@sEx=FSJL8@?)j*miCkTav1=1qWoB$pYk_56JF$d|Wu5sn6<{@Te%->u zz1n?{5Ek=XwB}%YPlbN=&KHxto3s#Lu!G>4UJcjc30+>BmcazGBe<xuKu{n@$#}Ij za^`*+XrNH9^s8HUEiEag9XmvNTrn0o=BVeG1VCYYK`K3huS3zkg02NJ6y`pgmU5>< z3P2RdG9o}hrdj$TB|hb_SbnLnU&_3YW}aY!0+(Yz!sd1>S|BS_C=_>K5uXs;GlG8; zksZ9&pRdCTuyGk79=9gJ>MUIMsE8aW^orKUB=kMLeEIOQwcHvFJ4GFU9Dcqu()|YQ z01+4OdO^x1+JHUeHmG?qiZmzu?jwG;h(xt^mni9Ch@>Wg^U1xo5Hoq?xb#a(ZK2V8 zcm#!I@}W@=nFv><YDFbxp{{A*>5T^K=JIZv8*x7F@f`D?Uc&SkA{D!K(KGpdR0nAP zAL@-WPRo4zsz5K@n+nfvp0X|da&gdc1(85l>Yi!L{;v=$h*o6y?lqU~tJWOUUjWSU zR8nKNS0gGFXPCAmU~YMsfCpcpJS?)yUzWHCGF&?s)wT%^JdAZS2sIkD!+2mjx1{g; zPMk9-&-&L!AGstnPJyq{gszwJYl)F{6IaVwQ5vZc<@c7ke_a)dj={#SiOj8if%4Ok zw_L8oSb-9R`o?V8*EgldPI0jMfR3n|PWZIk`|QR+TRt^e*TWHATWwWO{KpS{djoj* zDpw_<kh!4l)(<~OY6|;yTmk=b#7dP6jQ^h_x<%d+z&2C=XUE^DmM;7rMW#)o*Na}X zSRfw^DY&L9k%2|mo?+1;Af(5Ph%)6+nrXZYr-IiG{QJc+g_Irco%PK)78y(-L_Ek7 zRUvRR#MB|ytKkc>x)f9ZXli4`K@zLmLxydwS+8r)&ik)kQDei3*l|RXC>-Uz?zatx zx>SwxVE}5$?<b^_-?PF-LaQ-pF3dcQ3iLh7j{<e)93CJGQ=~blfMk;NYAMY@aI15Z zmKhnkIaNJa<RS%gI{B@H31gB>=1Levz0mO=4p}0~izbR#&-Uw0?tL+%;Cq2XcO}=p zCrAUTPTr38H6p&1#;1JnCz~eN<F`u3c~7{rq%(sdUXUcH0*w1XG)@y$p2w1Ds_q`M z)2!qzk~+EpKi%w5;*@5JZYFsj)8d4gCLl;anF-1Sx0@JebsiSv(u<!}T2#8D(D@d3 zz*a4-&vNnAzB_<D(&f?Zt|i}*bZ!|<s~j6i_Ydb<wPVCZ8SHjPCbC2qg4Q=dl!=fL z*m9&@w<GxKn8X&tMq+fKgPp_Y>+k)Q5*qVSK%w42oUJ$iOPj{8C#WAQRWc*I?cd;G zZeb4GSs}FgLm3IEK{<eD7$(-4qLCpekfakQURAGmx9aPtpNsSiWNX!@s4f_HpU~}h z`C?M?m2}k&AcyU?)hbIVcrB;c@^&l%wwK{y{{}aN!$6E3$C&FvNPr~qtj)I~orJLf zLogpw-lPh7#S}=R`wcBj)!}AZxN=pF)TD|*4#U!MkM5=ZE}#+fZm)y>z-wVUx~j1M z-P;}uK~})i5Sy>wjMd?YD$%#Ys^;}tz1)Ga_<dDc#VkcZ93>rP8GMMO9w#<<W|5Z8 zIR{>8kH>DO!^&2tK`iv=zPmooxCgC35Ev!vwoxj4SypRGXNlw2q0IoK9tY!O!CtBa z>EhY|mb_rF{qV#FPCfSu1Qm11!!z1<Tt_>?I7v?HDlA2hSCJy*Ju8XzdZ=g&0xO?W zZ|KMWY|67Wp`1WCB%9h8vC`c`@C-wx-r~2qG7gv>f>mBD$%V?ku@p)BTY+B&Y+jjq zQPN9W%+0ZJH9e?1!uZ84mJ=5;oAB)F77igd7#K+BY~3~HI>Yk^gdFHdtJ_&{{ZDf9 zO=e8E94{1Ch*?yUsFuT{sfZtO7{)9tDNH6OC6=%4d_V@MvjuRZqfa-gr}m^*U~2{J z)?|RN7^wi{Gb3Nh>M;z`r1rv)sK#2-{{b%^#;(YQ&`$#&k(NDj);<}s9D$>05oT{S zx&t}vHD|sZ(~Rh>Koz6FtfMb&6sF*g!~&5xA=7hBJvZn{?|;|R(7-%plfhf!DDG)s zJF>5kQ;lb<4hJLX#b2s<wHq(XAY_+teHN(_78C?dzmyoVS$w<9tAzpUOC3U0H@cUN z%C|uk04Tp%&MlYJzE@D*G9>9Zp6D@&$z`HFdD6eP>~UQ{yTUt*VuK5im%Hf!j**6W zSiodarc-$s>hCzPVip2|b$<coYPe6fSb~?{amXca;(Jf%a^O^))Y8RknHO{qOgGba ztECw46}vg&(Vh7B^x`T%w|ttJiET};=09z+rw>=RAjJw%pxwRFe2`5}@ymk_+`Mjf zWNjG6P3gBD0f~-U&;DUf8^=FLU{dSObTI%BVKTy`M4PuX98|bh)q(m%^goG_=-FJl z61Bw=B2dXq3+-7^b<tz4IC0+QHK}1mv{%D~0+UGw1jGG6A9b-+8v<{TsG_mUtp*KW zo`={_X|m_RVItR3p?G+wjpI*AIO!4K;{Ar*r(284VCPD59FGL^k2_gN_9oTNP%Rz% zb28TvHnUS_=tYrA{op0s7XS==M_Ja_JT;dy4tq+GZFiJGn7cul7*j~FnTcdC5?@Qz z9nXC$mJLD&3e;LwJ_lRWL8}p%m8zf*iKTxl+0%pM?ljC=jMxB`@McM?yZ}K^f8E4s z!)zc(a2ry?f2u4!#=JMyOsVXK=>Xej6;}R4m!&odlPt+KZX(pr749r&V$6>Qt-W$@ z?B=1AV#~lY=Bj5NXA~mULjeawx$ri)#p0Xvxcsm;t+CowI0q05pM}0TW>YPh-kw@S zfk6@{X1KV10A<GS8Fk#Qk8RCH{KHLzfqh~6f;`ok&|NM-7<Zq-0Bn$;U{FiypMqB! zAb!9K*>!o+iWj%0j9^`=7zoDRw$OsJ?(yicgrD1iL^9Rc46oqt8(}fyyX)eBqe=4? zFbTYYRs3C#tEU8rY4@h;0YIR>3xKCE;<*!ydgeTtQ0#%M<$9(5%(tUfi#Y^t(&#i| zw@bWE=UmuFX(%;dipL`siIxA{6tx@8hRiEgips-!qlz#T!)1*H;6mHnFcDGDG6Nsv zFOrGG+5=eesnJR{LJMQaDHZZL_Yl8X&bh+n1_o_03@>S;mm<YC*0$ea)7<x*U}cgl z6NjW>6yIU=+aT#rVNvW!?r|cO6p#GR;L{U03(L~@u`Nw(bb_;(ojAZ#b}|kGh@@So z%6H!jBvx?{R$Fv}CcQZ8A|R1+8=WrqW>xWPQ@6l4P28y^5t94x&X*)y1ur5T<7S)w zbW_z#gPsy#ZFam4cQ@Q3;$zbH?tym2EY=$v=}|klsHa&l=yB|5-8vw{fJ`v%9gW~? z%D>o@b~U-VZ`1%5)U6qH-D6R1-j}1L0D>yk(~!U%SXzT@$B$>?wd_JyZ?CbZcHcLI zi1~#!(B~=3!*5Nb>b7Qhdyn9KyUW+PqM5$0scI3%xJ-uM@e#O~=zrQ3`S3;R&`)>s zr-i$A4E#nD@(VV7(K(7*HGA;#6RVD0Y1s*6!ValXk>*A$An-%}))`tneiL6RA$Qs6 zl%Ww}uyeS<%mz$fK2dy27|!VN-rMLC^&+@*mbS4*w4$ybreP_%?z0|f^0~rSc5Nze z3&}G8Rn^k_O;QJ2Z>U>?lyx9mhZPBCT=mM2*~aY)WwENOIOg629F}LMzilVVK_Y=1 zgeuuIC`Hwy8Sy4)yFP%!QEwu(h%$py{_wJ8+yygdP;2z|%5!q?Ij-0D`Bp`K`bB)r zjy=u(5W@{T>$O>IEh3zI$$grYj}`xn;D#T>YQpSrAy|+_uFT91^o7#J&cQBA*5$|H zEFp6ISLu-19UU$9O10dT)t}}z8}|EUA8@==0eqVIF-*B+&g(s6v>C&ipr4w}gQ4iY ze-Y}^iNo`ka<t*!rCp}_DeGwl8Pi-ql*u*@@)NeH4ljGjJO?sM-2D*tf0jaXRGge8 zp~a`9`Q)$#C5`A6P$#p(lE%`sKM6qzmvDAflDamp8HZDnFXiiU_`yiai-|X363*}< zVPWkJt<qZ*cwU$Y*cX-pRZtpN6dI3n9%%7z&Vny1Q9o>Ng$SATa#R8X`~?|kdq**2 z7|FsdQk-&Fwma(MY=Nt7z#J<C)?QGgd@#9jlxGwFOTkN_Rh+fkYA>Kr-AMXbUkd%_ zV15xR+&(`sXKtMCpVpc8*5F{0qb~O`QBos}7L)T-uzB88U;_enLTyl%$np8}@xnes zdE62w0pyc12G+TLAAt9m;dn~pR59Zff2J>HTCqV+j|Qwo7fv*Rc*+v6i2Xt>&H=T< zhry@8S1IpzgU#)w?Z>RoD&<Ac+Tn`vnN505ndA8@X%ypg4_Z$HO$S+Qy*lkn_nhwV zSp7WbyOtfM7tp${Qp@?jRe+=##+V*YgGImlO7>E0MCQ_Z&ztIfadCSJx1S;dN5-m% z+DQLiZP6A7&}|P<3hK!x&+))Cal8xlz%8QPhv{7ga4YgUH@WW8mYALE)+DOIP(D^< zv+bvdcN68dhaTms=J4OLNM28me#7q;3**JucMe53y#PBD)vsg#q|y1==l3L|ycjbW zWH;H=%XO|-@%2C4xCv+;-92Xr@5H70Kjs_B@uN<wYAlXKIcc~L%*R+i`OeR?yK0Nr zF_$cfFT>v8{Qmxu68GJLw%s+f3jxNj8$a1LsnxZicTG~mAj@%8KzuRpwV9d_xeR4P zZ{buC<7>RvIWP?T<}cD}NgrpUFxf~Kt;UwLrY`O?yqtglG^9BT8~_6bZ#BP1k`w?F zGFJeQFODJA)>R(zPbu+xbO94TEs?p`?AtZ)3V|>JPVnF2M5@XjqG9#w%mF^Kb_>Iu z3b`|XF`}sa(*bbvG(N<%c0D(95(cEZHC7RZhITbT|AX1o*6!=fC4l=(($uV0mQg>2 zjt!24NfWZ3|K9=Md`z~9IMi<r_A+$N66yU=?NCgxW1gH|j?ISOKrxT?N2?9|it@xA z#iH_0JislGQ-i7GQgT$}ArmojB+=YBRsPq?%}RrK+N0LpE1Q}@3sK;1K1!hTEK+cg zf@$B16l^D*Ni7E_96Wu+NGHzPM}nd*EN3Usmzn&m>u-~m2Zvl?>s|MfCuMP$SfG`g zaDb77s2tmn%>8T?6AevJmQHxhF1N~X9JY|)3~*Q9zaj(@Vl4*UwiNAQo-o6L-2m{V z=C?5`vyME(P41{$Db949f;F%b6}+uCj#G@ngV$g~OAgC*O?%KFAB*DD%uc#vSM^e( zk{pp&BX$0a(WxGz2$!rCyf%o(#b-cQWQuVk%F~Yk&|k@Nh2Z`7i;{HB6Qj*?z~-!v zc!^D@ju9bdg-9~cd0wJTG7~j`E2pl0I1in~jR={$Z>)sU^nX+{SuNa>@mQA}XJA_z z)cW{!eX+3;tVGa(Fd+i`uPAQxg%V2o_D_Fq7}b3tdy+i8Gjc^cMok18=-45*kEUyf zm`@@5rhkq%9uZ`v)%Iqys$xEuSYE?VUJ=C&RPJ}4<LZX(LHniFS-zm@?pR>}uwenr z$wihNc=C_To!kh`mgB{w{$Dg^lo5fIgMa5QT%TQJqM&4;CjdM_NUR7_%a_cR30%Hm z<s}4@Lg;=^>rxlTFpOYD$67QER%N$ss|m@foVcp$2}j@>uof=aIm0Z5trN8*DiECp zwj7#G#T~=gew@NVOMh}Q9-;P}0h5$TydHV}^`27ibXvw+0eL98bk_{aO*E44V(|K+ zn=>xQlHxu&*JosFDmkcul*Skr@L3vOW1<vSv`hxtBG`->2~fKDkD)rZBx{d&KTDNO zEQiA?>UZy&8S8f}DRUS}eSezmt@TcXc-E#vq(Z46kdj(?f$6-;3^YSdIqueY;QdVY z1jmf+{fCHCL>TnLU6Wa7`!r<x)f;ia^;Xel^OG79o3cc)$oIospSck4!F>F8$h$ts ziL?I{36bOJ<G)161~TguXO<mkB7gX&nfbO+ZnLmqt}rrqGfmX9;fdlWJS8E2!N&{M z5pZy*>!rfr<m{zSv>`1Gf9%DzS#_cZ&P$W2%8MX(6UDeL^}Dpbo4CF6#ee#DAlny- zE=W6c&{ep%19*gH%Gt#g>yV<00czs|2Ofx>kfe9O;Px=PKZywMMPW;H=^Iv2lU~T+ z1>Udt%Jak>f7h5W7a)-$D@&^dod9l*ZPPr<EO74E9W5|^2FI|9*Rvc|0%bJeh9<S% zXiTer_H5w2zht&BG@SM$w3kEKaz1YAXB{lCfrUyX;f2_S4|Y*|oTvs_LsX~b`oG$D z5FjY?6r2fO&^*Y2@{(`_Me7sJQ7EWO8}%xI*%C~l@z=68)Fl`|`iFwbU_)2Pu`qkK zpth~MUG}fK55M0CD2H?vRSb=19%BE;{bXlY1P`zLEVK}(+oADLYOX>YT$^HTQu!KO z6nfU0uyVOo!Yk2--Ck|0%@u#cX-svdl2a9bCI>(>G)8M!11H4{f42Gz%}#{YU+So} z!3`wv#zd*yRML4eK|sA;cd<FZ0000!gL)o{Vjm=VzyJeQyMs1LQOm3hdKy$~0xE4r rxY96c8-DpPJPZN5<H!ZsT+9(b_Idz60>?V&E8B$-O^(^X00000Tsrhj diff --git a/src/plugins/home/public/assets/sample_data_resources/flights/dashboard.webp b/src/plugins/home/public/assets/sample_data_resources/flights/dashboard.webp index f73ae849befc94ef9d262725f2c440e4c0bc15b1..2c50c8a1471e93a4d6f8bc743968935b7d03559d 100644 GIT binary patch literal 31426 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 IgA3=N083iyNdN!< literal 21616 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 PA<n?_f_P}heRu!>b%4oO diff --git a/src/plugins/home/public/assets/sample_data_resources/flights/dashboard_dark.webp b/src/plugins/home/public/assets/sample_data_resources/flights/dashboard_dark.webp index 3cea45093ddac355b7b7a14dda7fb2b1ee12abaa..0c3769a3c8268d54fd771e3efca16db1bedf8bae 100644 GIT binary patch literal 31330 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 literal 28074 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 diff --git a/src/plugins/home/public/assets/sample_data_resources/logs/dashboard.webp b/src/plugins/home/public/assets/sample_data_resources/logs/dashboard.webp index c5b2250190ae0fb9df4cbaf7003d6d721773655f..8defebcc5fbbb10e8e20dff34f35f62a80deff73 100644 GIT binary patch literal 38660 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 literal 23410 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 ZWkJ?;O52{-yTKtI+^YKh@XEjd006sLNgMzG diff --git a/src/plugins/home/public/assets/sample_data_resources/logs/dashboard_dark.webp b/src/plugins/home/public/assets/sample_data_resources/logs/dashboard_dark.webp index 0c5ff2f6cd9f1195e61b0d702358a061355b2b0a..6f4dad0dad1f9e9dab373ea2d7d8383f05863faa 100644 GIT binary patch literal 39936 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 Uv80IEhv^8eC!w>3`2YX_0K7PpbN~PV literal 29896 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 diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index ad334470205aa..d90d6855879bb 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -189,44 +189,6 @@ export const getSavedObjects = (): SavedObject[] => [ updated_at: '2021-08-05T12:43:35.817Z', version: 'WzE3MSwxXQ==', }, - { - attributes: { - description: '', - kibanaSavedObjectMeta: { - searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}', - }, - title: '[eCommerce] Controls', - uiStateJSON: '{}', - version: 1, - visState: - '{"title":"[eCommerce] Controls","type":"input_control_vis","params":{"controls":[{"id":"1536977437774","fieldName":"manufacturer.keyword","parent":"","label":"Manufacturer","type":"list","options":{"type":"terms","multiselect":true,"dynamicOptions":true,"size":5,"order":"desc"},"indexPatternRefName":"control_0_index_pattern"},{"id":"1536977465554","fieldName":"category.keyword","parent":"","label":"Category","type":"list","options":{"type":"terms","multiselect":true,"dynamicOptions":true,"size":5,"order":"desc"},"indexPatternRefName":"control_1_index_pattern"},{"id":"1536977596163","fieldName":"total_quantity","parent":"","label":"Quantity","type":"range","options":{"decimalPlaces":0,"step":1},"indexPatternRefName":"control_2_index_pattern"}],"updateFiltersOnChange":false,"useTimeFilter":true,"pinFilters":false},"aggs":[]}', - }, - coreMigrationVersion: '8.0.0', - id: 'c3378480-f5ea-11eb-a78e-83aac3c38a60', - migrationVersion: { - visualization: '7.14.0', - }, - references: [ - { - id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', - name: 'control_0_index_pattern', - type: 'index-pattern', - }, - { - id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', - name: 'control_1_index_pattern', - type: 'index-pattern', - }, - { - id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', - name: 'control_2_index_pattern', - type: 'index-pattern', - }, - ], - type: 'visualization', - updated_at: '2021-08-05T12:43:41.128Z', - version: 'WzE3NiwxXQ==', - }, { attributes: { state: { @@ -1285,113 +1247,132 @@ export const getSavedObjects = (): SavedObject[] => [ version: 'WzIzMywxXQ==', }, { + id: '722b74f0-b882-11e8-a6d9-e546fe2bba5f', + type: 'dashboard', + namespaces: ['default'], + updated_at: '2022-09-26T17:19:19.470Z', + version: 'WzQ1MTgsMV0=', attributes: { + title: i18n.translate('home.sampleData.ecommerceSpec.revenueDashboardTitle', { + defaultMessage: '[eCommerce] Revenue Dashboard', + }), + hits: 0, description: i18n.translate('home.sampleData.ecommerceSpec.revenueDashboardDescription', { defaultMessage: 'Analyze mock eCommerce orders and revenue', }), - hits: 0, - kibanaSavedObjectMeta: { - searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', - }, - optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', panelsJSON: - '[{"version":"8.0.0-SNAPSHOT","type":"visualization","gridData":{"x":0,"y":22,"w":24,"h":10,"i":"5"},"panelIndex":"5","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_5"},{"version":"8.0.0-SNAPSHOT","type":"visualization","gridData":{"x":36,"y":15,"w":12,"h":7,"i":"7"},"panelIndex":"7","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_7"},{"version":"8.0.0-SNAPSHOT","type":"search","gridData":{"x":0,"y":55,"w":48,"h":18,"i":"10"},"panelIndex":"10","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_10"},{"version":"8.0.0-SNAPSHOT","type":"map","gridData":{"x":0,"y":32,"w":24,"h":14,"i":"11"},"panelIndex":"11","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":45.88578,"lon":-15.07605,"zoom":2.11},"mapBuffer":{"minLon":-135,"minLat":0,"maxLon":90,"maxLat":66.51326},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_11"},{"version":"8.0.0-SNAPSHOT","type":"visualization","gridData":{"x":0,"y":0,"w":18,"h":7,"i":"a71cf076-6895-491c-8878-63592e429ed5"},"panelIndex":"a71cf076-6895-491c-8878-63592e429ed5","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_a71cf076-6895-491c-8878-63592e429ed5"},{"version":"8.0.0-SNAPSHOT","type":"visualization","gridData":{"x":18,"y":0,"w":30,"h":7,"i":"adc0a2f4-481c-45eb-b422-0ea59a3e5163"},"panelIndex":"adc0a2f4-481c-45eb-b422-0ea59a3e5163","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_adc0a2f4-481c-45eb-b422-0ea59a3e5163"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":8,"i":"7077b79f-2a99-4fcb-bbd4-456982843278"},"panelIndex":"7077b79f-2a99-4fcb-bbd4-456982843278","embeddableConfig":{"enhancements":{},"hidePanelTitles":false},"title":"% of target revenue ($10k)","panelRefName":"panel_7077b79f-2a99-4fcb-bbd4-456982843278"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":8,"i":"19a3c101-ad2e-4421-a71b-a4734ec1f03e"},"panelIndex":"19a3c101-ad2e-4421-a71b-a4734ec1f03e","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_19a3c101-ad2e-4421-a71b-a4734ec1f03e"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":8,"i":"491469e7-7d24-4216-aeb3-bca00e5c8c1b"},"panelIndex":"491469e7-7d24-4216-aeb3-bca00e5c8c1b","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_491469e7-7d24-4216-aeb3-bca00e5c8c1b"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":0,"y":15,"w":24,"h":7,"i":"a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef"},"panelIndex":"a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":24,"y":15,"w":12,"h":7,"i":"da51079b-952f-43dc-96e6-6f9415a3708b"},"panelIndex":"da51079b-952f-43dc-96e6-6f9415a3708b","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_da51079b-952f-43dc-96e6-6f9415a3708b"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":24,"y":22,"w":24,"h":10,"i":"64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b"},"panelIndex":"64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":24,"y":32,"w":24,"h":14,"i":"bd330ede-2eef-4e2a-8100-22a21abf5038"},"panelIndex":"bd330ede-2eef-4e2a-8100-22a21abf5038","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_bd330ede-2eef-4e2a-8100-22a21abf5038"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":0,"y":46,"w":24,"h":9,"i":"b897d4be-cf83-46fb-a111-c7fbec9ef403"},"panelIndex":"b897d4be-cf83-46fb-a111-c7fbec9ef403","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"Top products this week","panelRefName":"panel_b897d4be-cf83-46fb-a111-c7fbec9ef403"},{"version":"8.0.0-SNAPSHOT","type":"lens","gridData":{"x":24,"y":46,"w":24,"h":9,"i":"e0f68f93-30f2-4da7-889a-6cd128a68d3f"},"panelIndex":"e0f68f93-30f2-4da7-889a-6cd128a68d3f","embeddableConfig":{"timeRange":{"from":"now-2w","to":"now-1w"},"hidePanelTitles":false,"enhancements":{}},"title":"Top products last week","panelRefName":"panel_e0f68f93-30f2-4da7-889a-6cd128a68d3f"}]', + '[{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":21,"w":24,"h":10,"i":"5"},"panelIndex":"5","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_5"},{"version":"8.6.0","type":"visualization","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"7"},"panelIndex":"7","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_7"},{"version":"8.6.0","type":"search","gridData":{"x":0,"y":54,"w":48,"h":18,"i":"10"},"panelIndex":"10","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_10"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":31,"w":24,"h":14,"i":"11"},"panelIndex":"11","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":45.88578,"lon":-15.07605,"zoom":2.11},"mapBuffer":{"minLon":-135,"minLat":0,"maxLon":90,"maxLat":66.51326},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_11"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"a71cf076-6895-491c-8878-63592e429ed5"},"panelIndex":"a71cf076-6895-491c-8878-63592e429ed5","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_a71cf076-6895-491c-8878-63592e429ed5"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"da51079b-952f-43dc-96e6-6f9415a3708b"},"panelIndex":"da51079b-952f-43dc-96e6-6f9415a3708b","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_da51079b-952f-43dc-96e6-6f9415a3708b"},{"version":"8.6.0","type":"lens","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"491469e7-7d24-4216-aeb3-bca00e5c8c1b"},"panelIndex":"491469e7-7d24-4216-aeb3-bca00e5c8c1b","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_491469e7-7d24-4216-aeb3-bca00e5c8c1b"},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"7077b79f-2a99-4fcb-bbd4-456982843278"},"panelIndex":"7077b79f-2a99-4fcb-bbd4-456982843278","embeddableConfig":{"enhancements":{},"hidePanelTitles":false},"title":"% of target revenue ($10k)","panelRefName":"panel_7077b79f-2a99-4fcb-bbd4-456982843278"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"19a3c101-ad2e-4421-a71b-a4734ec1f03e"},"panelIndex":"19a3c101-ad2e-4421-a71b-a4734ec1f03e","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_19a3c101-ad2e-4421-a71b-a4734ec1f03e"},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":14,"w":24,"h":7,"i":"a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef"},"panelIndex":"a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":14,"w":24,"h":17,"i":"64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b"},"panelIndex":"64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":31,"w":24,"h":14,"i":"bd330ede-2eef-4e2a-8100-22a21abf5038"},"panelIndex":"bd330ede-2eef-4e2a-8100-22a21abf5038","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_bd330ede-2eef-4e2a-8100-22a21abf5038"},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":45,"w":24,"h":9,"i":"b897d4be-cf83-46fb-a111-c7fbec9ef403"},"panelIndex":"b897d4be-cf83-46fb-a111-c7fbec9ef403","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"Top products this week","panelRefName":"panel_b897d4be-cf83-46fb-a111-c7fbec9ef403"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":45,"w":24,"h":9,"i":"e0f68f93-30f2-4da7-889a-6cd128a68d3f"},"panelIndex":"e0f68f93-30f2-4da7-889a-6cd128a68d3f","embeddableConfig":{"timeRange":{"from":"now-2w","to":"now-1w"},"hidePanelTitles":false,"enhancements":{}},"title":"Top products last week","panelRefName":"panel_e0f68f93-30f2-4da7-889a-6cd128a68d3f"}]', + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 1, + timeRestore: true, + timeTo: 'now', + timeFrom: 'now-7d', refreshInterval: { pause: true, value: 0, }, - timeFrom: 'now-7d', - timeRestore: true, - timeTo: 'now', - title: i18n.translate('home.sampleData.ecommerceSpec.revenueDashboardTitle', { - defaultMessage: '[eCommerce] Revenue Dashboard', - }), - version: 1, - }, - coreMigrationVersion: '8.0.0', - id: '722b74f0-b882-11e8-a6d9-e546fe2bba5f', - migrationVersion: { - dashboard: '7.14.0', + controlGroupInput: { + controlStyle: 'oneLine', + chainingSystem: 'HIERARCHICAL', + panelsJSON: + '{"1ee1617f-fd8e-45e4-bc6a-d5736710ea20":{"order":0,"width":"small","grow":true,"type":"optionsListControl","explicitInput":{"title":"Manufacturer","fieldName":"manufacturer.keyword","parentFieldName":"manufacturer","id":"1ee1617f-fd8e-45e4-bc6a-d5736710ea20","enhancements":{}}},"afa9fa0f-a002-41a5-bab9-b738316d2590":{"order":1,"width":"small","grow":true,"type":"optionsListControl","explicitInput":{"title":"Category","fieldName":"category.keyword","parentFieldName":"category","id":"afa9fa0f-a002-41a5-bab9-b738316d2590","enhancements":{}}},"d3f766cb-5f96-4a12-8d3c-034e08be8855":{"order":2,"width":"small","grow":true,"type":"rangeSliderControl","explicitInput":{"title":"Quantity","fieldName":"total_quantity","id":"d3f766cb-5f96-4a12-8d3c-034e08be8855","enhancements":{}}}}', + ignoreParentSettingsJSON: + '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', + }, + kibanaSavedObjectMeta: { + searchSourceJSON: '{"query":{"language":"kuery","query":""},"filter":[]}', + }, }, references: [ { - id: '45e07720-b890-11e8-a6d9-e546fe2bba5f', name: '5:panel_5', type: 'visualization', + id: '45e07720-b890-11e8-a6d9-e546fe2bba5f', }, { - id: 'b80e6540-b891-11e8-a6d9-e546fe2bba5f', name: '7:panel_7', type: 'visualization', + id: 'b80e6540-b891-11e8-a6d9-e546fe2bba5f', }, { - id: '3ba638e0-b894-11e8-a6d9-e546fe2bba5f', name: '10:panel_10', type: 'search', + id: '3ba638e0-b894-11e8-a6d9-e546fe2bba5f', }, { - id: '9c6f83f0-bb4d-11e8-9c84-77068524bcab', name: '11:panel_11', type: 'visualization', + id: '9c6f83f0-bb4d-11e8-9c84-77068524bcab', }, { - id: 'c00d1f90-f5ea-11eb-a78e-83aac3c38a60', name: 'a71cf076-6895-491c-8878-63592e429ed5:panel_a71cf076-6895-491c-8878-63592e429ed5', type: 'visualization', + id: 'c00d1f90-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'c3378480-f5ea-11eb-a78e-83aac3c38a60', - name: 'adc0a2f4-481c-45eb-b422-0ea59a3e5163:panel_adc0a2f4-481c-45eb-b422-0ea59a3e5163', - type: 'visualization', - }, - { - id: 'c762b7a0-f5ea-11eb-a78e-83aac3c38a60', - name: '7077b79f-2a99-4fcb-bbd4-456982843278:panel_7077b79f-2a99-4fcb-bbd4-456982843278', + name: 'da51079b-952f-43dc-96e6-6f9415a3708b:panel_da51079b-952f-43dc-96e6-6f9415a3708b', type: 'lens', + id: 'e3902840-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'ce02e260-f5ea-11eb-a78e-83aac3c38a60', - name: '19a3c101-ad2e-4421-a71b-a4734ec1f03e:panel_19a3c101-ad2e-4421-a71b-a4734ec1f03e', + name: '491469e7-7d24-4216-aeb3-bca00e5c8c1b:panel_491469e7-7d24-4216-aeb3-bca00e5c8c1b', type: 'lens', + id: 'd5f90030-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'd5f90030-f5ea-11eb-a78e-83aac3c38a60', - name: '491469e7-7d24-4216-aeb3-bca00e5c8c1b:panel_491469e7-7d24-4216-aeb3-bca00e5c8c1b', + name: '7077b79f-2a99-4fcb-bbd4-456982843278:panel_7077b79f-2a99-4fcb-bbd4-456982843278', type: 'lens', + id: 'c762b7a0-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'dde978b0-f5ea-11eb-a78e-83aac3c38a60', - name: 'a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef:panel_a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef', + name: '19a3c101-ad2e-4421-a71b-a4734ec1f03e:panel_19a3c101-ad2e-4421-a71b-a4734ec1f03e', type: 'lens', + id: 'ce02e260-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'e3902840-f5ea-11eb-a78e-83aac3c38a60', - name: 'da51079b-952f-43dc-96e6-6f9415a3708b:panel_da51079b-952f-43dc-96e6-6f9415a3708b', + name: 'a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef:panel_a1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef', type: 'lens', + id: 'dde978b0-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'eddf7850-f5ea-11eb-a78e-83aac3c38a60', name: '64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b:panel_64fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b', type: 'lens', + id: 'eddf7850-f5ea-11eb-a78e-83aac3c38a60', }, { - id: 'ff6a21b0-f5ea-11eb-a78e-83aac3c38a60', name: 'bd330ede-2eef-4e2a-8100-22a21abf5038:panel_bd330ede-2eef-4e2a-8100-22a21abf5038', type: 'lens', + id: 'ff6a21b0-f5ea-11eb-a78e-83aac3c38a60', }, { - id: '03071e90-f5eb-11eb-a78e-83aac3c38a60', name: 'b897d4be-cf83-46fb-a111-c7fbec9ef403:panel_b897d4be-cf83-46fb-a111-c7fbec9ef403', type: 'lens', + id: '03071e90-f5eb-11eb-a78e-83aac3c38a60', }, { - id: '06379e00-f5eb-11eb-a78e-83aac3c38a60', name: 'e0f68f93-30f2-4da7-889a-6cd128a68d3f:panel_e0f68f93-30f2-4da7-889a-6cd128a68d3f', type: 'lens', + id: '06379e00-f5eb-11eb-a78e-83aac3c38a60', + }, + { + name: 'controlGroup_1ee1617f-fd8e-45e4-bc6a-d5736710ea20:optionsListDataView', + type: 'index-pattern', + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + { + name: 'controlGroup_afa9fa0f-a002-41a5-bab9-b738316d2590:optionsListDataView', + type: 'index-pattern', + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + { + name: 'controlGroup_d3f766cb-5f96-4a12-8d3c-034e08be8855:rangeSliderDataView', + type: 'index-pattern', + id: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', }, ], - type: 'dashboard', - updated_at: '2021-08-05T12:45:46.525Z', - version: 'WzIzOSwxXQ==', + migrationVersion: { + dashboard: '8.5.0', + }, + coreMigrationVersion: '8.6.0', }, ]; diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 2e4413dcba0d3..db6d9ce049d23 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -220,62 +220,81 @@ export const getSavedObjects = (): SavedObject[] => [ { id: '7adfa750-4c81-11e8-b3d7-01146121b73d', type: 'dashboard', - updated_at: '2021-07-07T14:16:23.001Z', - version: '5', + namespaces: ['default'], + updated_at: '2022-09-26T17:13:00.935Z', + version: 'WzMzMTIsMV0=', + attributes: { + title: i18n.translate('home.sampleData.flightsSpec.globalFlightDashboardTitle', { + defaultMessage: '[Flights] Global Flight Dashboard', + }), + hits: 0, + description: i18n.translate('home.sampleData.flightsSpec.globalFlightDashboardDescription', { + defaultMessage: + 'Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats', + }), + panelsJSON: + '[{"version":"8.6.0","type":"search","gridData":{"x":0,"y":69,"w":48,"h":15,"i":"4"},"panelIndex":"4","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_4"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":16,"w":24,"h":9,"i":"7"},"panelIndex":"7","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_7"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":58,"w":24,"h":11,"i":"10"},"panelIndex":"10","embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false},"enhancements":{}},"panelRefName":"panel_10"},{"version":"8.6.0","type":"visualization","gridData":{"x":36,"y":58,"w":12,"h":11,"i":"21"},"panelIndex":"21","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_21"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":36,"w":24,"h":22,"i":"23"},"panelIndex":"23","embeddableConfig":{"isLayerTOCOpen":true,"enhancements":{},"mapCenter":{"lat":34.65823,"lon":-112.44472,"zoom":4.28},"mapBuffer":{"minLon":-135,"minLat":21.94305,"maxLon":-90,"maxLat":48.9225},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_23"},{"version":"8.6.0","type":"visualization","gridData":{"x":24,"y":36,"w":24,"h":22,"i":"31"},"panelIndex":"31","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_31"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":8,"i":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9"},"panelIndex":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9","embeddableConfig":{"savedVis":{"title":"[Flights] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":10,"openLinksInNewTab":true,"markdown":"## Sample Flight data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":0,"w":8,"h":8,"i":"392b4936-f753-47bc-a98d-a4e41a0a4cd4"},"panelIndex":"392b4936-f753-47bc-a98d-a4e41a0a4cd4","embeddableConfig":{"enhancements":{},"attributes":{"title":"[Flights] Total Flights","description":"","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"8fa993db-c147-4954-adf7-4ff264d42576":{"columns":{"81124c45-6ab6-42f4-8859-495d55eb8065":{"label":"Total flights","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["81124c45-6ab6-42f4-8859-495d55eb8065"],"incompleteColumns":{}}}}},"visualization":{"layerId":"8fa993db-c147-4954-adf7-4ff264d42576","accessor":"81124c45-6ab6-42f4-8859-495d55eb8065","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-8fa993db-c147-4954-adf7-4ff264d42576","type":"index-pattern"}]},"hidePanelTitles":true}},{"version":"8.6.0","type":"lens","gridData":{"x":32,"y":0,"w":8,"h":4,"i":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b"},"panelIndex":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"FlightDelay : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":41},"text":"count(kql=\'FlightDelay : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Delayed","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":40,"y":0,"w":8,"h":4,"i":"aa591c29-1a31-4ee1-a71d-b829c06fd162"},"panelIndex":"aa591c29-1a31-4ee1-a71d-b829c06fd162","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Delayed vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"FlightDelay","params":{"query":true},"index":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelay":true}},"$state":{"store":"appState"}}]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"filter-index-pattern-0","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":32,"y":4,"w":8,"h":4,"i":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2"},"panelIndex":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"Cancelled : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of Cancelled","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":39},"text":"count(kql=\'Cancelled : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Cancelled","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":40,"y":4,"w":8,"h":4,"i":"2e33ade5-96e5-40b4-b460-493e5d4fa834"},"panelIndex":"2e33ade5-96e5-40b4-b460-493e5d4fa834","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Cancelled vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"Cancelled","params":{"query":true},"index":"filter-index-pattern-0"},"query":{"match_phrase":{"Cancelled":true}},"$state":{"store":"appState"}}]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"filter-index-pattern-0","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":8,"w":24,"h":8,"i":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65"},"panelIndex":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"03c34665-471c-49c7-acf1-5a11f517421c":{"columns":{"a5b94e30-4e77-4b0a-9187-1d8b13de1456":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true}},"3e267327-7317-4310-aee3-320e0f7c1e70":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"}},"columnOrder":["a5b94e30-4e77-4b0a-9187-1d8b13de1456","3e267327-7317-4310-aee3-320e0f7c1e70"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"right","legendSize":"auto"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"custom","lowerBound":0,"upperBound":1},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"layerId":"03c34665-471c-49c7-acf1-5a11f517421c","accessors":["3e267327-7317-4310-aee3-320e0f7c1e70"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"a5b94e30-4e77-4b0a-9187-1d8b13de1456","layerType":"data"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-03c34665-471c-49c7-acf1-5a11f517421c","type":"index-pattern"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Flight count"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":8,"w":24,"h":28,"i":"fb86b32f-fb7a-45cf-9511-f366fef51bbd"},"panelIndex":"fb86b32f-fb7a-45cf-9511-f366fef51bbd","embeddableConfig":{"attributes":{"title":"Cities by delay, cancellation","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"f26e8f7a-4118-4227-bea0-5c02d8b270f7":{"columns":{"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0":{"label":"Top values of OriginCityName","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"OriginCityName","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"alphabetical","fallback":true},"orderDirection":"asc","otherBucket":true,"missingBucket":false}},"52f6f2e9-6242-4c44-be63-b799150e7e60X0":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"FlightDelay : true ","language":"kuery"},"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X1":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X2":{"label":"Part of Delay %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"location":{"min":0,"max":42},"text":"count(kql=\'FlightDelay : true \') / count()"}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60":{"label":"Delay %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true \') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X2"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"Cancelled: true","language":"kuery"},"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2":{"label":"Part of Cancel %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"location":{"min":0,"max":38},"text":"count(kql=\'Cancelled: true\') / count()"}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6":{"label":"Cancel %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled: true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2"],"customLabel":true}},"columnOrder":["3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","52f6f2e9-6242-4c44-be63-b799150e7e60","52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1","52f6f2e9-6242-4c44-be63-b799150e7e60X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6"],"incompleteColumns":{}}}}},"visualization":{"columns":[{"isTransposed":false,"columnId":"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","width":262.75},{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","isTransposed":false,"width":302.5,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":1}],"name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeType":"number","rangeMin":0.2,"rangeMax":0.6}},"alignment":"center"},{"columnId":"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6","isTransposed":false,"alignment":"center","colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":0.6666666666666666}],"rangeType":"number","name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeMin":0.2,"rangeMax":0.6}}}],"layerId":"f26e8f7a-4118-4227-bea0-5c02d8b270f7","sorting":{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-f26e8f7a-4118-4227-bea0-5c02d8b270f7","type":"index-pattern"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Most delayed cities"},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":25,"w":24,"h":11,"i":"0cc42484-16f7-42ec-b38c-9bf8be69cde7"},"panelIndex":"0cc42484-16f7-42ec-b38c-9bf8be69cde7","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"e80cc05e-c52a-4e5f-ac71-4b37274867f5":{"columns":{"caf7421e-93a3-439e-ab0a-fbdead93c21c":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"0233d302-ec81-4fbe-96cb-7fac84cf035c"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"13ec79e3-9d73-4536-9056-3d92802bb30a":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto","includeEmptyRows":true}},"0233d302-ec81-4fbe-96cb-7fac84cf035c":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"}},"columnOrder":["caf7421e-93a3-439e-ab0a-fbdead93c21c","13ec79e3-9d73-4536-9056-3d92802bb30a","0233d302-ec81-4fbe-96cb-7fac84cf035c"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"bottom","legendSize":"auto"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":true,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_percentage_stacked","layers":[{"layerId":"e80cc05e-c52a-4e5f-ac71-4b37274867f5","accessors":["0233d302-ec81-4fbe-96cb-7fac84cf035c"],"position":"top","seriesType":"bar_percentage_stacked","showGridlines":false,"palette":{"type":"palette","name":"cool"},"xAccessor":"13ec79e3-9d73-4536-9056-3d92802bb30a","splitAccessor":"caf7421e-93a3-439e-ab0a-fbdead93c21c","layerType":"data"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-e80cc05e-c52a-4e5f-ac71-4b37274867f5","type":"index-pattern"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Delay Type"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":58,"w":12,"h":11,"i":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0"},"panelIndex":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsPie","state":{"datasourceStates":{"indexpattern":{"layers":{"0c8e136b-a822-4fb3-836d-e06cbea4eea4":{"columns":{"d1cee8bf-34cf-4141-99d7-ff043ee77b56":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"aa152ace-ee2d-447b-b86d-459bef4d7880"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"aa152ace-ee2d-447b-b86d-459bef4d7880":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___"}},"columnOrder":["d1cee8bf-34cf-4141-99d7-ff043ee77b56","aa152ace-ee2d-447b-b86d-459bef4d7880"],"incompleteColumns":{}}}}},"visualization":{"shape":"pie","palette":{"type":"palette","name":"cool"},"layers":[{"layerId":"0c8e136b-a822-4fb3-836d-e06cbea4eea4","metric":"aa152ace-ee2d-447b-b86d-459bef4d7880","numberDisplay":"percent","categoryDisplay":"default","legendDisplay":"default","nestedLegend":false,"layerType":"data","legendSize":"auto","primaryGroups":["d1cee8bf-34cf-4141-99d7-ff043ee77b56"]}]},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"type":"phrase","key":"FlightDelayType","params":{"query":"No Delay"},"disabled":false,"negate":true,"alias":null,"index":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelayType":"No Delay"}},"$state":{"store":"appState"}}]},"references":[{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-0c8e136b-a822-4fb3-836d-e06cbea4eea4","type":"index-pattern"},{"id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"filter-index-pattern-0","type":"index-pattern"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Delay Type"}]', + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 1, + timeRestore: true, + timeTo: 'now', + timeFrom: 'now-7d', + refreshInterval: { + pause: true, + value: 0, + }, + controlGroupInput: { + controlStyle: 'oneLine', + chainingSystem: 'HIERARCHICAL', + panelsJSON: + '{"85b632c8-3b7b-408d-8223-b0caccf75bd3":{"order":0,"width":"small","grow":true,"type":"optionsListControl","explicitInput":{"title":"Origin City","fieldName":"OriginCityName","id":"85b632c8-3b7b-408d-8223-b0caccf75bd3","selectedOptions":[],"enhancements":{}}},"d4dc9d2b-5850-402a-921d-8a2cd0107156":{"order":1,"width":"small","grow":true,"type":"optionsListControl","explicitInput":{"title":"Destination City","fieldName":"DestCityName","id":"d4dc9d2b-5850-402a-921d-8a2cd0107156","enhancements":{}}},"bee4a16a-f5c1-40b2-887e-db1b9ad9e15f":{"order":2,"width":"small","grow":true,"type":"rangeSliderControl","explicitInput":{"title":"Average Ticket Price","fieldName":"AvgTicketPrice","id":"bee4a16a-f5c1-40b2-887e-db1b9ad9e15f","enhancements":{}}}}', + ignoreParentSettingsJSON: + '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', + }, + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"query":{"language":"kuery","query":""},"highlightAll":true,"version":true,"filter":[]}', + }, + }, references: [ { - id: '571aaf70-4c88-11e8-b3d7-01146121b73d', name: '4:panel_4', type: 'search', + id: '571aaf70-4c88-11e8-b3d7-01146121b73d', }, { - id: 'bcb63b50-4c89-11e8-b3d7-01146121b73d', name: '7:panel_7', type: 'visualization', + id: 'bcb63b50-4c89-11e8-b3d7-01146121b73d', }, { - id: '9886b410-4c8b-11e8-b3d7-01146121b73d', name: '10:panel_10', type: 'visualization', + id: '9886b410-4c8b-11e8-b3d7-01146121b73d', }, { - id: '293b5a30-4c8f-11e8-b3d7-01146121b73d', name: '21:panel_21', type: 'visualization', + id: '293b5a30-4c8f-11e8-b3d7-01146121b73d', }, { - id: '334084f0-52fd-11e8-a160-89cc2ad9e8e2', name: '23:panel_23', type: 'visualization', + id: '334084f0-52fd-11e8-a160-89cc2ad9e8e2', }, { - id: 'ed78a660-53a0-11e8-acbd-0be0ad9d822b', name: '31:panel_31', type: 'visualization', + id: 'ed78a660-53a0-11e8-acbd-0be0ad9d822b', }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: 'aa810aa2-29c9-4a75-b39e-f4f267de1732:control_aa810aa2-29c9-4a75-b39e-f4f267de1732_0_index_pattern', - type: 'index-pattern', - }, - { - id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: 'aa810aa2-29c9-4a75-b39e-f4f267de1732:control_aa810aa2-29c9-4a75-b39e-f4f267de1732_1_index_pattern', - type: 'index-pattern', - }, - { - id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: 'aa810aa2-29c9-4a75-b39e-f4f267de1732:control_aa810aa2-29c9-4a75-b39e-f4f267de1732_2_index_pattern', - type: 'index-pattern', - }, - { - id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65:indexpattern-datasource-current-indexpattern', + name: '392b4936-f753-47bc-a98d-a4e41a0a4cd4:indexpattern-datasource-current-indexpattern', type: 'index-pattern', }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65:indexpattern-datasource-layer-03c34665-471c-49c7-acf1-5a11f517421c', + name: '392b4936-f753-47bc-a98d-a4e41a0a4cd4:indexpattern-datasource-layer-8fa993db-c147-4954-adf7-4ff264d42576', type: 'index-pattern', }, { @@ -328,6 +347,16 @@ export const getSavedObjects = (): SavedObject[] => [ name: '2e33ade5-96e5-40b4-b460-493e5d4fa834:filter-index-pattern-0', type: 'index-pattern', }, + { + id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + name: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65:indexpattern-datasource-current-indexpattern', + type: 'index-pattern', + }, + { + id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', + name: '086ac2e9-dd16-4b45-92b8-1e43ff7e3f65:indexpattern-datasource-layer-03c34665-471c-49c7-acf1-5a11f517421c', + type: 'index-pattern', + }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', name: 'fb86b32f-fb7a-45cf-9511-f366fef51bbd:indexpattern-datasource-current-indexpattern', @@ -340,67 +369,48 @@ export const getSavedObjects = (): SavedObject[] => [ }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0:indexpattern-datasource-current-indexpattern', + name: '0cc42484-16f7-42ec-b38c-9bf8be69cde7:indexpattern-datasource-current-indexpattern', type: 'index-pattern', }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0:indexpattern-datasource-layer-0c8e136b-a822-4fb3-836d-e06cbea4eea4', + name: '0cc42484-16f7-42ec-b38c-9bf8be69cde7:indexpattern-datasource-layer-e80cc05e-c52a-4e5f-ac71-4b37274867f5', type: 'index-pattern', }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0:filter-index-pattern-0', + name: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0:indexpattern-datasource-current-indexpattern', type: 'index-pattern', }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '0cc42484-16f7-42ec-b38c-9bf8be69cde7:indexpattern-datasource-current-indexpattern', + name: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0:indexpattern-datasource-layer-0c8e136b-a822-4fb3-836d-e06cbea4eea4', type: 'index-pattern', }, { id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '0cc42484-16f7-42ec-b38c-9bf8be69cde7:indexpattern-datasource-layer-e80cc05e-c52a-4e5f-ac71-4b37274867f5', + name: '5d53db36-2d5a-4adc-af7b-cec4c1a294e0:filter-index-pattern-0', type: 'index-pattern', }, { - id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '392b4936-f753-47bc-a98d-a4e41a0a4cd4:indexpattern-datasource-current-indexpattern', + name: 'controlGroup_85b632c8-3b7b-408d-8223-b0caccf75bd3:optionsListDataView', type: 'index-pattern', + id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', }, { + name: 'controlGroup_d4dc9d2b-5850-402a-921d-8a2cd0107156:optionsListDataView', + type: 'index-pattern', id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', - name: '392b4936-f753-47bc-a98d-a4e41a0a4cd4:indexpattern-datasource-layer-8fa993db-c147-4954-adf7-4ff264d42576', + }, + { + name: 'controlGroup_bee4a16a-f5c1-40b2-887e-db1b9ad9e15f:rangeSliderDataView', type: 'index-pattern', + id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d', }, ], migrationVersion: { - dashboard: '7.14.0', - }, - attributes: { - title: i18n.translate('home.sampleData.flightsSpec.globalFlightDashboardTitle', { - defaultMessage: '[Flights] Global Flight Dashboard', - }), - hits: 0, - description: i18n.translate('home.sampleData.flightsSpec.globalFlightDashboardDescription', { - defaultMessage: - 'Analyze mock flight data for ES-Air, Logstash Airways, Kibana Airlines and JetBeats', - }), - panelsJSON: - '[{"version":"7.14.0","type":"search","gridData":{"x":0,"y":68,"w":48,"h":15,"i":"4"},"panelIndex":"4","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_4"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":15,"w":24,"h":9,"i":"7"},"panelIndex":"7","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_7"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":57,"w":24,"h":11,"i":"10"},"panelIndex":"10","embeddableConfig":{"vis":{"colors":{"Count":"#1F78C1"},"legendOpen":false},"enhancements":{}},"panelRefName":"panel_10"},{"version":"7.14.0","type":"visualization","gridData":{"x":36,"y":57,"w":12,"h":11,"i":"21"},"panelIndex":"21","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_21"},{"version":"7.14.0","type":"map","gridData":{"x":0,"y":35,"w":24,"h":22,"i":"23"},"panelIndex":"23","embeddableConfig":{"isLayerTOCOpen":true,"enhancements":{},"mapCenter":{"lat":34.65823,"lon":-112.44472,"zoom":4.28},"mapBuffer":{"minLon":-135,"minLat":21.94305,"maxLon":-90,"maxLat":48.9225},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_23"},{"version":"7.14.0","type":"visualization","gridData":{"x":24,"y":35,"w":24,"h":22,"i":"31"},"panelIndex":"31","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_31"},{"version":"7.14.0","type":"visualization","gridData":{"x":0,"y":0,"w":32,"h":7,"i":"aa810aa2-29c9-4a75-b39e-f4f267de1732"},"panelIndex":"aa810aa2-29c9-4a75-b39e-f4f267de1732","embeddableConfig":{"savedVis":{"title":"[Flights] Controls","description":"","type":"input_control_vis","params":{"controls":[{"id":"1525098134264","fieldName":"OriginCityName","parent":"","label":"Origin City","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_0_index_pattern"},{"id":"1525099277699","fieldName":"DestCityName","parent":"1525098134264","label":"Destination City","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_1_index_pattern"},{"id":"1525099307278","fieldName":"AvgTicketPrice","parent":"","label":"Average Ticket Price","type":"range","options":{"decimalPlaces":0,"step":10},"indexPatternRefName":"control_aa810aa2-29c9-4a75-b39e-f4f267de1732_2_index_pattern"}],"updateFiltersOnChange":false,"useTimeFilter":true,"pinFilters":false},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"7.14.0","type":"visualization","gridData":{"x":32,"y":0,"w":16,"h":7,"i":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9"},"panelIndex":"6afc61f7-e2d5-45a3-9e7a-281160ad3eb9","embeddableConfig":{"savedVis":{"title":"[Flights] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":10,"openLinksInNewTab":true,"markdown":"## Sample Flight data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{}}},"hidePanelTitles":true,"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":0,"y":7,"w":24,"h":8,"i":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65"},"panelIndex":"086ac2e9-dd16-4b45-92b8-1e43ff7e3f65","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"03c34665-471c-49c7-acf1-5a11f517421c":{"columns":{"a5b94e30-4e77-4b0a-9187-1d8b13de1456":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"3e267327-7317-4310-aee3-320e0f7c1e70":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["a5b94e30-4e77-4b0a-9187-1d8b13de1456","3e267327-7317-4310-aee3-320e0f7c1e70"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"right"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"custom","lowerBound":0,"upperBound":1},"axisTitlesVisibilitySettings":{"x":false,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_stacked","layers":[{"layerId":"03c34665-471c-49c7-acf1-5a11f517421c","accessors":["3e267327-7317-4310-aee3-320e0f7c1e70"],"position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"a5b94e30-4e77-4b0a-9187-1d8b13de1456"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-03c34665-471c-49c7-acf1-5a11f517421c"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Flight count"},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":7,"w":8,"h":8,"i":"392b4936-f753-47bc-a98d-a4e41a0a4cd4"},"panelIndex":"392b4936-f753-47bc-a98d-a4e41a0a4cd4","embeddableConfig":{"enhancements":{},"attributes":{"title":"[Flights] Total Flights","description":"","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"8fa993db-c147-4954-adf7-4ff264d42576":{"columns":{"81124c45-6ab6-42f4-8859-495d55eb8065":{"label":"Total flights","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true}},"columnOrder":["81124c45-6ab6-42f4-8859-495d55eb8065"],"incompleteColumns":{}}}}},"visualization":{"layerId":"8fa993db-c147-4954-adf7-4ff264d42576","accessor":"81124c45-6ab6-42f4-8859-495d55eb8065"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-8fa993db-c147-4954-adf7-4ff264d42576"}]},"hidePanelTitles":true}},{"version":"7.14.0","type":"lens","gridData":{"x":32,"y":7,"w":8,"h":4,"i":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b"},"panelIndex":"9271deff-5a61-4665-83fc-f9fdc6bf0c0b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"FlightDelay : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of count(kql=\'FlightDelay : true\') / count()","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":41},"text":"count(kql=\'FlightDelay : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Delayed","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":40,"y":7,"w":8,"h":4,"i":"aa591c29-1a31-4ee1-a71d-b829c06fd162"},"panelIndex":"aa591c29-1a31-4ee1-a71d-b829c06fd162","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Delayed vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"FlightDelay","params":{"query":true},"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelay":true}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":32,"y":11,"w":8,"h":4,"i":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2"},"panelIndex":"b766e3b8-4544-46ed-99e6-9ecc4847e2a2","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"Cancelled : true","language":"kuery"},"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1":{"label":"Part of Cancelled","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2":{"label":"Part of Cancelled","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"location":{"min":0,"max":39},"text":"count(kql=\'Cancelled : true\') / count()"}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1"],"customLabel":true},"7e8fe9b1-f45c-4f3d-9561-30febcd357ec":{"label":"Cancelled","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled : true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"customLabel":true}},"columnOrder":["7e8fe9b1-f45c-4f3d-9561-30febcd357ec","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX0","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX1","7e8fe9b1-f45c-4f3d-9561-30febcd357ecX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"7e8fe9b1-f45c-4f3d-9561-30febcd357ec"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":40,"y":11,"w":8,"h":4,"i":"2e33ade5-96e5-40b4-b460-493e5d4fa834"},"panelIndex":"2e33ade5-96e5-40b4-b460-493e5d4fa834","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"b4712d43-1e84-4f5b-878d-8e38ba748317":{"columns":{"c7851241-5526-499a-960b-357af8c2ce5bX0":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX1":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","timeShift":"1w","customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5bX2":{"label":"Part of Delayed vs 1 week earlier","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"subtract","args":[{"type":"function","name":"divide","args":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"location":{"min":0,"max":28},"text":"count() / count(shift=\'1w\') "},1],"location":{"min":0,"max":31},"text":"count() / count(shift=\'1w\') - 1"}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"customLabel":true},"c7851241-5526-499a-960b-357af8c2ce5b":{"label":"Cancelled vs 1 week earlier","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count() / count(shift=\'1w\') - 1","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["c7851241-5526-499a-960b-357af8c2ce5bX2"],"customLabel":true}},"columnOrder":["c7851241-5526-499a-960b-357af8c2ce5b","c7851241-5526-499a-960b-357af8c2ce5bX2","c7851241-5526-499a-960b-357af8c2ce5bX0","c7851241-5526-499a-960b-357af8c2ce5bX1"],"incompleteColumns":{}}}}},"visualization":{"layerId":"b4712d43-1e84-4f5b-878d-8e38ba748317","accessor":"c7851241-5526-499a-960b-357af8c2ce5b"},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"alias":null,"negate":false,"disabled":false,"type":"phrase","key":"Cancelled","params":{"query":true},"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"Cancelled":true}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-b4712d43-1e84-4f5b-878d-8e38ba748317"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{}}},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":15,"w":24,"h":20,"i":"fb86b32f-fb7a-45cf-9511-f366fef51bbd"},"panelIndex":"fb86b32f-fb7a-45cf-9511-f366fef51bbd","embeddableConfig":{"attributes":{"title":"Cities by delay, cancellation","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"f26e8f7a-4118-4227-bea0-5c02d8b270f7":{"columns":{"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0":{"label":"Top values of OriginCityName","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"OriginCityName","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"alphabetical","fallback":true},"orderDirection":"asc","otherBucket":true,"missingBucket":false}},"52f6f2e9-6242-4c44-be63-b799150e7e60X0":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"FlightDelay : true ","language":"kuery"},"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X1":{"label":"Part of Delay %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60X2":{"label":"Part of Delay %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"location":{"min":0,"max":42},"text":"count(kql=\'FlightDelay : true \') / count()"}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1"],"customLabel":true},"52f6f2e9-6242-4c44-be63-b799150e7e60":{"label":"Delay %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'FlightDelay : true \') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["52f6f2e9-6242-4c44-be63-b799150e7e60X2"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"Cancelled: true","language":"kuery"},"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1":{"label":"Part of Cancel %","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2":{"label":"Part of Cancel %","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"location":{"min":0,"max":38},"text":"count(kql=\'Cancelled: true\') / count()"}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1"],"customLabel":true},"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6":{"label":"Cancel %","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'Cancelled: true\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":0}}},"references":["7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2"],"customLabel":true}},"columnOrder":["3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","52f6f2e9-6242-4c44-be63-b799150e7e60","52f6f2e9-6242-4c44-be63-b799150e7e60X0","52f6f2e9-6242-4c44-be63-b799150e7e60X1","52f6f2e9-6242-4c44-be63-b799150e7e60X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X0","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X1","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6X2","7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6"],"incompleteColumns":{}}}}},"visualization":{"columns":[{"isTransposed":false,"columnId":"3dd24cb4-45ef-4dd8-b22a-d7b802cb6da0","width":262.75},{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","isTransposed":false,"width":302.5,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":1}],"name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeType":"number","rangeMin":0.2,"rangeMax":0.6}},"alignment":"center"},{"columnId":"7b9f3ece-9da3-4c27-b582-d3f8e8cc31d6","isTransposed":false,"alignment":"center","colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#f7e0b8","stop":0.6},{"color":"#e7664c","stop":0.6666666666666666}],"rangeType":"number","name":"custom","colorStops":[{"color":"#f7e0b8","stop":0.2},{"color":"#e7664c","stop":0.6}],"rangeMin":0.2,"rangeMax":0.6}}}],"layerId":"f26e8f7a-4118-4227-bea0-5c02d8b270f7","sorting":{"columnId":"52f6f2e9-6242-4c44-be63-b799150e7e60","direction":"desc"}},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-f26e8f7a-4118-4227-bea0-5c02d8b270f7"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Most delayed cities"},{"version":"7.14.0","type":"lens","gridData":{"x":0,"y":24,"w":24,"h":11,"i":"0cc42484-16f7-42ec-b38c-9bf8be69cde7"},"panelIndex":"0cc42484-16f7-42ec-b38c-9bf8be69cde7","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsXY","state":{"datasourceStates":{"indexpattern":{"layers":{"e80cc05e-c52a-4e5f-ac71-4b37274867f5":{"columns":{"caf7421e-93a3-439e-ab0a-fbdead93c21c":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"0233d302-ec81-4fbe-96cb-7fac84cf035c"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"13ec79e3-9d73-4536-9056-3d92802bb30a":{"label":"timestamp","dataType":"date","operationType":"date_histogram","sourceField":"timestamp","isBucketed":true,"scale":"interval","params":{"interval":"auto"}},"0233d302-ec81-4fbe-96cb-7fac84cf035c":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["caf7421e-93a3-439e-ab0a-fbdead93c21c","13ec79e3-9d73-4536-9056-3d92802bb30a","0233d302-ec81-4fbe-96cb-7fac84cf035c"],"incompleteColumns":{}}}}},"visualization":{"legend":{"isVisible":true,"position":"bottom"},"valueLabels":"hide","fittingFunction":"None","yLeftExtent":{"mode":"full"},"yRightExtent":{"mode":"full"},"axisTitlesVisibilitySettings":{"x":true,"yLeft":false,"yRight":true},"tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"preferredSeriesType":"bar_percentage_stacked","layers":[{"layerId":"e80cc05e-c52a-4e5f-ac71-4b37274867f5","accessors":["0233d302-ec81-4fbe-96cb-7fac84cf035c"],"position":"top","seriesType":"bar_percentage_stacked","showGridlines":false,"palette":{"type":"palette","name":"cool"},"xAccessor":"13ec79e3-9d73-4536-9056-3d92802bb30a","splitAccessor":"caf7421e-93a3-439e-ab0a-fbdead93c21c"}]},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-e80cc05e-c52a-4e5f-ac71-4b37274867f5"}]},"hidePanelTitles":false,"enhancements":{}},"title":"[Flights] Delay Type"},{"version":"7.14.0","type":"lens","gridData":{"x":24,"y":57,"w":12,"h":11,"i":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0"},"panelIndex":"5d53db36-2d5a-4adc-af7b-cec4c1a294e0","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsPie","state":{"datasourceStates":{"indexpattern":{"layers":{"0c8e136b-a822-4fb3-836d-e06cbea4eea4":{"columns":{"d1cee8bf-34cf-4141-99d7-ff043ee77b56":{"label":"Top values of FlightDelayType","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"FlightDelayType","isBucketed":true,"params":{"size":10,"orderBy":{"type":"column","columnId":"aa152ace-ee2d-447b-b86d-459bef4d7880"},"orderDirection":"desc","otherBucket":true,"missingBucket":false}},"aa152ace-ee2d-447b-b86d-459bef4d7880":{"label":"Count of records","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records"}},"columnOrder":["d1cee8bf-34cf-4141-99d7-ff043ee77b56","aa152ace-ee2d-447b-b86d-459bef4d7880"],"incompleteColumns":{}}}}},"visualization":{"shape":"pie","palette":{"type":"palette","name":"cool"},"layers":[{"layerId":"0c8e136b-a822-4fb3-836d-e06cbea4eea4","groups":["d1cee8bf-34cf-4141-99d7-ff043ee77b56"],"metric":"aa152ace-ee2d-447b-b86d-459bef4d7880","numberDisplay":"percent","categoryDisplay":"default","legendDisplay":"default","nestedLegend":false}]},"query":{"query":"","language":"kuery"},"filters":[{"meta":{"type":"phrase","key":"FlightDelayType","params":{"query":"No Delay"},"disabled":false,"negate":true,"alias":null,"indexRefName":"filter-index-pattern-0"},"query":{"match_phrase":{"FlightDelayType":"No Delay"}},"$state":{"store":"appState"}}]},"references":[{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d","name":"indexpattern-datasource-layer-0c8e136b-a822-4fb3-836d-e06cbea4eea4"},{"name":"filter-index-pattern-0","type":"index-pattern","id":"d3d7af60-4c81-11e8-b3d7-01146121b73d"}]},"enhancements":{},"hidePanelTitles":false},"title":"[Flights] Delay Type"}]', - optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', - version: 1, - timeRestore: true, - timeTo: 'now', - timeFrom: 'now-7d', - refreshInterval: { - pause: true, - value: 0, - }, - kibanaSavedObjectMeta: { - searchSourceJSON: - '{"query":{"language":"kuery","query":""},"filter":[],"highlightAll":true,"version":true}', - }, + dashboard: '8.5.0', }, + coreMigrationVersion: '8.6.0', }, ]; diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index b5f57e0edb2e6..5de45e08d0fec 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -381,48 +381,66 @@ export const getSavedObjects = (): SavedObject[] => [ { id: 'edf84fe0-e1a0-11e7-b6d5-4dc382ef7f5b', type: 'dashboard', - updated_at: '2021-10-28T15:07:36.622Z', - version: '3', + namespaces: ['default'], + updated_at: '2022-09-26T16:24:51.698Z', + version: 'WzE1NTIsMV0=', + attributes: { + title: i18n.translate('home.sampleData.logsSpec.webTrafficTitle', { + defaultMessage: '[Logs] Web Traffic', + }), + hits: 0, + description: i18n.translate('home.sampleData.logsSpec.webTrafficDescription', { + defaultMessage: "Analyze mock web traffic log data for Elastic's website", + }), + panelsJSON: + '[{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":14,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"mapBuffer":{"minLon":-112.5,"minLat":21.94305,"maxLon":-45,"maxLat":55.77657},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_4"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":47,"w":24,"h":13,"i":"9"},"panelIndex":"9","embeddableConfig":{"mapCenter":[36.8092847020594,-96.94335937500001],"vis":{"params":{"sort":{"columnIndex":null,"direction":null}}},"enhancements":{}},"panelRefName":"panel_9"},{"version":"8.6.0","type":"visualization","gridData":{"x":36,"y":0,"w":12,"h":7,"i":"11"},"panelIndex":"11","embeddableConfig":{"vis":{"colors":{"0 - 500":"#BF1B00","1000 - 1500":"#7EB26D","500 - 1000":"#F2C96D"},"defaultColors":{"0 - 500":"rgb(165,0,38)","1000 - 1500":"rgb(0,104,55)","500 - 1000":"rgb(255,255,190)"},"legendOpen":false},"enhancements":{},"hidePanelTitles":true},"title":"","panelRefName":"panel_11"},{"version":"8.6.0","type":"visualization","gridData":{"x":24,"y":14,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":7,"w":24,"h":7,"i":"15"},"panelIndex":"15","embeddableConfig":{"enhancements":{"dynamicActions":{"events":[]}}},"panelRefName":"panel_15"},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":0,"w":24,"h":7,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":0,"w":12,"h":7,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","accessor":"37430d12-7452-4cc9-b035-5cfd4061edf0","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":7,"w":12,"h":7,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"lens","gridData":{"x":36,"y":7,"w":12,"h":7,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsLegacyMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b","layerType":"data","textAlign":"center","titlePosition":"bottom","size":"xl"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a","type":"index-pattern"}]},"enhancements":{}}},{"version":"8.6.0","type":"visualization","gridData":{"x":0,"y":32,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.6.0","type":"lens","gridData":{"x":24,"y":47,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"},{"version":"8.6.0","type":"lens","gridData":{"x":0,"y":60,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"___records___","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"},"layerType":"data","rowHeight":"single","rowHeightLines":1},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","type":"index-pattern"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"}]', + optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', + version: 2, + timeRestore: true, + timeTo: 'now', + timeFrom: 'now-7d', + refreshInterval: { + pause: false, + value: 900000, + }, + controlGroupInput: { + controlStyle: 'oneLine', + chainingSystem: 'HIERARCHICAL', + panelsJSON: + '{"612f8db8-9ba9-41cf-a809-d133fe9b83a8":{"order":0,"width":"small","grow":true,"type":"optionsListControl","explicitInput":{"fieldName":"geo.src","title":"Source Country","id":"612f8db8-9ba9-41cf-a809-d133fe9b83a8","enhancements":{}}},"9807212f-5078-4c42-879c-6f28b3033fc9":{"order":1,"width":"small","grow":true,"type":"optionsListControl","explicitInput":{"fieldName":"machine.os.keyword","parentFieldName":"machine.os","title":"OS","id":"9807212f-5078-4c42-879c-6f28b3033fc9","enhancements":{}}},"6bf7a1b4-282e-43ac-aa46-81b97fa3acae":{"order":2,"width":"small","grow":true,"type":"rangeSliderControl","explicitInput":{"fieldName":"bytes","title":"Bytes","id":"6bf7a1b4-282e-43ac-aa46-81b97fa3acae","enhancements":{}}}}', + ignoreParentSettingsJSON: + '{"ignoreFilters":false,"ignoreQuery":false,"ignoreTimerange":false,"ignoreValidations":false}', + }, + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"query":{"language":"kuery","query":""},"highlightAll":true,"version":true,"filter":[]}', + }, + }, references: [ { - id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', name: '4:panel_4', type: 'visualization', + id: '06cf9c40-9ee8-11e7-8711-e7a007dcef99', }, { - id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b', name: '9:panel_9', type: 'visualization', + id: '4eb6e500-e1c7-11e7-b6d5-4dc382ef7f5b', }, { - id: '69a34b00-9ee8-11e7-8711-e7a007dcef99', name: '11:panel_11', type: 'visualization', + id: '69a34b00-9ee8-11e7-8711-e7a007dcef99', }, { - id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4', name: '14:panel_14', type: 'visualization', + id: '7cbd2350-2223-11e8-b802-5bcf64c2cfb4', }, { - id: '314c6f60-2224-11e8-b802-5bcf64c2cfb4', name: '15:panel_15', type: 'visualization', - }, - { - id: '90943e30-9a47-11e8-b64d-95841ca0b247', - name: '30326cdb-4ddd-49eb-a4f1-b555caa21d7c:control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_0_index_pattern', - type: 'index-pattern', - }, - { - id: '90943e30-9a47-11e8-b64d-95841ca0b247', - name: '30326cdb-4ddd-49eb-a4f1-b555caa21d7c:control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_1_index_pattern', - type: 'index-pattern', - }, - { - id: '90943e30-9a47-11e8-b64d-95841ca0b247', - name: '30326cdb-4ddd-49eb-a4f1-b555caa21d7c:control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_2_index_pattern', - type: 'index-pattern', + id: '314c6f60-2224-11e8-b802-5bcf64c2cfb4', }, { id: '90943e30-9a47-11e8-b64d-95841ca0b247', @@ -455,9 +473,14 @@ export const getSavedObjects = (): SavedObject[] => [ type: 'index-pattern', }, { - id: 'cb099a20-ea66-11eb-9425-113343a037e3', name: '8e59c7cf-6e42-4343-a113-c4a255fcf2ce:panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce', type: 'visualization', + id: 'cb099a20-ea66-11eb-9425-113343a037e3', + }, + { + name: 'cbca842c-b9fa-4523-9ce0-14e350866e33:panel_cbca842c-b9fa-4523-9ce0-14e350866e33', + type: 'lens', + id: '16b1d7d0-ea71-11eb-8b4b-f7b600de0f7d', }, { id: '90943e30-9a47-11e8-b64d-95841ca0b247', @@ -470,38 +493,25 @@ export const getSavedObjects = (): SavedObject[] => [ type: 'index-pattern', }, { - id: '16b1d7d0-ea71-11eb-8b4b-f7b600de0f7d', - name: 'cbca842c-b9fa-4523-9ce0-14e350866e33:panel_cbca842c-b9fa-4523-9ce0-14e350866e33', - type: 'lens', + name: 'controlGroup_612f8db8-9ba9-41cf-a809-d133fe9b83a8:optionsListDataView', + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', }, - ], - migrationVersion: { - dashboard: '7.14.0', - }, - attributes: { - title: i18n.translate('home.sampleData.logsSpec.webTrafficTitle', { - defaultMessage: '[Logs] Web Traffic', - }), - hits: 0, - description: i18n.translate('home.sampleData.logsSpec.webTrafficDescription', { - defaultMessage: "Analyze mock web traffic log data for Elastic's website", - }), - panelsJSON: - '[{"version":"8.0.0","type":"map","gridData":{"x":0,"y":19,"w":24,"h":18,"i":"4"},"panelIndex":"4","embeddableConfig":{"isLayerTOCOpen":false,"enhancements":{},"mapCenter":{"lat":42.16337,"lon":-88.92107,"zoom":3.64},"mapBuffer":{"minLon":-112.5,"minLat":21.94305,"maxLon":-45,"maxLat":55.77657},"openTOCDetails":[],"hiddenLayers":[]},"panelRefName":"panel_4"},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":52,"w":24,"h":13,"i":"9"},"panelIndex":"9","embeddableConfig":{"mapCenter":[36.8092847020594,-96.94335937500001],"vis":{"params":{"sort":{"columnIndex":null,"direction":null}}},"enhancements":{}},"panelRefName":"panel_9"},{"version":"8.0.0","type":"visualization","gridData":{"x":12,"y":6,"w":12,"h":8,"i":"11"},"panelIndex":"11","embeddableConfig":{"vis":{"colors":{"0 - 500":"#BF1B00","1000 - 1500":"#7EB26D","500 - 1000":"#F2C96D"},"defaultColors":{"0 - 500":"rgb(165,0,38)","1000 - 1500":"rgb(0,104,55)","500 - 1000":"rgb(255,255,190)"},"legendOpen":false},"enhancements":{}},"title":"","panelRefName":"panel_11"},{"version":"8.0.0","type":"visualization","gridData":{"x":24,"y":19,"w":24,"h":33,"i":"14"},"panelIndex":"14","embeddableConfig":{"enhancements":{}},"panelRefName":"panel_14"},{"version":"8.0.0","type":"visualization","gridData":{"x":24,"y":6,"w":24,"h":13,"i":"15"},"panelIndex":"15","embeddableConfig":{"enhancements":{"dynamicActions":{"events":[]}}},"panelRefName":"panel_15"},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":0,"w":17,"h":6,"i":"343f0bef-0b19-452e-b1c8-59beb18b6f0c"},"panelIndex":"343f0bef-0b19-452e-b1c8-59beb18b6f0c","embeddableConfig":{"savedVis":{"title":"[Logs] Markdown Instructions","description":"","type":"markdown","params":{"fontSize":12,"openLinksInNewTab":true,"markdown":"## Sample Logs Data\\nThis dashboard contains sample data for you to play with. You can view it, search it, and interact with the visualizations. For more information about Kibana, check our [docs](https://www.elastic.co/guide/en/kibana/current/index.html)."},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.0.0","type":"visualization","gridData":{"x":17,"y":0,"w":31,"h":6,"i":"30326cdb-4ddd-49eb-a4f1-b555caa21d7c"},"panelIndex":"30326cdb-4ddd-49eb-a4f1-b555caa21d7c","embeddableConfig":{"savedVis":{"title":"[Logs] Input Controls","description":"","type":"input_control_vis","params":{"controls":[{"id":"1523980210832","fieldName":"geo.src","label":"Source Country","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"parent":"","indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_0_index_pattern"},{"id":"1523980191978","fieldName":"machine.os.keyword","label":"OS","type":"list","options":{"type":"terms","multiselect":true,"size":100,"order":"desc"},"parent":"1523980210832","indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_1_index_pattern"},{"id":"1523980232790","fieldName":"bytes","label":"Bytes","type":"range","options":{"decimalPlaces":0,"step":1024},"indexPatternRefName":"control_30326cdb-4ddd-49eb-a4f1-b555caa21d7c_2_index_pattern"}],"updateFiltersOnChange":true,"useTimeFilter":true,"pinFilters":false},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{},"hidePanelTitles":true}},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":6,"w":12,"h":8,"i":"bb94016e-f4a6-49ca-87a9-296a2869d570"},"panelIndex":"bb94016e-f4a6-49ca-87a9-296a2869d570","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"483defd2-775b-4a62-bdef-496c819bb8ed":{"columns":{"37430d12-7452-4cc9-b035-5cfd4061edf0":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true}},"columnOrder":["37430d12-7452-4cc9-b035-5cfd4061edf0"],"incompleteColumns":{}}}}},"visualization":{"layerId":"483defd2-775b-4a62-bdef-496c819bb8ed","accessor":"37430d12-7452-4cc9-b035-5cfd4061edf0"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-483defd2-775b-4a62-bdef-496c819bb8ed"}]},"enhancements":{}}},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":14,"w":12,"h":5,"i":"01d8e435-91c0-484f-a11e-856747050b0a"},"panelIndex":"01d8e435-91c0-484f-a11e-856747050b0a","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}]},"enhancements":{}}},{"version":"8.0.0","type":"lens","gridData":{"x":12,"y":14,"w":12,"h":5,"i":"8c1456d4-1993-4ba2-b701-04aca02c9fef"},"panelIndex":"8c1456d4-1993-4ba2-b701-04aca02c9fef","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsMetric","state":{"datasourceStates":{"indexpattern":{"layers":{"f3793bb7-3971-4753-866d-4008e77a9f9a":{"columns":{"71c076a6-e782-4866-b8df-5fd85a41f08bX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08bX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1"],"customLabel":true},"71c076a6-e782-4866-b8df-5fd85a41f08b":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"customLabel":true}},"columnOrder":["71c076a6-e782-4866-b8df-5fd85a41f08b","71c076a6-e782-4866-b8df-5fd85a41f08bX0","71c076a6-e782-4866-b8df-5fd85a41f08bX1","71c076a6-e782-4866-b8df-5fd85a41f08bX2"],"incompleteColumns":{}}}}},"visualization":{"layerId":"f3793bb7-3971-4753-866d-4008e77a9f9a","accessor":"71c076a6-e782-4866-b8df-5fd85a41f08b"},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-f3793bb7-3971-4753-866d-4008e77a9f9a"}]},"enhancements":{}}},{"version":"8.0.0","type":"visualization","gridData":{"x":0,"y":37,"w":24,"h":15,"i":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},"panelIndex":"8e59c7cf-6e42-4343-a113-c4a255fcf2ce","embeddableConfig":{"savedVis":{"title":"","description":"","type":"vega","params":{"spec":"{\\n $schema: https://vega.github.io/schema/vega-lite/v5.json\\n data: {\\n url: {\\n %context%: true\\n %timefield%: @timestamp\\n index: kibana_sample_data_logs\\n body: {\\n aggs: {\\n countries: {\\n terms: {\\n field: geo.src\\n size: 25\\n }\\n aggs: {\\n hours: {\\n histogram: {\\n field: hour_of_day\\n interval: 1\\n }\\n aggs: {\\n unique: {\\n cardinality: {\\n field: clientip\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n size: 0\\n }\\n }\\n format: {property: \\"aggregations.countries.buckets\\"}\\n }\\n \\n transform: [\\n {\\n flatten: [\\"hours.buckets\\"],\\n as: [\\"buckets\\"]\\n }\\n ]\\n\\n mark: {\\n type: rect\\n tooltip: true\\n }\\n\\n encoding: {\\n x: {\\n field: buckets.key\\n type: ordinal\\n axis: {\\n title: false\\n labelAngle: 0\\n }\\n }\\n y: {\\n field: key\\n type: nominal\\n sort: {\\n field: -buckets.unique.value\\n }\\n axis: {title: false}\\n }\\n color: {\\n field: buckets.unique.value\\n type: quantitative\\n axis: {title: false}\\n scale: {\\n scheme: reds\\n }\\n }\\n }\\n}\\n"},"uiState":{},"data":{"aggs":[],"searchSource":{"query":{"query":"","language":"kuery"},"filter":[]}}},"enhancements":{}},"panelRefName":"panel_8e59c7cf-6e42-4343-a113-c4a255fcf2ce"},{"version":"8.0.0","type":"lens","gridData":{"x":0,"y":65,"w":48,"h":19,"i":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b"},"panelIndex":"1d5f0b3f-d9d2-4b26-997b-83bc5ca3090b","embeddableConfig":{"attributes":{"title":"","type":"lens","visualizationType":"lnsDatatable","state":{"datasourceStates":{"indexpattern":{"layers":{"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0":{"columns":{"42783ad7-dbcf-4310-bc06-f21f4eaaac96":{"label":"URL","dataType":"string","operationType":"terms","scale":"ordinal","sourceField":"url.keyword","isBucketed":true,"params":{"size":1000,"orderBy":{"type":"column","columnId":"f7835375-4d5b-4839-95ea-41928192a319"},"orderDirection":"desc","otherBucket":true,"missingBucket":false},"customLabel":true},"f7835375-4d5b-4839-95ea-41928192a319":{"label":"Visits","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX0":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 400 and response.keyword < 500","language":"kuery"},"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX1":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1daX2":{"label":"Part of HTTP 4xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"location":{"min":0,"max":73},"text":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()"}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1"],"customLabel":true},"07fc84ca-4147-4ba9-879e-d1b4e086e1da":{"label":"HTTP 4xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 400 and response.keyword < 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["07fc84ca-4147-4ba9-879e-d1b4e086e1daX2"],"customLabel":true},"791d5a5b-a7ba-4e9e-b533-51b33c7d7747":{"label":"Unique","dataType":"number","operationType":"unique_count","scale":"ratio","sourceField":"clientip","isBucketed":false,"customLabel":true},"611e3509-e834-4fdd-b573-44e959e95d27":{"label":"95th percentile of bytes","dataType":"number","operationType":"percentile","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"percentile":95,"format":{"id":"bytes","params":{"decimals":0}}}},"9f79ecca-123f-4098-a658-6b0e998da003":{"label":"Median of bytes","dataType":"number","operationType":"median","sourceField":"bytes","isBucketed":false,"scale":"ratio","params":{"format":{"id":"bytes","params":{"decimals":0}}}},"491285fd-0196-402c-9b7f-4660fdc1c22aX0":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","filter":{"query":"response.keyword >= 500","language":"kuery"},"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX1":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"count","isBucketed":false,"scale":"ratio","sourceField":"Records","customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22aX2":{"label":"Part of HTTP 5xx","dataType":"number","operationType":"math","isBucketed":false,"scale":"ratio","params":{"tinymathAst":{"type":"function","name":"divide","args":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"location":{"min":0,"max":46},"text":"count(kql=\'response.keyword >= 500\') / count()"}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1"],"customLabel":true},"491285fd-0196-402c-9b7f-4660fdc1c22a":{"label":"HTTP 5xx","dataType":"number","operationType":"formula","isBucketed":false,"scale":"ratio","params":{"formula":"count(kql=\'response.keyword >= 500\') / count()","isFormulaBroken":false,"format":{"id":"percent","params":{"decimals":1}}},"references":["491285fd-0196-402c-9b7f-4660fdc1c22aX2"],"customLabel":true}},"columnOrder":["42783ad7-dbcf-4310-bc06-f21f4eaaac96","f7835375-4d5b-4839-95ea-41928192a319","791d5a5b-a7ba-4e9e-b533-51b33c7d7747","07fc84ca-4147-4ba9-879e-d1b4e086e1da","491285fd-0196-402c-9b7f-4660fdc1c22a","491285fd-0196-402c-9b7f-4660fdc1c22aX0","491285fd-0196-402c-9b7f-4660fdc1c22aX1","491285fd-0196-402c-9b7f-4660fdc1c22aX2","07fc84ca-4147-4ba9-879e-d1b4e086e1daX0","07fc84ca-4147-4ba9-879e-d1b4e086e1daX1","07fc84ca-4147-4ba9-879e-d1b4e086e1daX2","611e3509-e834-4fdd-b573-44e959e95d27","9f79ecca-123f-4098-a658-6b0e998da003"],"incompleteColumns":{}}}}},"visualization":{"layerId":"c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0","columns":[{"columnId":"42783ad7-dbcf-4310-bc06-f21f4eaaac96","width":650.6666666666666},{"columnId":"f7835375-4d5b-4839-95ea-41928192a319"},{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","isTransposed":false,"width":81.66666666666669,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#CC5642","stop":1}],"rangeType":"number","name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#CC5642","stop":0.1}],"rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"07fc84ca-4147-4ba9-879e-d1b4e086e1da","isTransposed":false,"colorMode":"cell","palette":{"name":"custom","type":"palette","params":{"steps":5,"stops":[{"color":"#fbddd6","stop":0.1},{"color":"#cc5642","stop":1.1}],"name":"custom","colorStops":[{"color":"#fbddd6","stop":0.05},{"color":"#cc5642","stop":0.1}],"rangeType":"number","rangeMin":0.05,"rangeMax":0.1}}},{"columnId":"791d5a5b-a7ba-4e9e-b533-51b33c7d7747","isTransposed":false},{"columnId":"611e3509-e834-4fdd-b573-44e959e95d27","isTransposed":false},{"columnId":"9f79ecca-123f-4098-a658-6b0e998da003","isTransposed":false}],"sorting":{"columnId":"491285fd-0196-402c-9b7f-4660fdc1c22a","direction":"desc"}},"query":{"query":"","language":"kuery"},"filters":[]},"references":[{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-current-indexpattern"},{"type":"index-pattern","id":"90943e30-9a47-11e8-b64d-95841ca0b247","name":"indexpattern-datasource-layer-c35dc8ee-50d1-4ef7-8b4b-9c21a7e7d3b0"}]},"enhancements":{"dynamicActions":{"events":[]}},"hidePanelTitles":false},"title":"[Logs] Errors by host"},{"version":"8.0.0","type":"lens","gridData":{"x":24,"y":52,"w":24,"h":13,"i":"cbca842c-b9fa-4523-9ce0-14e350866e33"},"panelIndex":"cbca842c-b9fa-4523-9ce0-14e350866e33","embeddableConfig":{"hidePanelTitles":false,"enhancements":{}},"title":"[Logs] Bytes distribution","panelRefName":"panel_cbca842c-b9fa-4523-9ce0-14e350866e33"}]', - optionsJSON: '{"hidePanelTitles":false,"useMargins":true}', - version: 2, - timeRestore: true, - timeTo: 'now', - timeFrom: 'now-7d', - refreshInterval: { - pause: false, - value: 900000, + { + name: 'controlGroup_9807212f-5078-4c42-879c-6f28b3033fc9:optionsListDataView', + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', }, - kibanaSavedObjectMeta: { - searchSourceJSON: - '{"query":{"language":"kuery","query":""},"filter":[],"highlightAll":true,"version":true}', + { + name: 'controlGroup_6bf7a1b4-282e-43ac-aa46-81b97fa3acae:rangeSliderDataView', + type: 'index-pattern', + id: '90943e30-9a47-11e8-b64d-95841ca0b247', }, + ], + migrationVersion: { + dashboard: '8.5.0', }, + coreMigrationVersion: '8.6.0', }, { id: '2f360f30-ea74-11eb-b4c6-3d2afc1cb389', diff --git a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts index 0522ab5b41c2c..bc04d60c3fb54 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts @@ -200,7 +200,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); expect(event.properties.key2).to.be('num_of_panels'); - expect(event.properties.value2).to.be(17); + expect(event.properties.value2).to.be(16); }); /** diff --git a/test/functional/apps/home/_sample_data.ts b/test/functional/apps/home/_sample_data.ts index 7b362aefc609d..4120a9ecc1a54 100644 --- a/test/functional/apps/home/_sample_data.ts +++ b/test/functional/apps/home/_sample_data.ts @@ -95,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); const panelCount = await PageObjects.dashboard.getPanelCount(); - expect(panelCount).to.be(17); + expect(panelCount).to.be(16); }); it('should render visualizations', async () => { @@ -107,7 +107,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Checking saved searches rendered'); await dashboardExpect.savedSearchRowCount(10); log.debug('Checking input controls rendered'); - await dashboardExpect.inputControlItemCount(3); + await dashboardExpect.controlCount(3); log.debug('Checking tag cloud rendered'); await dashboardExpect.tagCloudWithValuesFound(['Sunny', 'Rain', 'Clear', 'Cloudy', 'Hail']); log.debug('Checking vega chart rendered'); @@ -119,7 +119,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); const panelCount = await PageObjects.dashboard.getPanelCount(); - expect(panelCount).to.be(13); + expect(panelCount).to.be(12); }); it('should launch sample ecommerce data set dashboard', async () => { @@ -127,7 +127,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); const panelCount = await PageObjects.dashboard.getPanelCount(); - expect(panelCount).to.be(15); + expect(panelCount).to.be(14); }); }); diff --git a/test/functional/services/dashboard/expectations.ts b/test/functional/services/dashboard/expectations.ts index 73c49525ef4a1..6e20e5b574202 100644 --- a/test/functional/services/dashboard/expectations.ts +++ b/test/functional/services/dashboard/expectations.ts @@ -281,6 +281,7 @@ export class DashboardExpectService extends FtrService { }); } + // legacy controls visualization async inputControlItemCount(expectedCount: number) { this.log.debug(`DashboardExpect.inputControlItemCount(${expectedCount})`); await this.retry.try(async () => { @@ -289,6 +290,14 @@ export class DashboardExpectService extends FtrService { }); } + async controlCount(expectedCount: number) { + this.log.debug(`DashboardExpect.controlCount(${expectedCount})`); + await this.retry.try(async () => { + const controls = await this.testSubjects.findAll('control-frame'); + expect(controls.length).to.be(expectedCount); + }); + } + async lineChartPointsCount(expectedCount: number) { this.log.debug(`DashboardExpect.lineChartPointsCount(${expectedCount})`); await this.retry.try(async () => { diff --git a/x-pack/performance/journeys/ecommerce_dashboard.ts b/x-pack/performance/journeys/ecommerce_dashboard.ts index 89f05902f4153..05e46eab851b9 100644 --- a/x-pack/performance/journeys/ecommerce_dashboard.ts +++ b/x-pack/performance/journeys/ecommerce_dashboard.ts @@ -52,5 +52,5 @@ export const journey = new Journey({ await page.click(subj('launchSampleDataSetecommerce')); await page.click(subj('viewSampleDataSetecommerce-dashboard')); - await waitForVisualizations(page, 13); + await waitForVisualizations(page, 12); }); diff --git a/x-pack/performance/journeys/flight_dashboard.ts b/x-pack/performance/journeys/flight_dashboard.ts index ac6e589d391a5..1fbf2e3e77cb2 100644 --- a/x-pack/performance/journeys/flight_dashboard.ts +++ b/x-pack/performance/journeys/flight_dashboard.ts @@ -52,7 +52,7 @@ export const journey = new Journey({ await page.click(subj('launchSampleDataSetflights')); await page.click(subj('viewSampleDataSetflights-dashboard')); - await waitForVisualizations(page, 15); + await waitForVisualizations(page, 14); }) .step('Go to Airport Connections Visualizations Edit', async ({ page }) => { diff --git a/x-pack/performance/journeys/web_logs_dashboard.ts b/x-pack/performance/journeys/web_logs_dashboard.ts index 64ea47d412e0e..efba62acc517e 100644 --- a/x-pack/performance/journeys/web_logs_dashboard.ts +++ b/x-pack/performance/journeys/web_logs_dashboard.ts @@ -52,5 +52,5 @@ export const journey = new Journey({ await page.click(subj('launchSampleDataSetlogs')); await page.click(subj('viewSampleDataSetlogs-dashboard')); - await waitForVisualizations(page, 12); + await waitForVisualizations(page, 11); }); diff --git a/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts b/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts index 09d353e631fed..80354eda9eefd 100644 --- a/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts +++ b/x-pack/test/functional/apps/dashboard/group2/_async_dashboard.ts @@ -179,7 +179,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Checking saved searches rendered'); await dashboardExpect.savedSearchRowCount(10); log.debug('Checking input controls rendered'); - await dashboardExpect.inputControlItemCount(3); + await dashboardExpect.controlCount(3); log.debug('Checking tag cloud rendered'); await dashboardExpect.tagCloudWithValuesFound(['Sunny', 'Rain', 'Clear', 'Cloudy', 'Hail']); log.debug('Checking vega chart rendered'); diff --git a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session_relative_time.ts b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session_relative_time.ts index 27b2914d3575b..86c36241b9ca7 100644 --- a/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session_relative_time.ts +++ b/x-pack/test/search_sessions_integration/tests/apps/dashboard/async_search/save_search_session_relative_time.ts @@ -91,7 +91,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('Checking saved searches rendered'); await dashboardExpect.savedSearchRowCount(11); log.debug('Checking input controls rendered'); - await dashboardExpect.inputControlItemCount(3); + await dashboardExpect.controlCount(3); log.debug('Checking tag cloud rendered'); await dashboardExpect.tagCloudWithValuesFound(['Sunny', 'Rain', 'Clear', 'Cloudy', 'Hail']); log.debug('Checking vega chart rendered'); diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index 9f1b14040c6a6..b156f909d7a5d 100644 --- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts @@ -58,7 +58,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { log.debug('Checking saved searches rendered'); await dashboardExpect.savedSearchRowCount(49); log.debug('Checking input controls rendered'); - await dashboardExpect.inputControlItemCount(3); + await dashboardExpect.controlCount(3); log.debug('Checking tag cloud rendered'); await dashboardExpect.tagCloudWithValuesFound([ 'Sunny', From e007ad6df510629da6e501ed55247496915f213f Mon Sep 17 00:00:00 2001 From: Ying Mao <ying.mao@elastic.co> Date: Mon, 3 Oct 2022 23:28:11 -0400 Subject: [PATCH 014/174] [Response Ops][Alerting] Fixing bug with using `runSoon` on pre-8.x rule (#142505) * Running task using scheduled task id. Adding functional test * dont run if rule is disable * Fixing i18n --- .../server/rules_client/rules_client.ts | 41 ++++++- .../rules_client/tests/run_soon.test.ts | 32 +++++- .../spaces_only/tests/alerting/index.ts | 1 + .../spaces_only/tests/alerting/run_soon.ts | 104 ++++++++++++++++++ 4 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 89ce20b59ae9b..7374868a11dbb 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -41,7 +41,11 @@ import { InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, } from '@kbn/security-plugin/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; -import { TaskManagerStartContract, TaskStatus } from '@kbn/task-manager-plugin/server'; +import { + ConcreteTaskInstance, + TaskManagerStartContract, + TaskStatus, +} from '@kbn/task-manager-plugin/server'; import { IEvent, IEventLogClient, @@ -2977,9 +2981,27 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); - const taskDoc = attributes.scheduledTaskId - ? await this.taskManager.get(attributes.scheduledTaskId) - : null; + // Check that the rule is enabled + if (!attributes.enabled) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.disabledRuleError', { + defaultMessage: 'Error running rule: rule is disabled', + }); + } + + let taskDoc: ConcreteTaskInstance | null = null; + try { + taskDoc = attributes.scheduledTaskId + ? await this.taskManager.get(attributes.scheduledTaskId) + : null; + } catch (err) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.getTaskError', { + defaultMessage: 'Error running rule: {errMessage}', + values: { + errMessage: err.message, + }, + }); + } + if ( taskDoc && (taskDoc.status === TaskStatus.Claiming || taskDoc.status === TaskStatus.Running) @@ -2989,7 +3011,16 @@ export class RulesClient { }); } - await this.taskManager.runSoon(id); + try { + await this.taskManager.runSoon(attributes.scheduledTaskId ? attributes.scheduledTaskId : id); + } catch (err) { + return i18n.translate('xpack.alerting.rulesClient.runSoon.runSoonError', { + defaultMessage: 'Error running rule: {errMessage}', + values: { + errMessage: err.message, + }, + }); + } } public async listAlertTypes() { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts index bea66a31ead25..138b167549c09 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/run_soon.test.ts @@ -59,7 +59,7 @@ describe('runSoon()', () => { consumer: 'myApp', schedule: { interval: '10s' }, alertTypeId: 'myType', - enabled: false, + enabled: true, apiKey: 'MTIzOmFiYw==', apiKeyOwner: 'elastic', actions: [ @@ -179,7 +179,7 @@ describe('runSoon()', () => { }); test('does not run a rule if that rule is already running', async () => { - taskManager.get.mockResolvedValue({ + taskManager.get.mockResolvedValueOnce({ id: '1', scheduledAt: new Date(), attempts: 0, @@ -196,4 +196,32 @@ describe('runSoon()', () => { expect(message).toBe('Rule is already running'); expect(taskManager.runSoon).not.toHaveBeenCalled(); }); + + test('does not run a rule if that rule is disabled', async () => { + unsecuredSavedObjectsClient.get.mockResolvedValue({ + ...existingRule, + attributes: { + ...existingRule.attributes, + enabled: false, + }, + }); + const message = await rulesClient.runSoon({ id: '1' }); + expect(message).toBe('Error running rule: rule is disabled'); + expect(taskManager.get).not.toHaveBeenCalled(); + expect(taskManager.runSoon).not.toHaveBeenCalled(); + }); + + test('gracefully handles errors getting task document', async () => { + taskManager.get.mockRejectedValueOnce(new Error('oh no!')); + const message = await rulesClient.runSoon({ id: '1' }); + expect(message).toBe('Error running rule: oh no!'); + expect(taskManager.runSoon).not.toHaveBeenCalled(); + }); + + test('gracefully handles errors calling runSoon', async () => { + taskManager.runSoon.mockRejectedValueOnce(new Error('fail!')); + const message = await rulesClient.runSoon({ id: '1' }); + expect(message).toBe('Error running rule: fail!'); + expect(taskManager.runSoon).toHaveBeenCalled(); + }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index 49e652d9a9a4e..e09cf3121adec 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -49,6 +49,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./bulk_edit')); loadTestFile(require.resolve('./capped_action_type')); loadTestFile(require.resolve('./scheduled_task_id')); + loadTestFile(require.resolve('./run_soon')); // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 // note that this test will destroy existing spaces diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts new file mode 100644 index 0000000000000..050c220ab1b0f --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts @@ -0,0 +1,104 @@ +/* + * 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 { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../common/lib'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +const LOADED_RULE_ID = '74f3e6d7-b7bb-477d-ac28-92ee22728e6e'; + +// eslint-disable-next-line import/no-default-export +export default function createRunSoonTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const retry = getService('retry'); + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('runSoon', () => { + const objectRemover = new ObjectRemover(supertest); + + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/rules_scheduled_task_id'); + }); + + afterEach(async () => { + await objectRemover.removeAll(); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/rules_scheduled_task_id'); + }); + + it('should successfully run rule where scheduled task id is different than rule id', async () => { + await retry.try(async () => { + // Sometimes the rule may already be running. Try until it isn't + const response = await supertest + .post(`${getUrlPrefix(``)}/internal/alerting/rule/${LOADED_RULE_ID}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(response.status).to.eql(204); + }); + }); + + it('should successfully run rule where scheduled task id is same as rule id', async () => { + const response = await supertest + .post(`${getUrlPrefix(``)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()); + + expect(response.status).to.eql(200); + objectRemover.add('default', response.body.id, 'rule', 'alerting'); + + const runSoonResponse = await supertest + .post(`${getUrlPrefix(``)}/internal/alerting/rule/${response.body.id}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoonResponse.status).to.eql(204); + }); + + it('should return message when task does not exist for rule', async () => { + const response = await supertest + .post(`${getUrlPrefix(``)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()); + + expect(response.status).to.eql(200); + objectRemover.add('default', response.body.id, 'rule', 'alerting'); + + await es.delete({ + id: `task:${response.body.id}`, + index: '.kibana_task_manager', + }); + + const runSoonResponse = await supertest + .post(`${getUrlPrefix(``)}/internal/alerting/rule/${response.body.id}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoonResponse.status).to.eql(200); + expect(runSoonResponse.text).to.eql( + `Error running rule: Saved object [task/${response.body.id}] not found` + ); + }); + + it('should return message when rule is disabled', async () => { + const response = await supertest + .post(`${getUrlPrefix(``)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send(getTestRuleData()); + + expect(response.status).to.eql(200); + objectRemover.add('default', response.body.id, 'rule', 'alerting'); + + await supertest + .post(`${getUrlPrefix(``)}/api/alerting/rule/${response.body.id}/_disable`) + .set('kbn-xsrf', 'foo'); + + const runSoonResponse = await supertest + .post(`${getUrlPrefix(``)}/internal/alerting/rule/${response.body.id}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoonResponse.status).to.eql(200); + expect(runSoonResponse.text).to.eql(`Error running rule: rule is disabled`); + }); + }); +} From d616210c43a9be54342cd0c5fca64ca9cb9ecd0c Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 3 Oct 2022 22:47:46 -0600 Subject: [PATCH 015/174] [api-docs] Daily api_docs build (#142551) --- api_docs/actions.mdx | 2 +- api_docs/advanced_settings.mdx | 2 +- api_docs/aiops.mdx | 2 +- api_docs/alerting.devdocs.json | 4 +- api_docs/alerting.mdx | 2 +- api_docs/apm.mdx | 2 +- api_docs/banners.mdx | 2 +- api_docs/bfetch.mdx | 2 +- api_docs/canvas.mdx | 2 +- api_docs/cases.mdx | 2 +- api_docs/charts.mdx | 2 +- api_docs/cloud.mdx | 2 +- api_docs/cloud_experiments.mdx | 2 +- api_docs/cloud_security_posture.mdx | 2 +- api_docs/console.mdx | 2 +- api_docs/controls.mdx | 2 +- api_docs/core.devdocs.json | 30 --- api_docs/core.mdx | 4 +- api_docs/custom_integrations.mdx | 2 +- api_docs/dashboard.mdx | 2 +- api_docs/dashboard_enhanced.mdx | 2 +- api_docs/data.mdx | 2 +- api_docs/data_query.mdx | 2 +- api_docs/data_search.mdx | 2 +- api_docs/data_view_editor.mdx | 2 +- api_docs/data_view_field_editor.mdx | 2 +- api_docs/data_view_management.mdx | 2 +- api_docs/data_views.mdx | 2 +- api_docs/data_visualizer.mdx | 2 +- api_docs/deprecations_by_api.mdx | 2 +- api_docs/deprecations_by_plugin.mdx | 2 +- api_docs/deprecations_by_team.mdx | 2 +- api_docs/dev_tools.mdx | 2 +- api_docs/discover.mdx | 2 +- api_docs/discover_enhanced.mdx | 2 +- api_docs/embeddable.mdx | 2 +- api_docs/embeddable_enhanced.mdx | 2 +- api_docs/encrypted_saved_objects.mdx | 2 +- api_docs/enterprise_search.mdx | 2 +- api_docs/es_ui_shared.mdx | 2 +- api_docs/event_annotation.mdx | 2 +- api_docs/event_log.devdocs.json | 6 +- api_docs/event_log.mdx | 2 +- api_docs/expression_error.mdx | 2 +- api_docs/expression_gauge.mdx | 2 +- api_docs/expression_heatmap.mdx | 2 +- api_docs/expression_image.mdx | 2 +- api_docs/expression_legacy_metric_vis.mdx | 2 +- api_docs/expression_metric.mdx | 2 +- api_docs/expression_metric_vis.mdx | 2 +- api_docs/expression_partition_vis.mdx | 2 +- api_docs/expression_repeat_image.mdx | 2 +- api_docs/expression_reveal_image.mdx | 2 +- api_docs/expression_shape.mdx | 2 +- api_docs/expression_tagcloud.mdx | 2 +- api_docs/expression_x_y.mdx | 2 +- api_docs/expressions.mdx | 2 +- api_docs/features.devdocs.json | 12 +- api_docs/features.mdx | 2 +- api_docs/field_formats.mdx | 2 +- api_docs/file_upload.mdx | 2 +- api_docs/files.mdx | 2 +- api_docs/fleet.devdocs.json | 2 +- api_docs/fleet.mdx | 2 +- api_docs/global_search.mdx | 2 +- api_docs/guided_onboarding.devdocs.json | 231 ++++++++++++------ api_docs/guided_onboarding.mdx | 12 +- api_docs/home.mdx | 2 +- api_docs/index_lifecycle_management.mdx | 2 +- api_docs/index_management.mdx | 2 +- api_docs/infra.mdx | 2 +- api_docs/inspector.mdx | 2 +- api_docs/interactive_setup.mdx | 2 +- api_docs/kbn_ace.mdx | 2 +- api_docs/kbn_aiops_components.mdx | 2 +- api_docs/kbn_aiops_utils.mdx | 2 +- api_docs/kbn_alerts.mdx | 2 +- api_docs/kbn_analytics.mdx | 2 +- api_docs/kbn_analytics_client.mdx | 2 +- ..._analytics_shippers_elastic_v3_browser.mdx | 2 +- ...n_analytics_shippers_elastic_v3_common.mdx | 2 +- ...n_analytics_shippers_elastic_v3_server.mdx | 2 +- api_docs/kbn_analytics_shippers_fullstory.mdx | 2 +- api_docs/kbn_apm_config_loader.mdx | 2 +- api_docs/kbn_apm_synthtrace.mdx | 2 +- api_docs/kbn_apm_utils.mdx | 2 +- api_docs/kbn_axe_config.mdx | 2 +- api_docs/kbn_chart_icons.mdx | 2 +- api_docs/kbn_ci_stats_core.mdx | 2 +- api_docs/kbn_ci_stats_performance_metrics.mdx | 2 +- api_docs/kbn_ci_stats_reporter.mdx | 2 +- api_docs/kbn_cli_dev_mode.mdx | 2 +- api_docs/kbn_coloring.mdx | 2 +- api_docs/kbn_config.mdx | 2 +- api_docs/kbn_config_mocks.mdx | 2 +- api_docs/kbn_config_schema.mdx | 2 +- .../kbn_content_management_table_list.mdx | 2 +- api_docs/kbn_core_analytics_browser.mdx | 2 +- .../kbn_core_analytics_browser_internal.mdx | 2 +- api_docs/kbn_core_analytics_browser_mocks.mdx | 2 +- api_docs/kbn_core_analytics_server.mdx | 2 +- .../kbn_core_analytics_server_internal.mdx | 2 +- api_docs/kbn_core_analytics_server_mocks.mdx | 2 +- api_docs/kbn_core_application_browser.mdx | 2 +- .../kbn_core_application_browser_internal.mdx | 2 +- .../kbn_core_application_browser_mocks.mdx | 2 +- api_docs/kbn_core_application_common.mdx | 2 +- api_docs/kbn_core_apps_browser_internal.mdx | 2 +- api_docs/kbn_core_apps_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_browser_mocks.mdx | 2 +- api_docs/kbn_core_base_common.mdx | 2 +- api_docs/kbn_core_base_server_internal.mdx | 2 +- api_docs/kbn_core_base_server_mocks.mdx | 2 +- .../kbn_core_capabilities_browser_mocks.mdx | 2 +- api_docs/kbn_core_capabilities_common.mdx | 2 +- api_docs/kbn_core_capabilities_server.mdx | 2 +- .../kbn_core_capabilities_server_mocks.mdx | 2 +- api_docs/kbn_core_chrome_browser.mdx | 2 +- api_docs/kbn_core_chrome_browser_mocks.mdx | 2 +- api_docs/kbn_core_config_server_internal.mdx | 2 +- api_docs/kbn_core_deprecations_browser.mdx | 2 +- ...kbn_core_deprecations_browser_internal.mdx | 2 +- .../kbn_core_deprecations_browser_mocks.mdx | 2 +- api_docs/kbn_core_deprecations_common.mdx | 2 +- api_docs/kbn_core_deprecations_server.mdx | 2 +- .../kbn_core_deprecations_server_internal.mdx | 2 +- .../kbn_core_deprecations_server_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_browser.mdx | 2 +- api_docs/kbn_core_doc_links_browser_mocks.mdx | 2 +- api_docs/kbn_core_doc_links_server.mdx | 2 +- api_docs/kbn_core_doc_links_server_mocks.mdx | 2 +- ...e_elasticsearch_client_server_internal.mdx | 2 +- ...core_elasticsearch_client_server_mocks.mdx | 2 +- api_docs/kbn_core_elasticsearch_server.mdx | 2 +- ...kbn_core_elasticsearch_server_internal.mdx | 2 +- .../kbn_core_elasticsearch_server_mocks.mdx | 2 +- .../kbn_core_environment_server_internal.mdx | 2 +- .../kbn_core_environment_server_mocks.mdx | 2 +- .../kbn_core_execution_context_browser.mdx | 2 +- ...ore_execution_context_browser_internal.mdx | 2 +- ...n_core_execution_context_browser_mocks.mdx | 2 +- .../kbn_core_execution_context_common.mdx | 2 +- .../kbn_core_execution_context_server.mdx | 2 +- ...core_execution_context_server_internal.mdx | 2 +- ...bn_core_execution_context_server_mocks.mdx | 2 +- api_docs/kbn_core_fatal_errors_browser.mdx | 2 +- .../kbn_core_fatal_errors_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_browser.mdx | 2 +- api_docs/kbn_core_http_browser_internal.mdx | 2 +- api_docs/kbn_core_http_browser_mocks.mdx | 2 +- api_docs/kbn_core_http_common.mdx | 2 +- .../kbn_core_http_context_server_mocks.mdx | 2 +- ...re_http_request_handler_context_server.mdx | 2 +- .../kbn_core_http_router_server_internal.mdx | 2 +- .../kbn_core_http_router_server_mocks.mdx | 2 +- api_docs/kbn_core_http_server.mdx | 2 +- api_docs/kbn_core_http_server_internal.mdx | 2 +- api_docs/kbn_core_http_server_mocks.mdx | 2 +- api_docs/kbn_core_i18n_browser.mdx | 2 +- api_docs/kbn_core_i18n_browser_mocks.mdx | 2 +- api_docs/kbn_core_i18n_server.mdx | 2 +- api_docs/kbn_core_i18n_server_internal.mdx | 2 +- api_docs/kbn_core_i18n_server_mocks.mdx | 2 +- .../kbn_core_injected_metadata_browser.mdx | 2 +- ...n_core_injected_metadata_browser_mocks.mdx | 2 +- ...kbn_core_integrations_browser_internal.mdx | 2 +- .../kbn_core_integrations_browser_mocks.mdx | 2 +- api_docs/kbn_core_lifecycle_browser.mdx | 2 +- api_docs/kbn_core_lifecycle_browser_mocks.mdx | 2 +- api_docs/kbn_core_logging_server.mdx | 2 +- api_docs/kbn_core_logging_server_internal.mdx | 2 +- api_docs/kbn_core_logging_server_mocks.mdx | 2 +- ...ore_metrics_collectors_server_internal.mdx | 2 +- ...n_core_metrics_collectors_server_mocks.mdx | 2 +- api_docs/kbn_core_metrics_server.mdx | 2 +- api_docs/kbn_core_metrics_server_internal.mdx | 2 +- api_docs/kbn_core_metrics_server_mocks.mdx | 2 +- api_docs/kbn_core_mount_utils_browser.mdx | 2 +- api_docs/kbn_core_node_server.mdx | 2 +- api_docs/kbn_core_node_server_internal.mdx | 2 +- api_docs/kbn_core_node_server_mocks.mdx | 2 +- api_docs/kbn_core_notifications_browser.mdx | 2 +- ...bn_core_notifications_browser_internal.mdx | 2 +- .../kbn_core_notifications_browser_mocks.mdx | 2 +- api_docs/kbn_core_overlays_browser.mdx | 2 +- .../kbn_core_overlays_browser_internal.mdx | 2 +- api_docs/kbn_core_overlays_browser_mocks.mdx | 2 +- api_docs/kbn_core_plugins_browser.mdx | 2 +- api_docs/kbn_core_plugins_browser_mocks.mdx | 2 +- api_docs/kbn_core_preboot_server.mdx | 2 +- api_docs/kbn_core_preboot_server_mocks.mdx | 2 +- api_docs/kbn_core_rendering_browser_mocks.mdx | 2 +- ...ore_rendering_server_internal.devdocs.json | 61 +++++ .../kbn_core_rendering_server_internal.mdx | 30 +++ ...n_core_rendering_server_mocks.devdocs.json | 95 +++++++ api_docs/kbn_core_rendering_server_mocks.mdx | 30 +++ .../kbn_core_saved_objects_api_browser.mdx | 2 +- .../kbn_core_saved_objects_api_server.mdx | 2 +- ...core_saved_objects_api_server_internal.mdx | 2 +- ...bn_core_saved_objects_api_server_mocks.mdx | 2 +- ...ore_saved_objects_base_server_internal.mdx | 2 +- ...n_core_saved_objects_base_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_browser.mdx | 2 +- ...bn_core_saved_objects_browser_internal.mdx | 2 +- .../kbn_core_saved_objects_browser_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_common.mdx | 2 +- ..._objects_import_export_server_internal.mdx | 2 +- ...ved_objects_import_export_server_mocks.mdx | 2 +- ...aved_objects_migration_server_internal.mdx | 2 +- ...e_saved_objects_migration_server_mocks.mdx | 2 +- api_docs/kbn_core_saved_objects_server.mdx | 2 +- ...kbn_core_saved_objects_server_internal.mdx | 2 +- .../kbn_core_saved_objects_server_mocks.mdx | 2 +- .../kbn_core_saved_objects_utils_server.mdx | 2 +- api_docs/kbn_core_status_common.mdx | 2 +- api_docs/kbn_core_status_common_internal.mdx | 2 +- api_docs/kbn_core_status_server.mdx | 2 +- api_docs/kbn_core_status_server_internal.mdx | 2 +- api_docs/kbn_core_status_server_mocks.mdx | 2 +- ...core_test_helpers_deprecations_getters.mdx | 2 +- ...n_core_test_helpers_http_setup_browser.mdx | 2 +- api_docs/kbn_core_theme_browser.mdx | 2 +- api_docs/kbn_core_theme_browser_internal.mdx | 2 +- api_docs/kbn_core_theme_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_browser.mdx | 2 +- .../kbn_core_ui_settings_browser_internal.mdx | 2 +- .../kbn_core_ui_settings_browser_mocks.mdx | 2 +- api_docs/kbn_core_ui_settings_common.mdx | 2 +- api_docs/kbn_core_ui_settings_server.mdx | 2 +- .../kbn_core_ui_settings_server_internal.mdx | 2 +- .../kbn_core_ui_settings_server_mocks.mdx | 2 +- api_docs/kbn_core_usage_data_server.mdx | 2 +- .../kbn_core_usage_data_server_internal.mdx | 2 +- api_docs/kbn_core_usage_data_server_mocks.mdx | 2 +- api_docs/kbn_crypto.mdx | 2 +- api_docs/kbn_crypto_browser.mdx | 2 +- api_docs/kbn_datemath.mdx | 2 +- api_docs/kbn_dev_cli_errors.mdx | 2 +- api_docs/kbn_dev_cli_runner.mdx | 2 +- api_docs/kbn_dev_proc_runner.mdx | 2 +- api_docs/kbn_dev_utils.mdx | 2 +- api_docs/kbn_doc_links.devdocs.json | 2 +- api_docs/kbn_doc_links.mdx | 2 +- api_docs/kbn_docs_utils.mdx | 2 +- api_docs/kbn_ebt_tools.mdx | 2 +- api_docs/kbn_es_archiver.mdx | 2 +- api_docs/kbn_es_errors.mdx | 2 +- api_docs/kbn_es_query.mdx | 2 +- api_docs/kbn_es_types.mdx | 2 +- api_docs/kbn_eslint_plugin_imports.mdx | 2 +- api_docs/kbn_field_types.mdx | 2 +- api_docs/kbn_find_used_node_modules.mdx | 2 +- .../kbn_ftr_common_functional_services.mdx | 2 +- api_docs/kbn_generate.mdx | 2 +- api_docs/kbn_get_repo_files.mdx | 2 +- api_docs/kbn_handlebars.mdx | 2 +- api_docs/kbn_hapi_mocks.mdx | 2 +- api_docs/kbn_home_sample_data_card.mdx | 2 +- api_docs/kbn_home_sample_data_tab.mdx | 2 +- api_docs/kbn_i18n.mdx | 2 +- api_docs/kbn_import_resolver.mdx | 2 +- api_docs/kbn_interpreter.mdx | 2 +- api_docs/kbn_io_ts_utils.mdx | 2 +- api_docs/kbn_jest_serializers.mdx | 2 +- api_docs/kbn_journeys.mdx | 2 +- api_docs/kbn_kibana_manifest_schema.mdx | 2 +- api_docs/kbn_logging.mdx | 2 +- api_docs/kbn_logging_mocks.mdx | 2 +- api_docs/kbn_managed_vscode_config.mdx | 2 +- api_docs/kbn_mapbox_gl.mdx | 2 +- api_docs/kbn_ml_agg_utils.mdx | 2 +- api_docs/kbn_ml_is_populated_object.mdx | 2 +- api_docs/kbn_ml_string_hash.mdx | 2 +- api_docs/kbn_monaco.mdx | 2 +- api_docs/kbn_optimizer.mdx | 2 +- api_docs/kbn_optimizer_webpack_helpers.mdx | 2 +- api_docs/kbn_osquery_io_ts_types.mdx | 2 +- ..._performance_testing_dataset_extractor.mdx | 2 +- api_docs/kbn_plugin_generator.mdx | 2 +- api_docs/kbn_plugin_helpers.mdx | 2 +- api_docs/kbn_react_field.mdx | 2 +- api_docs/kbn_repo_source_classifier.mdx | 2 +- api_docs/kbn_rule_data_utils.devdocs.json | 2 +- api_docs/kbn_rule_data_utils.mdx | 2 +- .../kbn_securitysolution_autocomplete.mdx | 2 +- api_docs/kbn_securitysolution_es_utils.mdx | 2 +- ...ritysolution_exception_list_components.mdx | 2 +- api_docs/kbn_securitysolution_hook_utils.mdx | 2 +- ..._securitysolution_io_ts_alerting_types.mdx | 2 +- .../kbn_securitysolution_io_ts_list_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_types.mdx | 2 +- api_docs/kbn_securitysolution_io_ts_utils.mdx | 2 +- api_docs/kbn_securitysolution_list_api.mdx | 2 +- .../kbn_securitysolution_list_constants.mdx | 2 +- api_docs/kbn_securitysolution_list_hooks.mdx | 2 +- api_docs/kbn_securitysolution_list_utils.mdx | 2 +- api_docs/kbn_securitysolution_rules.mdx | 2 +- api_docs/kbn_securitysolution_t_grid.mdx | 2 +- api_docs/kbn_securitysolution_utils.mdx | 2 +- api_docs/kbn_server_http_tools.mdx | 2 +- api_docs/kbn_server_route_repository.mdx | 2 +- api_docs/kbn_shared_svg.mdx | 2 +- ...ared_ux_avatar_user_profile_components.mdx | 2 +- ...hared_ux_button_exit_full_screen_mocks.mdx | 2 +- api_docs/kbn_shared_ux_button_toolbar.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data.mdx | 2 +- api_docs/kbn_shared_ux_card_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_link_redirect_app_mocks.mdx | 2 +- .../kbn_shared_ux_page_analytics_no_data.mdx | 2 +- ...shared_ux_page_analytics_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_no_data.mdx | 2 +- ...bn_shared_ux_page_kibana_no_data_mocks.mdx | 2 +- .../kbn_shared_ux_page_kibana_template.mdx | 2 +- ...n_shared_ux_page_kibana_template_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data.mdx | 2 +- .../kbn_shared_ux_page_no_data_config.mdx | 2 +- ...bn_shared_ux_page_no_data_config_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_no_data_mocks.mdx | 2 +- api_docs/kbn_shared_ux_page_solution_nav.mdx | 2 +- .../kbn_shared_ux_prompt_no_data_views.mdx | 2 +- ...n_shared_ux_prompt_no_data_views_mocks.mdx | 2 +- api_docs/kbn_shared_ux_router.mdx | 2 +- api_docs/kbn_shared_ux_router_mocks.mdx | 2 +- api_docs/kbn_shared_ux_storybook_config.mdx | 2 +- api_docs/kbn_shared_ux_storybook_mock.mdx | 2 +- api_docs/kbn_shared_ux_utility.mdx | 2 +- api_docs/kbn_some_dev_log.mdx | 2 +- api_docs/kbn_sort_package_json.mdx | 2 +- api_docs/kbn_std.mdx | 2 +- api_docs/kbn_stdio_dev_helpers.mdx | 2 +- api_docs/kbn_storybook.mdx | 2 +- api_docs/kbn_telemetry_tools.mdx | 2 +- api_docs/kbn_test.mdx | 2 +- api_docs/kbn_test_jest_helpers.mdx | 2 +- api_docs/kbn_test_subj_selector.mdx | 2 +- api_docs/kbn_tooling_log.mdx | 2 +- api_docs/kbn_type_summarizer.mdx | 2 +- api_docs/kbn_type_summarizer_core.mdx | 2 +- api_docs/kbn_typed_react_router_config.mdx | 2 +- api_docs/kbn_ui_theme.mdx | 2 +- .../kbn_user_profile_components.devdocs.json | 175 +++++++++++++ api_docs/kbn_user_profile_components.mdx | 4 +- api_docs/kbn_utility_types.mdx | 2 +- api_docs/kbn_utility_types_jest.mdx | 2 +- api_docs/kbn_utils.mdx | 2 +- api_docs/kbn_yarn_lock_validator.mdx | 2 +- api_docs/kibana_overview.mdx | 2 +- api_docs/kibana_react.mdx | 2 +- api_docs/kibana_utils.mdx | 2 +- api_docs/kubernetes_security.mdx | 2 +- api_docs/lens.mdx | 2 +- api_docs/license_api_guard.mdx | 2 +- api_docs/license_management.mdx | 2 +- api_docs/licensing.mdx | 2 +- api_docs/lists.mdx | 2 +- api_docs/management.mdx | 2 +- api_docs/maps.mdx | 2 +- api_docs/maps_ems.mdx | 2 +- api_docs/ml.mdx | 2 +- api_docs/monitoring.mdx | 2 +- api_docs/monitoring_collection.mdx | 2 +- api_docs/navigation.mdx | 2 +- api_docs/newsfeed.mdx | 2 +- api_docs/observability.devdocs.json | 2 +- api_docs/observability.mdx | 2 +- api_docs/osquery.mdx | 2 +- api_docs/plugin_directory.mdx | 18 +- api_docs/presentation_util.mdx | 2 +- api_docs/profiling.mdx | 2 +- api_docs/remote_clusters.mdx | 2 +- api_docs/reporting.mdx | 2 +- api_docs/rollup.mdx | 2 +- api_docs/rule_registry.mdx | 2 +- api_docs/runtime_fields.mdx | 2 +- api_docs/saved_objects.mdx | 2 +- api_docs/saved_objects_finder.mdx | 2 +- api_docs/saved_objects_management.mdx | 2 +- api_docs/saved_objects_tagging.mdx | 2 +- api_docs/saved_objects_tagging_oss.mdx | 2 +- api_docs/saved_search.mdx | 2 +- api_docs/screenshot_mode.mdx | 2 +- api_docs/screenshotting.mdx | 2 +- api_docs/security.devdocs.json | 48 ++++ api_docs/security.mdx | 4 +- api_docs/security_solution.mdx | 2 +- api_docs/session_view.mdx | 2 +- api_docs/share.mdx | 2 +- api_docs/snapshot_restore.mdx | 2 +- api_docs/spaces.mdx | 2 +- api_docs/stack_alerts.mdx | 2 +- api_docs/stack_connectors.mdx | 2 +- api_docs/task_manager.mdx | 2 +- api_docs/telemetry.mdx | 2 +- api_docs/telemetry_collection_manager.mdx | 2 +- api_docs/telemetry_collection_xpack.mdx | 2 +- api_docs/telemetry_management_section.mdx | 2 +- api_docs/threat_intelligence.devdocs.json | 97 ++++++++ api_docs/threat_intelligence.mdx | 4 +- api_docs/timelines.mdx | 2 +- api_docs/transform.mdx | 2 +- api_docs/triggers_actions_ui.mdx | 2 +- api_docs/ui_actions.mdx | 2 +- api_docs/ui_actions_enhanced.mdx | 2 +- api_docs/unified_field_list.mdx | 2 +- api_docs/unified_search.mdx | 2 +- api_docs/unified_search_autocomplete.mdx | 2 +- api_docs/url_forwarding.mdx | 2 +- api_docs/usage_collection.mdx | 2 +- api_docs/ux.mdx | 2 +- api_docs/vis_default_editor.mdx | 2 +- api_docs/vis_type_gauge.mdx | 2 +- api_docs/vis_type_heatmap.mdx | 2 +- api_docs/vis_type_pie.mdx | 2 +- api_docs/vis_type_table.mdx | 2 +- api_docs/vis_type_timelion.mdx | 2 +- api_docs/vis_type_timeseries.mdx | 2 +- api_docs/vis_type_vega.mdx | 2 +- api_docs/vis_type_vislib.mdx | 2 +- api_docs/vis_type_xy.mdx | 2 +- api_docs/visualizations.mdx | 2 +- 420 files changed, 1134 insertions(+), 535 deletions(-) create mode 100644 api_docs/kbn_core_rendering_server_internal.devdocs.json create mode 100644 api_docs/kbn_core_rendering_server_internal.mdx create mode 100644 api_docs/kbn_core_rendering_server_mocks.devdocs.json create mode 100644 api_docs/kbn_core_rendering_server_mocks.mdx diff --git a/api_docs/actions.mdx b/api_docs/actions.mdx index 8c70634191ac9..f845dffebd44e 100644 --- a/api_docs/actions.mdx +++ b/api_docs/actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/actions title: "actions" image: https://source.unsplash.com/400x175/?github description: API docs for the actions plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'actions'] --- import actionsObj from './actions.devdocs.json'; diff --git a/api_docs/advanced_settings.mdx b/api_docs/advanced_settings.mdx index 5f708b7bd5b22..c624a6191fca4 100644 --- a/api_docs/advanced_settings.mdx +++ b/api_docs/advanced_settings.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/advancedSettings title: "advancedSettings" image: https://source.unsplash.com/400x175/?github description: API docs for the advancedSettings plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'advancedSettings'] --- import advancedSettingsObj from './advanced_settings.devdocs.json'; diff --git a/api_docs/aiops.mdx b/api_docs/aiops.mdx index affc8c08a679c..bc33359fc9b04 100644 --- a/api_docs/aiops.mdx +++ b/api_docs/aiops.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/aiops title: "aiops" image: https://source.unsplash.com/400x175/?github description: API docs for the aiops plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'aiops'] --- import aiopsObj from './aiops.devdocs.json'; diff --git a/api_docs/alerting.devdocs.json b/api_docs/alerting.devdocs.json index 9971fcee0a58a..e0cc3c68239e4 100644 --- a/api_docs/alerting.devdocs.json +++ b/api_docs/alerting.devdocs.json @@ -5085,7 +5085,7 @@ "label": "status", "description": [], "signature": [ - "\"error\" | \"warning\" | \"unknown\" | \"pending\" | \"ok\" | \"active\"" + "\"error\" | \"warning\" | \"unknown\" | \"pending\" | \"active\" | \"ok\"" ], "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, @@ -6055,7 +6055,7 @@ "label": "RuleExecutionStatuses", "description": [], "signature": [ - "\"error\" | \"warning\" | \"unknown\" | \"pending\" | \"ok\" | \"active\"" + "\"error\" | \"warning\" | \"unknown\" | \"pending\" | \"active\" | \"ok\"" ], "path": "x-pack/plugins/alerting/common/rule.ts", "deprecated": false, diff --git a/api_docs/alerting.mdx b/api_docs/alerting.mdx index dba2b5b8a03fa..e2fad1a8907bd 100644 --- a/api_docs/alerting.mdx +++ b/api_docs/alerting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/alerting title: "alerting" image: https://source.unsplash.com/400x175/?github description: API docs for the alerting plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'alerting'] --- import alertingObj from './alerting.devdocs.json'; diff --git a/api_docs/apm.mdx b/api_docs/apm.mdx index e327d3d9ef266..c5590b95953e3 100644 --- a/api_docs/apm.mdx +++ b/api_docs/apm.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/apm title: "apm" image: https://source.unsplash.com/400x175/?github description: API docs for the apm plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'apm'] --- import apmObj from './apm.devdocs.json'; diff --git a/api_docs/banners.mdx b/api_docs/banners.mdx index 8465fcc62bbbe..fde29f929d3e8 100644 --- a/api_docs/banners.mdx +++ b/api_docs/banners.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/banners title: "banners" image: https://source.unsplash.com/400x175/?github description: API docs for the banners plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'banners'] --- import bannersObj from './banners.devdocs.json'; diff --git a/api_docs/bfetch.mdx b/api_docs/bfetch.mdx index b4d39167b8afc..1b66b97f34a98 100644 --- a/api_docs/bfetch.mdx +++ b/api_docs/bfetch.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/bfetch title: "bfetch" image: https://source.unsplash.com/400x175/?github description: API docs for the bfetch plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'bfetch'] --- import bfetchObj from './bfetch.devdocs.json'; diff --git a/api_docs/canvas.mdx b/api_docs/canvas.mdx index 964d7c080f50e..72ab30c5fdb70 100644 --- a/api_docs/canvas.mdx +++ b/api_docs/canvas.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/canvas title: "canvas" image: https://source.unsplash.com/400x175/?github description: API docs for the canvas plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'canvas'] --- import canvasObj from './canvas.devdocs.json'; diff --git a/api_docs/cases.mdx b/api_docs/cases.mdx index 1b56bb9f2bb5a..2a80740fc9956 100644 --- a/api_docs/cases.mdx +++ b/api_docs/cases.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cases title: "cases" image: https://source.unsplash.com/400x175/?github description: API docs for the cases plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cases'] --- import casesObj from './cases.devdocs.json'; diff --git a/api_docs/charts.mdx b/api_docs/charts.mdx index 5971c6140b9d0..18c757fc09259 100644 --- a/api_docs/charts.mdx +++ b/api_docs/charts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/charts title: "charts" image: https://source.unsplash.com/400x175/?github description: API docs for the charts plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'charts'] --- import chartsObj from './charts.devdocs.json'; diff --git a/api_docs/cloud.mdx b/api_docs/cloud.mdx index 07a722e9fc63c..dce65a128457b 100644 --- a/api_docs/cloud.mdx +++ b/api_docs/cloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloud title: "cloud" image: https://source.unsplash.com/400x175/?github description: API docs for the cloud plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloud'] --- import cloudObj from './cloud.devdocs.json'; diff --git a/api_docs/cloud_experiments.mdx b/api_docs/cloud_experiments.mdx index 8cb19bfe8beb9..653ae1b77adef 100644 --- a/api_docs/cloud_experiments.mdx +++ b/api_docs/cloud_experiments.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudExperiments title: "cloudExperiments" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudExperiments plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudExperiments'] --- import cloudExperimentsObj from './cloud_experiments.devdocs.json'; diff --git a/api_docs/cloud_security_posture.mdx b/api_docs/cloud_security_posture.mdx index 6c31131981413..b3b220266009b 100644 --- a/api_docs/cloud_security_posture.mdx +++ b/api_docs/cloud_security_posture.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/cloudSecurityPosture title: "cloudSecurityPosture" image: https://source.unsplash.com/400x175/?github description: API docs for the cloudSecurityPosture plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'cloudSecurityPosture'] --- import cloudSecurityPostureObj from './cloud_security_posture.devdocs.json'; diff --git a/api_docs/console.mdx b/api_docs/console.mdx index 69717a2855de0..ac8e3d27f519b 100644 --- a/api_docs/console.mdx +++ b/api_docs/console.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/console title: "console" image: https://source.unsplash.com/400x175/?github description: API docs for the console plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'console'] --- import consoleObj from './console.devdocs.json'; diff --git a/api_docs/controls.mdx b/api_docs/controls.mdx index d120725ebf6c3..79cb9a68aae5d 100644 --- a/api_docs/controls.mdx +++ b/api_docs/controls.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/controls title: "controls" image: https://source.unsplash.com/400x175/?github description: API docs for the controls plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'controls'] --- import controlsObj from './controls.devdocs.json'; diff --git a/api_docs/core.devdocs.json b/api_docs/core.devdocs.json index 67fa46292601f..dc8c7cd70e377 100644 --- a/api_docs/core.devdocs.json +++ b/api_docs/core.devdocs.json @@ -29385,36 +29385,6 @@ ], "initialIsOpen": false }, - { - "parentPluginId": "core", - "id": "def-server.IRenderOptions", - "type": "Interface", - "tags": [], - "label": "IRenderOptions", - "description": [], - "path": "src/core/server/rendering/types.ts", - "deprecated": false, - "trackAdoption": false, - "children": [ - { - "parentPluginId": "core", - "id": "def-server.IRenderOptions.isAnonymousPage", - "type": "CompoundType", - "tags": [], - "label": "isAnonymousPage", - "description": [ - "\nSet whether the page is anonymous, which determines what plugins are enabled and whether to output user settings in the page metadata.\n`false` by default." - ], - "signature": [ - "boolean | undefined" - ], - "path": "src/core/server/rendering/types.ts", - "deprecated": false, - "trackAdoption": false - } - ], - "initialIsOpen": false - }, { "parentPluginId": "core", "id": "def-server.IRouter", diff --git a/api_docs/core.mdx b/api_docs/core.mdx index 2ce73b73214aa..48204394f0a85 100644 --- a/api_docs/core.mdx +++ b/api_docs/core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/core title: "core" image: https://source.unsplash.com/400x175/?github description: API docs for the core plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'core'] --- import coreObj from './core.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) for que | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 2688 | 0 | 30 | 0 | +| 2686 | 0 | 29 | 0 | ## Client diff --git a/api_docs/custom_integrations.mdx b/api_docs/custom_integrations.mdx index c9bd9c16e7214..dc07d39503245 100644 --- a/api_docs/custom_integrations.mdx +++ b/api_docs/custom_integrations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/customIntegrations title: "customIntegrations" image: https://source.unsplash.com/400x175/?github description: API docs for the customIntegrations plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'customIntegrations'] --- import customIntegrationsObj from './custom_integrations.devdocs.json'; diff --git a/api_docs/dashboard.mdx b/api_docs/dashboard.mdx index 7fa93934def07..464789c8cd191 100644 --- a/api_docs/dashboard.mdx +++ b/api_docs/dashboard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboard title: "dashboard" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboard plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboard'] --- import dashboardObj from './dashboard.devdocs.json'; diff --git a/api_docs/dashboard_enhanced.mdx b/api_docs/dashboard_enhanced.mdx index 4f0b134f643ca..5ad9b56b00576 100644 --- a/api_docs/dashboard_enhanced.mdx +++ b/api_docs/dashboard_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dashboardEnhanced title: "dashboardEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the dashboardEnhanced plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dashboardEnhanced'] --- import dashboardEnhancedObj from './dashboard_enhanced.devdocs.json'; diff --git a/api_docs/data.mdx b/api_docs/data.mdx index 3cda1cdf63de6..f88ba52d6af1a 100644 --- a/api_docs/data.mdx +++ b/api_docs/data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data title: "data" image: https://source.unsplash.com/400x175/?github description: API docs for the data plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data'] --- import dataObj from './data.devdocs.json'; diff --git a/api_docs/data_query.mdx b/api_docs/data_query.mdx index 09c0ef0d696bf..554f11d22eb36 100644 --- a/api_docs/data_query.mdx +++ b/api_docs/data_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-query title: "data.query" image: https://source.unsplash.com/400x175/?github description: API docs for the data.query plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.query'] --- import dataQueryObj from './data_query.devdocs.json'; diff --git a/api_docs/data_search.mdx b/api_docs/data_search.mdx index c3b582462fd7d..ef1fb301e167d 100644 --- a/api_docs/data_search.mdx +++ b/api_docs/data_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/data-search title: "data.search" image: https://source.unsplash.com/400x175/?github description: API docs for the data.search plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'data.search'] --- import dataSearchObj from './data_search.devdocs.json'; diff --git a/api_docs/data_view_editor.mdx b/api_docs/data_view_editor.mdx index 34855c7100cec..96deb16679bb1 100644 --- a/api_docs/data_view_editor.mdx +++ b/api_docs/data_view_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewEditor title: "dataViewEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewEditor plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewEditor'] --- import dataViewEditorObj from './data_view_editor.devdocs.json'; diff --git a/api_docs/data_view_field_editor.mdx b/api_docs/data_view_field_editor.mdx index 5daa20fdddfef..7935fdee2a27e 100644 --- a/api_docs/data_view_field_editor.mdx +++ b/api_docs/data_view_field_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewFieldEditor title: "dataViewFieldEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewFieldEditor plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewFieldEditor'] --- import dataViewFieldEditorObj from './data_view_field_editor.devdocs.json'; diff --git a/api_docs/data_view_management.mdx b/api_docs/data_view_management.mdx index fa43c84f3166a..18bcd0b3d6185 100644 --- a/api_docs/data_view_management.mdx +++ b/api_docs/data_view_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViewManagement title: "dataViewManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViewManagement plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViewManagement'] --- import dataViewManagementObj from './data_view_management.devdocs.json'; diff --git a/api_docs/data_views.mdx b/api_docs/data_views.mdx index 817619ccf49c9..4f324ce195e16 100644 --- a/api_docs/data_views.mdx +++ b/api_docs/data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataViews title: "dataViews" image: https://source.unsplash.com/400x175/?github description: API docs for the dataViews plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataViews'] --- import dataViewsObj from './data_views.devdocs.json'; diff --git a/api_docs/data_visualizer.mdx b/api_docs/data_visualizer.mdx index 6df0430d04c27..881f77c01491f 100644 --- a/api_docs/data_visualizer.mdx +++ b/api_docs/data_visualizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/dataVisualizer title: "dataVisualizer" image: https://source.unsplash.com/400x175/?github description: API docs for the dataVisualizer plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'dataVisualizer'] --- import dataVisualizerObj from './data_visualizer.devdocs.json'; diff --git a/api_docs/deprecations_by_api.mdx b/api_docs/deprecations_by_api.mdx index 3c34e78a89e7f..0f7512a8f4125 100644 --- a/api_docs/deprecations_by_api.mdx +++ b/api_docs/deprecations_by_api.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByApi slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-api title: Deprecated API usage by API description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_plugin.mdx b/api_docs/deprecations_by_plugin.mdx index abe2810379ec2..efddd344cdaca 100644 --- a/api_docs/deprecations_by_plugin.mdx +++ b/api_docs/deprecations_by_plugin.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsByPlugin slug: /kibana-dev-docs/api-meta/deprecated-api-list-by-plugin title: Deprecated API usage by plugin description: A list of deprecated APIs, which plugins are still referencing them, and when they need to be removed by. -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/deprecations_by_team.mdx b/api_docs/deprecations_by_team.mdx index 882abd4cb6150..a3a41ce841cf8 100644 --- a/api_docs/deprecations_by_team.mdx +++ b/api_docs/deprecations_by_team.mdx @@ -7,7 +7,7 @@ id: kibDevDocsDeprecationsDueByTeam slug: /kibana-dev-docs/api-meta/deprecations-due-by-team title: Deprecated APIs due to be removed, by team description: Lists the teams that are referencing deprecated APIs with a remove by date. -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- diff --git a/api_docs/dev_tools.mdx b/api_docs/dev_tools.mdx index 76be86527aaed..d294a02405563 100644 --- a/api_docs/dev_tools.mdx +++ b/api_docs/dev_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/devTools title: "devTools" image: https://source.unsplash.com/400x175/?github description: API docs for the devTools plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'devTools'] --- import devToolsObj from './dev_tools.devdocs.json'; diff --git a/api_docs/discover.mdx b/api_docs/discover.mdx index 9c9793bd2bcfb..ddbdd9e335bb0 100644 --- a/api_docs/discover.mdx +++ b/api_docs/discover.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discover title: "discover" image: https://source.unsplash.com/400x175/?github description: API docs for the discover plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discover'] --- import discoverObj from './discover.devdocs.json'; diff --git a/api_docs/discover_enhanced.mdx b/api_docs/discover_enhanced.mdx index bc90ebb67c09b..4cb08ffc96f64 100644 --- a/api_docs/discover_enhanced.mdx +++ b/api_docs/discover_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/discoverEnhanced title: "discoverEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the discoverEnhanced plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'discoverEnhanced'] --- import discoverEnhancedObj from './discover_enhanced.devdocs.json'; diff --git a/api_docs/embeddable.mdx b/api_docs/embeddable.mdx index 4c4b222a02a7c..1559a8abc24e3 100644 --- a/api_docs/embeddable.mdx +++ b/api_docs/embeddable.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddable title: "embeddable" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddable plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddable'] --- import embeddableObj from './embeddable.devdocs.json'; diff --git a/api_docs/embeddable_enhanced.mdx b/api_docs/embeddable_enhanced.mdx index 5ab0874911417..ecf61f0f91ce3 100644 --- a/api_docs/embeddable_enhanced.mdx +++ b/api_docs/embeddable_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/embeddableEnhanced title: "embeddableEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the embeddableEnhanced plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'embeddableEnhanced'] --- import embeddableEnhancedObj from './embeddable_enhanced.devdocs.json'; diff --git a/api_docs/encrypted_saved_objects.mdx b/api_docs/encrypted_saved_objects.mdx index 5aa1eae40de53..ba3a3d8c876df 100644 --- a/api_docs/encrypted_saved_objects.mdx +++ b/api_docs/encrypted_saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/encryptedSavedObjects title: "encryptedSavedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the encryptedSavedObjects plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'encryptedSavedObjects'] --- import encryptedSavedObjectsObj from './encrypted_saved_objects.devdocs.json'; diff --git a/api_docs/enterprise_search.mdx b/api_docs/enterprise_search.mdx index ba021086e294a..276207b2a393d 100644 --- a/api_docs/enterprise_search.mdx +++ b/api_docs/enterprise_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/enterpriseSearch title: "enterpriseSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the enterpriseSearch plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'enterpriseSearch'] --- import enterpriseSearchObj from './enterprise_search.devdocs.json'; diff --git a/api_docs/es_ui_shared.mdx b/api_docs/es_ui_shared.mdx index bf991156be210..43d88ac7e1ed8 100644 --- a/api_docs/es_ui_shared.mdx +++ b/api_docs/es_ui_shared.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/esUiShared title: "esUiShared" image: https://source.unsplash.com/400x175/?github description: API docs for the esUiShared plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'esUiShared'] --- import esUiSharedObj from './es_ui_shared.devdocs.json'; diff --git a/api_docs/event_annotation.mdx b/api_docs/event_annotation.mdx index 2c09bd5d65d46..73c8ebf1471bd 100644 --- a/api_docs/event_annotation.mdx +++ b/api_docs/event_annotation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventAnnotation title: "eventAnnotation" image: https://source.unsplash.com/400x175/?github description: API docs for the eventAnnotation plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventAnnotation'] --- import eventAnnotationObj from './event_annotation.devdocs.json'; diff --git a/api_docs/event_log.devdocs.json b/api_docs/event_log.devdocs.json index 8361059c91079..b4c0370096cc2 100644 --- a/api_docs/event_log.devdocs.json +++ b/api_docs/event_log.devdocs.json @@ -1312,7 +1312,7 @@ "label": "data", "description": [], "signature": [ - "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" + "(Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined)[]" ], "path": "x-pack/plugins/event_log/server/es/cluster_client_adapter.ts", "deprecated": false, @@ -1332,7 +1332,7 @@ "label": "IEvent", "description": [], "signature": [ - "DeepPartial<DeepWriteable<Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" + "DeepPartial<DeepWriteable<Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}>>> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, @@ -1347,7 +1347,7 @@ "label": "IValidatedEvent", "description": [], "signature": [ - "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ recovered?: string | number | undefined; active?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" + "Readonly<{ error?: Readonly<{ type?: string | undefined; id?: string | undefined; message?: string | undefined; code?: string | undefined; stack_trace?: string | undefined; } & {}> | undefined; tags?: string[] | undefined; log?: Readonly<{ logger?: string | undefined; level?: string | undefined; } & {}> | undefined; user?: Readonly<{ name?: string | undefined; } & {}> | undefined; message?: string | undefined; kibana?: Readonly<{ alert?: Readonly<{ rule?: Readonly<{ consumer?: string | undefined; execution?: Readonly<{ status?: string | undefined; metrics?: Readonly<{ number_of_triggered_actions?: string | number | undefined; number_of_generated_actions?: string | number | undefined; alert_counts?: Readonly<{ active?: string | number | undefined; recovered?: string | number | undefined; new?: string | number | undefined; } & {}> | undefined; number_of_searches?: string | number | undefined; total_indexing_duration_ms?: string | number | undefined; es_search_duration_ms?: string | number | undefined; total_search_duration_ms?: string | number | undefined; execution_gap_duration_s?: string | number | undefined; rule_type_run_duration_ms?: string | number | undefined; process_alerts_duration_ms?: string | number | undefined; trigger_actions_duration_ms?: string | number | undefined; process_rule_duration_ms?: string | number | undefined; claim_to_start_duration_ms?: string | number | undefined; prepare_rule_duration_ms?: string | number | undefined; total_run_duration_ms?: string | number | undefined; total_enrichment_duration_ms?: string | number | undefined; } & {}> | undefined; uuid?: string | undefined; status_order?: string | number | undefined; } & {}> | undefined; rule_type_id?: string | undefined; } & {}> | undefined; } & {}> | undefined; version?: string | undefined; alerting?: Readonly<{ status?: string | undefined; instance_id?: string | undefined; action_group_id?: string | undefined; action_subgroup?: string | undefined; } & {}> | undefined; server_uuid?: string | undefined; task?: Readonly<{ id?: string | undefined; schedule_delay?: string | number | undefined; scheduled?: string | undefined; } & {}> | undefined; saved_objects?: Readonly<{ type?: string | undefined; id?: string | undefined; namespace?: string | undefined; rel?: string | undefined; type_id?: string | undefined; } & {}>[] | undefined; space_ids?: string[] | undefined; } & {}> | undefined; ecs?: Readonly<{ version?: string | undefined; } & {}> | undefined; rule?: Readonly<{ name?: string | undefined; description?: string | undefined; category?: string | undefined; id?: string | undefined; version?: string | undefined; license?: string | undefined; reference?: string | undefined; author?: string[] | undefined; ruleset?: string | undefined; uuid?: string | undefined; } & {}> | undefined; event?: Readonly<{ start?: string | undefined; category?: string[] | undefined; type?: string[] | undefined; id?: string | undefined; created?: string | undefined; outcome?: string | undefined; end?: string | undefined; original?: string | undefined; duration?: string | number | undefined; code?: string | undefined; url?: string | undefined; action?: string | undefined; kind?: string | undefined; hash?: string | undefined; severity?: string | number | undefined; dataset?: string | undefined; ingested?: string | undefined; module?: string | undefined; provider?: string | undefined; reason?: string | undefined; reference?: string | undefined; risk_score?: number | undefined; risk_score_norm?: number | undefined; sequence?: string | number | undefined; timezone?: string | undefined; } & {}> | undefined; '@timestamp'?: string | undefined; } & {}> | undefined" ], "path": "x-pack/plugins/event_log/generated/schemas.ts", "deprecated": false, diff --git a/api_docs/event_log.mdx b/api_docs/event_log.mdx index 482e1d361a157..140f35dea7cc4 100644 --- a/api_docs/event_log.mdx +++ b/api_docs/event_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/eventLog title: "eventLog" image: https://source.unsplash.com/400x175/?github description: API docs for the eventLog plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'eventLog'] --- import eventLogObj from './event_log.devdocs.json'; diff --git a/api_docs/expression_error.mdx b/api_docs/expression_error.mdx index 2ab9af9772d04..5d2d5c6c99e07 100644 --- a/api_docs/expression_error.mdx +++ b/api_docs/expression_error.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionError title: "expressionError" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionError plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionError'] --- import expressionErrorObj from './expression_error.devdocs.json'; diff --git a/api_docs/expression_gauge.mdx b/api_docs/expression_gauge.mdx index 908d90469c751..184191c6cd298 100644 --- a/api_docs/expression_gauge.mdx +++ b/api_docs/expression_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionGauge title: "expressionGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionGauge plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionGauge'] --- import expressionGaugeObj from './expression_gauge.devdocs.json'; diff --git a/api_docs/expression_heatmap.mdx b/api_docs/expression_heatmap.mdx index aa2ac31f046ec..205be987e15e5 100644 --- a/api_docs/expression_heatmap.mdx +++ b/api_docs/expression_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionHeatmap title: "expressionHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionHeatmap plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionHeatmap'] --- import expressionHeatmapObj from './expression_heatmap.devdocs.json'; diff --git a/api_docs/expression_image.mdx b/api_docs/expression_image.mdx index fd4412ad3f9e9..9d60a46814616 100644 --- a/api_docs/expression_image.mdx +++ b/api_docs/expression_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionImage title: "expressionImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionImage plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionImage'] --- import expressionImageObj from './expression_image.devdocs.json'; diff --git a/api_docs/expression_legacy_metric_vis.mdx b/api_docs/expression_legacy_metric_vis.mdx index 4f1bd9723f36a..b44d904cdc0e5 100644 --- a/api_docs/expression_legacy_metric_vis.mdx +++ b/api_docs/expression_legacy_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionLegacyMetricVis title: "expressionLegacyMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionLegacyMetricVis plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionLegacyMetricVis'] --- import expressionLegacyMetricVisObj from './expression_legacy_metric_vis.devdocs.json'; diff --git a/api_docs/expression_metric.mdx b/api_docs/expression_metric.mdx index d17081eed3cbf..22fc0e2475ccd 100644 --- a/api_docs/expression_metric.mdx +++ b/api_docs/expression_metric.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetric title: "expressionMetric" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetric plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetric'] --- import expressionMetricObj from './expression_metric.devdocs.json'; diff --git a/api_docs/expression_metric_vis.mdx b/api_docs/expression_metric_vis.mdx index daa61ecf37e7b..fd1807af21b02 100644 --- a/api_docs/expression_metric_vis.mdx +++ b/api_docs/expression_metric_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionMetricVis title: "expressionMetricVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionMetricVis plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionMetricVis'] --- import expressionMetricVisObj from './expression_metric_vis.devdocs.json'; diff --git a/api_docs/expression_partition_vis.mdx b/api_docs/expression_partition_vis.mdx index aaf66fd40fa4e..e0264ca7b2d72 100644 --- a/api_docs/expression_partition_vis.mdx +++ b/api_docs/expression_partition_vis.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionPartitionVis title: "expressionPartitionVis" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionPartitionVis plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionPartitionVis'] --- import expressionPartitionVisObj from './expression_partition_vis.devdocs.json'; diff --git a/api_docs/expression_repeat_image.mdx b/api_docs/expression_repeat_image.mdx index 8e5467dc9cff3..071e730b126d5 100644 --- a/api_docs/expression_repeat_image.mdx +++ b/api_docs/expression_repeat_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRepeatImage title: "expressionRepeatImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRepeatImage plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRepeatImage'] --- import expressionRepeatImageObj from './expression_repeat_image.devdocs.json'; diff --git a/api_docs/expression_reveal_image.mdx b/api_docs/expression_reveal_image.mdx index a2a032c0c0bde..b964071af3c95 100644 --- a/api_docs/expression_reveal_image.mdx +++ b/api_docs/expression_reveal_image.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionRevealImage title: "expressionRevealImage" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionRevealImage plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionRevealImage'] --- import expressionRevealImageObj from './expression_reveal_image.devdocs.json'; diff --git a/api_docs/expression_shape.mdx b/api_docs/expression_shape.mdx index e650bf57eac7f..3fb4a8e6e6545 100644 --- a/api_docs/expression_shape.mdx +++ b/api_docs/expression_shape.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionShape title: "expressionShape" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionShape plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionShape'] --- import expressionShapeObj from './expression_shape.devdocs.json'; diff --git a/api_docs/expression_tagcloud.mdx b/api_docs/expression_tagcloud.mdx index cf00311b239a8..4c84f9b680739 100644 --- a/api_docs/expression_tagcloud.mdx +++ b/api_docs/expression_tagcloud.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionTagcloud title: "expressionTagcloud" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionTagcloud plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionTagcloud'] --- import expressionTagcloudObj from './expression_tagcloud.devdocs.json'; diff --git a/api_docs/expression_x_y.mdx b/api_docs/expression_x_y.mdx index 0eed4b0b713d0..8b894b45d78d9 100644 --- a/api_docs/expression_x_y.mdx +++ b/api_docs/expression_x_y.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressionXY title: "expressionXY" image: https://source.unsplash.com/400x175/?github description: API docs for the expressionXY plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressionXY'] --- import expressionXYObj from './expression_x_y.devdocs.json'; diff --git a/api_docs/expressions.mdx b/api_docs/expressions.mdx index 353dfa02a7830..050f57af12aab 100644 --- a/api_docs/expressions.mdx +++ b/api_docs/expressions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/expressions title: "expressions" image: https://source.unsplash.com/400x175/?github description: API docs for the expressions plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'expressions'] --- import expressionsObj from './expressions.devdocs.json'; diff --git a/api_docs/features.devdocs.json b/api_docs/features.devdocs.json index a6e9773502284..7f9018f5e89ce 100644 --- a/api_docs/features.devdocs.json +++ b/api_docs/features.devdocs.json @@ -64,7 +64,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -1193,7 +1193,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -2872,7 +2872,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>[] | undefined; privilegesTooltip?: string | undefined; reserved?: Readonly<{ description: string; privileges: readonly Readonly<{ id: string; privilege: Readonly<{ excludeFromBasePrivileges?: boolean | undefined; requireAllSpaces?: boolean | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; api?: readonly string[] | undefined; app?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; ui: readonly string[]; }>; }>[]; }> | undefined; }>" ], "path": "x-pack/plugins/features/common/kibana_feature.ts", "deprecated": false, @@ -3122,7 +3122,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }>" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -3159,7 +3159,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, @@ -3181,7 +3181,7 @@ "section": "def-common.SubFeaturePrivilegeGroupType", "text": "SubFeaturePrivilegeGroupType" }, - "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }" + "; privileges: readonly Readonly<{ id: string; name: string; includeIn: \"none\" | \"all\" | \"read\"; minimumLicense?: \"basic\" | \"standard\" | \"gold\" | \"platinum\" | \"enterprise\" | \"trial\" | undefined; disabled?: boolean | undefined; management?: Readonly<{ [x: string]: readonly string[]; }> | undefined; cases?: Readonly<{ all?: readonly string[] | undefined; push?: readonly string[] | undefined; create?: readonly string[] | undefined; read?: readonly string[] | undefined; update?: readonly string[] | undefined; delete?: readonly string[] | undefined; }> | undefined; catalogue?: readonly string[] | undefined; alerting?: Readonly<{ rule?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; alert?: Readonly<{ all?: readonly string[] | undefined; read?: readonly string[] | undefined; }> | undefined; }> | undefined; ui: readonly string[]; app?: readonly string[] | undefined; requireAllSpaces?: boolean | undefined; api?: readonly string[] | undefined; savedObject: Readonly<{ all: readonly string[]; read: readonly string[]; }>; }>[]; }>[]; }" ], "path": "x-pack/plugins/features/common/sub_feature.ts", "deprecated": false, diff --git a/api_docs/features.mdx b/api_docs/features.mdx index 8b7f40f4a359b..513017f35056a 100644 --- a/api_docs/features.mdx +++ b/api_docs/features.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/features title: "features" image: https://source.unsplash.com/400x175/?github description: API docs for the features plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'features'] --- import featuresObj from './features.devdocs.json'; diff --git a/api_docs/field_formats.mdx b/api_docs/field_formats.mdx index 040d820604007..b49361795cb22 100644 --- a/api_docs/field_formats.mdx +++ b/api_docs/field_formats.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fieldFormats title: "fieldFormats" image: https://source.unsplash.com/400x175/?github description: API docs for the fieldFormats plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fieldFormats'] --- import fieldFormatsObj from './field_formats.devdocs.json'; diff --git a/api_docs/file_upload.mdx b/api_docs/file_upload.mdx index 768f6188371fc..c431e9780deb9 100644 --- a/api_docs/file_upload.mdx +++ b/api_docs/file_upload.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fileUpload title: "fileUpload" image: https://source.unsplash.com/400x175/?github description: API docs for the fileUpload plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fileUpload'] --- import fileUploadObj from './file_upload.devdocs.json'; diff --git a/api_docs/files.mdx b/api_docs/files.mdx index 8491d90df2446..ee3ba1cdfdacf 100644 --- a/api_docs/files.mdx +++ b/api_docs/files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/files title: "files" image: https://source.unsplash.com/400x175/?github description: API docs for the files plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'files'] --- import filesObj from './files.devdocs.json'; diff --git a/api_docs/fleet.devdocs.json b/api_docs/fleet.devdocs.json index 59f0a3a2c3c98..76458c52782a6 100644 --- a/api_docs/fleet.devdocs.json +++ b/api_docs/fleet.devdocs.json @@ -8390,7 +8390,7 @@ "label": "status", "description": [], "signature": [ - "\"active\" | \"inactive\"" + "\"inactive\" | \"active\"" ], "path": "x-pack/plugins/fleet/common/types/models/agent_policy.ts", "deprecated": false, diff --git a/api_docs/fleet.mdx b/api_docs/fleet.mdx index b63495e177924..0dc50255cea65 100644 --- a/api_docs/fleet.mdx +++ b/api_docs/fleet.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/fleet title: "fleet" image: https://source.unsplash.com/400x175/?github description: API docs for the fleet plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'fleet'] --- import fleetObj from './fleet.devdocs.json'; diff --git a/api_docs/global_search.mdx b/api_docs/global_search.mdx index 97d12a56906f0..b4fff902dfabe 100644 --- a/api_docs/global_search.mdx +++ b/api_docs/global_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/globalSearch title: "globalSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the globalSearch plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'globalSearch'] --- import globalSearchObj from './global_search.devdocs.json'; diff --git a/api_docs/guided_onboarding.devdocs.json b/api_docs/guided_onboarding.devdocs.json index 27b0b8446a7d8..8b44efa700b66 100644 --- a/api_docs/guided_onboarding.devdocs.json +++ b/api_docs/guided_onboarding.devdocs.json @@ -6,44 +6,111 @@ "interfaces": [ { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuidedOnboardingState", + "id": "def-public.GuideState", "type": "Interface", "tags": [], - "label": "GuidedOnboardingState", + "label": "GuideState", "description": [], - "path": "src/plugins/guided_onboarding/public/types.ts", + "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, "trackAdoption": false, "children": [ { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuidedOnboardingState.activeGuide", + "id": "def-public.GuideState.guideId", "type": "CompoundType", "tags": [], - "label": "activeGuide", + "label": "guideId", "description": [], "signature": [ - { - "pluginId": "guidedOnboarding", - "scope": "public", - "docId": "kibGuidedOnboardingPluginApi", - "section": "def-public.UseCase", - "text": "UseCase" - }, - " | \"unset\"" + "\"search\" | \"security\" | \"observability\"" ], - "path": "src/plugins/guided_onboarding/public/types.ts", + "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, "trackAdoption": false }, { "parentPluginId": "guidedOnboarding", - "id": "def-public.GuidedOnboardingState.activeStep", - "type": "string", + "id": "def-public.GuideState.status", + "type": "CompoundType", + "tags": [], + "label": "status", + "description": [], + "signature": [ + "\"complete\" | \"in_progress\" | \"ready_to_complete\"" + ], + "path": "src/plugins/guided_onboarding/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuideState.isActive", + "type": "CompoundType", + "tags": [], + "label": "isActive", + "description": [], + "signature": [ + "boolean | undefined" + ], + "path": "src/plugins/guided_onboarding/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuideState.steps", + "type": "Array", + "tags": [], + "label": "steps", + "description": [], + "signature": [ + "GuideStep", + "[]" + ], + "path": "src/plugins/guided_onboarding/common/types.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuideStep", + "type": "Interface", + "tags": [], + "label": "GuideStep", + "description": [], + "path": "src/plugins/guided_onboarding/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuideStep.id", + "type": "CompoundType", + "tags": [], + "label": "id", + "description": [], + "signature": [ + "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alerts\" | \"cases\" | \"browse_docs\" | \"search_experience\"" + ], + "path": "src/plugins/guided_onboarding/common/types.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuideStep.status", + "type": "CompoundType", "tags": [], - "label": "activeStep", + "label": "status", "description": [], - "path": "src/plugins/guided_onboarding/public/types.ts", + "signature": [ + "\"complete\" | \"in_progress\" | \"inactive\" | \"active\"" + ], + "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, "trackAdoption": false } @@ -55,21 +122,93 @@ "misc": [ { "parentPluginId": "guidedOnboarding", - "id": "def-public.UseCase", + "id": "def-public.GuideId", "type": "Type", "tags": [], - "label": "UseCase", + "label": "GuideId", "description": [], "signature": [ "\"search\" | \"security\" | \"observability\"" ], - "path": "src/plugins/guided_onboarding/public/types.ts", + "path": "src/plugins/guided_onboarding/common/types.ts", + "deprecated": false, + "trackAdoption": false, + "initialIsOpen": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.GuideStepIds", + "type": "Type", + "tags": [], + "label": "GuideStepIds", + "description": [], + "signature": [ + "\"add_data\" | \"view_dashboard\" | \"tour_observability\" | \"rules\" | \"alerts\" | \"cases\" | \"browse_docs\" | \"search_experience\"" + ], + "path": "src/plugins/guided_onboarding/common/types.ts", "deprecated": false, "trackAdoption": false, "initialIsOpen": false } ], - "objects": [], + "objects": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.guidesConfig", + "type": "Object", + "tags": [], + "label": "guidesConfig", + "description": [], + "path": "src/plugins/guided_onboarding/public/constants/guides_config/index.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.guidesConfig.security", + "type": "Object", + "tags": [], + "label": "security", + "description": [], + "signature": [ + "GuideConfig" + ], + "path": "src/plugins/guided_onboarding/public/constants/guides_config/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.guidesConfig.observability", + "type": "Object", + "tags": [], + "label": "observability", + "description": [], + "signature": [ + "GuideConfig" + ], + "path": "src/plugins/guided_onboarding/public/constants/guides_config/index.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "guidedOnboarding", + "id": "def-public.guidesConfig.search", + "type": "Object", + "tags": [], + "label": "search", + "description": [], + "signature": [ + "GuideConfig" + ], + "path": "src/plugins/guided_onboarding/public/constants/guides_config/index.ts", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false + } + ], "setup": { "parentPluginId": "guidedOnboarding", "id": "def-public.GuidedOnboardingPluginSetup", @@ -156,53 +295,7 @@ "functions": [], "interfaces": [], "enums": [], - "misc": [ - { - "parentPluginId": "guidedOnboarding", - "id": "def-common.API_BASE_PATH", - "type": "string", - "tags": [], - "label": "API_BASE_PATH", - "description": [], - "signature": [ - "\"/api/guided_onboarding\"" - ], - "path": "src/plugins/guided_onboarding/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "guidedOnboarding", - "id": "def-common.PLUGIN_ID", - "type": "string", - "tags": [], - "label": "PLUGIN_ID", - "description": [], - "signature": [ - "\"guidedOnboarding\"" - ], - "path": "src/plugins/guided_onboarding/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - }, - { - "parentPluginId": "guidedOnboarding", - "id": "def-common.PLUGIN_NAME", - "type": "string", - "tags": [], - "label": "PLUGIN_NAME", - "description": [], - "signature": [ - "\"guidedOnboarding\"" - ], - "path": "src/plugins/guided_onboarding/common/index.ts", - "deprecated": false, - "trackAdoption": false, - "initialIsOpen": false - } - ], + "misc": [], "objects": [] } } \ No newline at end of file diff --git a/api_docs/guided_onboarding.mdx b/api_docs/guided_onboarding.mdx index 899b3205a819c..9aa86b82df100 100644 --- a/api_docs/guided_onboarding.mdx +++ b/api_docs/guided_onboarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/guidedOnboarding title: "guidedOnboarding" image: https://source.unsplash.com/400x175/?github description: API docs for the guidedOnboarding plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'guidedOnboarding'] --- import guidedOnboardingObj from './guided_onboarding.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onbo | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 12 | 0 | 12 | 1 | +| 19 | 0 | 19 | 3 | ## Client @@ -31,6 +31,9 @@ Contact [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onbo ### Start <DocDefinitionList data={[guidedOnboardingObj.client.start]}/> +### Objects +<DocDefinitionList data={guidedOnboardingObj.client.objects}/> + ### Interfaces <DocDefinitionList data={guidedOnboardingObj.client.interfaces}/> @@ -45,8 +48,3 @@ Contact [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onbo ### Start <DocDefinitionList data={[guidedOnboardingObj.server.start]}/> -## Common - -### Consts, variables and types -<DocDefinitionList data={guidedOnboardingObj.common.misc}/> - diff --git a/api_docs/home.mdx b/api_docs/home.mdx index 30beb41d12fae..4c129f6d92a48 100644 --- a/api_docs/home.mdx +++ b/api_docs/home.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/home title: "home" image: https://source.unsplash.com/400x175/?github description: API docs for the home plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'home'] --- import homeObj from './home.devdocs.json'; diff --git a/api_docs/index_lifecycle_management.mdx b/api_docs/index_lifecycle_management.mdx index e3ee1798496d0..d6393f4b44397 100644 --- a/api_docs/index_lifecycle_management.mdx +++ b/api_docs/index_lifecycle_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexLifecycleManagement title: "indexLifecycleManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexLifecycleManagement plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexLifecycleManagement'] --- import indexLifecycleManagementObj from './index_lifecycle_management.devdocs.json'; diff --git a/api_docs/index_management.mdx b/api_docs/index_management.mdx index 34f9d8fcde852..b7f14de7ee103 100644 --- a/api_docs/index_management.mdx +++ b/api_docs/index_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/indexManagement title: "indexManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the indexManagement plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'indexManagement'] --- import indexManagementObj from './index_management.devdocs.json'; diff --git a/api_docs/infra.mdx b/api_docs/infra.mdx index 7ab9b9d8573c5..ea6ecce3d4d9b 100644 --- a/api_docs/infra.mdx +++ b/api_docs/infra.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/infra title: "infra" image: https://source.unsplash.com/400x175/?github description: API docs for the infra plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'infra'] --- import infraObj from './infra.devdocs.json'; diff --git a/api_docs/inspector.mdx b/api_docs/inspector.mdx index 4cb29a4632cab..c30aea80c5cb0 100644 --- a/api_docs/inspector.mdx +++ b/api_docs/inspector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/inspector title: "inspector" image: https://source.unsplash.com/400x175/?github description: API docs for the inspector plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'inspector'] --- import inspectorObj from './inspector.devdocs.json'; diff --git a/api_docs/interactive_setup.mdx b/api_docs/interactive_setup.mdx index 6f257bb854320..f4a2df60fa9ea 100644 --- a/api_docs/interactive_setup.mdx +++ b/api_docs/interactive_setup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/interactiveSetup title: "interactiveSetup" image: https://source.unsplash.com/400x175/?github description: API docs for the interactiveSetup plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'interactiveSetup'] --- import interactiveSetupObj from './interactive_setup.devdocs.json'; diff --git a/api_docs/kbn_ace.mdx b/api_docs/kbn_ace.mdx index 0dcf9a1b797bc..0dcbd76b6b451 100644 --- a/api_docs/kbn_ace.mdx +++ b/api_docs/kbn_ace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ace title: "@kbn/ace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ace plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ace'] --- import kbnAceObj from './kbn_ace.devdocs.json'; diff --git a/api_docs/kbn_aiops_components.mdx b/api_docs/kbn_aiops_components.mdx index 60ab8f506c3a5..ac530589f0fb0 100644 --- a/api_docs/kbn_aiops_components.mdx +++ b/api_docs/kbn_aiops_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-components title: "@kbn/aiops-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-components plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-components'] --- import kbnAiopsComponentsObj from './kbn_aiops_components.devdocs.json'; diff --git a/api_docs/kbn_aiops_utils.mdx b/api_docs/kbn_aiops_utils.mdx index c459ac8369da3..c87bec0a9bb9d 100644 --- a/api_docs/kbn_aiops_utils.mdx +++ b/api_docs/kbn_aiops_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-aiops-utils title: "@kbn/aiops-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/aiops-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/aiops-utils'] --- import kbnAiopsUtilsObj from './kbn_aiops_utils.devdocs.json'; diff --git a/api_docs/kbn_alerts.mdx b/api_docs/kbn_alerts.mdx index aaf236aab90f0..eb196b2621aed 100644 --- a/api_docs/kbn_alerts.mdx +++ b/api_docs/kbn_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-alerts title: "@kbn/alerts" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/alerts plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/alerts'] --- import kbnAlertsObj from './kbn_alerts.devdocs.json'; diff --git a/api_docs/kbn_analytics.mdx b/api_docs/kbn_analytics.mdx index 35df4e16f17cd..bc4a0560544de 100644 --- a/api_docs/kbn_analytics.mdx +++ b/api_docs/kbn_analytics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics title: "@kbn/analytics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics'] --- import kbnAnalyticsObj from './kbn_analytics.devdocs.json'; diff --git a/api_docs/kbn_analytics_client.mdx b/api_docs/kbn_analytics_client.mdx index 0d18980e0b1c0..a86d78bf6e573 100644 --- a/api_docs/kbn_analytics_client.mdx +++ b/api_docs/kbn_analytics_client.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-client title: "@kbn/analytics-client" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-client plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-client'] --- import kbnAnalyticsClientObj from './kbn_analytics_client.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx index c4b9427b1a333..42bf70404bb69 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-browser title: "@kbn/analytics-shippers-elastic-v3-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-browser'] --- import kbnAnalyticsShippersElasticV3BrowserObj from './kbn_analytics_shippers_elastic_v3_browser.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx index f06cb724cc729..a3faccdf22fce 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-common title: "@kbn/analytics-shippers-elastic-v3-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-common'] --- import kbnAnalyticsShippersElasticV3CommonObj from './kbn_analytics_shippers_elastic_v3_common.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx index cd5176593f6ad..263d6af109410 100644 --- a/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx +++ b/api_docs/kbn_analytics_shippers_elastic_v3_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-elastic-v3-server title: "@kbn/analytics-shippers-elastic-v3-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-elastic-v3-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-elastic-v3-server'] --- import kbnAnalyticsShippersElasticV3ServerObj from './kbn_analytics_shippers_elastic_v3_server.devdocs.json'; diff --git a/api_docs/kbn_analytics_shippers_fullstory.mdx b/api_docs/kbn_analytics_shippers_fullstory.mdx index d6317c64bf48c..1f272ffdf3eca 100644 --- a/api_docs/kbn_analytics_shippers_fullstory.mdx +++ b/api_docs/kbn_analytics_shippers_fullstory.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-analytics-shippers-fullstory title: "@kbn/analytics-shippers-fullstory" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/analytics-shippers-fullstory plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/analytics-shippers-fullstory'] --- import kbnAnalyticsShippersFullstoryObj from './kbn_analytics_shippers_fullstory.devdocs.json'; diff --git a/api_docs/kbn_apm_config_loader.mdx b/api_docs/kbn_apm_config_loader.mdx index e916c19f16197..9137a34ac7cf5 100644 --- a/api_docs/kbn_apm_config_loader.mdx +++ b/api_docs/kbn_apm_config_loader.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-config-loader title: "@kbn/apm-config-loader" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-config-loader plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-config-loader'] --- import kbnApmConfigLoaderObj from './kbn_apm_config_loader.devdocs.json'; diff --git a/api_docs/kbn_apm_synthtrace.mdx b/api_docs/kbn_apm_synthtrace.mdx index 9ba0d5aeeacf4..69b4ee9c3bd01 100644 --- a/api_docs/kbn_apm_synthtrace.mdx +++ b/api_docs/kbn_apm_synthtrace.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-synthtrace title: "@kbn/apm-synthtrace" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-synthtrace plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-synthtrace'] --- import kbnApmSynthtraceObj from './kbn_apm_synthtrace.devdocs.json'; diff --git a/api_docs/kbn_apm_utils.mdx b/api_docs/kbn_apm_utils.mdx index ca208a21625bd..e8948c5151a3e 100644 --- a/api_docs/kbn_apm_utils.mdx +++ b/api_docs/kbn_apm_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-apm-utils title: "@kbn/apm-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/apm-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/apm-utils'] --- import kbnApmUtilsObj from './kbn_apm_utils.devdocs.json'; diff --git a/api_docs/kbn_axe_config.mdx b/api_docs/kbn_axe_config.mdx index 7a1ad1863e07b..1660f71a7d421 100644 --- a/api_docs/kbn_axe_config.mdx +++ b/api_docs/kbn_axe_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-axe-config title: "@kbn/axe-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/axe-config plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/axe-config'] --- import kbnAxeConfigObj from './kbn_axe_config.devdocs.json'; diff --git a/api_docs/kbn_chart_icons.mdx b/api_docs/kbn_chart_icons.mdx index 6e102733b051b..548fc0c4f7426 100644 --- a/api_docs/kbn_chart_icons.mdx +++ b/api_docs/kbn_chart_icons.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-chart-icons title: "@kbn/chart-icons" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/chart-icons plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/chart-icons'] --- import kbnChartIconsObj from './kbn_chart_icons.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_core.mdx b/api_docs/kbn_ci_stats_core.mdx index cfd53f8e091ff..b57e2abe5c1d7 100644 --- a/api_docs/kbn_ci_stats_core.mdx +++ b/api_docs/kbn_ci_stats_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-core title: "@kbn/ci-stats-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-core plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-core'] --- import kbnCiStatsCoreObj from './kbn_ci_stats_core.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_performance_metrics.mdx b/api_docs/kbn_ci_stats_performance_metrics.mdx index c28b07464ba41..f8182e50d0e14 100644 --- a/api_docs/kbn_ci_stats_performance_metrics.mdx +++ b/api_docs/kbn_ci_stats_performance_metrics.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-performance-metrics title: "@kbn/ci-stats-performance-metrics" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-performance-metrics plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-performance-metrics'] --- import kbnCiStatsPerformanceMetricsObj from './kbn_ci_stats_performance_metrics.devdocs.json'; diff --git a/api_docs/kbn_ci_stats_reporter.mdx b/api_docs/kbn_ci_stats_reporter.mdx index d5c55b31b68c0..87058eea7d0c7 100644 --- a/api_docs/kbn_ci_stats_reporter.mdx +++ b/api_docs/kbn_ci_stats_reporter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ci-stats-reporter title: "@kbn/ci-stats-reporter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ci-stats-reporter plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ci-stats-reporter'] --- import kbnCiStatsReporterObj from './kbn_ci_stats_reporter.devdocs.json'; diff --git a/api_docs/kbn_cli_dev_mode.mdx b/api_docs/kbn_cli_dev_mode.mdx index c2de5353062a3..399bb2c474d90 100644 --- a/api_docs/kbn_cli_dev_mode.mdx +++ b/api_docs/kbn_cli_dev_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-cli-dev-mode title: "@kbn/cli-dev-mode" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/cli-dev-mode plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/cli-dev-mode'] --- import kbnCliDevModeObj from './kbn_cli_dev_mode.devdocs.json'; diff --git a/api_docs/kbn_coloring.mdx b/api_docs/kbn_coloring.mdx index c5026a994bbff..d3a842a24ce84 100644 --- a/api_docs/kbn_coloring.mdx +++ b/api_docs/kbn_coloring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-coloring title: "@kbn/coloring" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/coloring plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/coloring'] --- import kbnColoringObj from './kbn_coloring.devdocs.json'; diff --git a/api_docs/kbn_config.mdx b/api_docs/kbn_config.mdx index a81d5f720c20a..8a6f64ea706cc 100644 --- a/api_docs/kbn_config.mdx +++ b/api_docs/kbn_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config title: "@kbn/config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config'] --- import kbnConfigObj from './kbn_config.devdocs.json'; diff --git a/api_docs/kbn_config_mocks.mdx b/api_docs/kbn_config_mocks.mdx index 3ca7cf87f8819..175a3130aaf9c 100644 --- a/api_docs/kbn_config_mocks.mdx +++ b/api_docs/kbn_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-mocks title: "@kbn/config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-mocks'] --- import kbnConfigMocksObj from './kbn_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_config_schema.mdx b/api_docs/kbn_config_schema.mdx index f4fd135eead55..d369d314f87e1 100644 --- a/api_docs/kbn_config_schema.mdx +++ b/api_docs/kbn_config_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-config-schema title: "@kbn/config-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/config-schema plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/config-schema'] --- import kbnConfigSchemaObj from './kbn_config_schema.devdocs.json'; diff --git a/api_docs/kbn_content_management_table_list.mdx b/api_docs/kbn_content_management_table_list.mdx index 2cb822b645378..9dba4bbf3a309 100644 --- a/api_docs/kbn_content_management_table_list.mdx +++ b/api_docs/kbn_content_management_table_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-content-management-table-list title: "@kbn/content-management-table-list" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/content-management-table-list plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/content-management-table-list'] --- import kbnContentManagementTableListObj from './kbn_content_management_table_list.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser.mdx b/api_docs/kbn_core_analytics_browser.mdx index 2cf8df7f6fb13..3220f4f43ec66 100644 --- a/api_docs/kbn_core_analytics_browser.mdx +++ b/api_docs/kbn_core_analytics_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser title: "@kbn/core-analytics-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser'] --- import kbnCoreAnalyticsBrowserObj from './kbn_core_analytics_browser.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_internal.mdx b/api_docs/kbn_core_analytics_browser_internal.mdx index 217056d756e6c..bc77e3013d70c 100644 --- a/api_docs/kbn_core_analytics_browser_internal.mdx +++ b/api_docs/kbn_core_analytics_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-internal title: "@kbn/core-analytics-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-internal'] --- import kbnCoreAnalyticsBrowserInternalObj from './kbn_core_analytics_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_browser_mocks.mdx b/api_docs/kbn_core_analytics_browser_mocks.mdx index 8674be0b88a22..fe9a5a7a8033b 100644 --- a/api_docs/kbn_core_analytics_browser_mocks.mdx +++ b/api_docs/kbn_core_analytics_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-browser-mocks title: "@kbn/core-analytics-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-browser-mocks'] --- import kbnCoreAnalyticsBrowserMocksObj from './kbn_core_analytics_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server.mdx b/api_docs/kbn_core_analytics_server.mdx index a3e9193110cf9..8ba5c5458189a 100644 --- a/api_docs/kbn_core_analytics_server.mdx +++ b/api_docs/kbn_core_analytics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server title: "@kbn/core-analytics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server'] --- import kbnCoreAnalyticsServerObj from './kbn_core_analytics_server.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_internal.mdx b/api_docs/kbn_core_analytics_server_internal.mdx index 87edbd5da1573..b32913e076b17 100644 --- a/api_docs/kbn_core_analytics_server_internal.mdx +++ b/api_docs/kbn_core_analytics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-internal title: "@kbn/core-analytics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-internal'] --- import kbnCoreAnalyticsServerInternalObj from './kbn_core_analytics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_analytics_server_mocks.mdx b/api_docs/kbn_core_analytics_server_mocks.mdx index eba99f7740982..ad1626e300d32 100644 --- a/api_docs/kbn_core_analytics_server_mocks.mdx +++ b/api_docs/kbn_core_analytics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-analytics-server-mocks title: "@kbn/core-analytics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-analytics-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-analytics-server-mocks'] --- import kbnCoreAnalyticsServerMocksObj from './kbn_core_analytics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser.mdx b/api_docs/kbn_core_application_browser.mdx index 6cd2f00a3f32a..cb8729ded12a4 100644 --- a/api_docs/kbn_core_application_browser.mdx +++ b/api_docs/kbn_core_application_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser title: "@kbn/core-application-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser'] --- import kbnCoreApplicationBrowserObj from './kbn_core_application_browser.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_internal.mdx b/api_docs/kbn_core_application_browser_internal.mdx index 0e0674185951d..01f8418de314e 100644 --- a/api_docs/kbn_core_application_browser_internal.mdx +++ b/api_docs/kbn_core_application_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-internal title: "@kbn/core-application-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-internal'] --- import kbnCoreApplicationBrowserInternalObj from './kbn_core_application_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_application_browser_mocks.mdx b/api_docs/kbn_core_application_browser_mocks.mdx index f58bebcf2f5d0..8325f55e3db44 100644 --- a/api_docs/kbn_core_application_browser_mocks.mdx +++ b/api_docs/kbn_core_application_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-browser-mocks title: "@kbn/core-application-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-browser-mocks'] --- import kbnCoreApplicationBrowserMocksObj from './kbn_core_application_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_application_common.mdx b/api_docs/kbn_core_application_common.mdx index 3d4d7035f6c1d..bcd417fbc9ede 100644 --- a/api_docs/kbn_core_application_common.mdx +++ b/api_docs/kbn_core_application_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-application-common title: "@kbn/core-application-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-application-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-application-common'] --- import kbnCoreApplicationCommonObj from './kbn_core_application_common.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_internal.mdx b/api_docs/kbn_core_apps_browser_internal.mdx index 7681b7a4a3885..507b0ccfcab40 100644 --- a/api_docs/kbn_core_apps_browser_internal.mdx +++ b/api_docs/kbn_core_apps_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-internal title: "@kbn/core-apps-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-internal'] --- import kbnCoreAppsBrowserInternalObj from './kbn_core_apps_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_apps_browser_mocks.mdx b/api_docs/kbn_core_apps_browser_mocks.mdx index 02a409c5881de..0a3625bef9425 100644 --- a/api_docs/kbn_core_apps_browser_mocks.mdx +++ b/api_docs/kbn_core_apps_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-apps-browser-mocks title: "@kbn/core-apps-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-apps-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-apps-browser-mocks'] --- import kbnCoreAppsBrowserMocksObj from './kbn_core_apps_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_browser_mocks.mdx b/api_docs/kbn_core_base_browser_mocks.mdx index b60d8471e15dd..178a8ff27c8eb 100644 --- a/api_docs/kbn_core_base_browser_mocks.mdx +++ b/api_docs/kbn_core_base_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-browser-mocks title: "@kbn/core-base-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-browser-mocks'] --- import kbnCoreBaseBrowserMocksObj from './kbn_core_base_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_base_common.mdx b/api_docs/kbn_core_base_common.mdx index 39463461d9165..e634f6c30bfca 100644 --- a/api_docs/kbn_core_base_common.mdx +++ b/api_docs/kbn_core_base_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-common title: "@kbn/core-base-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-common'] --- import kbnCoreBaseCommonObj from './kbn_core_base_common.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_internal.mdx b/api_docs/kbn_core_base_server_internal.mdx index 2a64da20012e8..1c4445244f836 100644 --- a/api_docs/kbn_core_base_server_internal.mdx +++ b/api_docs/kbn_core_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-internal title: "@kbn/core-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-internal'] --- import kbnCoreBaseServerInternalObj from './kbn_core_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_base_server_mocks.mdx b/api_docs/kbn_core_base_server_mocks.mdx index b2056befb2acb..84bb74021f2bd 100644 --- a/api_docs/kbn_core_base_server_mocks.mdx +++ b/api_docs/kbn_core_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-base-server-mocks title: "@kbn/core-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-base-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-base-server-mocks'] --- import kbnCoreBaseServerMocksObj from './kbn_core_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_browser_mocks.mdx b/api_docs/kbn_core_capabilities_browser_mocks.mdx index 6d28c54984174..52fe8e2820310 100644 --- a/api_docs/kbn_core_capabilities_browser_mocks.mdx +++ b/api_docs/kbn_core_capabilities_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-browser-mocks title: "@kbn/core-capabilities-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-browser-mocks'] --- import kbnCoreCapabilitiesBrowserMocksObj from './kbn_core_capabilities_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_common.mdx b/api_docs/kbn_core_capabilities_common.mdx index c7628a5a59c63..f2e9fe632436f 100644 --- a/api_docs/kbn_core_capabilities_common.mdx +++ b/api_docs/kbn_core_capabilities_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-common title: "@kbn/core-capabilities-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-common'] --- import kbnCoreCapabilitiesCommonObj from './kbn_core_capabilities_common.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server.mdx b/api_docs/kbn_core_capabilities_server.mdx index e01e96164ef47..b2f4b911b54fc 100644 --- a/api_docs/kbn_core_capabilities_server.mdx +++ b/api_docs/kbn_core_capabilities_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server title: "@kbn/core-capabilities-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server'] --- import kbnCoreCapabilitiesServerObj from './kbn_core_capabilities_server.devdocs.json'; diff --git a/api_docs/kbn_core_capabilities_server_mocks.mdx b/api_docs/kbn_core_capabilities_server_mocks.mdx index 9ef6e61852ed2..dd0bc1fd746ea 100644 --- a/api_docs/kbn_core_capabilities_server_mocks.mdx +++ b/api_docs/kbn_core_capabilities_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-capabilities-server-mocks title: "@kbn/core-capabilities-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-capabilities-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-capabilities-server-mocks'] --- import kbnCoreCapabilitiesServerMocksObj from './kbn_core_capabilities_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser.mdx b/api_docs/kbn_core_chrome_browser.mdx index 57e8483cb0f74..598dc54038877 100644 --- a/api_docs/kbn_core_chrome_browser.mdx +++ b/api_docs/kbn_core_chrome_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser title: "@kbn/core-chrome-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser'] --- import kbnCoreChromeBrowserObj from './kbn_core_chrome_browser.devdocs.json'; diff --git a/api_docs/kbn_core_chrome_browser_mocks.mdx b/api_docs/kbn_core_chrome_browser_mocks.mdx index c4fabecffea39..a5854e8359bc1 100644 --- a/api_docs/kbn_core_chrome_browser_mocks.mdx +++ b/api_docs/kbn_core_chrome_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-chrome-browser-mocks title: "@kbn/core-chrome-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-chrome-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-chrome-browser-mocks'] --- import kbnCoreChromeBrowserMocksObj from './kbn_core_chrome_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_config_server_internal.mdx b/api_docs/kbn_core_config_server_internal.mdx index 1d7858167c974..b189b4cda1ce5 100644 --- a/api_docs/kbn_core_config_server_internal.mdx +++ b/api_docs/kbn_core_config_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-config-server-internal title: "@kbn/core-config-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-config-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-config-server-internal'] --- import kbnCoreConfigServerInternalObj from './kbn_core_config_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser.mdx b/api_docs/kbn_core_deprecations_browser.mdx index 3914aa766a64d..89d13dd6c05e3 100644 --- a/api_docs/kbn_core_deprecations_browser.mdx +++ b/api_docs/kbn_core_deprecations_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser title: "@kbn/core-deprecations-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser'] --- import kbnCoreDeprecationsBrowserObj from './kbn_core_deprecations_browser.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_internal.mdx b/api_docs/kbn_core_deprecations_browser_internal.mdx index fca144e245a76..0ca986ec796b5 100644 --- a/api_docs/kbn_core_deprecations_browser_internal.mdx +++ b/api_docs/kbn_core_deprecations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-internal title: "@kbn/core-deprecations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-internal'] --- import kbnCoreDeprecationsBrowserInternalObj from './kbn_core_deprecations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_browser_mocks.mdx b/api_docs/kbn_core_deprecations_browser_mocks.mdx index 5be603ffa1f96..54f912532a0f0 100644 --- a/api_docs/kbn_core_deprecations_browser_mocks.mdx +++ b/api_docs/kbn_core_deprecations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-browser-mocks title: "@kbn/core-deprecations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-browser-mocks'] --- import kbnCoreDeprecationsBrowserMocksObj from './kbn_core_deprecations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_common.mdx b/api_docs/kbn_core_deprecations_common.mdx index afde35a745d30..41e3c5f4eb0d8 100644 --- a/api_docs/kbn_core_deprecations_common.mdx +++ b/api_docs/kbn_core_deprecations_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-common title: "@kbn/core-deprecations-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-common'] --- import kbnCoreDeprecationsCommonObj from './kbn_core_deprecations_common.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server.mdx b/api_docs/kbn_core_deprecations_server.mdx index 92ba606e6044c..0511dc3c9909f 100644 --- a/api_docs/kbn_core_deprecations_server.mdx +++ b/api_docs/kbn_core_deprecations_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server title: "@kbn/core-deprecations-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server'] --- import kbnCoreDeprecationsServerObj from './kbn_core_deprecations_server.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_internal.mdx b/api_docs/kbn_core_deprecations_server_internal.mdx index 9b3c1070f6623..451bb29fe60c8 100644 --- a/api_docs/kbn_core_deprecations_server_internal.mdx +++ b/api_docs/kbn_core_deprecations_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-internal title: "@kbn/core-deprecations-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-internal'] --- import kbnCoreDeprecationsServerInternalObj from './kbn_core_deprecations_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_deprecations_server_mocks.mdx b/api_docs/kbn_core_deprecations_server_mocks.mdx index e871d2dc0a941..9d72c4c54543e 100644 --- a/api_docs/kbn_core_deprecations_server_mocks.mdx +++ b/api_docs/kbn_core_deprecations_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-deprecations-server-mocks title: "@kbn/core-deprecations-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-deprecations-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-deprecations-server-mocks'] --- import kbnCoreDeprecationsServerMocksObj from './kbn_core_deprecations_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser.mdx b/api_docs/kbn_core_doc_links_browser.mdx index d591288b81315..9640ef186485d 100644 --- a/api_docs/kbn_core_doc_links_browser.mdx +++ b/api_docs/kbn_core_doc_links_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser title: "@kbn/core-doc-links-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser'] --- import kbnCoreDocLinksBrowserObj from './kbn_core_doc_links_browser.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_browser_mocks.mdx b/api_docs/kbn_core_doc_links_browser_mocks.mdx index 7cd809b6b97e1..f5bb1f42b22ab 100644 --- a/api_docs/kbn_core_doc_links_browser_mocks.mdx +++ b/api_docs/kbn_core_doc_links_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-browser-mocks title: "@kbn/core-doc-links-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-browser-mocks'] --- import kbnCoreDocLinksBrowserMocksObj from './kbn_core_doc_links_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server.mdx b/api_docs/kbn_core_doc_links_server.mdx index 273051738c41f..efae931fc186b 100644 --- a/api_docs/kbn_core_doc_links_server.mdx +++ b/api_docs/kbn_core_doc_links_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server title: "@kbn/core-doc-links-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server'] --- import kbnCoreDocLinksServerObj from './kbn_core_doc_links_server.devdocs.json'; diff --git a/api_docs/kbn_core_doc_links_server_mocks.mdx b/api_docs/kbn_core_doc_links_server_mocks.mdx index 0cddffa5aeab6..2af154133867a 100644 --- a/api_docs/kbn_core_doc_links_server_mocks.mdx +++ b/api_docs/kbn_core_doc_links_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-doc-links-server-mocks title: "@kbn/core-doc-links-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-doc-links-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-doc-links-server-mocks'] --- import kbnCoreDocLinksServerMocksObj from './kbn_core_doc_links_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx index 88eb042d98206..1454820b8fa46 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-internal title: "@kbn/core-elasticsearch-client-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-internal'] --- import kbnCoreElasticsearchClientServerInternalObj from './kbn_core_elasticsearch_client_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx index bbf4d1f0a58e8..1385f7dc0e1f4 100644 --- a/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_client_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-client-server-mocks title: "@kbn/core-elasticsearch-client-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-client-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-client-server-mocks'] --- import kbnCoreElasticsearchClientServerMocksObj from './kbn_core_elasticsearch_client_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server.mdx b/api_docs/kbn_core_elasticsearch_server.mdx index c9f8fdbd9b45d..1deae83f65870 100644 --- a/api_docs/kbn_core_elasticsearch_server.mdx +++ b/api_docs/kbn_core_elasticsearch_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server title: "@kbn/core-elasticsearch-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server'] --- import kbnCoreElasticsearchServerObj from './kbn_core_elasticsearch_server.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_internal.mdx b/api_docs/kbn_core_elasticsearch_server_internal.mdx index f4032d04f97ca..15117157d71df 100644 --- a/api_docs/kbn_core_elasticsearch_server_internal.mdx +++ b/api_docs/kbn_core_elasticsearch_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-internal title: "@kbn/core-elasticsearch-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-internal'] --- import kbnCoreElasticsearchServerInternalObj from './kbn_core_elasticsearch_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_elasticsearch_server_mocks.mdx b/api_docs/kbn_core_elasticsearch_server_mocks.mdx index 835c21903b6bf..493f5c036f9a1 100644 --- a/api_docs/kbn_core_elasticsearch_server_mocks.mdx +++ b/api_docs/kbn_core_elasticsearch_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-elasticsearch-server-mocks title: "@kbn/core-elasticsearch-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-elasticsearch-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-elasticsearch-server-mocks'] --- import kbnCoreElasticsearchServerMocksObj from './kbn_core_elasticsearch_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_internal.mdx b/api_docs/kbn_core_environment_server_internal.mdx index 47f64412fe99e..9910a744c62da 100644 --- a/api_docs/kbn_core_environment_server_internal.mdx +++ b/api_docs/kbn_core_environment_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-internal title: "@kbn/core-environment-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-internal'] --- import kbnCoreEnvironmentServerInternalObj from './kbn_core_environment_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_environment_server_mocks.mdx b/api_docs/kbn_core_environment_server_mocks.mdx index 082f5dd46eced..b9c83fd476328 100644 --- a/api_docs/kbn_core_environment_server_mocks.mdx +++ b/api_docs/kbn_core_environment_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-environment-server-mocks title: "@kbn/core-environment-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-environment-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-environment-server-mocks'] --- import kbnCoreEnvironmentServerMocksObj from './kbn_core_environment_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser.mdx b/api_docs/kbn_core_execution_context_browser.mdx index 492f874f833eb..e152e5bb83a2f 100644 --- a/api_docs/kbn_core_execution_context_browser.mdx +++ b/api_docs/kbn_core_execution_context_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser title: "@kbn/core-execution-context-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser'] --- import kbnCoreExecutionContextBrowserObj from './kbn_core_execution_context_browser.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_internal.mdx b/api_docs/kbn_core_execution_context_browser_internal.mdx index f8426b783c45a..9392cffe6209d 100644 --- a/api_docs/kbn_core_execution_context_browser_internal.mdx +++ b/api_docs/kbn_core_execution_context_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-internal title: "@kbn/core-execution-context-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-internal'] --- import kbnCoreExecutionContextBrowserInternalObj from './kbn_core_execution_context_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_browser_mocks.mdx b/api_docs/kbn_core_execution_context_browser_mocks.mdx index 6efeffd1c91c3..3e511fafa9451 100644 --- a/api_docs/kbn_core_execution_context_browser_mocks.mdx +++ b/api_docs/kbn_core_execution_context_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-browser-mocks title: "@kbn/core-execution-context-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-browser-mocks'] --- import kbnCoreExecutionContextBrowserMocksObj from './kbn_core_execution_context_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_common.mdx b/api_docs/kbn_core_execution_context_common.mdx index ed8e5ae438b3d..ec1debc1c012a 100644 --- a/api_docs/kbn_core_execution_context_common.mdx +++ b/api_docs/kbn_core_execution_context_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-common title: "@kbn/core-execution-context-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-common'] --- import kbnCoreExecutionContextCommonObj from './kbn_core_execution_context_common.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server.mdx b/api_docs/kbn_core_execution_context_server.mdx index 6441c8e326395..eed2089898a81 100644 --- a/api_docs/kbn_core_execution_context_server.mdx +++ b/api_docs/kbn_core_execution_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server title: "@kbn/core-execution-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server'] --- import kbnCoreExecutionContextServerObj from './kbn_core_execution_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_internal.mdx b/api_docs/kbn_core_execution_context_server_internal.mdx index 4f0d3211fa39d..87ae45af703ac 100644 --- a/api_docs/kbn_core_execution_context_server_internal.mdx +++ b/api_docs/kbn_core_execution_context_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-internal title: "@kbn/core-execution-context-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-internal'] --- import kbnCoreExecutionContextServerInternalObj from './kbn_core_execution_context_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_execution_context_server_mocks.mdx b/api_docs/kbn_core_execution_context_server_mocks.mdx index 02fa99d7cb6ce..b77bce9a27dbf 100644 --- a/api_docs/kbn_core_execution_context_server_mocks.mdx +++ b/api_docs/kbn_core_execution_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-execution-context-server-mocks title: "@kbn/core-execution-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-execution-context-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-execution-context-server-mocks'] --- import kbnCoreExecutionContextServerMocksObj from './kbn_core_execution_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser.mdx b/api_docs/kbn_core_fatal_errors_browser.mdx index 40263469ee782..bfc781548810f 100644 --- a/api_docs/kbn_core_fatal_errors_browser.mdx +++ b/api_docs/kbn_core_fatal_errors_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser title: "@kbn/core-fatal-errors-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser'] --- import kbnCoreFatalErrorsBrowserObj from './kbn_core_fatal_errors_browser.devdocs.json'; diff --git a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx index a6af55aa79cbe..6f305680273e2 100644 --- a/api_docs/kbn_core_fatal_errors_browser_mocks.mdx +++ b/api_docs/kbn_core_fatal_errors_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-fatal-errors-browser-mocks title: "@kbn/core-fatal-errors-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-fatal-errors-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-fatal-errors-browser-mocks'] --- import kbnCoreFatalErrorsBrowserMocksObj from './kbn_core_fatal_errors_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser.mdx b/api_docs/kbn_core_http_browser.mdx index a1c258a426f33..22b6b9c666339 100644 --- a/api_docs/kbn_core_http_browser.mdx +++ b/api_docs/kbn_core_http_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser title: "@kbn/core-http-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser'] --- import kbnCoreHttpBrowserObj from './kbn_core_http_browser.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_internal.mdx b/api_docs/kbn_core_http_browser_internal.mdx index a937dccab7fb8..579277f0e7c5b 100644 --- a/api_docs/kbn_core_http_browser_internal.mdx +++ b/api_docs/kbn_core_http_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-internal title: "@kbn/core-http-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-internal'] --- import kbnCoreHttpBrowserInternalObj from './kbn_core_http_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_browser_mocks.mdx b/api_docs/kbn_core_http_browser_mocks.mdx index a6274dd34b37d..45b83d9e361cd 100644 --- a/api_docs/kbn_core_http_browser_mocks.mdx +++ b/api_docs/kbn_core_http_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-browser-mocks title: "@kbn/core-http-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-browser-mocks'] --- import kbnCoreHttpBrowserMocksObj from './kbn_core_http_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_common.mdx b/api_docs/kbn_core_http_common.mdx index 6293ae9b58af2..b063b1ec8777e 100644 --- a/api_docs/kbn_core_http_common.mdx +++ b/api_docs/kbn_core_http_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-common title: "@kbn/core-http-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-common'] --- import kbnCoreHttpCommonObj from './kbn_core_http_common.devdocs.json'; diff --git a/api_docs/kbn_core_http_context_server_mocks.mdx b/api_docs/kbn_core_http_context_server_mocks.mdx index f11227d039fcf..e42402eeb736e 100644 --- a/api_docs/kbn_core_http_context_server_mocks.mdx +++ b/api_docs/kbn_core_http_context_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-context-server-mocks title: "@kbn/core-http-context-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-context-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-context-server-mocks'] --- import kbnCoreHttpContextServerMocksObj from './kbn_core_http_context_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_request_handler_context_server.mdx b/api_docs/kbn_core_http_request_handler_context_server.mdx index d0ea0e82f7682..d636450713c72 100644 --- a/api_docs/kbn_core_http_request_handler_context_server.mdx +++ b/api_docs/kbn_core_http_request_handler_context_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-request-handler-context-server title: "@kbn/core-http-request-handler-context-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-request-handler-context-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-request-handler-context-server'] --- import kbnCoreHttpRequestHandlerContextServerObj from './kbn_core_http_request_handler_context_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_internal.mdx b/api_docs/kbn_core_http_router_server_internal.mdx index 7654b3f70cc87..5691276cc4c86 100644 --- a/api_docs/kbn_core_http_router_server_internal.mdx +++ b/api_docs/kbn_core_http_router_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-internal title: "@kbn/core-http-router-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-internal'] --- import kbnCoreHttpRouterServerInternalObj from './kbn_core_http_router_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_router_server_mocks.mdx b/api_docs/kbn_core_http_router_server_mocks.mdx index 8a2d3a3de3333..5d087fbc12590 100644 --- a/api_docs/kbn_core_http_router_server_mocks.mdx +++ b/api_docs/kbn_core_http_router_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-router-server-mocks title: "@kbn/core-http-router-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-router-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-router-server-mocks'] --- import kbnCoreHttpRouterServerMocksObj from './kbn_core_http_router_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_http_server.mdx b/api_docs/kbn_core_http_server.mdx index 62b834449c23f..87f06ed8d63e4 100644 --- a/api_docs/kbn_core_http_server.mdx +++ b/api_docs/kbn_core_http_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server title: "@kbn/core-http-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server'] --- import kbnCoreHttpServerObj from './kbn_core_http_server.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_internal.mdx b/api_docs/kbn_core_http_server_internal.mdx index bbda88d5c238c..266b3696995f8 100644 --- a/api_docs/kbn_core_http_server_internal.mdx +++ b/api_docs/kbn_core_http_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-internal title: "@kbn/core-http-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-internal'] --- import kbnCoreHttpServerInternalObj from './kbn_core_http_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_http_server_mocks.mdx b/api_docs/kbn_core_http_server_mocks.mdx index e99fba0127cc2..c2e9cef4a3f10 100644 --- a/api_docs/kbn_core_http_server_mocks.mdx +++ b/api_docs/kbn_core_http_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-http-server-mocks title: "@kbn/core-http-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-http-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-http-server-mocks'] --- import kbnCoreHttpServerMocksObj from './kbn_core_http_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser.mdx b/api_docs/kbn_core_i18n_browser.mdx index c6da1dc82bc90..23d1612d624c8 100644 --- a/api_docs/kbn_core_i18n_browser.mdx +++ b/api_docs/kbn_core_i18n_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser title: "@kbn/core-i18n-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser'] --- import kbnCoreI18nBrowserObj from './kbn_core_i18n_browser.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_browser_mocks.mdx b/api_docs/kbn_core_i18n_browser_mocks.mdx index 140e346128004..2809246ccc670 100644 --- a/api_docs/kbn_core_i18n_browser_mocks.mdx +++ b/api_docs/kbn_core_i18n_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-browser-mocks title: "@kbn/core-i18n-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-browser-mocks'] --- import kbnCoreI18nBrowserMocksObj from './kbn_core_i18n_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server.mdx b/api_docs/kbn_core_i18n_server.mdx index ddd02e61d7ee4..d8a8f0c780f5b 100644 --- a/api_docs/kbn_core_i18n_server.mdx +++ b/api_docs/kbn_core_i18n_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server title: "@kbn/core-i18n-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server'] --- import kbnCoreI18nServerObj from './kbn_core_i18n_server.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_internal.mdx b/api_docs/kbn_core_i18n_server_internal.mdx index 56636905f7afa..34bcc3c6cdcfb 100644 --- a/api_docs/kbn_core_i18n_server_internal.mdx +++ b/api_docs/kbn_core_i18n_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-internal title: "@kbn/core-i18n-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-internal'] --- import kbnCoreI18nServerInternalObj from './kbn_core_i18n_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_i18n_server_mocks.mdx b/api_docs/kbn_core_i18n_server_mocks.mdx index f8ed8c6f0bec6..0616e05e00234 100644 --- a/api_docs/kbn_core_i18n_server_mocks.mdx +++ b/api_docs/kbn_core_i18n_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-i18n-server-mocks title: "@kbn/core-i18n-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-i18n-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-i18n-server-mocks'] --- import kbnCoreI18nServerMocksObj from './kbn_core_i18n_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser.mdx b/api_docs/kbn_core_injected_metadata_browser.mdx index 9a02125114dc7..d494ce28acb14 100644 --- a/api_docs/kbn_core_injected_metadata_browser.mdx +++ b/api_docs/kbn_core_injected_metadata_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser title: "@kbn/core-injected-metadata-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser'] --- import kbnCoreInjectedMetadataBrowserObj from './kbn_core_injected_metadata_browser.devdocs.json'; diff --git a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx index d6a078aef0df0..30217e2fc3ff4 100644 --- a/api_docs/kbn_core_injected_metadata_browser_mocks.mdx +++ b/api_docs/kbn_core_injected_metadata_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-injected-metadata-browser-mocks title: "@kbn/core-injected-metadata-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-injected-metadata-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-injected-metadata-browser-mocks'] --- import kbnCoreInjectedMetadataBrowserMocksObj from './kbn_core_injected_metadata_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_internal.mdx b/api_docs/kbn_core_integrations_browser_internal.mdx index 38c283eaaf97e..5e7ba108a654c 100644 --- a/api_docs/kbn_core_integrations_browser_internal.mdx +++ b/api_docs/kbn_core_integrations_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-internal title: "@kbn/core-integrations-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-internal'] --- import kbnCoreIntegrationsBrowserInternalObj from './kbn_core_integrations_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_integrations_browser_mocks.mdx b/api_docs/kbn_core_integrations_browser_mocks.mdx index 5cc58ad5a914c..a6e9fb32a00b7 100644 --- a/api_docs/kbn_core_integrations_browser_mocks.mdx +++ b/api_docs/kbn_core_integrations_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-integrations-browser-mocks title: "@kbn/core-integrations-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-integrations-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-integrations-browser-mocks'] --- import kbnCoreIntegrationsBrowserMocksObj from './kbn_core_integrations_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser.mdx b/api_docs/kbn_core_lifecycle_browser.mdx index 1ab8822af2252..531b5b53a8295 100644 --- a/api_docs/kbn_core_lifecycle_browser.mdx +++ b/api_docs/kbn_core_lifecycle_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser title: "@kbn/core-lifecycle-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser'] --- import kbnCoreLifecycleBrowserObj from './kbn_core_lifecycle_browser.devdocs.json'; diff --git a/api_docs/kbn_core_lifecycle_browser_mocks.mdx b/api_docs/kbn_core_lifecycle_browser_mocks.mdx index 99d9d3f32d3e8..2e32f2efcec21 100644 --- a/api_docs/kbn_core_lifecycle_browser_mocks.mdx +++ b/api_docs/kbn_core_lifecycle_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-lifecycle-browser-mocks title: "@kbn/core-lifecycle-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-lifecycle-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-lifecycle-browser-mocks'] --- import kbnCoreLifecycleBrowserMocksObj from './kbn_core_lifecycle_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server.mdx b/api_docs/kbn_core_logging_server.mdx index 5065fff3211ff..15c79e1a0db38 100644 --- a/api_docs/kbn_core_logging_server.mdx +++ b/api_docs/kbn_core_logging_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server title: "@kbn/core-logging-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server'] --- import kbnCoreLoggingServerObj from './kbn_core_logging_server.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_internal.mdx b/api_docs/kbn_core_logging_server_internal.mdx index 2e6959cc87594..cd7319b284fb7 100644 --- a/api_docs/kbn_core_logging_server_internal.mdx +++ b/api_docs/kbn_core_logging_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-internal title: "@kbn/core-logging-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-internal'] --- import kbnCoreLoggingServerInternalObj from './kbn_core_logging_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_logging_server_mocks.mdx b/api_docs/kbn_core_logging_server_mocks.mdx index 81a53652b4ea2..93667b80ba7f8 100644 --- a/api_docs/kbn_core_logging_server_mocks.mdx +++ b/api_docs/kbn_core_logging_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-logging-server-mocks title: "@kbn/core-logging-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-logging-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-logging-server-mocks'] --- import kbnCoreLoggingServerMocksObj from './kbn_core_logging_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_internal.mdx b/api_docs/kbn_core_metrics_collectors_server_internal.mdx index 3d5b6fffdd513..649123ca8c6a9 100644 --- a/api_docs/kbn_core_metrics_collectors_server_internal.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-internal title: "@kbn/core-metrics-collectors-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-internal'] --- import kbnCoreMetricsCollectorsServerInternalObj from './kbn_core_metrics_collectors_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx index 14756f136bd3a..21c886d8ce972 100644 --- a/api_docs/kbn_core_metrics_collectors_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_collectors_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-collectors-server-mocks title: "@kbn/core-metrics-collectors-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-collectors-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-collectors-server-mocks'] --- import kbnCoreMetricsCollectorsServerMocksObj from './kbn_core_metrics_collectors_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server.mdx b/api_docs/kbn_core_metrics_server.mdx index 087dd2dd357ca..a3b481458c975 100644 --- a/api_docs/kbn_core_metrics_server.mdx +++ b/api_docs/kbn_core_metrics_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server title: "@kbn/core-metrics-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server'] --- import kbnCoreMetricsServerObj from './kbn_core_metrics_server.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_internal.mdx b/api_docs/kbn_core_metrics_server_internal.mdx index 5f5ea85f80056..b78cf2f752e5e 100644 --- a/api_docs/kbn_core_metrics_server_internal.mdx +++ b/api_docs/kbn_core_metrics_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-internal title: "@kbn/core-metrics-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-internal'] --- import kbnCoreMetricsServerInternalObj from './kbn_core_metrics_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_metrics_server_mocks.mdx b/api_docs/kbn_core_metrics_server_mocks.mdx index 6216a6d589ce5..bcb29c53743cb 100644 --- a/api_docs/kbn_core_metrics_server_mocks.mdx +++ b/api_docs/kbn_core_metrics_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-metrics-server-mocks title: "@kbn/core-metrics-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-metrics-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-metrics-server-mocks'] --- import kbnCoreMetricsServerMocksObj from './kbn_core_metrics_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_mount_utils_browser.mdx b/api_docs/kbn_core_mount_utils_browser.mdx index 82c2c06e08457..530ca8138c755 100644 --- a/api_docs/kbn_core_mount_utils_browser.mdx +++ b/api_docs/kbn_core_mount_utils_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-mount-utils-browser title: "@kbn/core-mount-utils-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-mount-utils-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-mount-utils-browser'] --- import kbnCoreMountUtilsBrowserObj from './kbn_core_mount_utils_browser.devdocs.json'; diff --git a/api_docs/kbn_core_node_server.mdx b/api_docs/kbn_core_node_server.mdx index b4eeaf823c522..0c5982822b6bd 100644 --- a/api_docs/kbn_core_node_server.mdx +++ b/api_docs/kbn_core_node_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server title: "@kbn/core-node-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server'] --- import kbnCoreNodeServerObj from './kbn_core_node_server.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_internal.mdx b/api_docs/kbn_core_node_server_internal.mdx index 04c3664f49b67..ce0799c8a57e1 100644 --- a/api_docs/kbn_core_node_server_internal.mdx +++ b/api_docs/kbn_core_node_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-internal title: "@kbn/core-node-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-internal'] --- import kbnCoreNodeServerInternalObj from './kbn_core_node_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_node_server_mocks.mdx b/api_docs/kbn_core_node_server_mocks.mdx index edcf4af2b62ad..ff9aa9bab3b16 100644 --- a/api_docs/kbn_core_node_server_mocks.mdx +++ b/api_docs/kbn_core_node_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-node-server-mocks title: "@kbn/core-node-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-node-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-node-server-mocks'] --- import kbnCoreNodeServerMocksObj from './kbn_core_node_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser.mdx b/api_docs/kbn_core_notifications_browser.mdx index d120b6ccece74..45e5581a7df10 100644 --- a/api_docs/kbn_core_notifications_browser.mdx +++ b/api_docs/kbn_core_notifications_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser title: "@kbn/core-notifications-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser'] --- import kbnCoreNotificationsBrowserObj from './kbn_core_notifications_browser.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_internal.mdx b/api_docs/kbn_core_notifications_browser_internal.mdx index e4152c45f1f60..4b5730a85899c 100644 --- a/api_docs/kbn_core_notifications_browser_internal.mdx +++ b/api_docs/kbn_core_notifications_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-internal title: "@kbn/core-notifications-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-internal'] --- import kbnCoreNotificationsBrowserInternalObj from './kbn_core_notifications_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_notifications_browser_mocks.mdx b/api_docs/kbn_core_notifications_browser_mocks.mdx index a571fb85c2ef4..e0dc054651702 100644 --- a/api_docs/kbn_core_notifications_browser_mocks.mdx +++ b/api_docs/kbn_core_notifications_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-notifications-browser-mocks title: "@kbn/core-notifications-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-notifications-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-notifications-browser-mocks'] --- import kbnCoreNotificationsBrowserMocksObj from './kbn_core_notifications_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser.mdx b/api_docs/kbn_core_overlays_browser.mdx index 8f4457512da88..b8f43851ff074 100644 --- a/api_docs/kbn_core_overlays_browser.mdx +++ b/api_docs/kbn_core_overlays_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser title: "@kbn/core-overlays-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser'] --- import kbnCoreOverlaysBrowserObj from './kbn_core_overlays_browser.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_internal.mdx b/api_docs/kbn_core_overlays_browser_internal.mdx index 6e25c69de32ee..3f88938a7d71c 100644 --- a/api_docs/kbn_core_overlays_browser_internal.mdx +++ b/api_docs/kbn_core_overlays_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-internal title: "@kbn/core-overlays-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-internal'] --- import kbnCoreOverlaysBrowserInternalObj from './kbn_core_overlays_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_overlays_browser_mocks.mdx b/api_docs/kbn_core_overlays_browser_mocks.mdx index ce41f3aac1f43..c0846ed65f58f 100644 --- a/api_docs/kbn_core_overlays_browser_mocks.mdx +++ b/api_docs/kbn_core_overlays_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-overlays-browser-mocks title: "@kbn/core-overlays-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-overlays-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-overlays-browser-mocks'] --- import kbnCoreOverlaysBrowserMocksObj from './kbn_core_overlays_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser.mdx b/api_docs/kbn_core_plugins_browser.mdx index 0b413f962b275..70c9fbee9c49a 100644 --- a/api_docs/kbn_core_plugins_browser.mdx +++ b/api_docs/kbn_core_plugins_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser title: "@kbn/core-plugins-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser'] --- import kbnCorePluginsBrowserObj from './kbn_core_plugins_browser.devdocs.json'; diff --git a/api_docs/kbn_core_plugins_browser_mocks.mdx b/api_docs/kbn_core_plugins_browser_mocks.mdx index b31af30c16030..4ebd8410d827d 100644 --- a/api_docs/kbn_core_plugins_browser_mocks.mdx +++ b/api_docs/kbn_core_plugins_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-plugins-browser-mocks title: "@kbn/core-plugins-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-plugins-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-plugins-browser-mocks'] --- import kbnCorePluginsBrowserMocksObj from './kbn_core_plugins_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server.mdx b/api_docs/kbn_core_preboot_server.mdx index ec6b64842af42..96f2610b0eb18 100644 --- a/api_docs/kbn_core_preboot_server.mdx +++ b/api_docs/kbn_core_preboot_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server title: "@kbn/core-preboot-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server'] --- import kbnCorePrebootServerObj from './kbn_core_preboot_server.devdocs.json'; diff --git a/api_docs/kbn_core_preboot_server_mocks.mdx b/api_docs/kbn_core_preboot_server_mocks.mdx index 583b5bc973add..876c2f5e25d90 100644 --- a/api_docs/kbn_core_preboot_server_mocks.mdx +++ b/api_docs/kbn_core_preboot_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-preboot-server-mocks title: "@kbn/core-preboot-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-preboot-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-preboot-server-mocks'] --- import kbnCorePrebootServerMocksObj from './kbn_core_preboot_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_browser_mocks.mdx b/api_docs/kbn_core_rendering_browser_mocks.mdx index 383e9c6511efa..31744f9615bb9 100644 --- a/api_docs/kbn_core_rendering_browser_mocks.mdx +++ b/api_docs/kbn_core_rendering_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-rendering-browser-mocks title: "@kbn/core-rendering-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-rendering-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-browser-mocks'] --- import kbnCoreRenderingBrowserMocksObj from './kbn_core_rendering_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_rendering_server_internal.devdocs.json b/api_docs/kbn_core_rendering_server_internal.devdocs.json new file mode 100644 index 0000000000000..d765832debee1 --- /dev/null +++ b/api_docs/kbn_core_rendering_server_internal.devdocs.json @@ -0,0 +1,61 @@ +{ + "id": "@kbn/core-rendering-server-internal", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [ + { + "parentPluginId": "@kbn/core-rendering-server-internal", + "id": "def-server.Fonts", + "type": "Function", + "tags": [], + "label": "Fonts", + "description": [], + "signature": [ + "({ url }: React.PropsWithChildren<Props>) => JSX.Element" + ], + "path": "packages/core/rendering/core-rendering-server-internal/src/views/fonts.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-rendering-server-internal", + "id": "def-server.Fonts.$1", + "type": "CompoundType", + "tags": [], + "label": "{ url }", + "description": [], + "signature": [ + "React.PropsWithChildren<Props>" + ], + "path": "packages/core/rendering/core-rendering-server-internal/src/views/fonts.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + } + ], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_rendering_server_internal.mdx b/api_docs/kbn_core_rendering_server_internal.mdx new file mode 100644 index 0000000000000..000dd0d4cedb8 --- /dev/null +++ b/api_docs/kbn_core_rendering_server_internal.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCoreRenderingServerInternalPluginApi +slug: /kibana-dev-docs/api/kbn-core-rendering-server-internal +title: "@kbn/core-rendering-server-internal" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-rendering-server-internal plugin +date: 2022-10-04 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-internal'] +--- +import kbnCoreRenderingServerInternalObj from './kbn_core_rendering_server_internal.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 2 | 0 | 2 | 0 | + +## Server + +### Functions +<DocDefinitionList data={kbnCoreRenderingServerInternalObj.server.functions}/> + diff --git a/api_docs/kbn_core_rendering_server_mocks.devdocs.json b/api_docs/kbn_core_rendering_server_mocks.devdocs.json new file mode 100644 index 0000000000000..fc43829e9518e --- /dev/null +++ b/api_docs/kbn_core_rendering_server_mocks.devdocs.json @@ -0,0 +1,95 @@ +{ + "id": "@kbn/core-rendering-server-mocks", + "client": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + }, + "server": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [ + { + "parentPluginId": "@kbn/core-rendering-server-mocks", + "id": "def-server.renderingServiceMock", + "type": "Object", + "tags": [], + "label": "renderingServiceMock", + "description": [], + "path": "packages/core/rendering/core-rendering-server-mocks/src/rendering_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/core-rendering-server-mocks", + "id": "def-server.renderingServiceMock.create", + "type": "Function", + "tags": [], + "label": "create", + "description": [], + "signature": [ + "() => ", + "RenderingServiceMock" + ], + "path": "packages/core/rendering/core-rendering-server-mocks/src/rendering_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-rendering-server-mocks", + "id": "def-server.renderingServiceMock.createPrebootContract", + "type": "Function", + "tags": [], + "label": "createPrebootContract", + "description": [], + "signature": [ + "() => jest.Mocked<", + "InternalRenderingServiceSetup", + ">" + ], + "path": "packages/core/rendering/core-rendering-server-mocks/src/rendering_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + }, + { + "parentPluginId": "@kbn/core-rendering-server-mocks", + "id": "def-server.renderingServiceMock.createSetupContract", + "type": "Function", + "tags": [], + "label": "createSetupContract", + "description": [], + "signature": [ + "() => jest.Mocked<", + "InternalRenderingServiceSetup", + ">" + ], + "path": "packages/core/rendering/core-rendering-server-mocks/src/rendering_service.mock.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [] + } + ], + "initialIsOpen": false + } + ] + }, + "common": { + "classes": [], + "functions": [], + "interfaces": [], + "enums": [], + "misc": [], + "objects": [] + } +} \ No newline at end of file diff --git a/api_docs/kbn_core_rendering_server_mocks.mdx b/api_docs/kbn_core_rendering_server_mocks.mdx new file mode 100644 index 0000000000000..7de298d846759 --- /dev/null +++ b/api_docs/kbn_core_rendering_server_mocks.mdx @@ -0,0 +1,30 @@ +--- +#### +#### This document is auto-generated and is meant to be viewed inside our experimental, new docs system. +#### Reach out in #docs-engineering for more info. +#### +id: kibKbnCoreRenderingServerMocksPluginApi +slug: /kibana-dev-docs/api/kbn-core-rendering-server-mocks +title: "@kbn/core-rendering-server-mocks" +image: https://source.unsplash.com/400x175/?github +description: API docs for the @kbn/core-rendering-server-mocks plugin +date: 2022-10-04 +tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-rendering-server-mocks'] +--- +import kbnCoreRenderingServerMocksObj from './kbn_core_rendering_server_mocks.devdocs.json'; + + + +Contact Kibana Core for questions regarding this plugin. + +**Code health stats** + +| Public API count | Any count | Items lacking comments | Missing exports | +|-------------------|-----------|------------------------|-----------------| +| 4 | 0 | 4 | 1 | + +## Server + +### Objects +<DocDefinitionList data={kbnCoreRenderingServerMocksObj.server.objects}/> + diff --git a/api_docs/kbn_core_saved_objects_api_browser.mdx b/api_docs/kbn_core_saved_objects_api_browser.mdx index e07f5797c7f72..51698a7096343 100644 --- a/api_docs/kbn_core_saved_objects_api_browser.mdx +++ b/api_docs/kbn_core_saved_objects_api_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-browser title: "@kbn/core-saved-objects-api-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-browser'] --- import kbnCoreSavedObjectsApiBrowserObj from './kbn_core_saved_objects_api_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server.mdx b/api_docs/kbn_core_saved_objects_api_server.mdx index 8a3662b8b470c..c8639a4b9c453 100644 --- a/api_docs/kbn_core_saved_objects_api_server.mdx +++ b/api_docs/kbn_core_saved_objects_api_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server title: "@kbn/core-saved-objects-api-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server'] --- import kbnCoreSavedObjectsApiServerObj from './kbn_core_saved_objects_api_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_internal.mdx b/api_docs/kbn_core_saved_objects_api_server_internal.mdx index 7244cfe8beb60..f14120830006f 100644 --- a/api_docs/kbn_core_saved_objects_api_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-internal title: "@kbn/core-saved-objects-api-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-internal'] --- import kbnCoreSavedObjectsApiServerInternalObj from './kbn_core_saved_objects_api_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx index 9a62ec00bb37c..6b0c824029fc3 100644 --- a/api_docs/kbn_core_saved_objects_api_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_api_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-api-server-mocks title: "@kbn/core-saved-objects-api-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-api-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-api-server-mocks'] --- import kbnCoreSavedObjectsApiServerMocksObj from './kbn_core_saved_objects_api_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_internal.mdx b/api_docs/kbn_core_saved_objects_base_server_internal.mdx index 4068fcf5e73b5..9e232227f556c 100644 --- a/api_docs/kbn_core_saved_objects_base_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-internal title: "@kbn/core-saved-objects-base-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-internal'] --- import kbnCoreSavedObjectsBaseServerInternalObj from './kbn_core_saved_objects_base_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx index 7af80f7527944..66a94ba868430 100644 --- a/api_docs/kbn_core_saved_objects_base_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_base_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-base-server-mocks title: "@kbn/core-saved-objects-base-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-base-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-base-server-mocks'] --- import kbnCoreSavedObjectsBaseServerMocksObj from './kbn_core_saved_objects_base_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser.mdx b/api_docs/kbn_core_saved_objects_browser.mdx index 83b6f08887286..eb654c74cca2e 100644 --- a/api_docs/kbn_core_saved_objects_browser.mdx +++ b/api_docs/kbn_core_saved_objects_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser title: "@kbn/core-saved-objects-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser'] --- import kbnCoreSavedObjectsBrowserObj from './kbn_core_saved_objects_browser.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_internal.mdx b/api_docs/kbn_core_saved_objects_browser_internal.mdx index 3d8e3d03be29b..f077ed64b63e2 100644 --- a/api_docs/kbn_core_saved_objects_browser_internal.mdx +++ b/api_docs/kbn_core_saved_objects_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-internal title: "@kbn/core-saved-objects-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-internal'] --- import kbnCoreSavedObjectsBrowserInternalObj from './kbn_core_saved_objects_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_browser_mocks.mdx b/api_docs/kbn_core_saved_objects_browser_mocks.mdx index ad03d6488f2a0..29da15ed698f8 100644 --- a/api_docs/kbn_core_saved_objects_browser_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-browser-mocks title: "@kbn/core-saved-objects-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-browser-mocks'] --- import kbnCoreSavedObjectsBrowserMocksObj from './kbn_core_saved_objects_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_common.mdx b/api_docs/kbn_core_saved_objects_common.mdx index b06d26ed9bea4..ac4e60624dcb7 100644 --- a/api_docs/kbn_core_saved_objects_common.mdx +++ b/api_docs/kbn_core_saved_objects_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-common title: "@kbn/core-saved-objects-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-common'] --- import kbnCoreSavedObjectsCommonObj from './kbn_core_saved_objects_common.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx index 6532dfcaeaf81..bebe1281e0ade 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-internal title: "@kbn/core-saved-objects-import-export-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-internal'] --- import kbnCoreSavedObjectsImportExportServerInternalObj from './kbn_core_saved_objects_import_export_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx index 53092b90fc151..4b0e97d192ea3 100644 --- a/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_import_export_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-import-export-server-mocks title: "@kbn/core-saved-objects-import-export-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-import-export-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-import-export-server-mocks'] --- import kbnCoreSavedObjectsImportExportServerMocksObj from './kbn_core_saved_objects_import_export_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx index 3d1544f4f110f..aed14d7466d26 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-internal title: "@kbn/core-saved-objects-migration-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-internal'] --- import kbnCoreSavedObjectsMigrationServerInternalObj from './kbn_core_saved_objects_migration_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx index c662be1f8fb09..8d8eb4b06ad7a 100644 --- a/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_migration_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-migration-server-mocks title: "@kbn/core-saved-objects-migration-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-migration-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-migration-server-mocks'] --- import kbnCoreSavedObjectsMigrationServerMocksObj from './kbn_core_saved_objects_migration_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server.mdx b/api_docs/kbn_core_saved_objects_server.mdx index 8a27161ad18de..2a8902a1e5184 100644 --- a/api_docs/kbn_core_saved_objects_server.mdx +++ b/api_docs/kbn_core_saved_objects_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server title: "@kbn/core-saved-objects-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server'] --- import kbnCoreSavedObjectsServerObj from './kbn_core_saved_objects_server.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_internal.mdx b/api_docs/kbn_core_saved_objects_server_internal.mdx index c73c73d974180..9bd945299b18d 100644 --- a/api_docs/kbn_core_saved_objects_server_internal.mdx +++ b/api_docs/kbn_core_saved_objects_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-internal title: "@kbn/core-saved-objects-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-internal'] --- import kbnCoreSavedObjectsServerInternalObj from './kbn_core_saved_objects_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_server_mocks.mdx b/api_docs/kbn_core_saved_objects_server_mocks.mdx index e8fe3856fa57f..6a53d6086266f 100644 --- a/api_docs/kbn_core_saved_objects_server_mocks.mdx +++ b/api_docs/kbn_core_saved_objects_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-server-mocks title: "@kbn/core-saved-objects-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-server-mocks'] --- import kbnCoreSavedObjectsServerMocksObj from './kbn_core_saved_objects_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_saved_objects_utils_server.mdx b/api_docs/kbn_core_saved_objects_utils_server.mdx index 5eb6ed5fabf23..3747e6c419527 100644 --- a/api_docs/kbn_core_saved_objects_utils_server.mdx +++ b/api_docs/kbn_core_saved_objects_utils_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-saved-objects-utils-server title: "@kbn/core-saved-objects-utils-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-saved-objects-utils-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-saved-objects-utils-server'] --- import kbnCoreSavedObjectsUtilsServerObj from './kbn_core_saved_objects_utils_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_common.mdx b/api_docs/kbn_core_status_common.mdx index 3f9ec5be08ad2..093798e431c32 100644 --- a/api_docs/kbn_core_status_common.mdx +++ b/api_docs/kbn_core_status_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common title: "@kbn/core-status-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common'] --- import kbnCoreStatusCommonObj from './kbn_core_status_common.devdocs.json'; diff --git a/api_docs/kbn_core_status_common_internal.mdx b/api_docs/kbn_core_status_common_internal.mdx index fd7956ee7ab43..3232d2d6e8099 100644 --- a/api_docs/kbn_core_status_common_internal.mdx +++ b/api_docs/kbn_core_status_common_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-common-internal title: "@kbn/core-status-common-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-common-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-common-internal'] --- import kbnCoreStatusCommonInternalObj from './kbn_core_status_common_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server.mdx b/api_docs/kbn_core_status_server.mdx index 71ceb6a7bede4..6dc392f2f16c7 100644 --- a/api_docs/kbn_core_status_server.mdx +++ b/api_docs/kbn_core_status_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server title: "@kbn/core-status-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server'] --- import kbnCoreStatusServerObj from './kbn_core_status_server.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_internal.mdx b/api_docs/kbn_core_status_server_internal.mdx index 3916cb3c3119c..b2d57509addeb 100644 --- a/api_docs/kbn_core_status_server_internal.mdx +++ b/api_docs/kbn_core_status_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-internal title: "@kbn/core-status-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-internal'] --- import kbnCoreStatusServerInternalObj from './kbn_core_status_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_status_server_mocks.mdx b/api_docs/kbn_core_status_server_mocks.mdx index 966876de9b0c2..d5e62a33ea077 100644 --- a/api_docs/kbn_core_status_server_mocks.mdx +++ b/api_docs/kbn_core_status_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-status-server-mocks title: "@kbn/core-status-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-status-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-status-server-mocks'] --- import kbnCoreStatusServerMocksObj from './kbn_core_status_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx index f3ad0c01cc377..2341795744ee2 100644 --- a/api_docs/kbn_core_test_helpers_deprecations_getters.mdx +++ b/api_docs/kbn_core_test_helpers_deprecations_getters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-deprecations-getters title: "@kbn/core-test-helpers-deprecations-getters" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-deprecations-getters plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-deprecations-getters'] --- import kbnCoreTestHelpersDeprecationsGettersObj from './kbn_core_test_helpers_deprecations_getters.devdocs.json'; diff --git a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx index 995497dffa505..1bdb4be978f3b 100644 --- a/api_docs/kbn_core_test_helpers_http_setup_browser.mdx +++ b/api_docs/kbn_core_test_helpers_http_setup_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-test-helpers-http-setup-browser title: "@kbn/core-test-helpers-http-setup-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-test-helpers-http-setup-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-test-helpers-http-setup-browser'] --- import kbnCoreTestHelpersHttpSetupBrowserObj from './kbn_core_test_helpers_http_setup_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser.mdx b/api_docs/kbn_core_theme_browser.mdx index 2db8ecd68b0dc..8c1171396e641 100644 --- a/api_docs/kbn_core_theme_browser.mdx +++ b/api_docs/kbn_core_theme_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser title: "@kbn/core-theme-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser'] --- import kbnCoreThemeBrowserObj from './kbn_core_theme_browser.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_internal.mdx b/api_docs/kbn_core_theme_browser_internal.mdx index 38778f82b4a73..38293fb7bd7ed 100644 --- a/api_docs/kbn_core_theme_browser_internal.mdx +++ b/api_docs/kbn_core_theme_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-internal title: "@kbn/core-theme-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-internal'] --- import kbnCoreThemeBrowserInternalObj from './kbn_core_theme_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_theme_browser_mocks.mdx b/api_docs/kbn_core_theme_browser_mocks.mdx index d561c846ba528..f0b9a5dcf855d 100644 --- a/api_docs/kbn_core_theme_browser_mocks.mdx +++ b/api_docs/kbn_core_theme_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-theme-browser-mocks title: "@kbn/core-theme-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-theme-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-theme-browser-mocks'] --- import kbnCoreThemeBrowserMocksObj from './kbn_core_theme_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser.mdx b/api_docs/kbn_core_ui_settings_browser.mdx index f4b08646423d6..b4a0a55091a4f 100644 --- a/api_docs/kbn_core_ui_settings_browser.mdx +++ b/api_docs/kbn_core_ui_settings_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser title: "@kbn/core-ui-settings-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser'] --- import kbnCoreUiSettingsBrowserObj from './kbn_core_ui_settings_browser.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_internal.mdx b/api_docs/kbn_core_ui_settings_browser_internal.mdx index 607eaa47b682d..09b0a2dc57320 100644 --- a/api_docs/kbn_core_ui_settings_browser_internal.mdx +++ b/api_docs/kbn_core_ui_settings_browser_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-internal title: "@kbn/core-ui-settings-browser-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-internal'] --- import kbnCoreUiSettingsBrowserInternalObj from './kbn_core_ui_settings_browser_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_browser_mocks.mdx b/api_docs/kbn_core_ui_settings_browser_mocks.mdx index 15ecdc9fee508..31d7e5da5da67 100644 --- a/api_docs/kbn_core_ui_settings_browser_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_browser_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-browser-mocks title: "@kbn/core-ui-settings-browser-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-browser-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-browser-mocks'] --- import kbnCoreUiSettingsBrowserMocksObj from './kbn_core_ui_settings_browser_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_common.mdx b/api_docs/kbn_core_ui_settings_common.mdx index 6644389a6513b..5b6eb33ecc40a 100644 --- a/api_docs/kbn_core_ui_settings_common.mdx +++ b/api_docs/kbn_core_ui_settings_common.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-common title: "@kbn/core-ui-settings-common" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-common plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-common'] --- import kbnCoreUiSettingsCommonObj from './kbn_core_ui_settings_common.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server.mdx b/api_docs/kbn_core_ui_settings_server.mdx index d486425f2067d..812a5a98b6200 100644 --- a/api_docs/kbn_core_ui_settings_server.mdx +++ b/api_docs/kbn_core_ui_settings_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server title: "@kbn/core-ui-settings-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server'] --- import kbnCoreUiSettingsServerObj from './kbn_core_ui_settings_server.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_internal.mdx b/api_docs/kbn_core_ui_settings_server_internal.mdx index 98b9aa8e6584a..b4098d7a68294 100644 --- a/api_docs/kbn_core_ui_settings_server_internal.mdx +++ b/api_docs/kbn_core_ui_settings_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-internal title: "@kbn/core-ui-settings-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-internal'] --- import kbnCoreUiSettingsServerInternalObj from './kbn_core_ui_settings_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_ui_settings_server_mocks.mdx b/api_docs/kbn_core_ui_settings_server_mocks.mdx index c287c7d3717f4..6350fd7ee5068 100644 --- a/api_docs/kbn_core_ui_settings_server_mocks.mdx +++ b/api_docs/kbn_core_ui_settings_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-ui-settings-server-mocks title: "@kbn/core-ui-settings-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-ui-settings-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-ui-settings-server-mocks'] --- import kbnCoreUiSettingsServerMocksObj from './kbn_core_ui_settings_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server.mdx b/api_docs/kbn_core_usage_data_server.mdx index 8d5b6922438e8..54fc17cd96f04 100644 --- a/api_docs/kbn_core_usage_data_server.mdx +++ b/api_docs/kbn_core_usage_data_server.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server title: "@kbn/core-usage-data-server" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server'] --- import kbnCoreUsageDataServerObj from './kbn_core_usage_data_server.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_internal.mdx b/api_docs/kbn_core_usage_data_server_internal.mdx index e433b18463057..7722ffd3f68a5 100644 --- a/api_docs/kbn_core_usage_data_server_internal.mdx +++ b/api_docs/kbn_core_usage_data_server_internal.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-internal title: "@kbn/core-usage-data-server-internal" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-internal plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-internal'] --- import kbnCoreUsageDataServerInternalObj from './kbn_core_usage_data_server_internal.devdocs.json'; diff --git a/api_docs/kbn_core_usage_data_server_mocks.mdx b/api_docs/kbn_core_usage_data_server_mocks.mdx index ad3dac588eb63..131c2f323331b 100644 --- a/api_docs/kbn_core_usage_data_server_mocks.mdx +++ b/api_docs/kbn_core_usage_data_server_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-core-usage-data-server-mocks title: "@kbn/core-usage-data-server-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/core-usage-data-server-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/core-usage-data-server-mocks'] --- import kbnCoreUsageDataServerMocksObj from './kbn_core_usage_data_server_mocks.devdocs.json'; diff --git a/api_docs/kbn_crypto.mdx b/api_docs/kbn_crypto.mdx index 0e41431342876..6ca5ba080918c 100644 --- a/api_docs/kbn_crypto.mdx +++ b/api_docs/kbn_crypto.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto title: "@kbn/crypto" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto'] --- import kbnCryptoObj from './kbn_crypto.devdocs.json'; diff --git a/api_docs/kbn_crypto_browser.mdx b/api_docs/kbn_crypto_browser.mdx index 7f67262cf91c9..7da4515e5c5da 100644 --- a/api_docs/kbn_crypto_browser.mdx +++ b/api_docs/kbn_crypto_browser.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-crypto-browser title: "@kbn/crypto-browser" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/crypto-browser plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/crypto-browser'] --- import kbnCryptoBrowserObj from './kbn_crypto_browser.devdocs.json'; diff --git a/api_docs/kbn_datemath.mdx b/api_docs/kbn_datemath.mdx index 86cc145ec159a..c0ab47b943fe5 100644 --- a/api_docs/kbn_datemath.mdx +++ b/api_docs/kbn_datemath.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-datemath title: "@kbn/datemath" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/datemath plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/datemath'] --- import kbnDatemathObj from './kbn_datemath.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_errors.mdx b/api_docs/kbn_dev_cli_errors.mdx index e46730cf719c3..81afcdef5fadb 100644 --- a/api_docs/kbn_dev_cli_errors.mdx +++ b/api_docs/kbn_dev_cli_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-errors title: "@kbn/dev-cli-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-errors plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-errors'] --- import kbnDevCliErrorsObj from './kbn_dev_cli_errors.devdocs.json'; diff --git a/api_docs/kbn_dev_cli_runner.mdx b/api_docs/kbn_dev_cli_runner.mdx index 54d0f8713d0fb..1e541a024defe 100644 --- a/api_docs/kbn_dev_cli_runner.mdx +++ b/api_docs/kbn_dev_cli_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-cli-runner title: "@kbn/dev-cli-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-cli-runner plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-cli-runner'] --- import kbnDevCliRunnerObj from './kbn_dev_cli_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_proc_runner.mdx b/api_docs/kbn_dev_proc_runner.mdx index b646fc86f43f5..c9e8aeb0cceea 100644 --- a/api_docs/kbn_dev_proc_runner.mdx +++ b/api_docs/kbn_dev_proc_runner.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-proc-runner title: "@kbn/dev-proc-runner" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-proc-runner plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-proc-runner'] --- import kbnDevProcRunnerObj from './kbn_dev_proc_runner.devdocs.json'; diff --git a/api_docs/kbn_dev_utils.mdx b/api_docs/kbn_dev_utils.mdx index 83091d9879dc4..83bf4e00c6541 100644 --- a/api_docs/kbn_dev_utils.mdx +++ b/api_docs/kbn_dev_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-dev-utils title: "@kbn/dev-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/dev-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/dev-utils'] --- import kbnDevUtilsObj from './kbn_dev_utils.devdocs.json'; diff --git a/api_docs/kbn_doc_links.devdocs.json b/api_docs/kbn_doc_links.devdocs.json index d74d520ff8430..0cf3b152d8668 100644 --- a/api_docs/kbn_doc_links.devdocs.json +++ b/api_docs/kbn_doc_links.devdocs.json @@ -546,7 +546,7 @@ "label": "securitySolution", "description": [], "signature": [ - "{ readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; }" + "{ readonly trustedApps: string; readonly eventFilters: string; readonly blocklist: string; readonly endpointArtifacts: string; readonly policyResponseTroubleshooting: { full_disk_access: string; macos_system_ext: string; linux_deadlock: string; }; readonly packageActionTroubleshooting: { es_connection: string; }; readonly threatIntelInt: string; readonly responseActions: string; }" ], "path": "packages/kbn-doc-links/src/types.ts", "deprecated": false, diff --git a/api_docs/kbn_doc_links.mdx b/api_docs/kbn_doc_links.mdx index 8913a1a03ab85..f20c5c3e4621f 100644 --- a/api_docs/kbn_doc_links.mdx +++ b/api_docs/kbn_doc_links.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-doc-links title: "@kbn/doc-links" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/doc-links plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/doc-links'] --- import kbnDocLinksObj from './kbn_doc_links.devdocs.json'; diff --git a/api_docs/kbn_docs_utils.mdx b/api_docs/kbn_docs_utils.mdx index a5d4079c284b0..c92f20d4e82eb 100644 --- a/api_docs/kbn_docs_utils.mdx +++ b/api_docs/kbn_docs_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-docs-utils title: "@kbn/docs-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/docs-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/docs-utils'] --- import kbnDocsUtilsObj from './kbn_docs_utils.devdocs.json'; diff --git a/api_docs/kbn_ebt_tools.mdx b/api_docs/kbn_ebt_tools.mdx index 8370c22a01f19..f301c7fde5a81 100644 --- a/api_docs/kbn_ebt_tools.mdx +++ b/api_docs/kbn_ebt_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ebt-tools title: "@kbn/ebt-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ebt-tools plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ebt-tools'] --- import kbnEbtToolsObj from './kbn_ebt_tools.devdocs.json'; diff --git a/api_docs/kbn_es_archiver.mdx b/api_docs/kbn_es_archiver.mdx index 4ad77bfff6559..1fd6cb7fc92bc 100644 --- a/api_docs/kbn_es_archiver.mdx +++ b/api_docs/kbn_es_archiver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-archiver title: "@kbn/es-archiver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-archiver plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-archiver'] --- import kbnEsArchiverObj from './kbn_es_archiver.devdocs.json'; diff --git a/api_docs/kbn_es_errors.mdx b/api_docs/kbn_es_errors.mdx index bf9c27ac92b27..0c2ba2f9d4488 100644 --- a/api_docs/kbn_es_errors.mdx +++ b/api_docs/kbn_es_errors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-errors title: "@kbn/es-errors" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-errors plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-errors'] --- import kbnEsErrorsObj from './kbn_es_errors.devdocs.json'; diff --git a/api_docs/kbn_es_query.mdx b/api_docs/kbn_es_query.mdx index 632303fa82c97..7b7bc93b57313 100644 --- a/api_docs/kbn_es_query.mdx +++ b/api_docs/kbn_es_query.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-query title: "@kbn/es-query" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-query plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-query'] --- import kbnEsQueryObj from './kbn_es_query.devdocs.json'; diff --git a/api_docs/kbn_es_types.mdx b/api_docs/kbn_es_types.mdx index 6a6ac63f69acd..fe461a5b010f5 100644 --- a/api_docs/kbn_es_types.mdx +++ b/api_docs/kbn_es_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-es-types title: "@kbn/es-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/es-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/es-types'] --- import kbnEsTypesObj from './kbn_es_types.devdocs.json'; diff --git a/api_docs/kbn_eslint_plugin_imports.mdx b/api_docs/kbn_eslint_plugin_imports.mdx index e0525000b5f9f..facb9ad743434 100644 --- a/api_docs/kbn_eslint_plugin_imports.mdx +++ b/api_docs/kbn_eslint_plugin_imports.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-eslint-plugin-imports title: "@kbn/eslint-plugin-imports" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/eslint-plugin-imports plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/eslint-plugin-imports'] --- import kbnEslintPluginImportsObj from './kbn_eslint_plugin_imports.devdocs.json'; diff --git a/api_docs/kbn_field_types.mdx b/api_docs/kbn_field_types.mdx index f9ad3846f19fa..af6a78abd324a 100644 --- a/api_docs/kbn_field_types.mdx +++ b/api_docs/kbn_field_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-field-types title: "@kbn/field-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/field-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/field-types'] --- import kbnFieldTypesObj from './kbn_field_types.devdocs.json'; diff --git a/api_docs/kbn_find_used_node_modules.mdx b/api_docs/kbn_find_used_node_modules.mdx index 9bc0b92168240..81929ed2bb345 100644 --- a/api_docs/kbn_find_used_node_modules.mdx +++ b/api_docs/kbn_find_used_node_modules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-find-used-node-modules title: "@kbn/find-used-node-modules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/find-used-node-modules plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/find-used-node-modules'] --- import kbnFindUsedNodeModulesObj from './kbn_find_used_node_modules.devdocs.json'; diff --git a/api_docs/kbn_ftr_common_functional_services.mdx b/api_docs/kbn_ftr_common_functional_services.mdx index 359334478d03d..9c5b2b494ed5f 100644 --- a/api_docs/kbn_ftr_common_functional_services.mdx +++ b/api_docs/kbn_ftr_common_functional_services.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ftr-common-functional-services title: "@kbn/ftr-common-functional-services" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ftr-common-functional-services plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ftr-common-functional-services'] --- import kbnFtrCommonFunctionalServicesObj from './kbn_ftr_common_functional_services.devdocs.json'; diff --git a/api_docs/kbn_generate.mdx b/api_docs/kbn_generate.mdx index 151e0da4dd3b1..cbe8ec573dfd5 100644 --- a/api_docs/kbn_generate.mdx +++ b/api_docs/kbn_generate.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-generate title: "@kbn/generate" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/generate plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/generate'] --- import kbnGenerateObj from './kbn_generate.devdocs.json'; diff --git a/api_docs/kbn_get_repo_files.mdx b/api_docs/kbn_get_repo_files.mdx index 161354bbbcb14..0958cd3c3ebf7 100644 --- a/api_docs/kbn_get_repo_files.mdx +++ b/api_docs/kbn_get_repo_files.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-get-repo-files title: "@kbn/get-repo-files" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/get-repo-files plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/get-repo-files'] --- import kbnGetRepoFilesObj from './kbn_get_repo_files.devdocs.json'; diff --git a/api_docs/kbn_handlebars.mdx b/api_docs/kbn_handlebars.mdx index 1e054c0706c6f..2ec2679987952 100644 --- a/api_docs/kbn_handlebars.mdx +++ b/api_docs/kbn_handlebars.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-handlebars title: "@kbn/handlebars" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/handlebars plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/handlebars'] --- import kbnHandlebarsObj from './kbn_handlebars.devdocs.json'; diff --git a/api_docs/kbn_hapi_mocks.mdx b/api_docs/kbn_hapi_mocks.mdx index 0852dda61f29a..7f42b8a468d7e 100644 --- a/api_docs/kbn_hapi_mocks.mdx +++ b/api_docs/kbn_hapi_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-hapi-mocks title: "@kbn/hapi-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/hapi-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/hapi-mocks'] --- import kbnHapiMocksObj from './kbn_hapi_mocks.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_card.mdx b/api_docs/kbn_home_sample_data_card.mdx index d4cca06818606..d4e8d54488ba4 100644 --- a/api_docs/kbn_home_sample_data_card.mdx +++ b/api_docs/kbn_home_sample_data_card.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-card title: "@kbn/home-sample-data-card" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-card plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-card'] --- import kbnHomeSampleDataCardObj from './kbn_home_sample_data_card.devdocs.json'; diff --git a/api_docs/kbn_home_sample_data_tab.mdx b/api_docs/kbn_home_sample_data_tab.mdx index 54d4d15fc741e..a0b43f842438b 100644 --- a/api_docs/kbn_home_sample_data_tab.mdx +++ b/api_docs/kbn_home_sample_data_tab.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-home-sample-data-tab title: "@kbn/home-sample-data-tab" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/home-sample-data-tab plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/home-sample-data-tab'] --- import kbnHomeSampleDataTabObj from './kbn_home_sample_data_tab.devdocs.json'; diff --git a/api_docs/kbn_i18n.mdx b/api_docs/kbn_i18n.mdx index c7ff5b8ab5056..7b9d043059c71 100644 --- a/api_docs/kbn_i18n.mdx +++ b/api_docs/kbn_i18n.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-i18n title: "@kbn/i18n" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/i18n plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/i18n'] --- import kbnI18nObj from './kbn_i18n.devdocs.json'; diff --git a/api_docs/kbn_import_resolver.mdx b/api_docs/kbn_import_resolver.mdx index 262e79df335d1..d11c22a74ecda 100644 --- a/api_docs/kbn_import_resolver.mdx +++ b/api_docs/kbn_import_resolver.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-import-resolver title: "@kbn/import-resolver" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/import-resolver plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/import-resolver'] --- import kbnImportResolverObj from './kbn_import_resolver.devdocs.json'; diff --git a/api_docs/kbn_interpreter.mdx b/api_docs/kbn_interpreter.mdx index df20b5d894c5e..39f9d65660d07 100644 --- a/api_docs/kbn_interpreter.mdx +++ b/api_docs/kbn_interpreter.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-interpreter title: "@kbn/interpreter" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/interpreter plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/interpreter'] --- import kbnInterpreterObj from './kbn_interpreter.devdocs.json'; diff --git a/api_docs/kbn_io_ts_utils.mdx b/api_docs/kbn_io_ts_utils.mdx index 4994d60766a45..19a46d0750ca6 100644 --- a/api_docs/kbn_io_ts_utils.mdx +++ b/api_docs/kbn_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-io-ts-utils title: "@kbn/io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/io-ts-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/io-ts-utils'] --- import kbnIoTsUtilsObj from './kbn_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_jest_serializers.mdx b/api_docs/kbn_jest_serializers.mdx index e5e069f91c957..7e5e0932af67d 100644 --- a/api_docs/kbn_jest_serializers.mdx +++ b/api_docs/kbn_jest_serializers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-jest-serializers title: "@kbn/jest-serializers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/jest-serializers plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/jest-serializers'] --- import kbnJestSerializersObj from './kbn_jest_serializers.devdocs.json'; diff --git a/api_docs/kbn_journeys.mdx b/api_docs/kbn_journeys.mdx index 21477f1c435c7..f4c9aec83599f 100644 --- a/api_docs/kbn_journeys.mdx +++ b/api_docs/kbn_journeys.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-journeys title: "@kbn/journeys" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/journeys plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/journeys'] --- import kbnJourneysObj from './kbn_journeys.devdocs.json'; diff --git a/api_docs/kbn_kibana_manifest_schema.mdx b/api_docs/kbn_kibana_manifest_schema.mdx index 6d9b4a9bb317c..2ab7c2cad23dd 100644 --- a/api_docs/kbn_kibana_manifest_schema.mdx +++ b/api_docs/kbn_kibana_manifest_schema.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-kibana-manifest-schema title: "@kbn/kibana-manifest-schema" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/kibana-manifest-schema plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/kibana-manifest-schema'] --- import kbnKibanaManifestSchemaObj from './kbn_kibana_manifest_schema.devdocs.json'; diff --git a/api_docs/kbn_logging.mdx b/api_docs/kbn_logging.mdx index 712dd79f55359..4c78e0d66ea73 100644 --- a/api_docs/kbn_logging.mdx +++ b/api_docs/kbn_logging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging title: "@kbn/logging" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging'] --- import kbnLoggingObj from './kbn_logging.devdocs.json'; diff --git a/api_docs/kbn_logging_mocks.mdx b/api_docs/kbn_logging_mocks.mdx index 6dcc57446aaad..5cc97dbc4f9de 100644 --- a/api_docs/kbn_logging_mocks.mdx +++ b/api_docs/kbn_logging_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-logging-mocks title: "@kbn/logging-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/logging-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/logging-mocks'] --- import kbnLoggingMocksObj from './kbn_logging_mocks.devdocs.json'; diff --git a/api_docs/kbn_managed_vscode_config.mdx b/api_docs/kbn_managed_vscode_config.mdx index 788e96c22f5c3..110b3b19a0fa8 100644 --- a/api_docs/kbn_managed_vscode_config.mdx +++ b/api_docs/kbn_managed_vscode_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-managed-vscode-config title: "@kbn/managed-vscode-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/managed-vscode-config plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/managed-vscode-config'] --- import kbnManagedVscodeConfigObj from './kbn_managed_vscode_config.devdocs.json'; diff --git a/api_docs/kbn_mapbox_gl.mdx b/api_docs/kbn_mapbox_gl.mdx index 8e2ce8b3d9fdc..11c646693dc0b 100644 --- a/api_docs/kbn_mapbox_gl.mdx +++ b/api_docs/kbn_mapbox_gl.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-mapbox-gl title: "@kbn/mapbox-gl" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/mapbox-gl plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/mapbox-gl'] --- import kbnMapboxGlObj from './kbn_mapbox_gl.devdocs.json'; diff --git a/api_docs/kbn_ml_agg_utils.mdx b/api_docs/kbn_ml_agg_utils.mdx index 25b2b17e3997b..ccb0243714c5a 100644 --- a/api_docs/kbn_ml_agg_utils.mdx +++ b/api_docs/kbn_ml_agg_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-agg-utils title: "@kbn/ml-agg-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-agg-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-agg-utils'] --- import kbnMlAggUtilsObj from './kbn_ml_agg_utils.devdocs.json'; diff --git a/api_docs/kbn_ml_is_populated_object.mdx b/api_docs/kbn_ml_is_populated_object.mdx index 5f24dc1806b03..9e756982a3c4b 100644 --- a/api_docs/kbn_ml_is_populated_object.mdx +++ b/api_docs/kbn_ml_is_populated_object.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-is-populated-object title: "@kbn/ml-is-populated-object" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-is-populated-object plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-is-populated-object'] --- import kbnMlIsPopulatedObjectObj from './kbn_ml_is_populated_object.devdocs.json'; diff --git a/api_docs/kbn_ml_string_hash.mdx b/api_docs/kbn_ml_string_hash.mdx index 2fa9ac8d84b7a..5a08b38fdc366 100644 --- a/api_docs/kbn_ml_string_hash.mdx +++ b/api_docs/kbn_ml_string_hash.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ml-string-hash title: "@kbn/ml-string-hash" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ml-string-hash plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ml-string-hash'] --- import kbnMlStringHashObj from './kbn_ml_string_hash.devdocs.json'; diff --git a/api_docs/kbn_monaco.mdx b/api_docs/kbn_monaco.mdx index c5b48fbeff006..13902cebdc873 100644 --- a/api_docs/kbn_monaco.mdx +++ b/api_docs/kbn_monaco.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-monaco title: "@kbn/monaco" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/monaco plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/monaco'] --- import kbnMonacoObj from './kbn_monaco.devdocs.json'; diff --git a/api_docs/kbn_optimizer.mdx b/api_docs/kbn_optimizer.mdx index 2a5d7bc10afb8..60b2e27c0fc5e 100644 --- a/api_docs/kbn_optimizer.mdx +++ b/api_docs/kbn_optimizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer title: "@kbn/optimizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer'] --- import kbnOptimizerObj from './kbn_optimizer.devdocs.json'; diff --git a/api_docs/kbn_optimizer_webpack_helpers.mdx b/api_docs/kbn_optimizer_webpack_helpers.mdx index 3f87a2b8b79d8..805c9413ad27f 100644 --- a/api_docs/kbn_optimizer_webpack_helpers.mdx +++ b/api_docs/kbn_optimizer_webpack_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-optimizer-webpack-helpers title: "@kbn/optimizer-webpack-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/optimizer-webpack-helpers plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/optimizer-webpack-helpers'] --- import kbnOptimizerWebpackHelpersObj from './kbn_optimizer_webpack_helpers.devdocs.json'; diff --git a/api_docs/kbn_osquery_io_ts_types.mdx b/api_docs/kbn_osquery_io_ts_types.mdx index 76d9089526cec..a2a0a87619793 100644 --- a/api_docs/kbn_osquery_io_ts_types.mdx +++ b/api_docs/kbn_osquery_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-osquery-io-ts-types title: "@kbn/osquery-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/osquery-io-ts-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/osquery-io-ts-types'] --- import kbnOsqueryIoTsTypesObj from './kbn_osquery_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_performance_testing_dataset_extractor.mdx b/api_docs/kbn_performance_testing_dataset_extractor.mdx index 3e83e64f435dc..66b4513e3e93d 100644 --- a/api_docs/kbn_performance_testing_dataset_extractor.mdx +++ b/api_docs/kbn_performance_testing_dataset_extractor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-performance-testing-dataset-extractor title: "@kbn/performance-testing-dataset-extractor" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/performance-testing-dataset-extractor plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/performance-testing-dataset-extractor'] --- import kbnPerformanceTestingDatasetExtractorObj from './kbn_performance_testing_dataset_extractor.devdocs.json'; diff --git a/api_docs/kbn_plugin_generator.mdx b/api_docs/kbn_plugin_generator.mdx index dc887c9ce3056..d0487d7546b1e 100644 --- a/api_docs/kbn_plugin_generator.mdx +++ b/api_docs/kbn_plugin_generator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-generator title: "@kbn/plugin-generator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-generator plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-generator'] --- import kbnPluginGeneratorObj from './kbn_plugin_generator.devdocs.json'; diff --git a/api_docs/kbn_plugin_helpers.mdx b/api_docs/kbn_plugin_helpers.mdx index 30cf3560edd3a..fe379e3b837c0 100644 --- a/api_docs/kbn_plugin_helpers.mdx +++ b/api_docs/kbn_plugin_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-plugin-helpers title: "@kbn/plugin-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/plugin-helpers plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/plugin-helpers'] --- import kbnPluginHelpersObj from './kbn_plugin_helpers.devdocs.json'; diff --git a/api_docs/kbn_react_field.mdx b/api_docs/kbn_react_field.mdx index ae485f79249a3..d1b5eb542c882 100644 --- a/api_docs/kbn_react_field.mdx +++ b/api_docs/kbn_react_field.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-react-field title: "@kbn/react-field" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/react-field plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/react-field'] --- import kbnReactFieldObj from './kbn_react_field.devdocs.json'; diff --git a/api_docs/kbn_repo_source_classifier.mdx b/api_docs/kbn_repo_source_classifier.mdx index 8249b1eae5a39..6d2caad781235 100644 --- a/api_docs/kbn_repo_source_classifier.mdx +++ b/api_docs/kbn_repo_source_classifier.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-repo-source-classifier title: "@kbn/repo-source-classifier" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/repo-source-classifier plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/repo-source-classifier'] --- import kbnRepoSourceClassifierObj from './kbn_repo_source_classifier.devdocs.json'; diff --git a/api_docs/kbn_rule_data_utils.devdocs.json b/api_docs/kbn_rule_data_utils.devdocs.json index 2d6403bd41dfe..8db17c376261c 100644 --- a/api_docs/kbn_rule_data_utils.devdocs.json +++ b/api_docs/kbn_rule_data_utils.devdocs.json @@ -942,7 +942,7 @@ "label": "AlertStatus", "description": [], "signature": [ - "\"recovered\" | \"active\"" + "\"active\" | \"recovered\"" ], "path": "packages/kbn-rule-data-utils/src/alerts_as_data_status.ts", "deprecated": false, diff --git a/api_docs/kbn_rule_data_utils.mdx b/api_docs/kbn_rule_data_utils.mdx index a4ef149f8033b..2e861a2bd3a13 100644 --- a/api_docs/kbn_rule_data_utils.mdx +++ b/api_docs/kbn_rule_data_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-rule-data-utils title: "@kbn/rule-data-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/rule-data-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/rule-data-utils'] --- import kbnRuleDataUtilsObj from './kbn_rule_data_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_autocomplete.mdx b/api_docs/kbn_securitysolution_autocomplete.mdx index ea7380185e08a..3d11d856ffe07 100644 --- a/api_docs/kbn_securitysolution_autocomplete.mdx +++ b/api_docs/kbn_securitysolution_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-autocomplete title: "@kbn/securitysolution-autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-autocomplete plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-autocomplete'] --- import kbnSecuritysolutionAutocompleteObj from './kbn_securitysolution_autocomplete.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_es_utils.mdx b/api_docs/kbn_securitysolution_es_utils.mdx index 4f2b01b578636..4ce360705125a 100644 --- a/api_docs/kbn_securitysolution_es_utils.mdx +++ b/api_docs/kbn_securitysolution_es_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-es-utils title: "@kbn/securitysolution-es-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-es-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-es-utils'] --- import kbnSecuritysolutionEsUtilsObj from './kbn_securitysolution_es_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_exception_list_components.mdx b/api_docs/kbn_securitysolution_exception_list_components.mdx index 35651464e503c..fe34878dde867 100644 --- a/api_docs/kbn_securitysolution_exception_list_components.mdx +++ b/api_docs/kbn_securitysolution_exception_list_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-exception-list-components title: "@kbn/securitysolution-exception-list-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-exception-list-components plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-exception-list-components'] --- import kbnSecuritysolutionExceptionListComponentsObj from './kbn_securitysolution_exception_list_components.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_hook_utils.mdx b/api_docs/kbn_securitysolution_hook_utils.mdx index 74a60ea3a6e0d..b7857cb66e9ec 100644 --- a/api_docs/kbn_securitysolution_hook_utils.mdx +++ b/api_docs/kbn_securitysolution_hook_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-hook-utils title: "@kbn/securitysolution-hook-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-hook-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-hook-utils'] --- import kbnSecuritysolutionHookUtilsObj from './kbn_securitysolution_hook_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx index a2e5cb79e7ec0..9f0b39175cdd0 100644 --- a/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_alerting_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-alerting-types title: "@kbn/securitysolution-io-ts-alerting-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-alerting-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-alerting-types'] --- import kbnSecuritysolutionIoTsAlertingTypesObj from './kbn_securitysolution_io_ts_alerting_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_list_types.mdx b/api_docs/kbn_securitysolution_io_ts_list_types.mdx index 878d21884f5ab..bf6b60393ec53 100644 --- a/api_docs/kbn_securitysolution_io_ts_list_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_list_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-list-types title: "@kbn/securitysolution-io-ts-list-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-list-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-list-types'] --- import kbnSecuritysolutionIoTsListTypesObj from './kbn_securitysolution_io_ts_list_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_types.mdx b/api_docs/kbn_securitysolution_io_ts_types.mdx index 7009b4610d376..ccbb90b450959 100644 --- a/api_docs/kbn_securitysolution_io_ts_types.mdx +++ b/api_docs/kbn_securitysolution_io_ts_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-types title: "@kbn/securitysolution-io-ts-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-types'] --- import kbnSecuritysolutionIoTsTypesObj from './kbn_securitysolution_io_ts_types.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_io_ts_utils.mdx b/api_docs/kbn_securitysolution_io_ts_utils.mdx index 1a8635741da38..2b3c7663b879f 100644 --- a/api_docs/kbn_securitysolution_io_ts_utils.mdx +++ b/api_docs/kbn_securitysolution_io_ts_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-io-ts-utils title: "@kbn/securitysolution-io-ts-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-io-ts-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-io-ts-utils'] --- import kbnSecuritysolutionIoTsUtilsObj from './kbn_securitysolution_io_ts_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_api.mdx b/api_docs/kbn_securitysolution_list_api.mdx index ba0f3639d8e00..a5e08ee825fae 100644 --- a/api_docs/kbn_securitysolution_list_api.mdx +++ b/api_docs/kbn_securitysolution_list_api.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-api title: "@kbn/securitysolution-list-api" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-api plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-api'] --- import kbnSecuritysolutionListApiObj from './kbn_securitysolution_list_api.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_constants.mdx b/api_docs/kbn_securitysolution_list_constants.mdx index cc6cd2e0e30ab..7b169ce42760c 100644 --- a/api_docs/kbn_securitysolution_list_constants.mdx +++ b/api_docs/kbn_securitysolution_list_constants.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-constants title: "@kbn/securitysolution-list-constants" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-constants plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-constants'] --- import kbnSecuritysolutionListConstantsObj from './kbn_securitysolution_list_constants.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_hooks.mdx b/api_docs/kbn_securitysolution_list_hooks.mdx index c160feacf522f..5c977abb1b280 100644 --- a/api_docs/kbn_securitysolution_list_hooks.mdx +++ b/api_docs/kbn_securitysolution_list_hooks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-hooks title: "@kbn/securitysolution-list-hooks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-hooks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-hooks'] --- import kbnSecuritysolutionListHooksObj from './kbn_securitysolution_list_hooks.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_list_utils.mdx b/api_docs/kbn_securitysolution_list_utils.mdx index 1a0fb17e5ced7..768c6cf059b57 100644 --- a/api_docs/kbn_securitysolution_list_utils.mdx +++ b/api_docs/kbn_securitysolution_list_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-list-utils title: "@kbn/securitysolution-list-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-list-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-list-utils'] --- import kbnSecuritysolutionListUtilsObj from './kbn_securitysolution_list_utils.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_rules.mdx b/api_docs/kbn_securitysolution_rules.mdx index 7aa3b1237d1bd..3496eef3b6346 100644 --- a/api_docs/kbn_securitysolution_rules.mdx +++ b/api_docs/kbn_securitysolution_rules.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-rules title: "@kbn/securitysolution-rules" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-rules plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-rules'] --- import kbnSecuritysolutionRulesObj from './kbn_securitysolution_rules.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_t_grid.mdx b/api_docs/kbn_securitysolution_t_grid.mdx index 7b831d8213377..2d25ede3c66ec 100644 --- a/api_docs/kbn_securitysolution_t_grid.mdx +++ b/api_docs/kbn_securitysolution_t_grid.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-t-grid title: "@kbn/securitysolution-t-grid" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-t-grid plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-t-grid'] --- import kbnSecuritysolutionTGridObj from './kbn_securitysolution_t_grid.devdocs.json'; diff --git a/api_docs/kbn_securitysolution_utils.mdx b/api_docs/kbn_securitysolution_utils.mdx index e82e1785f2490..26c6aa46ba082 100644 --- a/api_docs/kbn_securitysolution_utils.mdx +++ b/api_docs/kbn_securitysolution_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-securitysolution-utils title: "@kbn/securitysolution-utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/securitysolution-utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/securitysolution-utils'] --- import kbnSecuritysolutionUtilsObj from './kbn_securitysolution_utils.devdocs.json'; diff --git a/api_docs/kbn_server_http_tools.mdx b/api_docs/kbn_server_http_tools.mdx index 60f6a7802671b..16eac1230f6d8 100644 --- a/api_docs/kbn_server_http_tools.mdx +++ b/api_docs/kbn_server_http_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-http-tools title: "@kbn/server-http-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-http-tools plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-http-tools'] --- import kbnServerHttpToolsObj from './kbn_server_http_tools.devdocs.json'; diff --git a/api_docs/kbn_server_route_repository.mdx b/api_docs/kbn_server_route_repository.mdx index d5b974e9060a3..675a4f7818c66 100644 --- a/api_docs/kbn_server_route_repository.mdx +++ b/api_docs/kbn_server_route_repository.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-server-route-repository title: "@kbn/server-route-repository" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/server-route-repository plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/server-route-repository'] --- import kbnServerRouteRepositoryObj from './kbn_server_route_repository.devdocs.json'; diff --git a/api_docs/kbn_shared_svg.mdx b/api_docs/kbn_shared_svg.mdx index 434f6ebf7360f..bda0bfc9746fa 100644 --- a/api_docs/kbn_shared_svg.mdx +++ b/api_docs/kbn_shared_svg.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-svg title: "@kbn/shared-svg" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-svg plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-svg'] --- import kbnSharedSvgObj from './kbn_shared_svg.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx index aca5844ce1aa5..23abaffe3cc78 100644 --- a/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx +++ b/api_docs/kbn_shared_ux_avatar_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-avatar-user-profile-components title: "@kbn/shared-ux-avatar-user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-avatar-user-profile-components plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-avatar-user-profile-components'] --- import kbnSharedUxAvatarUserProfileComponentsObj from './kbn_shared_ux_avatar_user_profile_components.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx index 64ff483b257b8..e553b3a2c6c5f 100644 --- a/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx +++ b/api_docs/kbn_shared_ux_button_exit_full_screen_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-exit-full-screen-mocks title: "@kbn/shared-ux-button-exit-full-screen-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-exit-full-screen-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-exit-full-screen-mocks'] --- import kbnSharedUxButtonExitFullScreenMocksObj from './kbn_shared_ux_button_exit_full_screen_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_button_toolbar.mdx b/api_docs/kbn_shared_ux_button_toolbar.mdx index 98810e33e0a49..6465028feeb17 100644 --- a/api_docs/kbn_shared_ux_button_toolbar.mdx +++ b/api_docs/kbn_shared_ux_button_toolbar.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-button-toolbar title: "@kbn/shared-ux-button-toolbar" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-button-toolbar plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-button-toolbar'] --- import kbnSharedUxButtonToolbarObj from './kbn_shared_ux_button_toolbar.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data.mdx b/api_docs/kbn_shared_ux_card_no_data.mdx index f7ca5b1619118..c570628e0e331 100644 --- a/api_docs/kbn_shared_ux_card_no_data.mdx +++ b/api_docs/kbn_shared_ux_card_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data title: "@kbn/shared-ux-card-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data'] --- import kbnSharedUxCardNoDataObj from './kbn_shared_ux_card_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx index bae9391ff07c9..82c15083cc996 100644 --- a/api_docs/kbn_shared_ux_card_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_card_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-card-no-data-mocks title: "@kbn/shared-ux-card-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-card-no-data-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-card-no-data-mocks'] --- import kbnSharedUxCardNoDataMocksObj from './kbn_shared_ux_card_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx index aeabec4b1421b..56afa671aa65c 100644 --- a/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx +++ b/api_docs/kbn_shared_ux_link_redirect_app_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-link-redirect-app-mocks title: "@kbn/shared-ux-link-redirect-app-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-link-redirect-app-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-link-redirect-app-mocks'] --- import kbnSharedUxLinkRedirectAppMocksObj from './kbn_shared_ux_link_redirect_app_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx index 7dc8d4c973138..557e698f2e512 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data title: "@kbn/shared-ux-page-analytics-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data'] --- import kbnSharedUxPageAnalyticsNoDataObj from './kbn_shared_ux_page_analytics_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx index 5386d337930ef..352a0014b2cc0 100644 --- a/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_analytics_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-analytics-no-data-mocks title: "@kbn/shared-ux-page-analytics-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-analytics-no-data-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-analytics-no-data-mocks'] --- import kbnSharedUxPageAnalyticsNoDataMocksObj from './kbn_shared_ux_page_analytics_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx index 77248704888b4..b28052e80a11d 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data title: "@kbn/shared-ux-page-kibana-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data'] --- import kbnSharedUxPageKibanaNoDataObj from './kbn_shared_ux_page_kibana_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx index 100b84a823c93..15a591c9aecdf 100644 --- a/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-no-data-mocks title: "@kbn/shared-ux-page-kibana-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-no-data-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-no-data-mocks'] --- import kbnSharedUxPageKibanaNoDataMocksObj from './kbn_shared_ux_page_kibana_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template.mdx b/api_docs/kbn_shared_ux_page_kibana_template.mdx index fc9392a85f8ee..4c476350996e4 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template title: "@kbn/shared-ux-page-kibana-template" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template'] --- import kbnSharedUxPageKibanaTemplateObj from './kbn_shared_ux_page_kibana_template.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx index 75c7b818d2b99..2e4fc87798604 100644 --- a/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_kibana_template_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-kibana-template-mocks title: "@kbn/shared-ux-page-kibana-template-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-kibana-template-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-kibana-template-mocks'] --- import kbnSharedUxPageKibanaTemplateMocksObj from './kbn_shared_ux_page_kibana_template_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data.mdx b/api_docs/kbn_shared_ux_page_no_data.mdx index 46c1575ac211e..f22caeb920fc5 100644 --- a/api_docs/kbn_shared_ux_page_no_data.mdx +++ b/api_docs/kbn_shared_ux_page_no_data.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data title: "@kbn/shared-ux-page-no-data" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data'] --- import kbnSharedUxPageNoDataObj from './kbn_shared_ux_page_no_data.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config.mdx b/api_docs/kbn_shared_ux_page_no_data_config.mdx index ab3b48e848639..0f95fd61f2579 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config title: "@kbn/shared-ux-page-no-data-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config'] --- import kbnSharedUxPageNoDataConfigObj from './kbn_shared_ux_page_no_data_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx index be35872625c46..80f6cf9b8f34d 100644 --- a/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_config_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-config-mocks title: "@kbn/shared-ux-page-no-data-config-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-config-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-config-mocks'] --- import kbnSharedUxPageNoDataConfigMocksObj from './kbn_shared_ux_page_no_data_config_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx index b5afba9ff421d..fadea535b8245 100644 --- a/api_docs/kbn_shared_ux_page_no_data_mocks.mdx +++ b/api_docs/kbn_shared_ux_page_no_data_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-no-data-mocks title: "@kbn/shared-ux-page-no-data-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-no-data-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-no-data-mocks'] --- import kbnSharedUxPageNoDataMocksObj from './kbn_shared_ux_page_no_data_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_page_solution_nav.mdx b/api_docs/kbn_shared_ux_page_solution_nav.mdx index da1478041d724..aa9686f1314b2 100644 --- a/api_docs/kbn_shared_ux_page_solution_nav.mdx +++ b/api_docs/kbn_shared_ux_page_solution_nav.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-page-solution-nav title: "@kbn/shared-ux-page-solution-nav" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-page-solution-nav plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-page-solution-nav'] --- import kbnSharedUxPageSolutionNavObj from './kbn_shared_ux_page_solution_nav.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx index 1b1327a86bd0a..95295947ea4c3 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views title: "@kbn/shared-ux-prompt-no-data-views" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views'] --- import kbnSharedUxPromptNoDataViewsObj from './kbn_shared_ux_prompt_no_data_views.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx index 0e7e8889a58c6..a136219cf2d8c 100644 --- a/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx +++ b/api_docs/kbn_shared_ux_prompt_no_data_views_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-prompt-no-data-views-mocks title: "@kbn/shared-ux-prompt-no-data-views-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-prompt-no-data-views-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-prompt-no-data-views-mocks'] --- import kbnSharedUxPromptNoDataViewsMocksObj from './kbn_shared_ux_prompt_no_data_views_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router.mdx b/api_docs/kbn_shared_ux_router.mdx index f9c07c50257c8..cbcc71d025c8b 100644 --- a/api_docs/kbn_shared_ux_router.mdx +++ b/api_docs/kbn_shared_ux_router.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router title: "@kbn/shared-ux-router" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router'] --- import kbnSharedUxRouterObj from './kbn_shared_ux_router.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_router_mocks.mdx b/api_docs/kbn_shared_ux_router_mocks.mdx index a0aca2fcd5aaf..40896d72b0dc8 100644 --- a/api_docs/kbn_shared_ux_router_mocks.mdx +++ b/api_docs/kbn_shared_ux_router_mocks.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-router-mocks title: "@kbn/shared-ux-router-mocks" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-router-mocks plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-router-mocks'] --- import kbnSharedUxRouterMocksObj from './kbn_shared_ux_router_mocks.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_config.mdx b/api_docs/kbn_shared_ux_storybook_config.mdx index aeae1c356a861..133cbe3bb9994 100644 --- a/api_docs/kbn_shared_ux_storybook_config.mdx +++ b/api_docs/kbn_shared_ux_storybook_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-config title: "@kbn/shared-ux-storybook-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-config plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-config'] --- import kbnSharedUxStorybookConfigObj from './kbn_shared_ux_storybook_config.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_storybook_mock.mdx b/api_docs/kbn_shared_ux_storybook_mock.mdx index d5712323de82d..443625c76310c 100644 --- a/api_docs/kbn_shared_ux_storybook_mock.mdx +++ b/api_docs/kbn_shared_ux_storybook_mock.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-storybook-mock title: "@kbn/shared-ux-storybook-mock" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-storybook-mock plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-storybook-mock'] --- import kbnSharedUxStorybookMockObj from './kbn_shared_ux_storybook_mock.devdocs.json'; diff --git a/api_docs/kbn_shared_ux_utility.mdx b/api_docs/kbn_shared_ux_utility.mdx index 3466072027056..1182906621731 100644 --- a/api_docs/kbn_shared_ux_utility.mdx +++ b/api_docs/kbn_shared_ux_utility.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-shared-ux-utility title: "@kbn/shared-ux-utility" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/shared-ux-utility plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/shared-ux-utility'] --- import kbnSharedUxUtilityObj from './kbn_shared_ux_utility.devdocs.json'; diff --git a/api_docs/kbn_some_dev_log.mdx b/api_docs/kbn_some_dev_log.mdx index d6c26edac107d..ea558aa9e073b 100644 --- a/api_docs/kbn_some_dev_log.mdx +++ b/api_docs/kbn_some_dev_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-some-dev-log title: "@kbn/some-dev-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/some-dev-log plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/some-dev-log'] --- import kbnSomeDevLogObj from './kbn_some_dev_log.devdocs.json'; diff --git a/api_docs/kbn_sort_package_json.mdx b/api_docs/kbn_sort_package_json.mdx index 13db8c8155fd0..e3a9c03528b20 100644 --- a/api_docs/kbn_sort_package_json.mdx +++ b/api_docs/kbn_sort_package_json.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-sort-package-json title: "@kbn/sort-package-json" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/sort-package-json plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/sort-package-json'] --- import kbnSortPackageJsonObj from './kbn_sort_package_json.devdocs.json'; diff --git a/api_docs/kbn_std.mdx b/api_docs/kbn_std.mdx index e96bc3e61652b..e5c5d241941bd 100644 --- a/api_docs/kbn_std.mdx +++ b/api_docs/kbn_std.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-std title: "@kbn/std" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/std plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/std'] --- import kbnStdObj from './kbn_std.devdocs.json'; diff --git a/api_docs/kbn_stdio_dev_helpers.mdx b/api_docs/kbn_stdio_dev_helpers.mdx index b4aac204fc2fc..1d5e0e3792587 100644 --- a/api_docs/kbn_stdio_dev_helpers.mdx +++ b/api_docs/kbn_stdio_dev_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-stdio-dev-helpers title: "@kbn/stdio-dev-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/stdio-dev-helpers plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/stdio-dev-helpers'] --- import kbnStdioDevHelpersObj from './kbn_stdio_dev_helpers.devdocs.json'; diff --git a/api_docs/kbn_storybook.mdx b/api_docs/kbn_storybook.mdx index 13b0046b3c5f5..49592c87fc217 100644 --- a/api_docs/kbn_storybook.mdx +++ b/api_docs/kbn_storybook.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-storybook title: "@kbn/storybook" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/storybook plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/storybook'] --- import kbnStorybookObj from './kbn_storybook.devdocs.json'; diff --git a/api_docs/kbn_telemetry_tools.mdx b/api_docs/kbn_telemetry_tools.mdx index 5dc57b83e3caf..548085afb06a6 100644 --- a/api_docs/kbn_telemetry_tools.mdx +++ b/api_docs/kbn_telemetry_tools.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-telemetry-tools title: "@kbn/telemetry-tools" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/telemetry-tools plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/telemetry-tools'] --- import kbnTelemetryToolsObj from './kbn_telemetry_tools.devdocs.json'; diff --git a/api_docs/kbn_test.mdx b/api_docs/kbn_test.mdx index 924a8f1d2d36d..130c68213d1dc 100644 --- a/api_docs/kbn_test.mdx +++ b/api_docs/kbn_test.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test title: "@kbn/test" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test'] --- import kbnTestObj from './kbn_test.devdocs.json'; diff --git a/api_docs/kbn_test_jest_helpers.mdx b/api_docs/kbn_test_jest_helpers.mdx index cc4a486b6dc8e..c9b070d08edf9 100644 --- a/api_docs/kbn_test_jest_helpers.mdx +++ b/api_docs/kbn_test_jest_helpers.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-jest-helpers title: "@kbn/test-jest-helpers" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-jest-helpers plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-jest-helpers'] --- import kbnTestJestHelpersObj from './kbn_test_jest_helpers.devdocs.json'; diff --git a/api_docs/kbn_test_subj_selector.mdx b/api_docs/kbn_test_subj_selector.mdx index 38fd9ee192959..e93c241e2cdac 100644 --- a/api_docs/kbn_test_subj_selector.mdx +++ b/api_docs/kbn_test_subj_selector.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-test-subj-selector title: "@kbn/test-subj-selector" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/test-subj-selector plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/test-subj-selector'] --- import kbnTestSubjSelectorObj from './kbn_test_subj_selector.devdocs.json'; diff --git a/api_docs/kbn_tooling_log.mdx b/api_docs/kbn_tooling_log.mdx index efc952ecde5a0..da5f83dd7a47b 100644 --- a/api_docs/kbn_tooling_log.mdx +++ b/api_docs/kbn_tooling_log.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-tooling-log title: "@kbn/tooling-log" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/tooling-log plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/tooling-log'] --- import kbnToolingLogObj from './kbn_tooling_log.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer.mdx b/api_docs/kbn_type_summarizer.mdx index 1c63f3fb99305..9ce0e8072dec2 100644 --- a/api_docs/kbn_type_summarizer.mdx +++ b/api_docs/kbn_type_summarizer.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer title: "@kbn/type-summarizer" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer'] --- import kbnTypeSummarizerObj from './kbn_type_summarizer.devdocs.json'; diff --git a/api_docs/kbn_type_summarizer_core.mdx b/api_docs/kbn_type_summarizer_core.mdx index 346fc07253bf5..d6c96609eee48 100644 --- a/api_docs/kbn_type_summarizer_core.mdx +++ b/api_docs/kbn_type_summarizer_core.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-type-summarizer-core title: "@kbn/type-summarizer-core" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/type-summarizer-core plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/type-summarizer-core'] --- import kbnTypeSummarizerCoreObj from './kbn_type_summarizer_core.devdocs.json'; diff --git a/api_docs/kbn_typed_react_router_config.mdx b/api_docs/kbn_typed_react_router_config.mdx index cc16ed53a0ad1..365d565746dbc 100644 --- a/api_docs/kbn_typed_react_router_config.mdx +++ b/api_docs/kbn_typed_react_router_config.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-typed-react-router-config title: "@kbn/typed-react-router-config" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/typed-react-router-config plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/typed-react-router-config'] --- import kbnTypedReactRouterConfigObj from './kbn_typed_react_router_config.devdocs.json'; diff --git a/api_docs/kbn_ui_theme.mdx b/api_docs/kbn_ui_theme.mdx index 768d7656e3a83..d8a381bf82680 100644 --- a/api_docs/kbn_ui_theme.mdx +++ b/api_docs/kbn_ui_theme.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-ui-theme title: "@kbn/ui-theme" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/ui-theme plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/ui-theme'] --- import kbnUiThemeObj from './kbn_ui_theme.devdocs.json'; diff --git a/api_docs/kbn_user_profile_components.devdocs.json b/api_docs/kbn_user_profile_components.devdocs.json index d8cd0cfd94f9e..55ddd03b7d6d4 100644 --- a/api_docs/kbn_user_profile_components.devdocs.json +++ b/api_docs/kbn_user_profile_components.devdocs.json @@ -121,6 +121,57 @@ "returnComment": [], "initialIsOpen": false }, + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserAvatarTip", + "type": "Function", + "tags": [], + "label": "UserAvatarTip", + "description": [ + "\nRenders a user avatar with tooltip" + ], + "signature": [ + "({ user, avatar, ...rest }: React.PropsWithChildren<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserAvatarProps", + "text": "UserAvatarProps" + }, + ">) => JSX.Element" + ], + "path": "packages/kbn-user-profile-components/src/user_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserAvatarTip.$1", + "type": "CompoundType", + "tags": [], + "label": "{ user, avatar, ...rest }", + "description": [], + "signature": [ + "React.PropsWithChildren<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserAvatarProps", + "text": "UserAvatarProps" + }, + ">" + ], + "path": "packages/kbn-user-profile-components/src/user_avatar_tip.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false + }, { "parentPluginId": "@kbn/user-profile-components", "id": "def-common.UserProfilesPopover", @@ -236,6 +287,57 @@ ], "returnComment": [], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserToolTip", + "type": "Function", + "tags": [], + "label": "UserToolTip", + "description": [ + "\nRenders a tooltip with user information" + ], + "signature": [ + "({ user, avatar, ...rest }: React.PropsWithChildren<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserToolTipProps", + "text": "UserToolTipProps" + }, + ">) => JSX.Element" + ], + "path": "packages/kbn-user-profile-components/src/user_tooltip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserToolTip.$1", + "type": "CompoundType", + "tags": [], + "label": "{ user, avatar, ...rest }", + "description": [], + "signature": [ + "React.PropsWithChildren<", + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserToolTipProps", + "text": "UserToolTipProps" + }, + ">" + ], + "path": "packages/kbn-user-profile-components/src/user_tooltip.tsx", + "deprecated": false, + "trackAdoption": false, + "isRequired": true + } + ], + "returnComment": [], + "initialIsOpen": false } ], "interfaces": [ @@ -938,6 +1040,79 @@ } ], "initialIsOpen": false + }, + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserToolTipProps", + "type": "Interface", + "tags": [], + "label": "UserToolTipProps", + "description": [ + "\nProps of {@link UserToolTip} component" + ], + "signature": [ + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserToolTipProps", + "text": "UserToolTipProps" + }, + " extends Omit<", + "EuiToolTipProps", + ", \"title\" | \"content\">" + ], + "path": "packages/kbn-user-profile-components/src/user_tooltip.tsx", + "deprecated": false, + "trackAdoption": false, + "children": [ + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserToolTipProps.user", + "type": "Object", + "tags": [], + "label": "user", + "description": [ + "\nUser to be rendered" + ], + "signature": [ + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfileUserInfo", + "text": "UserProfileUserInfo" + } + ], + "path": "packages/kbn-user-profile-components/src/user_tooltip.tsx", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "@kbn/user-profile-components", + "id": "def-common.UserToolTipProps.avatar", + "type": "Object", + "tags": [], + "label": "avatar", + "description": [ + "\nAvatar data of user to be rendered" + ], + "signature": [ + { + "pluginId": "@kbn/user-profile-components", + "scope": "common", + "docId": "kibKbnUserProfileComponentsPluginApi", + "section": "def-common.UserProfileAvatarData", + "text": "UserProfileAvatarData" + }, + " | undefined" + ], + "path": "packages/kbn-user-profile-components/src/user_tooltip.tsx", + "deprecated": false, + "trackAdoption": false + } + ], + "initialIsOpen": false } ], "enums": [], diff --git a/api_docs/kbn_user_profile_components.mdx b/api_docs/kbn_user_profile_components.mdx index 321db07786d48..1fb9abaf0ee8b 100644 --- a/api_docs/kbn_user_profile_components.mdx +++ b/api_docs/kbn_user_profile_components.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-user-profile-components title: "@kbn/user-profile-components" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/user-profile-components plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/user-profile-components'] --- import kbnUserProfileComponentsObj from './kbn_user_profile_components.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Owner missing] for questions regarding this plugin. | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 48 | 0 | 3 | 0 | +| 55 | 0 | 5 | 0 | ## Common diff --git a/api_docs/kbn_utility_types.mdx b/api_docs/kbn_utility_types.mdx index 59733af696053..1dd233e2b3c7c 100644 --- a/api_docs/kbn_utility_types.mdx +++ b/api_docs/kbn_utility_types.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types title: "@kbn/utility-types" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types'] --- import kbnUtilityTypesObj from './kbn_utility_types.devdocs.json'; diff --git a/api_docs/kbn_utility_types_jest.mdx b/api_docs/kbn_utility_types_jest.mdx index d34f5eb451f66..ffc882e7050cf 100644 --- a/api_docs/kbn_utility_types_jest.mdx +++ b/api_docs/kbn_utility_types_jest.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utility-types-jest title: "@kbn/utility-types-jest" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utility-types-jest plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utility-types-jest'] --- import kbnUtilityTypesJestObj from './kbn_utility_types_jest.devdocs.json'; diff --git a/api_docs/kbn_utils.mdx b/api_docs/kbn_utils.mdx index 3301abb7449f7..32d4e0b6f4f27 100644 --- a/api_docs/kbn_utils.mdx +++ b/api_docs/kbn_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-utils title: "@kbn/utils" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/utils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/utils'] --- import kbnUtilsObj from './kbn_utils.devdocs.json'; diff --git a/api_docs/kbn_yarn_lock_validator.mdx b/api_docs/kbn_yarn_lock_validator.mdx index 7aba97a043a20..8321d021b0720 100644 --- a/api_docs/kbn_yarn_lock_validator.mdx +++ b/api_docs/kbn_yarn_lock_validator.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kbn-yarn-lock-validator title: "@kbn/yarn-lock-validator" image: https://source.unsplash.com/400x175/?github description: API docs for the @kbn/yarn-lock-validator plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', '@kbn/yarn-lock-validator'] --- import kbnYarnLockValidatorObj from './kbn_yarn_lock_validator.devdocs.json'; diff --git a/api_docs/kibana_overview.mdx b/api_docs/kibana_overview.mdx index 99437dfa2d79f..6ea90bb2df76a 100644 --- a/api_docs/kibana_overview.mdx +++ b/api_docs/kibana_overview.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaOverview title: "kibanaOverview" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaOverview plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaOverview'] --- import kibanaOverviewObj from './kibana_overview.devdocs.json'; diff --git a/api_docs/kibana_react.mdx b/api_docs/kibana_react.mdx index b563f5461da0f..1a43fddabf34d 100644 --- a/api_docs/kibana_react.mdx +++ b/api_docs/kibana_react.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaReact title: "kibanaReact" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaReact plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaReact'] --- import kibanaReactObj from './kibana_react.devdocs.json'; diff --git a/api_docs/kibana_utils.mdx b/api_docs/kibana_utils.mdx index 5bb528b470212..7658f6b8dfa6c 100644 --- a/api_docs/kibana_utils.mdx +++ b/api_docs/kibana_utils.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kibanaUtils title: "kibanaUtils" image: https://source.unsplash.com/400x175/?github description: API docs for the kibanaUtils plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kibanaUtils'] --- import kibanaUtilsObj from './kibana_utils.devdocs.json'; diff --git a/api_docs/kubernetes_security.mdx b/api_docs/kubernetes_security.mdx index b08fc593fdcc7..f51fe23a0772a 100644 --- a/api_docs/kubernetes_security.mdx +++ b/api_docs/kubernetes_security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/kubernetesSecurity title: "kubernetesSecurity" image: https://source.unsplash.com/400x175/?github description: API docs for the kubernetesSecurity plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'kubernetesSecurity'] --- import kubernetesSecurityObj from './kubernetes_security.devdocs.json'; diff --git a/api_docs/lens.mdx b/api_docs/lens.mdx index ee26d7bf7b870..e930504ffaee4 100644 --- a/api_docs/lens.mdx +++ b/api_docs/lens.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lens title: "lens" image: https://source.unsplash.com/400x175/?github description: API docs for the lens plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lens'] --- import lensObj from './lens.devdocs.json'; diff --git a/api_docs/license_api_guard.mdx b/api_docs/license_api_guard.mdx index f3039f89e8db3..14b1e5c6f4612 100644 --- a/api_docs/license_api_guard.mdx +++ b/api_docs/license_api_guard.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseApiGuard title: "licenseApiGuard" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseApiGuard plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseApiGuard'] --- import licenseApiGuardObj from './license_api_guard.devdocs.json'; diff --git a/api_docs/license_management.mdx b/api_docs/license_management.mdx index a2bbb989390e5..b71a0f65fbba0 100644 --- a/api_docs/license_management.mdx +++ b/api_docs/license_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licenseManagement title: "licenseManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the licenseManagement plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licenseManagement'] --- import licenseManagementObj from './license_management.devdocs.json'; diff --git a/api_docs/licensing.mdx b/api_docs/licensing.mdx index a1ea39b243d1e..914e71d3130cf 100644 --- a/api_docs/licensing.mdx +++ b/api_docs/licensing.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/licensing title: "licensing" image: https://source.unsplash.com/400x175/?github description: API docs for the licensing plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'licensing'] --- import licensingObj from './licensing.devdocs.json'; diff --git a/api_docs/lists.mdx b/api_docs/lists.mdx index 167f95ca81b40..014535d3acb8d 100644 --- a/api_docs/lists.mdx +++ b/api_docs/lists.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/lists title: "lists" image: https://source.unsplash.com/400x175/?github description: API docs for the lists plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'lists'] --- import listsObj from './lists.devdocs.json'; diff --git a/api_docs/management.mdx b/api_docs/management.mdx index 7b13b081164ff..de79c0505493d 100644 --- a/api_docs/management.mdx +++ b/api_docs/management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/management title: "management" image: https://source.unsplash.com/400x175/?github description: API docs for the management plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'management'] --- import managementObj from './management.devdocs.json'; diff --git a/api_docs/maps.mdx b/api_docs/maps.mdx index 84567432aed62..0c1d388602858 100644 --- a/api_docs/maps.mdx +++ b/api_docs/maps.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/maps title: "maps" image: https://source.unsplash.com/400x175/?github description: API docs for the maps plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'maps'] --- import mapsObj from './maps.devdocs.json'; diff --git a/api_docs/maps_ems.mdx b/api_docs/maps_ems.mdx index 7e0c0528fef4a..82536b7c87b8d 100644 --- a/api_docs/maps_ems.mdx +++ b/api_docs/maps_ems.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/mapsEms title: "mapsEms" image: https://source.unsplash.com/400x175/?github description: API docs for the mapsEms plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'mapsEms'] --- import mapsEmsObj from './maps_ems.devdocs.json'; diff --git a/api_docs/ml.mdx b/api_docs/ml.mdx index a7a7a0517b9b8..9d94398a882da 100644 --- a/api_docs/ml.mdx +++ b/api_docs/ml.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ml title: "ml" image: https://source.unsplash.com/400x175/?github description: API docs for the ml plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ml'] --- import mlObj from './ml.devdocs.json'; diff --git a/api_docs/monitoring.mdx b/api_docs/monitoring.mdx index e884c213dddbc..c76aac1bd57ad 100644 --- a/api_docs/monitoring.mdx +++ b/api_docs/monitoring.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoring title: "monitoring" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoring plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoring'] --- import monitoringObj from './monitoring.devdocs.json'; diff --git a/api_docs/monitoring_collection.mdx b/api_docs/monitoring_collection.mdx index 37856b288370a..e59f472f07ee4 100644 --- a/api_docs/monitoring_collection.mdx +++ b/api_docs/monitoring_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/monitoringCollection title: "monitoringCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the monitoringCollection plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'monitoringCollection'] --- import monitoringCollectionObj from './monitoring_collection.devdocs.json'; diff --git a/api_docs/navigation.mdx b/api_docs/navigation.mdx index 283d52b0a4c3a..0dccfeb623a38 100644 --- a/api_docs/navigation.mdx +++ b/api_docs/navigation.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/navigation title: "navigation" image: https://source.unsplash.com/400x175/?github description: API docs for the navigation plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'navigation'] --- import navigationObj from './navigation.devdocs.json'; diff --git a/api_docs/newsfeed.mdx b/api_docs/newsfeed.mdx index 278b15e23d47f..08394f81d45d8 100644 --- a/api_docs/newsfeed.mdx +++ b/api_docs/newsfeed.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/newsfeed title: "newsfeed" image: https://source.unsplash.com/400x175/?github description: API docs for the newsfeed plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'newsfeed'] --- import newsfeedObj from './newsfeed.devdocs.json'; diff --git a/api_docs/observability.devdocs.json b/api_docs/observability.devdocs.json index fd03a2f285ab5..08f23a62393da 100644 --- a/api_docs/observability.devdocs.json +++ b/api_docs/observability.devdocs.json @@ -738,7 +738,7 @@ }, " | undefined; list: () => string[]; }; selectedAlertId?: string | undefined; } & ", "CommonProps", - " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes<HTMLDivElement> | \"css\"> & { ref?: React.RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined; }, \"children\" | \"ref\" | \"type\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"className\" | \"size\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"onClose\" | \"data-test-subj\" | \"css\" | \"as\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"maxWidth\" | \"hideCloseButton\" | \"closeButtonAriaLabel\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\">, \"children\" | \"type\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"alert\" | \"className\" | \"size\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"onClose\" | \"data-test-subj\" | \"css\" | \"as\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"alerts\" | \"maxWidth\" | \"hideCloseButton\" | \"closeButtonAriaLabel\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: React.RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined; }> & { readonly _result: ({ alert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" + " & { as?: \"div\" | undefined; } & _EuiFlyoutProps & Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, keyof _EuiFlyoutProps> & Omit<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, \"key\" | keyof React.HTMLAttributes<HTMLDivElement> | \"css\"> & { ref?: React.RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined; }, \"children\" | \"ref\" | \"type\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"className\" | \"size\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"onClose\" | \"data-test-subj\" | \"css\" | \"as\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"maxWidth\" | \"hideCloseButton\" | \"closeButtonAriaLabel\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\">, \"children\" | \"type\" | \"onError\" | \"hidden\" | \"color\" | \"id\" | \"alert\" | \"className\" | \"size\" | \"title\" | \"onChange\" | \"onKeyDown\" | \"onClick\" | \"security\" | \"key\" | \"defaultValue\" | \"lang\" | \"defaultChecked\" | \"suppressContentEditableWarning\" | \"suppressHydrationWarning\" | \"accessKey\" | \"contentEditable\" | \"contextMenu\" | \"dir\" | \"draggable\" | \"placeholder\" | \"slot\" | \"spellCheck\" | \"style\" | \"tabIndex\" | \"translate\" | \"radioGroup\" | \"role\" | \"about\" | \"datatype\" | \"inlist\" | \"prefix\" | \"property\" | \"resource\" | \"typeof\" | \"vocab\" | \"autoCapitalize\" | \"autoCorrect\" | \"autoSave\" | \"itemProp\" | \"itemScope\" | \"itemType\" | \"itemID\" | \"itemRef\" | \"results\" | \"unselectable\" | \"inputMode\" | \"is\" | \"aria-activedescendant\" | \"aria-atomic\" | \"aria-autocomplete\" | \"aria-busy\" | \"aria-checked\" | \"aria-colcount\" | \"aria-colindex\" | \"aria-colspan\" | \"aria-controls\" | \"aria-current\" | \"aria-describedby\" | \"aria-details\" | \"aria-disabled\" | \"aria-dropeffect\" | \"aria-errormessage\" | \"aria-expanded\" | \"aria-flowto\" | \"aria-grabbed\" | \"aria-haspopup\" | \"aria-hidden\" | \"aria-invalid\" | \"aria-keyshortcuts\" | \"aria-label\" | \"aria-labelledby\" | \"aria-level\" | \"aria-live\" | \"aria-modal\" | \"aria-multiline\" | \"aria-multiselectable\" | \"aria-orientation\" | \"aria-owns\" | \"aria-placeholder\" | \"aria-posinset\" | \"aria-pressed\" | \"aria-readonly\" | \"aria-relevant\" | \"aria-required\" | \"aria-roledescription\" | \"aria-rowcount\" | \"aria-rowindex\" | \"aria-rowspan\" | \"aria-selected\" | \"aria-setsize\" | \"aria-sort\" | \"aria-valuemax\" | \"aria-valuemin\" | \"aria-valuenow\" | \"aria-valuetext\" | \"dangerouslySetInnerHTML\" | \"onCopy\" | \"onCopyCapture\" | \"onCut\" | \"onCutCapture\" | \"onPaste\" | \"onPasteCapture\" | \"onCompositionEnd\" | \"onCompositionEndCapture\" | \"onCompositionStart\" | \"onCompositionStartCapture\" | \"onCompositionUpdate\" | \"onCompositionUpdateCapture\" | \"onFocus\" | \"onFocusCapture\" | \"onBlur\" | \"onBlurCapture\" | \"onChangeCapture\" | \"onBeforeInput\" | \"onBeforeInputCapture\" | \"onInput\" | \"onInputCapture\" | \"onReset\" | \"onResetCapture\" | \"onSubmit\" | \"onSubmitCapture\" | \"onInvalid\" | \"onInvalidCapture\" | \"onLoad\" | \"onLoadCapture\" | \"onErrorCapture\" | \"onKeyDownCapture\" | \"onKeyPress\" | \"onKeyPressCapture\" | \"onKeyUp\" | \"onKeyUpCapture\" | \"onAbort\" | \"onAbortCapture\" | \"onCanPlay\" | \"onCanPlayCapture\" | \"onCanPlayThrough\" | \"onCanPlayThroughCapture\" | \"onDurationChange\" | \"onDurationChangeCapture\" | \"onEmptied\" | \"onEmptiedCapture\" | \"onEncrypted\" | \"onEncryptedCapture\" | \"onEnded\" | \"onEndedCapture\" | \"onLoadedData\" | \"onLoadedDataCapture\" | \"onLoadedMetadata\" | \"onLoadedMetadataCapture\" | \"onLoadStart\" | \"onLoadStartCapture\" | \"onPause\" | \"onPauseCapture\" | \"onPlay\" | \"onPlayCapture\" | \"onPlaying\" | \"onPlayingCapture\" | \"onProgress\" | \"onProgressCapture\" | \"onRateChange\" | \"onRateChangeCapture\" | \"onSeeked\" | \"onSeekedCapture\" | \"onSeeking\" | \"onSeekingCapture\" | \"onStalled\" | \"onStalledCapture\" | \"onSuspend\" | \"onSuspendCapture\" | \"onTimeUpdate\" | \"onTimeUpdateCapture\" | \"onVolumeChange\" | \"onVolumeChangeCapture\" | \"onWaiting\" | \"onWaitingCapture\" | \"onAuxClick\" | \"onAuxClickCapture\" | \"onClickCapture\" | \"onContextMenu\" | \"onContextMenuCapture\" | \"onDoubleClick\" | \"onDoubleClickCapture\" | \"onDrag\" | \"onDragCapture\" | \"onDragEnd\" | \"onDragEndCapture\" | \"onDragEnter\" | \"onDragEnterCapture\" | \"onDragExit\" | \"onDragExitCapture\" | \"onDragLeave\" | \"onDragLeaveCapture\" | \"onDragOver\" | \"onDragOverCapture\" | \"onDragStart\" | \"onDragStartCapture\" | \"onDrop\" | \"onDropCapture\" | \"onMouseDown\" | \"onMouseDownCapture\" | \"onMouseEnter\" | \"onMouseLeave\" | \"onMouseMove\" | \"onMouseMoveCapture\" | \"onMouseOut\" | \"onMouseOutCapture\" | \"onMouseOver\" | \"onMouseOverCapture\" | \"onMouseUp\" | \"onMouseUpCapture\" | \"onSelect\" | \"onSelectCapture\" | \"onTouchCancel\" | \"onTouchCancelCapture\" | \"onTouchEnd\" | \"onTouchEndCapture\" | \"onTouchMove\" | \"onTouchMoveCapture\" | \"onTouchStart\" | \"onTouchStartCapture\" | \"onPointerDown\" | \"onPointerDownCapture\" | \"onPointerMove\" | \"onPointerMoveCapture\" | \"onPointerUp\" | \"onPointerUpCapture\" | \"onPointerCancel\" | \"onPointerCancelCapture\" | \"onPointerEnter\" | \"onPointerEnterCapture\" | \"onPointerLeave\" | \"onPointerLeaveCapture\" | \"onPointerOver\" | \"onPointerOverCapture\" | \"onPointerOut\" | \"onPointerOutCapture\" | \"onGotPointerCapture\" | \"onGotPointerCaptureCapture\" | \"onLostPointerCapture\" | \"onLostPointerCaptureCapture\" | \"onScroll\" | \"onScrollCapture\" | \"onWheel\" | \"onWheelCapture\" | \"onAnimationStart\" | \"onAnimationStartCapture\" | \"onAnimationEnd\" | \"onAnimationEndCapture\" | \"onAnimationIteration\" | \"onAnimationIterationCapture\" | \"onTransitionEnd\" | \"onTransitionEndCapture\" | \"onClose\" | \"data-test-subj\" | \"css\" | \"as\" | \"alerts\" | \"paddingSize\" | \"focusTrapProps\" | \"ownFocus\" | \"maxWidth\" | \"hideCloseButton\" | \"closeButtonAriaLabel\" | \"closeButtonProps\" | \"closeButtonPosition\" | \"maskProps\" | \"outsideClickCloses\" | \"side\" | \"pushMinBreakpoint\" | \"isInApp\" | \"observabilityRuleTypeRegistry\" | \"selectedAlertId\"> & { ref?: React.RefObject<HTMLDivElement> | ((instance: HTMLDivElement | null) => void) | null | undefined; }> & { readonly _result: ({ alert, alerts, isInApp, observabilityRuleTypeRegistry, onClose, selectedAlertId, }: AlertsFlyoutProps) => JSX.Element | null; }" ], "path": "x-pack/plugins/observability/public/index.ts", "deprecated": false, diff --git a/api_docs/observability.mdx b/api_docs/observability.mdx index 2f29ba44a8c06..aaa79beceb7aa 100644 --- a/api_docs/observability.mdx +++ b/api_docs/observability.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/observability title: "observability" image: https://source.unsplash.com/400x175/?github description: API docs for the observability plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'observability'] --- import observabilityObj from './observability.devdocs.json'; diff --git a/api_docs/osquery.mdx b/api_docs/osquery.mdx index 5b05f78a15a04..e79c8673975d0 100644 --- a/api_docs/osquery.mdx +++ b/api_docs/osquery.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/osquery title: "osquery" image: https://source.unsplash.com/400x175/?github description: API docs for the osquery plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'osquery'] --- import osqueryObj from './osquery.devdocs.json'; diff --git a/api_docs/plugin_directory.mdx b/api_docs/plugin_directory.mdx index b6b1d856686da..655626fbdb653 100644 --- a/api_docs/plugin_directory.mdx +++ b/api_docs/plugin_directory.mdx @@ -7,7 +7,7 @@ id: kibDevDocsPluginDirectory slug: /kibana-dev-docs/api-meta/plugin-api-directory title: Directory description: Directory of public APIs available through plugins or packages. -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana'] --- @@ -15,13 +15,13 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | Count | Plugins or Packages with a <br /> public API | Number of teams | |--------------|----------|------------------------| -| 477 | 397 | 38 | +| 480 | 399 | 38 | ### Public API health stats | API Count | Any Count | Missing comments | Missing exports | |--------------|----------|-----------------|--------| -| 32016 | 179 | 21562 | 1007 | +| 32043 | 179 | 21580 | 1010 | ## Plugin Directory @@ -42,7 +42,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibCloudSecurityPosturePluginApi" text="cloudSecurityPosture"/> | [Cloud Security Posture](https://github.com/orgs/elastic/teams/cloud-posture-security) | The cloud security posture plugin | 18 | 0 | 2 | 3 | | <DocLink id="kibConsolePluginApi" text="console"/> | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 13 | 0 | 13 | 1 | | <DocLink id="kibControlsPluginApi" text="controls"/> | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | The Controls Plugin contains embeddable components intended to create a simple query interface for end users, and a powerful editing suite that allows dashboard authors to build controls | 212 | 0 | 204 | 7 | -| <DocLink id="kibCorePluginApi" text="core"/> | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2688 | 0 | 30 | 0 | +| <DocLink id="kibCorePluginApi" text="core"/> | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 2686 | 0 | 29 | 0 | | crossClusterReplication | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | | <DocLink id="kibCustomIntegrationsPluginApi" text="customIntegrations"/> | [Fleet](https://github.com/orgs/elastic/teams/fleet) | Add custom data integrations so they can be displayed in the Fleet integrations app | 104 | 0 | 85 | 1 | | <DocLink id="kibDashboardPluginApi" text="dashboard"/> | [Kibana Presentation](https://github.com/orgs/elastic/teams/kibana-presentation) | Adds the Dashboard app to Kibana | 120 | 0 | 113 | 3 | @@ -87,7 +87,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | globalSearchProviders | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 0 | 0 | 0 | 0 | | graph | [Data Discovery](https://github.com/orgs/elastic/teams/kibana-data-discovery) | - | 0 | 0 | 0 | 0 | | grokdebugger | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| <DocLink id="kibGuidedOnboardingPluginApi" text="guidedOnboarding"/> | [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Guided onboarding framework | 12 | 0 | 12 | 1 | +| <DocLink id="kibGuidedOnboardingPluginApi" text="guidedOnboarding"/> | [Journey Onboarding](https://github.com/orgs/elastic/teams/platform-onboarding) | Guided onboarding framework | 19 | 0 | 19 | 3 | | <DocLink id="kibHomePluginApi" text="home"/> | [Kibana Core](https://github.com/orgs/elastic/teams/kibana-core) | - | 143 | 0 | 104 | 0 | | <DocLink id="kibIndexLifecycleManagementPluginApi" text="indexLifecycleManagement"/> | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 4 | 0 | 4 | 0 | | <DocLink id="kibIndexManagementPluginApi" text="indexManagement"/> | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 177 | 0 | 172 | 3 | @@ -134,7 +134,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibScreenshotModePluginApi" text="screenshotMode"/> | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | - | 32 | 0 | 13 | 0 | | <DocLink id="kibScreenshottingPluginApi" text="screenshotting"/> | [Kibana Reporting Services](https://github.com/orgs/elastic/teams/kibana-reporting-services) | Kibana Screenshotting Plugin | 27 | 0 | 8 | 4 | | searchprofiler | [Stack Management](https://github.com/orgs/elastic/teams/kibana-stack-management) | - | 0 | 0 | 0 | 0 | -| <DocLink id="kibSecurityPluginApi" text="security"/> | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 247 | 0 | 90 | 0 | +| <DocLink id="kibSecurityPluginApi" text="security"/> | [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) | This plugin provides authentication and authorization features, and exposes functionality to understand the capabilities of the currently authenticated user. | 250 | 0 | 90 | 0 | | <DocLink id="kibSecuritySolutionPluginApi" text="securitySolution"/> | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 55 | 0 | 54 | 23 | | <DocLink id="kibSessionViewPluginApi" text="sessionView"/> | [Security Team](https://github.com/orgs/elastic/teams/security-team) | - | 3 | 0 | 3 | 1 | | <DocLink id="kibSharePluginApi" text="share"/> | [App Services](https://github.com/orgs/elastic/teams/kibana-app-services) | Adds URL Service and sharing capabilities to Kibana | 114 | 0 | 55 | 10 | @@ -148,7 +148,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibTelemetryCollectionManagerPluginApi" text="telemetryCollectionManager"/> | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 31 | 0 | 26 | 6 | | <DocLink id="kibTelemetryCollectionXpackPluginApi" text="telemetryCollectionXpack"/> | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 1 | 0 | 1 | 0 | | <DocLink id="kibTelemetryManagementSectionPluginApi" text="telemetryManagementSection"/> | [Kibana Telemetry](https://github.com/orgs/elastic/teams/kibana-telemetry) | - | 11 | 0 | 10 | 0 | -| <DocLink id="kibThreatIntelligencePluginApi" text="threatIntelligence"/> | [Protections Experience Team](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 20 | 0 | 4 | 3 | +| <DocLink id="kibThreatIntelligencePluginApi" text="threatIntelligence"/> | [Protections Experience Team](https://github.com/orgs/elastic/teams/protections-experience) | Elastic threat intelligence helps you see if you are open to or have been subject to current or historical known threats | 26 | 0 | 8 | 3 | | <DocLink id="kibTimelinesPluginApi" text="timelines"/> | [Security solution](https://github.com/orgs/elastic/teams/security-solution) | - | 452 | 1 | 346 | 33 | | <DocLink id="kibTransformPluginApi" text="transform"/> | [Machine Learning UI](https://github.com/orgs/elastic/teams/ml-ui) | This plugin provides access to the transforms features provided by Elastic. Transforms enable you to convert existing Elasticsearch indices into summarized indices, which provide opportunities for new insights and analytics. | 4 | 0 | 4 | 1 | | translations | [Kibana Localization](https://github.com/orgs/elastic/teams/kibana-localization) | - | 0 | 0 | 0 | 0 | @@ -301,6 +301,8 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnCorePrebootServerPluginApi" text="@kbn/core-preboot-server"/> | Kibana Core | - | 5 | 0 | 0 | 0 | | <DocLink id="kibKbnCorePrebootServerMocksPluginApi" text="@kbn/core-preboot-server-mocks"/> | Kibana Core | - | 6 | 0 | 6 | 0 | | <DocLink id="kibKbnCoreRenderingBrowserMocksPluginApi" text="@kbn/core-rendering-browser-mocks"/> | Kibana Core | - | 2 | 0 | 2 | 0 | +| <DocLink id="kibKbnCoreRenderingServerInternalPluginApi" text="@kbn/core-rendering-server-internal"/> | Kibana Core | - | 2 | 0 | 2 | 0 | +| <DocLink id="kibKbnCoreRenderingServerMocksPluginApi" text="@kbn/core-rendering-server-mocks"/> | Kibana Core | - | 4 | 0 | 4 | 1 | | <DocLink id="kibKbnCoreSavedObjectsApiBrowserPluginApi" text="@kbn/core-saved-objects-api-browser"/> | Kibana Core | - | 106 | 1 | 75 | 0 | | <DocLink id="kibKbnCoreSavedObjectsApiServerPluginApi" text="@kbn/core-saved-objects-api-server"/> | Kibana Core | - | 308 | 1 | 137 | 0 | | <DocLink id="kibKbnCoreSavedObjectsApiServerInternalPluginApi" text="@kbn/core-saved-objects-api-server-internal"/> | Kibana Core | - | 71 | 0 | 51 | 0 | @@ -443,7 +445,7 @@ tags: ['contributor', 'dev', 'apidocs', 'kibana'] | <DocLink id="kibKbnTypeSummarizerCorePluginApi" text="@kbn/type-summarizer-core"/> | [Owner missing] | - | 113 | 1 | 65 | 0 | | <DocLink id="kibKbnTypedReactRouterConfigPluginApi" text="@kbn/typed-react-router-config"/> | [Owner missing] | - | 83 | 0 | 83 | 1 | | <DocLink id="kibKbnUiThemePluginApi" text="@kbn/ui-theme"/> | [Owner missing] | - | 7 | 0 | 6 | 0 | -| <DocLink id="kibKbnUserProfileComponentsPluginApi" text="@kbn/user-profile-components"/> | [Owner missing] | - | 48 | 0 | 3 | 0 | +| <DocLink id="kibKbnUserProfileComponentsPluginApi" text="@kbn/user-profile-components"/> | [Owner missing] | - | 55 | 0 | 5 | 0 | | <DocLink id="kibKbnUtilityTypesPluginApi" text="@kbn/utility-types"/> | [Owner missing] | - | 34 | 0 | 14 | 1 | | <DocLink id="kibKbnUtilityTypesJestPluginApi" text="@kbn/utility-types-jest"/> | [Owner missing] | - | 2 | 0 | 2 | 0 | | <DocLink id="kibKbnUtilsPluginApi" text="@kbn/utils"/> | [Owner missing] | - | 30 | 0 | 20 | 0 | diff --git a/api_docs/presentation_util.mdx b/api_docs/presentation_util.mdx index f9b349676713b..7e399db93218d 100644 --- a/api_docs/presentation_util.mdx +++ b/api_docs/presentation_util.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/presentationUtil title: "presentationUtil" image: https://source.unsplash.com/400x175/?github description: API docs for the presentationUtil plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'presentationUtil'] --- import presentationUtilObj from './presentation_util.devdocs.json'; diff --git a/api_docs/profiling.mdx b/api_docs/profiling.mdx index 489f7c32b600e..7efed272b7261 100644 --- a/api_docs/profiling.mdx +++ b/api_docs/profiling.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/profiling title: "profiling" image: https://source.unsplash.com/400x175/?github description: API docs for the profiling plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'profiling'] --- import profilingObj from './profiling.devdocs.json'; diff --git a/api_docs/remote_clusters.mdx b/api_docs/remote_clusters.mdx index 6a8252bfebe78..f59d7c397918a 100644 --- a/api_docs/remote_clusters.mdx +++ b/api_docs/remote_clusters.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/remoteClusters title: "remoteClusters" image: https://source.unsplash.com/400x175/?github description: API docs for the remoteClusters plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'remoteClusters'] --- import remoteClustersObj from './remote_clusters.devdocs.json'; diff --git a/api_docs/reporting.mdx b/api_docs/reporting.mdx index fbdfd3cb5bd01..dbd1e74d06cd4 100644 --- a/api_docs/reporting.mdx +++ b/api_docs/reporting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/reporting title: "reporting" image: https://source.unsplash.com/400x175/?github description: API docs for the reporting plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'reporting'] --- import reportingObj from './reporting.devdocs.json'; diff --git a/api_docs/rollup.mdx b/api_docs/rollup.mdx index 11313a6dc5f24..397ea6ae4dc91 100644 --- a/api_docs/rollup.mdx +++ b/api_docs/rollup.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/rollup title: "rollup" image: https://source.unsplash.com/400x175/?github description: API docs for the rollup plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'rollup'] --- import rollupObj from './rollup.devdocs.json'; diff --git a/api_docs/rule_registry.mdx b/api_docs/rule_registry.mdx index 3ac4843ea4176..f48194ae5bd14 100644 --- a/api_docs/rule_registry.mdx +++ b/api_docs/rule_registry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ruleRegistry title: "ruleRegistry" image: https://source.unsplash.com/400x175/?github description: API docs for the ruleRegistry plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ruleRegistry'] --- import ruleRegistryObj from './rule_registry.devdocs.json'; diff --git a/api_docs/runtime_fields.mdx b/api_docs/runtime_fields.mdx index 17f5b0c7f230d..2be7beb68c708 100644 --- a/api_docs/runtime_fields.mdx +++ b/api_docs/runtime_fields.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/runtimeFields title: "runtimeFields" image: https://source.unsplash.com/400x175/?github description: API docs for the runtimeFields plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'runtimeFields'] --- import runtimeFieldsObj from './runtime_fields.devdocs.json'; diff --git a/api_docs/saved_objects.mdx b/api_docs/saved_objects.mdx index 5063707c3abd9..d582458137cf9 100644 --- a/api_docs/saved_objects.mdx +++ b/api_docs/saved_objects.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjects title: "savedObjects" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjects plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjects'] --- import savedObjectsObj from './saved_objects.devdocs.json'; diff --git a/api_docs/saved_objects_finder.mdx b/api_docs/saved_objects_finder.mdx index 53186cbce4362..4e6d34158bb84 100644 --- a/api_docs/saved_objects_finder.mdx +++ b/api_docs/saved_objects_finder.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsFinder title: "savedObjectsFinder" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsFinder plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsFinder'] --- import savedObjectsFinderObj from './saved_objects_finder.devdocs.json'; diff --git a/api_docs/saved_objects_management.mdx b/api_docs/saved_objects_management.mdx index 0032f71ba6484..7722b187a54d7 100644 --- a/api_docs/saved_objects_management.mdx +++ b/api_docs/saved_objects_management.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsManagement title: "savedObjectsManagement" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsManagement plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsManagement'] --- import savedObjectsManagementObj from './saved_objects_management.devdocs.json'; diff --git a/api_docs/saved_objects_tagging.mdx b/api_docs/saved_objects_tagging.mdx index b3c954d358cb1..c112fd930e4de 100644 --- a/api_docs/saved_objects_tagging.mdx +++ b/api_docs/saved_objects_tagging.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTagging title: "savedObjectsTagging" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTagging plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTagging'] --- import savedObjectsTaggingObj from './saved_objects_tagging.devdocs.json'; diff --git a/api_docs/saved_objects_tagging_oss.mdx b/api_docs/saved_objects_tagging_oss.mdx index 280c60872da7d..5299c1ca0eaca 100644 --- a/api_docs/saved_objects_tagging_oss.mdx +++ b/api_docs/saved_objects_tagging_oss.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedObjectsTaggingOss title: "savedObjectsTaggingOss" image: https://source.unsplash.com/400x175/?github description: API docs for the savedObjectsTaggingOss plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedObjectsTaggingOss'] --- import savedObjectsTaggingOssObj from './saved_objects_tagging_oss.devdocs.json'; diff --git a/api_docs/saved_search.mdx b/api_docs/saved_search.mdx index eac6e8187a83f..06e4379d4afd9 100644 --- a/api_docs/saved_search.mdx +++ b/api_docs/saved_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/savedSearch title: "savedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the savedSearch plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'savedSearch'] --- import savedSearchObj from './saved_search.devdocs.json'; diff --git a/api_docs/screenshot_mode.mdx b/api_docs/screenshot_mode.mdx index 50deed0a47fdd..fda8615811c46 100644 --- a/api_docs/screenshot_mode.mdx +++ b/api_docs/screenshot_mode.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotMode title: "screenshotMode" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotMode plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotMode'] --- import screenshotModeObj from './screenshot_mode.devdocs.json'; diff --git a/api_docs/screenshotting.mdx b/api_docs/screenshotting.mdx index 64b2f1e872dd9..f4735a3d7a260 100644 --- a/api_docs/screenshotting.mdx +++ b/api_docs/screenshotting.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/screenshotting title: "screenshotting" image: https://source.unsplash.com/400x175/?github description: API docs for the screenshotting plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'screenshotting'] --- import screenshottingObj from './screenshotting.devdocs.json'; diff --git a/api_docs/security.devdocs.json b/api_docs/security.devdocs.json index d400f91576d7d..1b69dcaad6959 100644 --- a/api_docs/security.devdocs.json +++ b/api_docs/security.devdocs.json @@ -125,6 +125,22 @@ "path": "x-pack/plugins/security/common/model/authenticated_user.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "security", + "id": "def-public.AuthenticatedUser.profile_uid", + "type": "string", + "tags": [], + "label": "profile_uid", + "description": [ + "\nUser profile ID of this user." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/security/common/model/authenticated_user.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -1522,6 +1538,22 @@ "path": "x-pack/plugins/security/common/model/authenticated_user.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "security", + "id": "def-server.AuthenticatedUser.profile_uid", + "type": "string", + "tags": [], + "label": "profile_uid", + "description": [ + "\nUser profile ID of this user." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/security/common/model/authenticated_user.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false @@ -3077,6 +3109,22 @@ "path": "x-pack/plugins/security/common/model/authenticated_user.ts", "deprecated": false, "trackAdoption": false + }, + { + "parentPluginId": "security", + "id": "def-common.AuthenticatedUser.profile_uid", + "type": "string", + "tags": [], + "label": "profile_uid", + "description": [ + "\nUser profile ID of this user." + ], + "signature": [ + "string | undefined" + ], + "path": "x-pack/plugins/security/common/model/authenticated_user.ts", + "deprecated": false, + "trackAdoption": false } ], "initialIsOpen": false diff --git a/api_docs/security.mdx b/api_docs/security.mdx index b62b787d9d0e4..2800db424ba6e 100644 --- a/api_docs/security.mdx +++ b/api_docs/security.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/security title: "security" image: https://source.unsplash.com/400x175/?github description: API docs for the security plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'security'] --- import securityObj from './security.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Platform Security](https://github.com/orgs/elastic/teams/kibana-securit | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 247 | 0 | 90 | 0 | +| 250 | 0 | 90 | 0 | ## Client diff --git a/api_docs/security_solution.mdx b/api_docs/security_solution.mdx index 088b9d7a12e2b..3ddca540a2d16 100644 --- a/api_docs/security_solution.mdx +++ b/api_docs/security_solution.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/securitySolution title: "securitySolution" image: https://source.unsplash.com/400x175/?github description: API docs for the securitySolution plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securitySolution'] --- import securitySolutionObj from './security_solution.devdocs.json'; diff --git a/api_docs/session_view.mdx b/api_docs/session_view.mdx index eaddd45f9b778..e54037feb3552 100644 --- a/api_docs/session_view.mdx +++ b/api_docs/session_view.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/sessionView title: "sessionView" image: https://source.unsplash.com/400x175/?github description: API docs for the sessionView plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'sessionView'] --- import sessionViewObj from './session_view.devdocs.json'; diff --git a/api_docs/share.mdx b/api_docs/share.mdx index c6ba8145d4c64..976c243de6395 100644 --- a/api_docs/share.mdx +++ b/api_docs/share.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/share title: "share" image: https://source.unsplash.com/400x175/?github description: API docs for the share plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'share'] --- import shareObj from './share.devdocs.json'; diff --git a/api_docs/snapshot_restore.mdx b/api_docs/snapshot_restore.mdx index daaaceba668b4..f83be535a94bc 100644 --- a/api_docs/snapshot_restore.mdx +++ b/api_docs/snapshot_restore.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/snapshotRestore title: "snapshotRestore" image: https://source.unsplash.com/400x175/?github description: API docs for the snapshotRestore plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'snapshotRestore'] --- import snapshotRestoreObj from './snapshot_restore.devdocs.json'; diff --git a/api_docs/spaces.mdx b/api_docs/spaces.mdx index 1196b40d5c3bf..7b751efc3d1dc 100644 --- a/api_docs/spaces.mdx +++ b/api_docs/spaces.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/spaces title: "spaces" image: https://source.unsplash.com/400x175/?github description: API docs for the spaces plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'spaces'] --- import spacesObj from './spaces.devdocs.json'; diff --git a/api_docs/stack_alerts.mdx b/api_docs/stack_alerts.mdx index fdd5dfbc025b1..7c124256a7976 100644 --- a/api_docs/stack_alerts.mdx +++ b/api_docs/stack_alerts.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackAlerts title: "stackAlerts" image: https://source.unsplash.com/400x175/?github description: API docs for the stackAlerts plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackAlerts'] --- import stackAlertsObj from './stack_alerts.devdocs.json'; diff --git a/api_docs/stack_connectors.mdx b/api_docs/stack_connectors.mdx index 89056c9ebd088..7bf19fb25c36b 100644 --- a/api_docs/stack_connectors.mdx +++ b/api_docs/stack_connectors.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/stackConnectors title: "stackConnectors" image: https://source.unsplash.com/400x175/?github description: API docs for the stackConnectors plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'stackConnectors'] --- import stackConnectorsObj from './stack_connectors.devdocs.json'; diff --git a/api_docs/task_manager.mdx b/api_docs/task_manager.mdx index 1b7e13dd4d650..3cabbc8a83828 100644 --- a/api_docs/task_manager.mdx +++ b/api_docs/task_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/taskManager title: "taskManager" image: https://source.unsplash.com/400x175/?github description: API docs for the taskManager plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'taskManager'] --- import taskManagerObj from './task_manager.devdocs.json'; diff --git a/api_docs/telemetry.mdx b/api_docs/telemetry.mdx index 8e865875b83fe..e3220b9e593a8 100644 --- a/api_docs/telemetry.mdx +++ b/api_docs/telemetry.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetry title: "telemetry" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetry plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetry'] --- import telemetryObj from './telemetry.devdocs.json'; diff --git a/api_docs/telemetry_collection_manager.mdx b/api_docs/telemetry_collection_manager.mdx index e5cbc94c567a3..03d8bceec41f2 100644 --- a/api_docs/telemetry_collection_manager.mdx +++ b/api_docs/telemetry_collection_manager.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionManager title: "telemetryCollectionManager" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionManager plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionManager'] --- import telemetryCollectionManagerObj from './telemetry_collection_manager.devdocs.json'; diff --git a/api_docs/telemetry_collection_xpack.mdx b/api_docs/telemetry_collection_xpack.mdx index 7b775d1ce3bce..a3721f57358da 100644 --- a/api_docs/telemetry_collection_xpack.mdx +++ b/api_docs/telemetry_collection_xpack.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryCollectionXpack title: "telemetryCollectionXpack" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryCollectionXpack plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryCollectionXpack'] --- import telemetryCollectionXpackObj from './telemetry_collection_xpack.devdocs.json'; diff --git a/api_docs/telemetry_management_section.mdx b/api_docs/telemetry_management_section.mdx index 7312616ea8a6e..5c29d491cf1d1 100644 --- a/api_docs/telemetry_management_section.mdx +++ b/api_docs/telemetry_management_section.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/telemetryManagementSection title: "telemetryManagementSection" image: https://source.unsplash.com/400x175/?github description: API docs for the telemetryManagementSection plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'telemetryManagementSection'] --- import telemetryManagementSectionObj from './telemetry_management_section.devdocs.json'; diff --git a/api_docs/threat_intelligence.devdocs.json b/api_docs/threat_intelligence.devdocs.json index a29de53bf77a5..d5d35c60068c4 100644 --- a/api_docs/threat_intelligence.devdocs.json +++ b/api_docs/threat_intelligence.devdocs.json @@ -274,6 +274,103 @@ } ], "returnComment": [] + }, + { + "parentPluginId": "threatIntelligence", + "id": "def-public.SecuritySolutionPluginContext.useQuery", + "type": "Function", + "tags": [], + "label": "useQuery", + "description": [], + "signature": [ + "() => ", + "Query" + ], + "path": "x-pack/plugins/threat_intelligence/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "threatIntelligence", + "id": "def-public.SecuritySolutionPluginContext.useFilters", + "type": "Function", + "tags": [], + "label": "useFilters", + "description": [], + "signature": [ + "() => ", + "Filter", + "[]" + ], + "path": "x-pack/plugins/threat_intelligence/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "threatIntelligence", + "id": "def-public.SecuritySolutionPluginContext.useGlobalTime", + "type": "Function", + "tags": [], + "label": "useGlobalTime", + "description": [], + "signature": [ + "() => ", + "TimeRange" + ], + "path": "x-pack/plugins/threat_intelligence/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "children": [], + "returnComment": [] + }, + { + "parentPluginId": "threatIntelligence", + "id": "def-public.SecuritySolutionPluginContext.SiemSearchBar", + "type": "Function", + "tags": [], + "label": "SiemSearchBar", + "description": [], + "signature": [ + "React.VoidFunctionComponent<any>" + ], + "path": "x-pack/plugins/threat_intelligence/public/types.ts", + "deprecated": false, + "trackAdoption": false, + "returnComment": [], + "children": [ + { + "parentPluginId": "threatIntelligence", + "id": "def-public.SecuritySolutionPluginContext.SiemSearchBar.$1", + "type": "Uncategorized", + "tags": [], + "label": "props", + "description": [], + "signature": [ + "P" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + }, + { + "parentPluginId": "threatIntelligence", + "id": "def-public.SecuritySolutionPluginContext.SiemSearchBar.$2", + "type": "Any", + "tags": [], + "label": "context", + "description": [], + "signature": [ + "any" + ], + "path": "node_modules/@types/react/index.d.ts", + "deprecated": false, + "trackAdoption": false + } + ] } ], "initialIsOpen": false diff --git a/api_docs/threat_intelligence.mdx b/api_docs/threat_intelligence.mdx index 4abe6fedf3bf3..5834a118d4c1e 100644 --- a/api_docs/threat_intelligence.mdx +++ b/api_docs/threat_intelligence.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/threatIntelligence title: "threatIntelligence" image: https://source.unsplash.com/400x175/?github description: API docs for the threatIntelligence plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'threatIntelligence'] --- import threatIntelligenceObj from './threat_intelligence.devdocs.json'; @@ -21,7 +21,7 @@ Contact [Protections Experience Team](https://github.com/orgs/elastic/teams/prot | Public API count | Any count | Items lacking comments | Missing exports | |-------------------|-----------|------------------------|-----------------| -| 20 | 0 | 4 | 3 | +| 26 | 0 | 8 | 3 | ## Client diff --git a/api_docs/timelines.mdx b/api_docs/timelines.mdx index 692501acd7de0..f33b1267f8f43 100644 --- a/api_docs/timelines.mdx +++ b/api_docs/timelines.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/timelines title: "timelines" image: https://source.unsplash.com/400x175/?github description: API docs for the timelines plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'timelines'] --- import timelinesObj from './timelines.devdocs.json'; diff --git a/api_docs/transform.mdx b/api_docs/transform.mdx index 9603096cd2b99..3237f674591f6 100644 --- a/api_docs/transform.mdx +++ b/api_docs/transform.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/transform title: "transform" image: https://source.unsplash.com/400x175/?github description: API docs for the transform plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'transform'] --- import transformObj from './transform.devdocs.json'; diff --git a/api_docs/triggers_actions_ui.mdx b/api_docs/triggers_actions_ui.mdx index a9c60110ba139..5146401d1a6e8 100644 --- a/api_docs/triggers_actions_ui.mdx +++ b/api_docs/triggers_actions_ui.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/triggersActionsUi title: "triggersActionsUi" image: https://source.unsplash.com/400x175/?github description: API docs for the triggersActionsUi plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'triggersActionsUi'] --- import triggersActionsUiObj from './triggers_actions_ui.devdocs.json'; diff --git a/api_docs/ui_actions.mdx b/api_docs/ui_actions.mdx index 734e896cbdf27..3997d6297067a 100644 --- a/api_docs/ui_actions.mdx +++ b/api_docs/ui_actions.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActions title: "uiActions" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActions plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActions'] --- import uiActionsObj from './ui_actions.devdocs.json'; diff --git a/api_docs/ui_actions_enhanced.mdx b/api_docs/ui_actions_enhanced.mdx index 5ac50aa29c085..0644b977bc92c 100644 --- a/api_docs/ui_actions_enhanced.mdx +++ b/api_docs/ui_actions_enhanced.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/uiActionsEnhanced title: "uiActionsEnhanced" image: https://source.unsplash.com/400x175/?github description: API docs for the uiActionsEnhanced plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'uiActionsEnhanced'] --- import uiActionsEnhancedObj from './ui_actions_enhanced.devdocs.json'; diff --git a/api_docs/unified_field_list.mdx b/api_docs/unified_field_list.mdx index 2818641379fb7..bfb157318d95b 100644 --- a/api_docs/unified_field_list.mdx +++ b/api_docs/unified_field_list.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedFieldList title: "unifiedFieldList" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedFieldList plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedFieldList'] --- import unifiedFieldListObj from './unified_field_list.devdocs.json'; diff --git a/api_docs/unified_search.mdx b/api_docs/unified_search.mdx index ed31e74c5d7eb..7ea6ab07192c7 100644 --- a/api_docs/unified_search.mdx +++ b/api_docs/unified_search.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch title: "unifiedSearch" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch'] --- import unifiedSearchObj from './unified_search.devdocs.json'; diff --git a/api_docs/unified_search_autocomplete.mdx b/api_docs/unified_search_autocomplete.mdx index e75d86bbab218..76e6013841c75 100644 --- a/api_docs/unified_search_autocomplete.mdx +++ b/api_docs/unified_search_autocomplete.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/unifiedSearch-autocomplete title: "unifiedSearch.autocomplete" image: https://source.unsplash.com/400x175/?github description: API docs for the unifiedSearch.autocomplete plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'unifiedSearch.autocomplete'] --- import unifiedSearchAutocompleteObj from './unified_search_autocomplete.devdocs.json'; diff --git a/api_docs/url_forwarding.mdx b/api_docs/url_forwarding.mdx index 0ac5762c42af3..bc3da076b5916 100644 --- a/api_docs/url_forwarding.mdx +++ b/api_docs/url_forwarding.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/urlForwarding title: "urlForwarding" image: https://source.unsplash.com/400x175/?github description: API docs for the urlForwarding plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'urlForwarding'] --- import urlForwardingObj from './url_forwarding.devdocs.json'; diff --git a/api_docs/usage_collection.mdx b/api_docs/usage_collection.mdx index b493774dc1eb3..b67acbd363bf0 100644 --- a/api_docs/usage_collection.mdx +++ b/api_docs/usage_collection.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/usageCollection title: "usageCollection" image: https://source.unsplash.com/400x175/?github description: API docs for the usageCollection plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'usageCollection'] --- import usageCollectionObj from './usage_collection.devdocs.json'; diff --git a/api_docs/ux.mdx b/api_docs/ux.mdx index f3c60dc8b7b43..1c83be4e34381 100644 --- a/api_docs/ux.mdx +++ b/api_docs/ux.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/ux title: "ux" image: https://source.unsplash.com/400x175/?github description: API docs for the ux plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'ux'] --- import uxObj from './ux.devdocs.json'; diff --git a/api_docs/vis_default_editor.mdx b/api_docs/vis_default_editor.mdx index ec4208a4a06da..7c1cb5bae3809 100644 --- a/api_docs/vis_default_editor.mdx +++ b/api_docs/vis_default_editor.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visDefaultEditor title: "visDefaultEditor" image: https://source.unsplash.com/400x175/?github description: API docs for the visDefaultEditor plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visDefaultEditor'] --- import visDefaultEditorObj from './vis_default_editor.devdocs.json'; diff --git a/api_docs/vis_type_gauge.mdx b/api_docs/vis_type_gauge.mdx index 43f73daa08070..4b95ba2b035fd 100644 --- a/api_docs/vis_type_gauge.mdx +++ b/api_docs/vis_type_gauge.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeGauge title: "visTypeGauge" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeGauge plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeGauge'] --- import visTypeGaugeObj from './vis_type_gauge.devdocs.json'; diff --git a/api_docs/vis_type_heatmap.mdx b/api_docs/vis_type_heatmap.mdx index d537797699cab..1ea7a1e4b9250 100644 --- a/api_docs/vis_type_heatmap.mdx +++ b/api_docs/vis_type_heatmap.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeHeatmap title: "visTypeHeatmap" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeHeatmap plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeHeatmap'] --- import visTypeHeatmapObj from './vis_type_heatmap.devdocs.json'; diff --git a/api_docs/vis_type_pie.mdx b/api_docs/vis_type_pie.mdx index 08324c72b813f..3ffea5a4f58e6 100644 --- a/api_docs/vis_type_pie.mdx +++ b/api_docs/vis_type_pie.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypePie title: "visTypePie" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypePie plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypePie'] --- import visTypePieObj from './vis_type_pie.devdocs.json'; diff --git a/api_docs/vis_type_table.mdx b/api_docs/vis_type_table.mdx index 7d052e7dd40fb..414b4de0b2363 100644 --- a/api_docs/vis_type_table.mdx +++ b/api_docs/vis_type_table.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTable title: "visTypeTable" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTable plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTable'] --- import visTypeTableObj from './vis_type_table.devdocs.json'; diff --git a/api_docs/vis_type_timelion.mdx b/api_docs/vis_type_timelion.mdx index 696edbd5d321e..9306f4a52fd35 100644 --- a/api_docs/vis_type_timelion.mdx +++ b/api_docs/vis_type_timelion.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimelion title: "visTypeTimelion" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimelion plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimelion'] --- import visTypeTimelionObj from './vis_type_timelion.devdocs.json'; diff --git a/api_docs/vis_type_timeseries.mdx b/api_docs/vis_type_timeseries.mdx index ef87c344661e6..8d5ffda6491b3 100644 --- a/api_docs/vis_type_timeseries.mdx +++ b/api_docs/vis_type_timeseries.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeTimeseries title: "visTypeTimeseries" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeTimeseries plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeTimeseries'] --- import visTypeTimeseriesObj from './vis_type_timeseries.devdocs.json'; diff --git a/api_docs/vis_type_vega.mdx b/api_docs/vis_type_vega.mdx index 1a5a44160bf46..9a7a8eb698283 100644 --- a/api_docs/vis_type_vega.mdx +++ b/api_docs/vis_type_vega.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVega title: "visTypeVega" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVega plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVega'] --- import visTypeVegaObj from './vis_type_vega.devdocs.json'; diff --git a/api_docs/vis_type_vislib.mdx b/api_docs/vis_type_vislib.mdx index 8651bb82ad813..443abb630d8da 100644 --- a/api_docs/vis_type_vislib.mdx +++ b/api_docs/vis_type_vislib.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeVislib title: "visTypeVislib" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeVislib plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeVislib'] --- import visTypeVislibObj from './vis_type_vislib.devdocs.json'; diff --git a/api_docs/vis_type_xy.mdx b/api_docs/vis_type_xy.mdx index 440839b51793a..958b60a5150f5 100644 --- a/api_docs/vis_type_xy.mdx +++ b/api_docs/vis_type_xy.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visTypeXy title: "visTypeXy" image: https://source.unsplash.com/400x175/?github description: API docs for the visTypeXy plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visTypeXy'] --- import visTypeXyObj from './vis_type_xy.devdocs.json'; diff --git a/api_docs/visualizations.mdx b/api_docs/visualizations.mdx index 2edc6b736c5b6..4c4afd430bceb 100644 --- a/api_docs/visualizations.mdx +++ b/api_docs/visualizations.mdx @@ -8,7 +8,7 @@ slug: /kibana-dev-docs/api/visualizations title: "visualizations" image: https://source.unsplash.com/400x175/?github description: API docs for the visualizations plugin -date: 2022-10-03 +date: 2022-10-04 tags: ['contributor', 'dev', 'apidocs', 'kibana', 'visualizations'] --- import visualizationsObj from './visualizations.devdocs.json'; From c7a56604e77c944bf2069096ce2fa71fc1ec1aa0 Mon Sep 17 00:00:00 2001 From: Coen Warmer <coen.warmer@gmail.com> Date: Tue, 4 Oct 2022 09:10:38 +0200 Subject: [PATCH 016/174] [Actionable Observability] Add Alert Details page header (#140299) Co-authored-by: Faisal Kanout <faisal.kanout@elastic.co> --- .../case_view/components/case_view_alerts.tsx | 1 + x-pack/plugins/cases/public/mocks.ts | 6 +- .../public/hooks/use_fetch_alert_detail.ts | 1 - .../components/alert_details.test.tsx | 55 +++++- .../components/alert_details.tsx | 42 ++++- .../components/alert_summary.tsx | 4 +- .../components/header_actions.test.tsx | 83 +++++++++ .../components/header_actions.tsx | 160 ++++++++++++++++++ .../pages/alert_details/components/index.ts | 2 + .../components/page_title.stories.tsx | 42 +++++ .../components/page_title.test.tsx | 44 +++++ .../alert_details/components/page_title.tsx | 43 +++++ .../public/pages/alert_details/mock/alert.ts | 4 +- .../public/application/sections/index.tsx | 3 + .../components/rule_snooze_modal.tsx | 104 ++++++++++++ .../public/common/get_rule_snooze_modal.tsx | 15 ++ .../triggers_actions_ui/public/mocks.ts | 4 + .../triggers_actions_ui/public/plugin.ts | 6 + 18 files changed, 599 insertions(+), 20 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/alert_details/components/header_actions.test.tsx create mode 100644 x-pack/plugins/observability/public/pages/alert_details/components/header_actions.tsx create mode 100644 x-pack/plugins/observability/public/pages/alert_details/components/page_title.stories.tsx create mode 100644 x-pack/plugins/observability/public/pages/alert_details/components/page_title.test.tsx create mode 100644 x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx create mode 100644 x-pack/plugins/triggers_actions_ui/public/common/get_rule_snooze_modal.tsx diff --git a/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.tsx b/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.tsx index f492240a8f9b8..0bd582e1cef62 100644 --- a/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.tsx +++ b/x-pack/plugins/cases/public/components/case_view/components/case_view_alerts.tsx @@ -28,6 +28,7 @@ export const CaseViewAlerts = ({ caseData }: CaseViewAlertsProps) => { }), [caseData.comments] ); + const alertRegistrationContexts = useMemo( () => getRegistrationContextFromAlerts(caseData.comments), [caseData.comments] diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index 57eead3b80b10..10a4c1f6fd059 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -21,9 +21,13 @@ const uiMock: jest.Mocked<CasesUiStart['ui']> = { getRecentCases: jest.fn(), }; +export const openAddToExistingCaseModalMock = jest.fn(); + const hooksMock: jest.Mocked<CasesUiStart['hooks']> = { getUseCasesAddToNewCaseFlyout: jest.fn(), - getUseCasesAddToExistingCaseModal: jest.fn(), + getUseCasesAddToExistingCaseModal: jest.fn().mockImplementation(() => ({ + open: openAddToExistingCaseModalMock, + })), }; const helpersMock: jest.Mocked<CasesUiStart['helpers']> = { diff --git a/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.ts b/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.ts index aa604659f8c08..a86b97741f24c 100644 --- a/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.ts +++ b/x-pack/plugins/observability/public/hooks/use_fetch_alert_detail.ts @@ -21,7 +21,6 @@ interface AlertDetailParams { export const useFetchAlertDetail = (id: string): [boolean, TopAlert | null] => { const { observabilityRuleTypeRegistry } = usePluginContext(); - const params = useMemo( () => ({ id, ruleType: observabilityRuleTypeRegistry }), [id, observabilityRuleTypeRegistry] diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.test.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.test.tsx index d2c67c85c021e..eaa253efbc51f 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.test.tsx @@ -7,23 +7,58 @@ import React from 'react'; import * as useUiSettingHook from '@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting'; +import { useParams } from 'react-router-dom'; +import { Chance } from 'chance'; +import { waitFor } from '@testing-library/react'; +import { casesPluginMock } from '@kbn/cases-plugin/public/mocks'; +import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; + import { render } from '../../../utils/test_helper'; +import { useKibana } from '../../../utils/kibana_react'; +import { kibanaStartMock } from '../../../utils/kibana_react.mock'; import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail'; -import { AlertDetails } from './alert_details'; -import { Chance } from 'chance'; -import { useParams } from 'react-router-dom'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; +import { AlertDetails } from './alert_details'; import { ConfigSchema } from '../../../plugin'; import { alert, alertWithNoData } from '../mock/alert'; -import { waitFor } from '@testing-library/react'; -jest.mock('../../../hooks/use_fetch_alert_detail'); -jest.mock('../../../hooks/use_breadcrumbs'); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useParams: jest.fn(), })); +jest.mock('../../../utils/kibana_react'); + +const useKibanaMock = useKibana as jest.Mock; + +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...kibanaStartMock.startContract(), + cases: casesPluginMock.createStartContract(), + http: { + basePath: { + prepend: jest.fn(), + }, + }, + triggersActionsUi: triggersActionsUiMock.createStart(), + }, + }); +}; + +jest.mock('../../../hooks/use_fetch_alert_detail'); +jest.mock('../../../hooks/use_breadcrumbs'); +jest.mock('../../../hooks/use_get_user_cases_permissions', () => ({ + useGetUserCasesPermissions: () => ({ + all: true, + create: true, + delete: true, + push: true, + read: true, + update: true, + }), +})); + const useFetchAlertDetailMock = useFetchAlertDetail as jest.Mock; const useParamsMock = useParams as jest.Mock; const useBreadcrumbsMock = useBreadcrumbs as jest.Mock; @@ -49,16 +84,20 @@ describe('Alert details', () => { jest.clearAllMocks(); useParamsMock.mockReturnValue(params); useBreadcrumbsMock.mockReturnValue([]); + mockKibana(); }); - it('should show alert summary', async () => { + it('should show the alert detail page with all necessary components', async () => { useFetchAlertDetailMock.mockReturnValue([false, alert]); const alertDetails = render(<AlertDetails />, config); - expect(alertDetails.queryByTestId('alertDetails')).toBeTruthy(); await waitFor(() => expect(alertDetails.queryByTestId('centerJustifiedSpinner')).toBeFalsy()); + + expect(alertDetails.queryByTestId('alertDetails')).toBeTruthy(); expect(alertDetails.queryByTestId('alertDetailsError')).toBeFalsy(); + expect(alertDetails.queryByTestId('page-title-container')).toBeTruthy(); + expect(alertDetails.queryByTestId('alert-summary-container')).toBeTruthy(); }); it('should show error loading the alert details', async () => { diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.tsx index a2cd7fd68a2ce..1b501e62a7dde 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/components/alert_details.tsx @@ -9,23 +9,36 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { useParams } from 'react-router-dom'; import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui'; + import { useKibana } from '../../../utils/kibana_react'; -import { ObservabilityAppServices } from '../../../application/types'; import { usePluginContext } from '../../../hooks/use_plugin_context'; import { useBreadcrumbs } from '../../../hooks/use_breadcrumbs'; -import { paths } from '../../../config/paths'; -import { AlertDetailsPathParams } from '../types'; +import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail'; + +import { AlertSummary, HeaderActions, PageTitle } from '.'; import { CenterJustifiedSpinner } from '../../rule_details/components/center_justified_spinner'; -import { AlertSummary } from '.'; import PageNotFound from '../../404'; -import { useFetchAlertDetail } from '../../../hooks/use_fetch_alert_detail'; + +import { ObservabilityAppServices } from '../../../application/types'; +import { AlertDetailsPathParams } from '../types'; +import { observabilityFeatureId } from '../../../../common'; +import { paths } from '../../../config/paths'; export function AlertDetails() { - const { http } = useKibana<ObservabilityAppServices>().services; + const { + http, + cases: { + helpers: { canUseCases }, + ui: { getCasesContext }, + }, + } = useKibana<ObservabilityAppServices>().services; const { ObservabilityPageTemplate, config } = usePluginContext(); const { alertId } = useParams<AlertDetailsPathParams>(); const [isLoading, alert] = useFetchAlertDetail(alertId); + const CasesContext = getCasesContext(); + const userCasesPermissions = canUseCases(); + useBreadcrumbs([ { href: http.basePath.prepend(paths.observability.alerts), @@ -69,7 +82,22 @@ export function AlertDetails() { ); return ( - <ObservabilityPageTemplate data-test-subj="alertDetails"> + <ObservabilityPageTemplate + pageHeader={{ + pageTitle: <PageTitle title={alert?.reason} active={Boolean(alert?.active)} />, + rightSideItems: [ + <CasesContext + owner={[observabilityFeatureId]} + permissions={userCasesPermissions} + features={{ alerts: { sync: false } }} + > + <HeaderActions alert={alert} /> + </CasesContext>, + ], + bottomBorder: false, + }} + data-test-subj="alertDetails" + > <AlertSummary alert={alert} /> </ObservabilityPageTemplate> ); diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx index 4a1d88e928fb2..eada6a3925521 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx +++ b/x-pack/plugins/observability/public/pages/alert_details/components/alert_summary.tsx @@ -30,7 +30,7 @@ export function AlertSummary({ alert }: AlertSummaryProps) { const tags = alert?.fields[ALERT_RULE_TAGS]; return ( - <> + <div data-test-subj="alert-summary-container"> <EuiFlexGroup> <EuiFlexItem> <EuiTitle size="xxs"> @@ -161,6 +161,6 @@ export function AlertSummary({ alert }: AlertSummaryProps) { </div> </EuiFlexItem> </EuiFlexGroup> - </> + </div> ); } diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/header_actions.test.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/header_actions.test.tsx new file mode 100644 index 0000000000000..8bbec59c52b95 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alert_details/components/header_actions.test.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; +import { casesPluginMock, openAddToExistingCaseModalMock } from '@kbn/cases-plugin/public/mocks'; + +import { render } from '../../../utils/test_helper'; +import { useKibana } from '../../../utils/kibana_react'; +import { kibanaStartMock } from '../../../utils/kibana_react.mock'; +import { alertWithTags, mockAlertUuid } from '../mock/alert'; + +import { HeaderActions } from './header_actions'; + +jest.mock('../../../utils/kibana_react'); + +const useKibanaMock = useKibana as jest.Mock; + +const mockKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...kibanaStartMock.startContract(), + triggersActionsUi: triggersActionsUiMock.createStart(), + cases: casesPluginMock.createStartContract(), + }, + }); +}; + +const ruleId = '123'; +const ruleName = '456'; + +jest.mock('../../../hooks/use_fetch_rule', () => { + return { + useFetchRule: () => ({ + reloadRule: jest.fn(), + rule: { + id: ruleId, + name: ruleName, + }, + }), + }; +}); + +describe('Header Actions', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockKibana(); + }); + + it('should display an actions button', () => { + const { queryByTestId } = render(<HeaderActions alert={alertWithTags} />); + expect(queryByTestId('alert-details-header-actions-menu-button')).toBeTruthy(); + }); + + describe('when clicking the actions button', () => { + it('should offer an "add to case" button which opens the add to case modal', async () => { + const { getByTestId, findByRole } = render(<HeaderActions alert={alertWithTags} />); + + fireEvent.click(await findByRole('button', { name: 'Actions' })); + + fireEvent.click(getByTestId('add-to-case-button')); + + expect(openAddToExistingCaseModalMock).toBeCalledWith({ + attachments: [ + { + alertId: mockAlertUuid, + index: '.internal.alerts-observability.metrics.alerts-*', + rule: { + id: ruleId, + name: ruleName, + }, + type: 'alert', + }, + ], + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/header_actions.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/header_actions.tsx new file mode 100644 index 0000000000000..e7a2c773dc68f --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alert_details/components/header_actions.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { noop } from 'lodash'; +import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public/types'; +import { CommentType } from '@kbn/cases-plugin/common'; +import { EuiButton, EuiButtonEmpty, EuiFlexGroup, EuiPopover, EuiText } from '@elastic/eui'; +import { ALERT_RULE_UUID, ALERT_UUID } from '@kbn/rule-data-utils'; + +import { useKibana } from '../../../utils/kibana_react'; +import { useFetchRule } from '../../../hooks/use_fetch_rule'; +import { ObservabilityAppServices } from '../../../application/types'; +import { TopAlert } from '../../alerts'; + +export interface HeaderActionsProps { + alert: TopAlert | null; +} + +export function HeaderActions({ alert }: HeaderActionsProps) { + const { + http, + cases: { + hooks: { getUseCasesAddToExistingCaseModal }, + }, + triggersActionsUi: { getEditAlertFlyout, getRuleSnoozeModal }, + } = useKibana<ObservabilityAppServices>().services; + + const { rule, reloadRule } = useFetchRule({ + http, + ruleId: alert?.fields[ALERT_RULE_UUID] || '', + }); + + const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false); + const [ruleConditionsFlyoutOpen, setRuleConditionsFlyoutOpen] = useState<boolean>(false); + const [snoozeModalOpen, setSnoozeModalOpen] = useState<boolean>(false); + + const selectCaseModal = getUseCasesAddToExistingCaseModal(); + + const handleTogglePopover = () => setIsPopoverOpen(!isPopoverOpen); + const handleClosePopover = () => setIsPopoverOpen(false); + + const attachments: CaseAttachmentsWithoutOwner = + alert && rule + ? [ + { + alertId: alert?.fields[ALERT_UUID] || '', + index: '.internal.alerts-observability.metrics.alerts-*', + rule: { + id: rule.id, + name: rule.name, + }, + type: CommentType.alert, + }, + ] + : []; + + const handleAddToCase = () => { + setIsPopoverOpen(false); + selectCaseModal.open({ attachments }); + }; + + const handleViewRuleDetails = () => { + setIsPopoverOpen(false); + setRuleConditionsFlyoutOpen(true); + }; + + const handleOpenSnoozeModal = () => { + setIsPopoverOpen(false); + setSnoozeModalOpen(true); + }; + + return ( + <> + <EuiPopover + isOpen={isPopoverOpen} + closePopover={handleClosePopover} + button={ + <EuiButton + fill + iconType="arrowDown" + iconSide="right" + onClick={handleTogglePopover} + data-test-subj="alert-details-header-actions-menu-button" + > + {i18n.translate('xpack.observability.alertDetails.actionsButtonLabel', { + defaultMessage: 'Actions', + })} + </EuiButton> + } + > + <EuiFlexGroup direction="column" alignItems="flexStart"> + <EuiButtonEmpty + size="s" + color="text" + disabled={!alert?.fields[ALERT_RULE_UUID]} + onClick={handleViewRuleDetails} + data-test-subj="view-rule-details-button" + > + <EuiText size="s"> + {i18n.translate('xpack.observability.alertDetails.viewRuleDetails', { + defaultMessage: 'View rule details', + })} + </EuiText> + </EuiButtonEmpty> + + <EuiButtonEmpty + size="s" + color="text" + onClick={handleOpenSnoozeModal} + data-test-subj="snooze-rule-button" + > + <EuiText size="s"> + {i18n.translate('xpack.observability.alertDetails.editSnoozeRule', { + defaultMessage: 'Snooze the rule', + })} + </EuiText> + </EuiButtonEmpty> + + <EuiButtonEmpty + size="s" + color="text" + onClick={handleAddToCase} + data-test-subj="add-to-case-button" + > + <EuiText size="s"> + {i18n.translate('xpack.observability.alertDetails.addToCase', { + defaultMessage: 'Add to case', + })} + </EuiText> + </EuiButtonEmpty> + </EuiFlexGroup> + </EuiPopover> + + {rule && ruleConditionsFlyoutOpen + ? getEditAlertFlyout({ + initialRule: rule, + onClose: () => { + setRuleConditionsFlyoutOpen(false); + }, + onSave: reloadRule, + }) + : null} + + {rule && snoozeModalOpen + ? getRuleSnoozeModal({ + rule, + onClose: () => setSnoozeModalOpen(false), + onRuleChanged: reloadRule, + onLoading: noop, + }) + : null} + </> + ); +} diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/index.ts b/x-pack/plugins/observability/public/pages/alert_details/components/index.ts index f49d7bd3c721a..9e2ae5d34dc18 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/components/index.ts +++ b/x-pack/plugins/observability/public/pages/alert_details/components/index.ts @@ -5,5 +5,7 @@ * 2.0. */ +export { HeaderActions } from './header_actions'; export { AlertSummary } from './alert_summary'; export { AlertDetails } from './alert_details'; +export { PageTitle } from './page_title'; diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/page_title.stories.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.stories.tsx new file mode 100644 index 0000000000000..ab0109ef1c214 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.stories.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ComponentStory } from '@storybook/react'; +import { EuiPageTemplate } from '@elastic/eui'; + +import { PageTitle as Component, PageTitleProps } from './page_title'; + +export default { + component: Component, + title: 'app/AlertDetails/PageTitle', + argTypes: { + title: { control: 'text' }, + active: { control: 'boolean' }, + }, +}; + +const Template: ComponentStory<typeof Component> = (props: PageTitleProps) => ( + <Component {...props} /> +); + +const TemplateWithPageTemplate: ComponentStory<typeof Component> = (props: PageTitleProps) => ( + <EuiPageTemplate> + <EuiPageTemplate.Header pageTitle={<Component {...props} />} bottomBorder={false} /> + </EuiPageTemplate> +); + +const defaultProps = { + title: 'host.cpu.usage is 0.2024 in the last 1 min for all hosts. Alert when > 0.02.', + active: true, +}; + +export const PageTitle = Template.bind({}); +PageTitle.args = defaultProps; + +export const PageTitleUsedWithinPageTemplate = TemplateWithPageTemplate.bind({}); +PageTitleUsedWithinPageTemplate.args = defaultProps; diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/page_title.test.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.test.tsx new file mode 100644 index 0000000000000..bd0b15e8ffc2a --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; + +import { PageTitle, PageTitleProps } from './page_title'; + +describe('Page Title', () => { + const defaultProps = { + title: 'Great success', + active: true, + }; + + const renderComp = (props: PageTitleProps) => { + return render(<PageTitle {...props} />); + }; + + it('should display a title when it is passed', () => { + const { getByText } = renderComp(defaultProps); + expect(getByText(defaultProps.title)).toBeTruthy(); + }); + + it('should display an active badge when active is true', async () => { + const { getByText } = renderComp(defaultProps); + expect(getByText('Active')).toBeTruthy(); + }); + + it('should display an inactive badge when active is false', async () => { + const { getByText } = renderComp({ ...defaultProps, active: false }); + + expect(getByText('Recovered')).toBeTruthy(); + }); + + it('should display no badge when active is not passed', async () => { + const { queryByTestId } = renderComp({ title: '123' }); + + expect(queryByTestId('page-title-active-badge')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx new file mode 100644 index 0000000000000..61301f16e37a9 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alert_details/components/page_title.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface PageTitleProps { + title: string | undefined; + active?: boolean; +} + +export function PageTitle({ title, active }: PageTitleProps) { + const label = active + ? i18n.translate('xpack.observability.alertDetails.alertActiveState', { + defaultMessage: 'Active', + }) + : i18n.translate('xpack.observability.alertDetails.alertRecoveredState', { + defaultMessage: 'Recovered', + }); + + return ( + <div data-test-subj="page-title-container"> + {title} + + <EuiFlexGroup> + <EuiFlexItem> + <div> + {typeof active === 'boolean' ? ( + <EuiBadge color="#BD271E" data-test-subj="page-title-active-badge"> + {label} + </EuiBadge> + ) : null} + </div> + </EuiFlexItem> + </EuiFlexGroup> + </div> + ); +} diff --git a/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts b/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts index ed129fc3d24ec..a3031fb0aa18d 100644 --- a/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts +++ b/x-pack/plugins/observability/public/pages/alert_details/mock/alert.ts @@ -32,6 +32,8 @@ import { TopAlert } from '../../alerts'; export const tags: string[] = ['tag1', 'tag2', 'tag3']; +export const mockAlertUuid = '756240e5-92fb-452f-b08e-cd3e0dc51738'; + export const alert: TopAlert = { reason: '1957 log entries (more than 100.25) match the conditions.', fields: { @@ -50,7 +52,7 @@ export const alert: TopAlert = { [ALERT_EVALUATION_VALUE]: 1957, [ALERT_INSTANCE_ID]: '*', [ALERT_RULE_NAME]: 'Log threshold (from logs)', - [ALERT_UUID]: '756240e5-92fb-452f-b08e-cd3e0dc51738', + [ALERT_UUID]: mockAlertUuid, [SPACE_IDS]: ['default'], [VERSION]: '8.0.0', [EVENT_KIND]: 'signal', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx index f4ae5d399e6f9..2e476855926d4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/index.tsx @@ -49,6 +49,9 @@ export const RulesList = suspendedComponentWithProps( export const RulesListNotifyBadge = suspendedComponentWithProps( lazy(() => import('./rules_list/components/rules_list_notify_badge')) ); +export const RuleSnoozeModal = suspendedComponentWithProps( + lazy(() => import('./rules_list/components/rule_snooze_modal')) +); export const RuleDefinition = suspendedComponentWithProps( lazy(() => import('./rule_details/components/rule_definition')) ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx new file mode 100644 index 0000000000000..c4624e7ea8e61 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_snooze_modal.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback, useMemo } from 'react'; +import { EuiModal, EuiModalBody, EuiSpacer } from '@elastic/eui'; + +import { useKibana } from '../../../../common/lib/kibana'; +import { snoozeRule, unsnoozeRule } from '../../../lib/rule_api'; +import { + SNOOZE_FAILED_MESSAGE, + SNOOZE_SUCCESS_MESSAGE, + UNSNOOZE_SUCCESS_MESSAGE, +} from './rules_list_notify_badge'; +import { SnoozePanel, futureTimeToInterval } from './rule_snooze'; +import { Rule, RuleTypeParams, SnoozeSchedule } from '../../../../types'; + +export interface RuleSnoozeModalProps { + rule: Rule<RuleTypeParams>; + onClose: () => void; + onLoading: (isLoading: boolean) => void; + onRuleChanged: () => void; +} + +const isRuleSnoozed = (rule: { isSnoozedUntil?: Date | null; muteAll: boolean }) => + Boolean( + (rule.isSnoozedUntil && new Date(rule.isSnoozedUntil).getTime() > Date.now()) || rule.muteAll + ); + +export const RuleSnoozeModal: React.FunctionComponent<RuleSnoozeModalProps> = ({ + rule, + onClose, + onLoading, + onRuleChanged, +}) => { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const isSnoozed = useMemo(() => { + return isRuleSnoozed(rule); + }, [rule]); + + const onApplySnooze = useCallback( + async (snoozeSchedule: SnoozeSchedule) => { + try { + onLoading(true); + onClose(); + + await snoozeRule({ http, id: rule.id, snoozeSchedule }); + + onRuleChanged(); + + toasts.addSuccess(SNOOZE_SUCCESS_MESSAGE); + } catch (e) { + toasts.addDanger(SNOOZE_FAILED_MESSAGE); + } finally { + onLoading(false); + } + }, + [onLoading, onClose, http, rule.id, onRuleChanged, toasts] + ); + + const onApplyUnsnooze = useCallback( + async (scheduleIds?: string[]) => { + try { + onLoading(true); + onClose(); + await unsnoozeRule({ http, id: rule.id, scheduleIds }); + onRuleChanged(); + toasts.addSuccess(UNSNOOZE_SUCCESS_MESSAGE); + } catch (e) { + toasts.addDanger(SNOOZE_FAILED_MESSAGE); + } finally { + onLoading(false); + } + }, + [onLoading, onClose, http, rule.id, onRuleChanged, toasts] + ); + + return ( + <EuiModal onClose={onClose} data-test-subj="ruleSnoozeModal"> + <EuiModalBody> + <EuiSpacer size="s" /> + <SnoozePanel + inPopover={false} + interval={futureTimeToInterval(rule.isSnoozedUntil)} + activeSnoozes={rule.activeSnoozes ?? []} + scheduledSnoozes={rule.snoozeSchedule ?? []} + showCancel={isSnoozed} + snoozeRule={onApplySnooze} + unsnoozeRule={onApplyUnsnooze} + /> + </EuiModalBody> + </EuiModal> + ); +}; + +// eslint-disable-next-line import/no-default-export +export { RuleSnoozeModal as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_rule_snooze_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_snooze_modal.tsx new file mode 100644 index 0000000000000..b22fe16f77312 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_rule_snooze_modal.tsx @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { RuleSnoozeModal } from '../application/sections'; +import { RuleSnoozeModalProps } from '../application/sections/rules_list/components/rule_snooze_modal'; + +export const getRuleSnoozeModalLazy = (props: RuleSnoozeModalProps) => { + return <RuleSnoozeModal {...props} />; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 02722bc0ee73b..7c18ea1b6fa2c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -43,6 +43,7 @@ import { getFieldBrowserLazy } from './common/get_field_browser'; import { getRuleAlertsSummaryLazy } from './common/get_rule_alerts_summary'; import { getRuleDefinitionLazy } from './common/get_rule_definition'; import { getRuleStatusPanelLazy } from './common/get_rule_status_panel'; +import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal'; function createStartMock(): TriggersAndActionsUIPublicPluginStart { const actionTypeRegistry = new TypeRegistry<ActionTypeModel>(); @@ -124,6 +125,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { getRuleStatusPanel: (props) => { return getRuleStatusPanelLazy(props); }, + getRuleSnoozeModal: (props) => { + return getRuleSnoozeModalLazy(props); + }, }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 10c5e5637f159..1374f10355e16 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -78,6 +78,8 @@ import { getRuleDefinitionLazy } from './common/get_rule_definition'; import { RuleStatusPanelProps } from './application/sections/rule_details/components/rule_status_panel'; import { RuleAlertsSummaryProps } from './application/sections/rule_details/components/alert_summary'; import { getRuleAlertsSummaryLazy } from './common/get_rule_alerts_summary'; +import { RuleSnoozeModalProps } from './application/sections/rules_list/components/rule_snooze_modal'; +import { getRuleSnoozeModalLazy } from './common/get_rule_snooze_modal'; export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry<ActionTypeModel>; @@ -123,6 +125,7 @@ export interface TriggersAndActionsUIPublicPluginStart { getRuleDefinition: (props: RuleDefinitionProps) => ReactElement<RuleDefinitionProps>; getRuleStatusPanel: (props: RuleStatusPanelProps) => ReactElement<RuleStatusPanelProps>; getRuleAlertsSummary: (props: RuleAlertsSummaryProps) => ReactElement<RuleAlertsSummaryProps>; + getRuleSnoozeModal: (props: RuleSnoozeModalProps) => ReactElement<RuleSnoozeModalProps>; } interface PluginsSetup { @@ -352,6 +355,9 @@ export class Plugin getRuleAlertsSummary: (props: RuleAlertsSummaryProps) => { return getRuleAlertsSummaryLazy(props); }, + getRuleSnoozeModal: (props: RuleSnoozeModalProps) => { + return getRuleSnoozeModalLazy(props); + }, }; } From 3c0086b76478f8c011883c0a0ea94354dd0c8f60 Mon Sep 17 00:00:00 2001 From: Pablo Machado <pablo.nevesmachado@elastic.co> Date: Tue, 4 Oct 2022 10:16:54 +0200 Subject: [PATCH 017/174] Add Url state parameter for external alerts checkbox (#142344) * Refactor global_query_string to move reusabel code to helper * Add Url state parameter for external alerts checkbox Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../events_tab/events_query_tab_body.test.tsx | 6 ++ .../events_tab/events_query_tab_body.tsx | 56 +++++++++++++- .../utils/global_query_string/helpers.ts | 62 ++++++++++++++- .../utils/global_query_string/index.test.tsx | 30 +++++++- .../common/utils/global_query_string/index.ts | 75 +++++++------------ 5 files changed, 178 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx index 0b13363a653a0..cb12aaa7f0e79 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx @@ -15,6 +15,7 @@ import { EventsQueryTabBody, ALERTS_EVENTS_HISTOGRAM_ID } from './events_query_t import { useGlobalFullScreen } from '../../containers/use_full_screen'; import * as tGridActions from '@kbn/timelines-plugin/public/store/t_grid/actions'; import { licenseService } from '../../hooks/use_license'; +import { mockHistory } from '../../mock/router'; const mockGetDefaultControlColumn = jest.fn(); jest.mock('../../../timelines/components/timeline/body/control_columns', () => ({ @@ -39,6 +40,11 @@ jest.mock('../../lib/kibana', () => { }; }); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => mockHistory, +})); + const FakeStatefulEventsViewer = ({ additionalFilters }: { additionalFilters: JSX.Element }) => ( <div> {additionalFilters} diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx index db663c1f0fc6d..07c9995ddbd9e 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.tsx @@ -42,6 +42,11 @@ import { useLicense } from '../../hooks/use_license'; import { useUiSetting$ } from '../../lib/kibana'; import { defaultAlertsFilters } from '../events_viewer/external_alerts_filter'; +import { + useGetInitialUrlParamValue, + useReplaceUrlParams, +} from '../../utils/global_query_string/helpers'; + export const ALERTS_EVENTS_HISTOGRAM_ID = 'alertsOrEventsHistogramQuery'; type QueryTabBodyProps = UserQueryTabBodyProps | HostQueryTabBodyProps | NetworkQueryTabBodyProps; @@ -55,6 +60,8 @@ export type EventsQueryTabBodyComponentProps = QueryTabBodyProps & { timelineId: TimelineId; }; +const EXTERNAL_ALERTS_URL_PARAM = 'onlyExternalAlerts'; + const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = ({ deleteQuery, endDate, @@ -70,7 +77,6 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = const { globalFullScreen } = useGlobalFullScreen(); const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled'); const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT); - const [showExternalAlerts, setShowExternalAlerts] = useState(false); const isEnterprisePlus = useLicense().isEnterprise(); const ACTION_BUTTON_COUNT = isEnterprisePlus ? 5 : 4; const leadingControlColumns = useMemo( @@ -78,6 +84,14 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> = [ACTION_BUTTON_COUNT] ); + const showExternalAlertsInitialUrlState = useExternalAlertsInitialUrlState(); + + const [showExternalAlerts, setShowExternalAlerts] = useState( + showExternalAlertsInitialUrlState ?? false + ); + + useSyncExternalAlertsUrlState(showExternalAlerts); + const toggleExternalAlerts = useCallback(() => setShowExternalAlerts((s) => !s), []); const getHistogramSubtitle = useMemo( () => getSubtitleFunction(defaultNumberFormat, showExternalAlerts), @@ -178,3 +192,43 @@ EventsQueryTabBodyComponent.displayName = 'EventsQueryTabBodyComponent'; export const EventsQueryTabBody = React.memo(EventsQueryTabBodyComponent); EventsQueryTabBody.displayName = 'EventsQueryTabBody'; + +const useExternalAlertsInitialUrlState = () => { + const replaceUrlParams = useReplaceUrlParams(); + + const getInitialUrlParamValue = useGetInitialUrlParamValue<boolean>(EXTERNAL_ALERTS_URL_PARAM); + + const { decodedParam: showExternalAlertsInitialUrlState } = useMemo( + () => getInitialUrlParamValue(), + [getInitialUrlParamValue] + ); + + useEffect(() => { + // Only called on component unmount + return () => { + replaceUrlParams([ + { + key: EXTERNAL_ALERTS_URL_PARAM, + value: null, + }, + ]); + }; + }, [replaceUrlParams]); + + return showExternalAlertsInitialUrlState; +}; + +/** + * Update URL state when showExternalAlerts value changes + */ +const useSyncExternalAlertsUrlState = (showExternalAlerts: boolean) => { + const replaceUrlParams = useReplaceUrlParams(); + useEffect(() => { + replaceUrlParams([ + { + key: EXTERNAL_ALERTS_URL_PARAM, + value: showExternalAlerts ? 'true' : null, + }, + ]); + }, [showExternalAlerts, replaceUrlParams]); +}; diff --git a/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.ts b/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.ts index 63684f1985049..a5f7e93146750 100644 --- a/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/utils/global_query_string/helpers.ts @@ -5,8 +5,12 @@ * 2.0. */ -import { parse } from 'query-string'; import { decode, encode } from 'rison-node'; +import type { ParsedQuery } from 'query-string'; +import { parse, stringify } from 'query-string'; +import { url } from '@kbn/kibana-utils-plugin/public'; +import { useHistory } from 'react-router-dom'; +import { useCallback } from 'react'; import { SecurityPageName } from '../../../app/types'; export const isDetectionsPages = (pageName: string) => @@ -40,3 +44,59 @@ export const getParamFromQueryString = ( return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; + +/** + * + * Gets the value of the URL param from the query string. + * It doesn't update when the URL changes. + * + */ +export const useGetInitialUrlParamValue = <State>(urlParamKey: string) => { + // window.location.search provides the most updated representation of the url search. + // It also guarantees that we don't overwrite URL param managed outside react-router. + const getInitialUrlParamValue = useCallback(() => { + const param = getParamFromQueryString( + getQueryStringFromLocation(window.location.search), + urlParamKey + ); + + const decodedParam = decodeRisonUrlState<State>(param ?? undefined); + + return { param, decodedParam }; + }, [urlParamKey]); + + return getInitialUrlParamValue; +}; + +export const encodeQueryString = (urlParams: ParsedQuery<string>): string => + stringify(url.encodeQuery(urlParams), { sort: false, encode: false }); + +export const useReplaceUrlParams = () => { + const history = useHistory(); + + const replaceUrlParams = useCallback( + (params: Array<{ key: string; value: string | null }>) => { + // window.location.search provides the most updated representation of the url search. + // It prevents unnecessary re-renders which useLocation would create because 'replaceUrlParams' does update the location. + // window.location.search also guarantees that we don't overwrite URL param managed outside react-router. + const search = window.location.search; + const urlParams = parse(search, { sort: false }); + + params.forEach(({ key, value }) => { + if (value == null || value === '') { + delete urlParams[key]; + } else { + urlParams[key] = value; + } + }); + + const newSearch = encodeQueryString(urlParams); + + if (getQueryStringFromLocation(search) !== newSearch) { + history.replace({ search: newSearch }); + } + }, + [history] + ); + return replaceUrlParams; +}; diff --git a/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.test.tsx b/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.test.tsx index a400ab24e06dd..dede5125775c4 100644 --- a/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { act, renderHook } from '@testing-library/react-hooks'; import { useInitializeUrlParam, useGlobalQueryString, @@ -296,5 +296,33 @@ describe('global query string', () => { expect(mockHistory.replace).not.toHaveBeenCalledWith(); }); + + it('deletes unregistered URL params', async () => { + const urlParamKey = 'testKey'; + const value = '123'; + window.location.search = `?${urlParamKey}=${value}`; + const globalUrlParam = { + [urlParamKey]: value, + }; + const store = makeStore(globalUrlParam); + + const { waitForNextUpdate } = renderHook(() => useSyncGlobalQueryString(), { + wrapper: ({ children }: { children: React.ReactElement }) => ( + <TestProviders store={store}>{children}</TestProviders> + ), + }); + + mockHistory.replace.mockClear(); + + act(() => { + store.dispatch(globalUrlParamActions.deregisterUrlParam({ key: urlParamKey })); + }); + + waitForNextUpdate(); + + expect(mockHistory.replace).toHaveBeenCalledWith({ + search: ``, + }); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.ts b/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.ts index 09588c7298c09..96834d39fd644 100644 --- a/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.ts +++ b/x-pack/plugins/security_solution/public/common/utils/global_query_string/index.ts @@ -5,20 +5,15 @@ * 2.0. */ -import type * as H from 'history'; -import type { ParsedQuery } from 'query-string'; -import { parse, stringify } from 'query-string'; import { useCallback, useEffect, useMemo } from 'react'; - -import { url } from '@kbn/kibana-utils-plugin/public'; -import { isEmpty, pickBy } from 'lodash/fp'; -import { useHistory } from 'react-router-dom'; +import { difference, isEmpty, pickBy } from 'lodash/fp'; import { useDispatch } from 'react-redux'; +import usePrevious from 'react-use/lib/usePrevious'; import { - decodeRisonUrlState, + encodeQueryString, encodeRisonUrlState, - getParamFromQueryString, - getQueryStringFromLocation, + useGetInitialUrlParamValue, + useReplaceUrlParams, } from './helpers'; import { useShallowEqualSelector } from '../../hooks/use_selector'; import { globalUrlParamActions, globalUrlParamSelectors } from '../../store/global_url_param'; @@ -43,13 +38,10 @@ export const useInitializeUrlParam = <State>( ) => { const dispatch = useDispatch(); + const getInitialUrlParamValue = useGetInitialUrlParamValue<State>(urlParamKey); + useEffect(() => { - // window.location.search provides the most updated representation of the url search. - // It also guarantees that we don't overwrite URL param managed outside react-router. - const initialValue = getParamFromQueryString( - getQueryStringFromLocation(window.location.search), - urlParamKey - ); + const { param: initialValue, decodedParam: decodedInitialValue } = getInitialUrlParamValue(); dispatch( globalUrlParamActions.registerUrlParam({ @@ -59,7 +51,7 @@ export const useInitializeUrlParam = <State>( ); // execute consumer initialization - onInitialize(decodeRisonUrlState<State>(initialValue ?? undefined)); + onInitialize(decodedInitialValue); return () => { dispatch(globalUrlParamActions.deregisterUrlParam({ key: urlParamKey })); @@ -103,9 +95,16 @@ export const useGlobalQueryString = (): string => { * - It updates the URL when globalUrlParam store updates. */ export const useSyncGlobalQueryString = () => { - const history = useHistory(); const [{ pageName }] = useRouteSpy(); const globalUrlParam = useShallowEqualSelector(globalUrlParamSelectors.selectGlobalUrlParam); + const previousGlobalUrlParams = usePrevious(globalUrlParam); + const replaceUrlParams = useReplaceUrlParams(); + + // Url params that got deleted from GlobalUrlParams + const unregisteredKeys = useMemo( + () => difference(Object.keys(previousGlobalUrlParams ?? {}), Object.keys(globalUrlParam)), + [previousGlobalUrlParams, globalUrlParam] + ); useEffect(() => { const linkInfo = getLinkInfo(pageName) ?? { skipUrlState: true }; @@ -114,36 +113,16 @@ export const useSyncGlobalQueryString = () => { value: linkInfo.skipUrlState ? null : value, })); - if (params.length > 0) { - // window.location.search provides the most updated representation of the url search. - // It prevents unnecessary re-renders which useLocation would create because 'replaceUrlParams' does update the location. - // window.location.search also guarantees that we don't overwrite URL param managed outside react-router. - replaceUrlParams(params, history, window.location.search); - } - }, [globalUrlParam, pageName, history]); -}; - -const encodeQueryString = (urlParams: ParsedQuery<string>): string => - stringify(url.encodeQuery(urlParams), { sort: false, encode: false }); - -const replaceUrlParams = ( - params: Array<{ key: string; value: string | null }>, - history: H.History, - search: string -) => { - const urlParams = parse(search, { sort: false }); + // Delete unregistered Url params + unregisteredKeys.forEach((key) => { + params.push({ + key, + value: null, + }); + }); - params.forEach(({ key, value }) => { - if (value == null || value === '') { - delete urlParams[key]; - } else { - urlParams[key] = value; + if (params.length > 0) { + replaceUrlParams(params); } - }); - - const newSearch = encodeQueryString(urlParams); - - if (getQueryStringFromLocation(search) !== newSearch) { - history.replace({ search: newSearch }); - } + }, [globalUrlParam, pageName, unregisteredKeys, replaceUrlParams]); }; From f1c12a097acf9c5720bb12676787725b4739fbf7 Mon Sep 17 00:00:00 2001 From: suchcodemuchwow <baturalp.gurdin@elastic.co> Date: Tue, 4 Oct 2022 11:34:24 +0200 Subject: [PATCH 018/174] Revert "[Synthetics UI] Serialize errors before sending to redux store to prevent warnings (#142259)" This reverts commit b3a749e55a55f5ab1df4d236916dc270209e83fe. --- .../public/apps/synthetics/state/index_status/actions.ts | 5 ++--- .../public/apps/synthetics/state/index_status/index.ts | 4 ++-- .../apps/synthetics/state/monitor_details/index.ts | 7 ++++--- .../public/apps/synthetics/state/monitor_list/actions.ts | 9 ++++----- .../public/apps/synthetics/state/monitor_list/effects.ts | 4 ++-- .../public/apps/synthetics/state/monitor_list/index.ts | 4 ++-- .../public/apps/synthetics/state/overview/index.ts | 6 +++--- .../apps/synthetics/state/service_locations/actions.ts | 5 +---- .../apps/synthetics/state/service_locations/index.ts | 3 +-- .../synthetics/state/synthetics_enablement/actions.ts | 7 +++---- .../apps/synthetics/state/synthetics_enablement/index.ts | 3 +-- .../public/apps/synthetics/state/utils/actions.ts | 4 ++-- .../public/apps/synthetics/state/utils/fetch_effect.ts | 7 +++---- .../legacy_uptime/state/private_locations/index.ts | 6 +++--- 14 files changed, 33 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts index e522af3bfed7c..36e2e2514910e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts @@ -5,11 +5,10 @@ * 2.0. */ +import type { IHttpFetchError } from '@kbn/core-http-browser'; import { createAction } from '@reduxjs/toolkit'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; export const getIndexStatus = createAction<void>('[INDEX STATUS] GET'); export const getIndexStatusSuccess = createAction<StatesIndexStatus>('[INDEX STATUS] GET SUCCESS'); -export const getIndexStatusFail = - createAction<IHttpSerializedFetchError>('[INDEX STATUS] GET FAIL'); +export const getIndexStatusFail = createAction<IHttpFetchError>('[INDEX STATUS] GET FAIL'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts index 19ef8f94938a3..f5351c65d0d6b 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts @@ -6,7 +6,7 @@ */ import { createReducer } from '@reduxjs/toolkit'; -import { IHttpSerializedFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './actions'; @@ -33,7 +33,7 @@ export const indexStatusReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getIndexStatusFail, (state, action) => { - state.error = action.payload; + state.error = serializeHttpFetchError(action.payload); state.loading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts index b1fb95d5d5ee4..a2d9379df778e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts @@ -5,8 +5,9 @@ * 2.0. */ +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { createReducer } from '@reduxjs/toolkit'; -import { IHttpSerializedFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { getMonitorRecentPingsAction, setMonitorDetailsLocationAction, @@ -46,7 +47,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getMonitorRecentPingsAction.fail, (state, action) => { - state.error = action.payload; + state.error = serializeHttpFetchError(action.payload as IHttpFetchError<ResponseErrorBody>); state.loading = false; }) @@ -58,7 +59,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { state.syntheticsMonitorLoading = false; }) .addCase(getMonitorAction.fail, (state, action) => { - state.error = action.payload; + state.error = serializeHttpFetchError(action.payload as IHttpFetchError<ResponseErrorBody>); state.syntheticsMonitorLoading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index 5a8c38284e034..fcfc3d4f22cf7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -5,13 +5,13 @@ * 2.0. */ +import { IHttpFetchError } from '@kbn/core-http-browser'; import { createAction } from '@reduxjs/toolkit'; import { EncryptedSyntheticsMonitor, MonitorManagementListResult, } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; -import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; @@ -29,8 +29,7 @@ export const fetchUpsertSuccessAction = createAction<{ id: string; attributes: { enabled: boolean }; }>('fetchUpsertMonitorSuccess'); -export const fetchUpsertFailureAction = createAction<{ - id: string; - error: IHttpSerializedFetchError; -}>('fetchUpsertMonitorFailure'); +export const fetchUpsertFailureAction = createAction<{ id: string; error: IHttpFetchError }>( + 'fetchUpsertMonitorFailure' +); export const clearMonitorUpsertStatus = createAction<string>('clearMonitorUpsertStatus'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts index 67aaa4ec982ed..0dee2edfd7903 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts @@ -5,10 +5,10 @@ * 2.0. */ +import { IHttpFetchError } from '@kbn/core-http-browser'; import { PayloadAction } from '@reduxjs/toolkit'; import { call, put, takeEvery, takeLeading } from 'redux-saga/effects'; import { fetchEffectFactory } from '../utils/fetch_effect'; -import { serializeHttpFetchError } from '../utils/http_error'; import { fetchMonitorListAction, fetchUpsertFailureAction, @@ -40,7 +40,7 @@ export function* upsertMonitorEffect() { ); } catch (error) { yield put( - fetchUpsertFailureAction({ id: action.payload.id, error: serializeHttpFetchError(error) }) + fetchUpsertFailureAction({ id: action.payload.id, error: error as IHttpFetchError }) ); } } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index 997f853c9bfc5..e1f564c0d0a3f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -10,7 +10,7 @@ import { FETCH_STATUS } from '@kbn/observability-plugin/public'; import { ConfigKey, MonitorManagementListResult } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; import { @@ -58,7 +58,7 @@ export const monitorListReducer = createReducer(initialState, (builder) => { }) .addCase(fetchMonitorListAction.fail, (state, action) => { state.loading = false; - state.error = action.payload; + state.error = serializeHttpFetchError(action.payload); }) .addCase(fetchUpsertMonitorAction, (state, action) => { state.monitorUpsertStatuses[action.payload.id] = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts index aa4a8db73b98c..49159b29ef461 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts @@ -9,7 +9,7 @@ import { createReducer } from '@reduxjs/toolkit'; import { MonitorOverviewResult, OverviewStatus } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; import { MonitorOverviewPageState } from './models'; import { @@ -60,13 +60,13 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => { }) .addCase(fetchMonitorOverviewAction.fail, (state, action) => { state.loading = false; - state.error = action.payload; + state.error = serializeHttpFetchError(action.payload); }) .addCase(quietFetchOverviewAction.success, (state, action) => { state.data = action.payload; }) .addCase(quietFetchOverviewAction.fail, (state, action) => { - state.error = action.payload; + state.error = serializeHttpFetchError(action.payload); }) .addCase(setOverviewPerPageAction, (state, action) => { state.pageState = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts index dbdd53d4cbcb7..794e16d0292c5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts @@ -7,13 +7,10 @@ import { createAction } from '@reduxjs/toolkit'; import { ServiceLocations, ThrottlingOptions } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; export const getServiceLocations = createAction('[SERVICE LOCATIONS] GET'); export const getServiceLocationsSuccess = createAction<{ throttling: ThrottlingOptions | undefined; locations: ServiceLocations; }>('[SERVICE LOCATIONS] GET SUCCESS'); -export const getServiceLocationsFailure = createAction<IHttpSerializedFetchError>( - '[SERVICE LOCATIONS] GET FAILURE' -); +export const getServiceLocationsFailure = createAction<Error>('[SERVICE LOCATIONS] GET FAILURE'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts index 9a338458e603f..e13fe756ec7fd 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts @@ -11,7 +11,6 @@ import { ServiceLocations, ThrottlingOptions, } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; import { getServiceLocations, @@ -23,7 +22,7 @@ export interface ServiceLocationsState { locations: ServiceLocations; throttling: ThrottlingOptions | null; loading: boolean; - error: IHttpSerializedFetchError | null; + error: Error | null; locationsLoaded?: boolean; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts index 0c7abffd1b289..c38fadc0952a6 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts @@ -7,24 +7,23 @@ import { createAction } from '@reduxjs/toolkit'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; export const getSyntheticsEnablement = createAction('[SYNTHETICS_ENABLEMENT] GET'); export const getSyntheticsEnablementSuccess = createAction<MonitorManagementEnablementResult>( '[SYNTHETICS_ENABLEMENT] GET SUCCESS' ); -export const getSyntheticsEnablementFailure = createAction<IHttpSerializedFetchError>( +export const getSyntheticsEnablementFailure = createAction<Error>( '[SYNTHETICS_ENABLEMENT] GET FAILURE' ); export const disableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] DISABLE'); export const disableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] DISABLE SUCCESS'); -export const disableSyntheticsFailure = createAction<IHttpSerializedFetchError>( +export const disableSyntheticsFailure = createAction<Error>( '[SYNTHETICS_ENABLEMENT] DISABLE FAILURE' ); export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE'); export const enableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS'); -export const enableSyntheticsFailure = createAction<IHttpSerializedFetchError>( +export const enableSyntheticsFailure = createAction<Error>( '[SYNTHETICS_ENABLEMENT] ENABLE FAILURE' ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts index 3bf9ff69bf005..62ed85ad17e86 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts @@ -18,11 +18,10 @@ import { getSyntheticsEnablementFailure, } from './actions'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError } from '../utils/http_error'; export interface SyntheticsEnablementState { loading: boolean; - error: IHttpSerializedFetchError | null; + error: Error | null; enablement: MonitorManagementEnablementResult | null; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts index 35e93fd91484e..416c3134d6034 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts @@ -6,13 +6,13 @@ */ import { createAction } from '@reduxjs/toolkit'; -import type { IHttpSerializedFetchError } from './http_error'; +import type { IHttpFetchError } from '@kbn/core-http-browser'; export function createAsyncAction<Payload, SuccessPayload>(actionStr: string) { return { get: createAction<Payload>(actionStr), success: createAction<SuccessPayload>(`${actionStr}_SUCCESS`), - fail: createAction<IHttpSerializedFetchError>(`${actionStr}_FAIL`), + fail: createAction<IHttpFetchError>(`${actionStr}_FAIL`), }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts index 294da718a6fd3..b07f1fa542633 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts @@ -8,7 +8,6 @@ import { call, put } from 'redux-saga/effects'; import { PayloadAction } from '@reduxjs/toolkit'; import type { IHttpFetchError } from '@kbn/core-http-browser'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from './http_error'; /** * Factory function for a fetch effect. It expects three action creators, @@ -24,7 +23,7 @@ import { IHttpSerializedFetchError, serializeHttpFetchError } from './http_error export function fetchEffectFactory<T, R, S, F>( fetch: (request: T) => Promise<R>, success: (response: R) => PayloadAction<S>, - fail: (error: IHttpSerializedFetchError) => PayloadAction<F> + fail: (error: IHttpFetchError) => PayloadAction<F> ) { return function* (action: PayloadAction<T>): Generator { try { @@ -33,14 +32,14 @@ export function fetchEffectFactory<T, R, S, F>( // eslint-disable-next-line no-console console.error(response); - yield put(fail(serializeHttpFetchError(response as IHttpFetchError))); + yield put(fail(response as IHttpFetchError)); } else { yield put(success(response as R)); } } catch (error) { // eslint-disable-next-line no-console console.error(error); - yield put(fail(serializeHttpFetchError(error))); + yield put(fail(error as IHttpFetchError)); } }; } diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts index 831f8a9cbf6bb..0ff45023143ec 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts @@ -5,9 +5,9 @@ * 2.0. */ +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { createReducer } from '@reduxjs/toolkit'; import { AgentPolicy } from '@kbn/fleet-plugin/common'; -import { IHttpSerializedFetchError } from '../../../apps/synthetics/state'; import { getAgentPoliciesAction, setAddingNewPrivateLocation, @@ -24,7 +24,7 @@ export interface AgentPoliciesList { export interface AgentPoliciesState { data: AgentPoliciesList | null; loading: boolean; - error: IHttpSerializedFetchError | null; + error: IHttpFetchError<ResponseErrorBody> | null; isManageFlyoutOpen?: boolean; isAddingNewPrivateLocation?: boolean; } @@ -47,7 +47,7 @@ export const agentPoliciesReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getAgentPoliciesAction.fail, (state, action) => { - state.error = action.payload; + state.error = action.payload as IHttpFetchError<ResponseErrorBody>; state.loading = false; }) .addCase(setManageFlyoutOpen, (state, action) => { From 10884e6a5fa57bab81ec61f15c320b1472bc0df9 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:39:50 +0100 Subject: [PATCH 019/174] [Security Solution][Detections] refactors update rule actions tests (#142464) ## Summary - addresses https://github.com/elastic/kibana/issues/138757 according to proposal in above task: - removes step of updating immutable rule with mock data - makes assertions whether rule properties were not modified against fetched earlier immutable rule ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --- .../group1/update_actions.ts | 86 +++++++++++-------- .../utils/index.ts | 1 + .../utils/rule_to_update_schema.ts | 36 ++++++++ 3 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts index e9e7e18ea3189..8ee61c0705452 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group1/update_actions.ts @@ -6,6 +6,7 @@ */ import expect from '@kbn/expect'; +import { omit } from 'lodash'; import { CreateRulesSchema } from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; import { FtrProviderContext } from '../../common/ftr_provider_context'; @@ -26,14 +27,24 @@ import { findImmutableRuleById, getPrePackagedRulesStatus, getSimpleRuleOutput, + ruleToUpdateSchema, } from '../../utils'; +// Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: +// x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security.json +const RULE_ID = '9a1a2dae-0b5f-4c3d-8305-a268d404c306'; + // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const log = getService('log'); + const getImmutableRule = async () => { + await installPrePackagedRules(supertest, log); + return getRule(supertest, log, RULE_ID); + }; + describe('update_actions', () => { describe('updating actions', () => { before(async () => { @@ -105,50 +116,53 @@ export default ({ getService }: FtrProviderContext) => { }); it('should not change properties of immutable rule when applying actions to it', async () => { - await installPrePackagedRules(supertest, log); - // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + // actions and throttle to be removed from assertion (it asserted in a separate test case) + const actionsProps = ['actions', 'throttle']; + + const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); const updatedRule = await updateRule(supertest, log, ruleToUpdate); - const bodyToCompare = removeServerGeneratedProperties(updatedRule); + const expected = omit(removeServerGeneratedProperties(updatedRule), actionsProps); - const expected = { - ...getSimpleRuleOutputWithWebHookAction(`${bodyToCompare.actions?.[0].id}`), - rule_id: immutableRule.rule_id, // Rule id should match the same as the immutable rule - version: immutableRule.version, // This version number should not change when an immutable rule is updated - immutable: true, // It should stay immutable true when returning - required_fields: immutableRule.required_fields, // required_fields cannot be modified, so newRuleToUpdate will have required_fields from immutable rule - }; - expect(bodyToCompare).to.eql(expected); + const immutableRuleToAssert = omit( + removeServerGeneratedProperties(immutableRule), + actionsProps + ); + + expect(immutableRuleToAssert).to.eql(expected); + expect(expected.immutable).to.be(true); // It should stay immutable true when returning }); it('should be able to create a new webhook action and attach it to an immutable rule', async () => { - await installPrePackagedRules(supertest, log); - // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); const updatedRule = await updateRule(supertest, log, ruleToUpdate); const bodyToCompare = removeServerGeneratedProperties(updatedRule); const expected = getSimpleRuleOutputWithWebHookAction(`${bodyToCompare.actions?.[0].id}`); expect(bodyToCompare.actions).to.eql(expected.actions); + expect(bodyToCompare.throttle).to.eql(expected.throttle); }); it('should be able to create a new webhook action, attach it to an immutable rule and the count of prepackaged rules should not increase. If this fails, suspect the immutable tags are not staying on the rule correctly.', async () => { - await installPrePackagedRules(supertest, log); - // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) + ); await updateRule(supertest, log, ruleToUpdate); const status = await getPrePackagedRulesStatus(supertest, log); @@ -156,19 +170,15 @@ export default ({ getService }: FtrProviderContext) => { }); it('should be able to create a new webhook action, attach it to an immutable rule and the rule should stay immutable when searching against immutable tags', async () => { - await installPrePackagedRules(supertest, log); - // Rule id of "9a1a2dae-0b5f-4c3d-8305-a268d404c306" is from the file: - // x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint.json - const immutableRule = await getRule(supertest, log, '9a1a2dae-0b5f-4c3d-8305-a268d404c306'); + const immutableRule = await getImmutableRule(); const hookAction = await createNewAction(supertest, log); - const newRuleToUpdate = getSimpleRule(immutableRule.rule_id); - const ruleToUpdate = getRuleWithWebHookAction(hookAction.id, false, newRuleToUpdate); - await updateRule(supertest, log, ruleToUpdate); - const body = await findImmutableRuleById( - supertest, - log, - '9a1a2dae-0b5f-4c3d-8305-a268d404c306' + const ruleToUpdate = getRuleWithWebHookAction( + hookAction.id, + immutableRule.enabled, + ruleToUpdateSchema(immutableRule) ); + await updateRule(supertest, log, ruleToUpdate); + const body = await findImmutableRuleById(supertest, log, RULE_ID); expect(body.data.length).to.eql(1); // should have only one length to the data set, otherwise we have duplicates or the tags were removed and that is incredibly bad. const bodyToCompare = removeServerGeneratedProperties(body.data[0]); diff --git a/x-pack/test/detection_engine_api_integration/utils/index.ts b/x-pack/test/detection_engine_api_integration/utils/index.ts index 093be64c26d8a..866136f172f12 100644 --- a/x-pack/test/detection_engine_api_integration/utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/utils/index.ts @@ -85,6 +85,7 @@ export * from './remove_server_generated_properties'; export * from './remove_server_generated_properties_including_rule_id'; export * from './resolve_simple_rule_output'; export * from './rule_to_ndjson'; +export * from './rule_to_update_schema'; export * from './set_signal_status'; export * from './start_signals_migration'; export * from './update_rule'; diff --git a/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts b/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts new file mode 100644 index 0000000000000..32766c88978cd --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/utils/rule_to_update_schema.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + FullResponseSchema, + UpdateRulesSchema, +} from '@kbn/security-solution-plugin/common/detection_engine/schemas/request'; +import { omit, pickBy } from 'lodash'; + +const propertiesToRemove = [ + 'id', + 'immutable', + 'updated_at', + 'updated_by', + 'created_at', + 'created_by', + 'related_integrations', + 'required_fields', + 'setup', + 'execution_summary', +]; + +/** + * transforms FullResponseSchema rule to UpdateRulesSchema + * returned result can be used in rule update API calls + */ +export const ruleToUpdateSchema = (rule: FullResponseSchema): UpdateRulesSchema => { + const removedProperties = omit(rule, propertiesToRemove); + + // We're only removing undefined values, so this cast correctly narrows the type + return pickBy(removedProperties, (value) => value !== undefined) as UpdateRulesSchema; +}; From 74f30dcf8e0284d2e09deba714244df4195c0b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= <alejandro.haro@elastic.co> Date: Tue, 4 Oct 2022 12:25:25 +0200 Subject: [PATCH 020/174] Move Cloud Integrations out of the `cloud` plugin (#141103) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../steps/storybooks/build_and_upload.ts | 2 +- docs/developer/plugin-list.asciidoc | 12 + packages/kbn-optimizer/limits.yml | 3 + src/dev/storybook/aliases.ts | 2 +- src/plugins/home/kibana.json | 2 +- src/plugins/home/public/plugin.test.ts | 98 ++-- src/plugins/home/public/plugin.ts | 23 +- .../services/environment/environment.mock.ts | 3 +- src/plugins/home/tsconfig.json | 3 +- test/plugin_functional/config.ts | 8 + .../test_suites/core_plugins/rendering.ts | 13 +- tsconfig.base.json | 6 + x-pack/.i18nrc.json | 2 + x-pack/plugins/cloud/common/constants.ts | 1 - x-pack/plugins/cloud/kibana.json | 2 +- x-pack/plugins/cloud/public/index.ts | 2 - x-pack/plugins/cloud/public/mocks.tsx | 21 +- x-pack/plugins/cloud/public/plugin.test.ts | 517 +----------------- x-pack/plugins/cloud/public/plugin.tsx | 340 +++--------- x-pack/plugins/cloud/server/config.ts | 23 - x-pack/plugins/cloud/server/mocks.ts | 25 + x-pack/plugins/cloud/server/plugin.test.ts | 125 ++--- x-pack/plugins/cloud/server/plugin.ts | 50 +- x-pack/plugins/cloud/tsconfig.json | 3 - .../cloud_chat}/.storybook/decorator.tsx | 5 +- .../cloud_chat}/.storybook/index.ts | 0 .../cloud_chat}/.storybook/main.ts | 0 .../cloud_chat}/.storybook/manager.ts | 0 .../cloud_chat}/.storybook/preview.ts | 0 .../cloud_integrations/cloud_chat/README.md | 3 + .../cloud_chat/common/constants.ts | 8 + .../cloud_chat}/common/types.ts | 0 .../cloud_chat/jest.config.js | 18 + .../cloud_integrations/cloud_chat/kibana.json | 15 + .../public/components/chat/chat.stories.tsx | 1 - .../public/components/chat/chat.tsx | 4 +- .../components/chat/get_chat_context.test.ts | 2 +- .../components/chat/get_chat_context.ts | 0 .../public/components/chat/index.ts | 0 .../public/components/chat/use_chat_config.ts | 8 +- .../cloud_chat}/public/components/index.tsx | 0 .../cloud_chat/public/index.ts | 15 + .../cloud_chat/public/plugin.test.ts | 103 ++++ .../cloud_chat/public/plugin.tsx | 88 +++ .../cloud_chat}/public/services/index.tsx | 21 +- .../cloud_chat/server/config.ts | 74 +++ .../cloud_chat/server/index.ts | 15 + .../cloud_chat/server/plugin.ts | 43 ++ .../cloud_chat}/server/routes/chat.test.ts | 0 .../cloud_chat}/server/routes/chat.ts | 0 .../cloud_chat/server/routes/index.ts | 8 + .../server/util/generate_jwt.test.ts | 0 .../cloud_chat}/server/util/generate_jwt.ts | 0 .../cloud_chat/tsconfig.json | 21 + .../cloud_experiments/common/index.ts | 1 - .../cloud_experiments/common/mocks.ts | 9 +- .../cloud_experiments/common/types.ts | 21 - .../cloud_experiments/kibana.json | 2 +- .../cloud_experiments/public/plugin.test.ts | 78 ++- .../cloud_experiments/public/plugin.ts | 53 +- .../cloud_experiments/server/plugin.test.ts | 58 +- .../cloud_experiments/server/plugin.ts | 35 +- .../cloud_experiments/tsconfig.json | 1 + .../cloud_full_story/.i18nrc.json | 7 + .../cloud_full_story/README.md | 3 + .../cloud_full_story/jest.config.js | 18 + .../cloud_full_story/kibana.json | 15 + .../cloud_full_story/public/index.ts | 13 + .../cloud_full_story/public/plugin.test.ts | 70 +++ .../cloud_full_story/public/plugin.ts | 75 +++ .../server/assets/fullstory_library.js | 0 .../cloud_full_story}/server/config.test.ts | 18 +- .../cloud_full_story/server/config.ts | 82 +++ .../cloud_full_story/server/index.ts | 15 + .../server/plugin.test.mock.ts | 12 + .../cloud_full_story/server/plugin.test.ts | 33 ++ .../cloud_full_story/server/plugin.ts | 32 ++ .../server/routes/fullstory.test.ts | 0 .../server/routes/fullstory.ts | 2 +- .../cloud_full_story/server/routes/index.ts | 8 + .../cloud_full_story/tsconfig.json | 20 + .../cloud_integrations/cloud_links/README.md | 3 + .../cloud_links/jest.config.js | 18 + .../cloud_links/kibana.json | 14 + .../cloud_links/public/index.ts | 12 + .../public/maybe_add_cloud_links/index.ts | 8 + .../maybe_add_cloud_links.test.ts | 134 +++++ .../maybe_add_cloud_links.ts | 48 ++ .../maybe_add_cloud_links}/user_menu_links.ts | 21 +- .../cloud_links/public/plugin.test.mocks.ts | 12 + .../cloud_links/public/plugin.test.ts | 77 +++ .../cloud_links/public/plugin.ts | 39 ++ .../cloud_links/tsconfig.json | 21 + x-pack/plugins/data_visualizer/kibana.json | 2 +- .../file_data_visualizer_view.js | 2 +- x-pack/plugins/data_visualizer/tsconfig.json | 1 + x-pack/plugins/enterprise_search/kibana.json | 4 +- .../__mocks__/kea_logic/kibana_logic.mock.ts | 2 + .../product_selector/product_selector.tsx | 2 +- .../plugins/enterprise_search/tsconfig.json | 1 + .../plugins/fleet/.storybook/context/cloud.ts | 1 + x-pack/plugins/fleet/kibana.json | 2 +- .../public/applications/integrations/app.tsx | 2 +- x-pack/plugins/fleet/tsconfig.json | 1 + .../cloud_aware_behavior.test.ts | 3 +- .../features/searchable_snapshots.test.ts | 5 +- x-pack/plugins/security/kibana.json | 2 +- .../analytics/analytics_service.test.ts | 43 +- .../public/analytics/analytics_service.ts | 13 +- .../analytics/register_user_context.test.ts | 126 +++++ .../public/analytics/register_user_context.ts | 67 +++ x-pack/plugins/security/public/plugin.tsx | 12 +- x-pack/plugins/security/server/plugin.test.ts | 9 - x-pack/plugins/security/server/plugin.ts | 28 +- x-pack/plugins/security/tsconfig.json | 1 + .../translations/translations/fr-FR.json | 5 - .../translations/translations/ja-JP.json | 5 - .../translations/translations/zh-CN.json | 5 - 118 files changed, 1907 insertions(+), 1263 deletions(-) create mode 100644 x-pack/plugins/cloud/server/mocks.ts rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/decorator.tsx (89%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/index.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/main.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/manager.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/.storybook/preview.ts (100%) create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/README.md create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/common/types.ts (100%) create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/kibana.json rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/chat.stories.tsx (99%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/chat.tsx (94%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/get_chat_context.test.ts (97%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/get_chat_context.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/index.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/chat/use_chat_config.ts (95%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/components/index.tsx (100%) create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/public/index.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/public/services/index.tsx (58%) create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/server/config.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/server/index.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/routes/chat.test.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/routes/chat.ts (100%) create mode 100755 x-pack/plugins/cloud_integrations/cloud_chat/server/routes/index.ts rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/util/generate_jwt.test.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_chat}/server/util/generate_jwt.ts (100%) create mode 100644 x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/.i18nrc.json create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/README.md create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/jest.config.js create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/kibana.json create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/public/index.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.test.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/assets/fullstory_library.js (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/config.test.ts (50%) create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/server/index.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.mock.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.ts rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/routes/fullstory.test.ts (100%) rename x-pack/plugins/{cloud => cloud_integrations/cloud_full_story}/server/routes/fullstory.ts (98%) create mode 100755 x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/index.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/README.md create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/jest.config.js create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/kibana.json create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/public/index.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/index.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts rename x-pack/plugins/{cloud/public => cloud_integrations/cloud_links/public/maybe_add_cloud_links}/user_menu_links.ts (50%) create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.mocks.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts create mode 100755 x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts create mode 100644 x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json create mode 100644 x-pack/plugins/security/public/analytics/register_user_context.test.ts create mode 100644 x-pack/plugins/security/public/analytics/register_user_context.ts diff --git a/.buildkite/scripts/steps/storybooks/build_and_upload.ts b/.buildkite/scripts/steps/storybooks/build_and_upload.ts index dcceca7848910..945f85a820971 100644 --- a/.buildkite/scripts/steps/storybooks/build_and_upload.ts +++ b/.buildkite/scripts/steps/storybooks/build_and_upload.ts @@ -15,7 +15,7 @@ const STORYBOOKS = [ 'apm', 'canvas', 'ci_composite', - 'cloud', + 'cloud_chat', 'coloring', 'chart_icons', 'controls', diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index f4fc9c67508ef..407261c6f1d7e 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -424,10 +424,22 @@ The plugin exposes the static DefaultEditorController class to consume. |The cloud plugin adds Cloud-specific features to Kibana. +|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_chat/README.md[cloudChat] +|Integrates with DriftChat in order to provide live support to our Elastic Cloud users. This plugin should only run on Elastic Cloud. + + |{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_experiments/README.mdx[cloudExperiments] |The Cloud Experiments Service provides the necessary APIs to implement A/B testing scenarios, fetching the variations in configuration and reporting back metrics to track conversion rates of the experiments. +|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_full_story/README.md[cloudFullStory] +|Integrates with FullStory in order to provide better product analytics, so we can understand how our users make use of Kibana. This plugin should only run on Elastic Cloud. + + +|{kib-repo}blob/{branch}/x-pack/plugins/cloud_integrations/cloud_links/README.md[cloudLinks] +|Adds all the links to the Elastic Cloud console. + + |{kib-repo}blob/{branch}/x-pack/plugins/cloud_security_posture/README.md[cloudSecurityPosture] |Cloud Posture automates the identification and remediation of risks across cloud infrastructures diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 5cd1458028626..67064af8cddc5 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -10,7 +10,10 @@ pageLoadAssetSize: cases: 144442 charts: 55000 cloud: 21076 + cloudChat: 19894 cloudExperiments: 59358 + cloudFullStory: 18493 + cloudLinks: 17629 cloudSecurityPosture: 19109 console: 46091 controls: 40000 diff --git a/src/dev/storybook/aliases.ts b/src/dev/storybook/aliases.ts index b4224e154def5..6f82ec078f7ab 100644 --- a/src/dev/storybook/aliases.ts +++ b/src/dev/storybook/aliases.ts @@ -11,7 +11,7 @@ export const storybookAliases = { apm: 'x-pack/plugins/apm/.storybook', canvas: 'x-pack/plugins/canvas/storybook', ci_composite: '.ci/.storybook', - cloud: 'x-pack/plugins/cloud/.storybook', + cloud_chat: 'x-pack/plugins/cloud_integrations/cloud_chat/.storybook', coloring: 'packages/kbn-coloring/.storybook', chart_icons: 'packages/kbn-chart-icons/.storybook', content_management: 'packages/content-management/.storybook', diff --git a/src/plugins/home/kibana.json b/src/plugins/home/kibana.json index 02b33e814e2a1..72b4d6cb8fd0b 100644 --- a/src/plugins/home/kibana.json +++ b/src/plugins/home/kibana.json @@ -8,6 +8,6 @@ "server": true, "ui": true, "requiredPlugins": ["dataViews", "share", "urlForwarding"], - "optionalPlugins": ["usageCollection", "customIntegrations"], + "optionalPlugins": ["usageCollection", "customIntegrations", "cloud"], "requiredBundles": ["kibanaReact"] } diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts index 12243944ef0f0..a6c6012a28ed6 100644 --- a/src/plugins/home/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -11,6 +11,7 @@ import { HomePublicPlugin } from './plugin'; import { coreMock } from '@kbn/core/public/mocks'; import { urlForwardingPluginMock } from '@kbn/url-forwarding-plugin/public/mocks'; import { SharePluginSetup } from '@kbn/share-plugin/public'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; const mockInitializerContext = coreMock.createPluginInitializerContext(); const mockShare = {} as SharePluginSetup; @@ -24,14 +25,11 @@ describe('HomePublicPlugin', () => { }); describe('setup', () => { - test('registers tutorial directory to feature catalogue', async () => { - const setup = await new HomePublicPlugin(mockInitializerContext).setup( - coreMock.createSetup() as any, - { - share: mockShare, - urlForwarding: urlForwardingPluginMock.createSetupContract(), - } - ); + test('registers tutorial directory to feature catalogue', () => { + const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), { + share: mockShare, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); expect(setup).toHaveProperty('featureCatalogue'); expect(setup.featureCatalogue.register).toHaveBeenCalledTimes(1); expect(setup.featureCatalogue.register).toHaveBeenCalledWith( @@ -44,53 +42,73 @@ describe('HomePublicPlugin', () => { ); }); - test('wires up and returns registry', async () => { - const setup = await new HomePublicPlugin(mockInitializerContext).setup( - coreMock.createSetup() as any, - { - share: mockShare, - urlForwarding: urlForwardingPluginMock.createSetupContract(), - } - ); + test('wires up and returns registry', () => { + const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), { + share: mockShare, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); expect(setup).toHaveProperty('featureCatalogue'); expect(setup.featureCatalogue).toHaveProperty('register'); }); - test('wires up and returns environment service', async () => { - const setup = await new HomePublicPlugin(mockInitializerContext).setup( - coreMock.createSetup() as any, - { - share: {} as SharePluginSetup, - urlForwarding: urlForwardingPluginMock.createSetupContract(), - } - ); + test('wires up and returns environment service', () => { + const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), { + share: {} as SharePluginSetup, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); expect(setup).toHaveProperty('environment'); expect(setup.environment).toHaveProperty('update'); }); - test('wires up and returns tutorial service', async () => { - const setup = await new HomePublicPlugin(mockInitializerContext).setup( - coreMock.createSetup() as any, - { - share: mockShare, - urlForwarding: urlForwardingPluginMock.createSetupContract(), - } - ); + test('wires up and returns tutorial service', () => { + const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), { + share: mockShare, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); expect(setup).toHaveProperty('tutorials'); expect(setup.tutorials).toHaveProperty('setVariable'); }); - test('wires up and returns welcome service', async () => { - const setup = await new HomePublicPlugin(mockInitializerContext).setup( - coreMock.createSetup() as any, - { - share: mockShare, - urlForwarding: urlForwardingPluginMock.createSetupContract(), - } - ); + test('wires up and returns welcome service', () => { + const setup = new HomePublicPlugin(mockInitializerContext).setup(coreMock.createSetup(), { + share: mockShare, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); expect(setup).toHaveProperty('welcomeScreen'); expect(setup.welcomeScreen).toHaveProperty('registerOnRendered'); expect(setup.welcomeScreen).toHaveProperty('registerTelemetryNoticeRenderer'); }); + + test('sets the cloud environment variable when the cloud plugin is present but isCloudEnabled: false', () => { + const cloud = { ...cloudMock.createSetup(), isCloudEnabled: false }; + const plugin = new HomePublicPlugin(mockInitializerContext); + const setup = plugin.setup(coreMock.createSetup(), { + cloud, + share: mockShare, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); + expect(setup.environment.update).toHaveBeenCalledTimes(1); + expect(setup.environment.update).toHaveBeenCalledWith({ cloud: false }); + expect(setup.tutorials.setVariable).toHaveBeenCalledTimes(0); + }); + + test('when cloud is enabled, it sets the cloud environment and the tutorials variable "cloud"', () => { + const cloud = { ...cloudMock.createSetup(), isCloudEnabled: true }; + const plugin = new HomePublicPlugin(mockInitializerContext); + const setup = plugin.setup(coreMock.createSetup(), { + cloud, + share: mockShare, + urlForwarding: urlForwardingPluginMock.createSetupContract(), + }); + expect(setup.environment.update).toHaveBeenCalledTimes(1); + expect(setup.environment.update).toHaveBeenCalledWith({ cloud: true }); + expect(setup.tutorials.setVariable).toHaveBeenCalledTimes(1); + expect(setup.tutorials.setVariable).toHaveBeenCalledWith('cloud', { + id: 'mock-cloud-id', + baseUrl: 'base-url', + deploymentUrl: 'deployment-url', + profileUrl: 'profile-url', + }); + }); }); }); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index 642a8d575e078..e27ddf107a5ee 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -20,6 +20,7 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { UrlForwardingSetup, UrlForwardingStart } from '@kbn/url-forwarding-plugin/public'; import { AppNavLinkStatus } from '@kbn/core/public'; import { SharePluginSetup } from '@kbn/share-plugin/public'; +import type { CloudSetup } from '@kbn/cloud-plugin/public'; import { PLUGIN_ID, HOME_APP_BASE_PATH } from '../common/constants'; import { setServices } from './application/kibana_services'; import { ConfigSchema } from '../config'; @@ -42,6 +43,7 @@ export interface HomePluginStartDependencies { } export interface HomePluginSetupDependencies { + cloud?: CloudSetup; share: SharePluginSetup; usageCollection?: UsageCollectionSetup; urlForwarding: UrlForwardingSetup; @@ -66,7 +68,7 @@ export class HomePublicPlugin public setup( core: CoreSetup<HomePluginStartDependencies>, - { share, urlForwarding, usageCollection }: HomePluginSetupDependencies + { cloud, share, urlForwarding, usageCollection }: HomePluginSetupDependencies ): HomePublicPluginSetup { core.application.register({ id: PLUGIN_ID, @@ -127,10 +129,25 @@ export class HomePublicPlugin order: 500, }); + const environment = { ...this.environmentService.setup() }; + const tutorials = { ...this.tutorialService.setup() }; + if (cloud) { + environment.update({ cloud: cloud.isCloudEnabled }); + if (cloud.isCloudEnabled) { + tutorials.setVariable('cloud', { + id: cloud.cloudId, + baseUrl: cloud.baseUrl, + // Cloud's API already provides the full URLs + profileUrl: cloud.profileUrl?.replace(cloud.baseUrl ?? '', ''), + deploymentUrl: cloud.deploymentUrl?.replace(cloud.baseUrl ?? '', ''), + }); + } + } + return { featureCatalogue, - environment: { ...this.environmentService.setup() }, - tutorials: { ...this.tutorialService.setup() }, + environment, + tutorials, addData: { ...this.addDataService.setup() }, welcomeScreen: { ...this.welcomeService.setup() }, }; diff --git a/src/plugins/home/public/services/environment/environment.mock.ts b/src/plugins/home/public/services/environment/environment.mock.ts index 713a59ceac7bf..f2d4747d44d6a 100644 --- a/src/plugins/home/public/services/environment/environment.mock.ts +++ b/src/plugins/home/public/services/environment/environment.mock.ts @@ -18,14 +18,13 @@ const createSetupMock = (): jest.Mocked<EnvironmentServiceSetup> => { const createMock = (): jest.Mocked<PublicMethodsOf<EnvironmentService>> => { const service = { - setup: jest.fn(), + setup: jest.fn(createSetupMock), getEnvironment: jest.fn(() => ({ cloud: false, apmUi: false, ml: false, })), }; - service.setup.mockImplementation(createSetupMock); return service; }; diff --git a/src/plugins/home/tsconfig.json b/src/plugins/home/tsconfig.json index 8e617896e3f96..af121720eee0e 100644 --- a/src/plugins/home/tsconfig.json +++ b/src/plugins/home/tsconfig.json @@ -15,6 +15,7 @@ { "path": "../kibana_react/tsconfig.json" }, { "path": "../share/tsconfig.json" }, { "path": "../url_forwarding/tsconfig.json" }, - { "path": "../usage_collection/tsconfig.json" } + { "path": "../usage_collection/tsconfig.json" }, + { "path": "../../../x-pack/plugins/cloud/tsconfig.json" } ] } diff --git a/test/plugin_functional/config.ts b/test/plugin_functional/config.ts index b2dbc762ab657..750da63e27d1c 100644 --- a/test/plugin_functional/config.ts +++ b/test/plugin_functional/config.ts @@ -60,6 +60,14 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { '--corePluginDeprecations.noLongerUsed=still_using', // for testing set buffer duration to 0 to immediately flush counters into saved objects. '--usageCollection.usageCounters.bufferDuration=0', + // explicitly enable the cloud integration plugins to validate the rendered config keys + '--xpack.cloud_integrations.chat.enabled=true', + '--xpack.cloud_integrations.chat.chatURL=a_string', + '--xpack.cloud_integrations.experiments.enabled=true', + '--xpack.cloud_integrations.experiments.launch_darkly.sdk_key=a_string', + '--xpack.cloud_integrations.experiments.launch_darkly.client_id=a_string', + '--xpack.cloud_integrations.full_story.enabled=true', + '--xpack.cloud_integrations.full_story.org_id=a_string', ...plugins.map( (pluginDir) => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index cbc98ec7bb07b..4633a374ee9d5 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -171,14 +171,17 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.cases.markdownPlugins.lens (boolean)', 'xpack.ccr.ui.enabled (boolean)', 'xpack.cloud.base_url (string)', - 'xpack.cloud.chat.chatURL (string)', - 'xpack.cloud.chat.enabled (boolean)', 'xpack.cloud.cname (string)', 'xpack.cloud.deployment_url (string)', - 'xpack.cloud.full_story.enabled (boolean)', - 'xpack.cloud.full_story.org_id (any)', + 'xpack.cloud_integrations.chat.chatURL (string)', + // No PII. This is an escape patch to override LaunchDarkly's flag resolution mechanism for testing or quick fix. + 'xpack.cloud_integrations.experiments.flag_overrides (record)', + // Commented because it's inside a schema conditional, and the test is not able to resolve it. But it's shared. + // Added here for documentation purposes. + // 'xpack.cloud_integrations.experiments.launch_darkly.client_id (string)', + 'xpack.cloud_integrations.full_story.org_id (any)', // No PII. Just the list of event types we want to forward to FullStory. - 'xpack.cloud.full_story.eventTypesAllowlist (array)', + 'xpack.cloud_integrations.full_story.eventTypesAllowlist (array)', 'xpack.cloud.id (string)', 'xpack.cloud.organization_url (string)', 'xpack.cloud.profile_url (string)', diff --git a/tsconfig.base.json b/tsconfig.base.json index b62beb6650448..3054a36f2bb86 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -313,8 +313,14 @@ "@kbn/canvas-plugin/*": ["x-pack/plugins/canvas/*"], "@kbn/cases-plugin": ["x-pack/plugins/cases"], "@kbn/cases-plugin/*": ["x-pack/plugins/cases/*"], + "@kbn/cloud-chat-plugin": ["x-pack/plugins/cloud_integrations/cloud_chat"], + "@kbn/cloud-chat-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_chat/*"], "@kbn/cloud-experiments-plugin": ["x-pack/plugins/cloud_integrations/cloud_experiments"], "@kbn/cloud-experiments-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_experiments/*"], + "@kbn/cloud-full-story-plugin": ["x-pack/plugins/cloud_integrations/cloud_full_story"], + "@kbn/cloud-full-story-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_full_story/*"], + "@kbn/cloud-links-plugin": ["x-pack/plugins/cloud_integrations/cloud_links"], + "@kbn/cloud-links-plugin/*": ["x-pack/plugins/cloud_integrations/cloud_links/*"], "@kbn/cloud-security-posture-plugin": ["x-pack/plugins/cloud_security_posture"], "@kbn/cloud-security-posture-plugin/*": ["x-pack/plugins/cloud_security_posture/*"], "@kbn/cloud-plugin": ["x-pack/plugins/cloud"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 83466ba749605..4f89798c71faf 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -10,6 +10,8 @@ "xpack.canvas": "plugins/canvas", "xpack.cases": "plugins/cases", "xpack.cloud": "plugins/cloud", + "xpack.cloudChat": "plugins/cloud_integrations/cloud_chat", + "xpack.cloudLinks": "plugins/cloud_integrations/cloud_links", "xpack.csp": "plugins/cloud_security_posture", "xpack.dashboard": "plugins/dashboard_enhanced", "xpack.discover": "plugins/discover_enhanced", diff --git a/x-pack/plugins/cloud/common/constants.ts b/x-pack/plugins/cloud/common/constants.ts index 09333e3773fe9..fc37906299d14 100644 --- a/x-pack/plugins/cloud/common/constants.ts +++ b/x-pack/plugins/cloud/common/constants.ts @@ -6,7 +6,6 @@ */ export const ELASTIC_SUPPORT_LINK = 'https://cloud.elastic.co/support'; -export const GET_CHAT_USER_DATA_ROUTE_PATH = '/internal/cloud/chat_user'; /** * This is the page for managing your snapshots on Cloud. diff --git a/x-pack/plugins/cloud/kibana.json b/x-pack/plugins/cloud/kibana.json index 51df5d20d81b9..85434abc87ede 100644 --- a/x-pack/plugins/cloud/kibana.json +++ b/x-pack/plugins/cloud/kibana.json @@ -7,7 +7,7 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "cloud"], - "optionalPlugins": ["cloudExperiments", "usageCollection", "home", "security"], + "optionalPlugins": ["usageCollection"], "server": true, "ui": true } diff --git a/x-pack/plugins/cloud/public/index.ts b/x-pack/plugins/cloud/public/index.ts index d50798cb15cd2..ee37f85dfb6a7 100644 --- a/x-pack/plugins/cloud/public/index.ts +++ b/x-pack/plugins/cloud/public/index.ts @@ -13,5 +13,3 @@ export type { CloudSetup, CloudConfigType, CloudStart } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new CloudPlugin(initializerContext); } - -export { Chat } from './components'; diff --git a/x-pack/plugins/cloud/public/mocks.tsx b/x-pack/plugins/cloud/public/mocks.tsx index f31596f3930f5..608e826657b73 100644 --- a/x-pack/plugins/cloud/public/mocks.tsx +++ b/x-pack/plugins/cloud/public/mocks.tsx @@ -8,7 +8,6 @@ import React from 'react'; import { CloudStart } from '.'; -import { ServicesProvider } from './services'; function createSetupMock() { return { @@ -19,28 +18,22 @@ function createSetupMock() { deploymentUrl: 'deployment-url', profileUrl: 'profile-url', organizationUrl: 'organization-url', + registerCloudService: jest.fn(), }; } -const config = { - chat: { - enabled: true, - chatURL: 'chat-url', - user: { - id: 'user-id', - email: 'test-user@elastic.co', - jwt: 'identity-jwt', - }, - }, -}; - const getContextProvider: () => React.FC = () => ({ children }) => - <ServicesProvider {...config}>{children}</ServicesProvider>; + <>{children}</>; const createStartMock = (): jest.Mocked<CloudStart> => ({ CloudContextProvider: jest.fn(getContextProvider()), + cloudId: 'mock-cloud-id', + isCloudEnabled: true, + deploymentUrl: 'deployment-url', + profileUrl: 'profile-url', + organizationUrl: 'organization-url', }); export const cloudMock = { diff --git a/x-pack/plugins/cloud/public/plugin.test.ts b/x-pack/plugins/cloud/public/plugin.test.ts index 599dee5e707b7..efb566761e22a 100644 --- a/x-pack/plugins/cloud/public/plugin.test.ts +++ b/x-pack/plugins/cloud/public/plugin.test.ts @@ -5,308 +5,18 @@ * 2.0. */ -import { firstValueFrom } from 'rxjs'; -import { Sha256 } from '@kbn/crypto-browser'; -import { nextTick } from '@kbn/test-jest-helpers'; import { coreMock } from '@kbn/core/public/mocks'; -import { homePluginMock } from '@kbn/home-plugin/public/mocks'; -import { securityMock } from '@kbn/security-plugin/public/mocks'; -import { CloudPlugin, type CloudConfigType } from './plugin'; -import { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; -import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; +import { CloudPlugin } from './plugin'; const baseConfig = { base_url: 'https://cloud.elastic.co', deployment_url: '/abc123', profile_url: '/user/settings/', organization_url: '/account/', - full_story: { - enabled: false, - }, - chat: { - enabled: false, - }, }; describe('Cloud Plugin', () => { describe('#setup', () => { - describe('setupFullStory', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - const setupPlugin = async ({ config = {} }: { config?: Partial<CloudConfigType> }) => { - const initContext = coreMock.createPluginInitializerContext({ - ...baseConfig, - id: 'cloudId', - ...config, - }); - - const plugin = new CloudPlugin(initContext); - - const coreSetup = coreMock.createSetup(); - - const setup = plugin.setup(coreSetup, {}); - - // Wait for FullStory dynamic import to resolve - await new Promise((r) => setImmediate(r)); - - return { initContext, plugin, setup, coreSetup }; - }; - - test('register the shipper FullStory with correct args when enabled and org_id are set', async () => { - const { coreSetup } = await setupPlugin({ - config: { full_story: { enabled: true, org_id: 'foo' } }, - }); - - expect(coreSetup.analytics.registerShipper).toHaveBeenCalled(); - expect(coreSetup.analytics.registerShipper).toHaveBeenCalledWith(expect.anything(), { - fullStoryOrgId: 'foo', - scriptUrl: '/internal/cloud/100/fullstory.js', - namespace: 'FSKibana', - }); - }); - - it('does not call initializeFullStory when enabled=false', async () => { - const { coreSetup } = await setupPlugin({ - config: { full_story: { enabled: false, org_id: 'foo' } }, - }); - expect(coreSetup.analytics.registerShipper).not.toHaveBeenCalled(); - }); - - it('does not call initializeFullStory when org_id is undefined', async () => { - const { coreSetup } = await setupPlugin({ config: { full_story: { enabled: true } } }); - expect(coreSetup.analytics.registerShipper).not.toHaveBeenCalled(); - }); - }); - - describe('setupTelemetryContext', () => { - const username = '1234'; - const expectedHashedPlainUsername = new Sha256().update(username, 'utf8').digest('hex'); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - const setupPlugin = async ({ - config = {}, - securityEnabled = true, - currentUserProps = {}, - }: { - config?: Partial<CloudConfigType>; - securityEnabled?: boolean; - currentUserProps?: Record<string, any> | Error; - }) => { - const initContext = coreMock.createPluginInitializerContext({ - ...baseConfig, - ...config, - }); - - const plugin = new CloudPlugin(initContext); - - const coreSetup = coreMock.createSetup(); - const securitySetup = securityMock.createSetup(); - if (currentUserProps instanceof Error) { - securitySetup.authc.getCurrentUser.mockRejectedValue(currentUserProps); - } else { - securitySetup.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser(currentUserProps) - ); - } - - const setup = plugin.setup(coreSetup, securityEnabled ? { security: securitySetup } : {}); - - return { initContext, plugin, setup, coreSetup }; - }; - - test('register the context provider for the cloud user with hashed user ID when security is available', async () => { - const { coreSetup } = await setupPlugin({ - config: { id: 'cloudId' }, - currentUserProps: { username }, - }); - - expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled(); - - const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find( - ([{ name }]) => name === 'cloud_user_id' - )!; - - await expect(firstValueFrom(context$)).resolves.toEqual({ - userId: '5ef112cfdae3dea57097bc276e275b2816e73ef2a398dc0ffaf5b6b4e3af2041', - isElasticCloudUser: false, - }); - }); - - it('user hash includes cloud id', async () => { - const { coreSetup: coreSetup1 } = await setupPlugin({ - config: { id: 'esOrg1' }, - currentUserProps: { username }, - }); - - const [{ context$: context1$ }] = - coreSetup1.analytics.registerContextProvider.mock.calls.find( - ([{ name }]) => name === 'cloud_user_id' - )!; - - const { userId: hashId1 } = (await firstValueFrom(context1$)) as { userId: string }; - expect(hashId1).not.toEqual(expectedHashedPlainUsername); - - const { coreSetup: coreSetup2 } = await setupPlugin({ - config: { full_story: { enabled: true, org_id: 'foo' }, id: 'esOrg2' }, - currentUserProps: { username }, - }); - - const [{ context$: context2$ }] = - coreSetup2.analytics.registerContextProvider.mock.calls.find( - ([{ name }]) => name === 'cloud_user_id' - )!; - - const { userId: hashId2 } = (await firstValueFrom(context2$)) as { userId: string }; - expect(hashId2).not.toEqual(expectedHashedPlainUsername); - - expect(hashId1).not.toEqual(hashId2); - }); - - test('user hash does not include cloudId when user is an Elastic Cloud user', async () => { - const { coreSetup } = await setupPlugin({ - config: { id: 'cloudDeploymentId' }, - currentUserProps: { username, elastic_cloud_user: true }, - }); - - expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled(); - - const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find( - ([{ name }]) => name === 'cloud_user_id' - )!; - - await expect(firstValueFrom(context$)).resolves.toEqual({ - userId: expectedHashedPlainUsername, - isElasticCloudUser: true, - }); - }); - - test('user hash does not include cloudId when not provided', async () => { - const { coreSetup } = await setupPlugin({ - config: {}, - currentUserProps: { username }, - }); - - expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled(); - - const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find( - ([{ name }]) => name === 'cloud_user_id' - )!; - - await expect(firstValueFrom(context$)).resolves.toEqual({ - userId: expectedHashedPlainUsername, - isElasticCloudUser: false, - }); - }); - - test('user hash is undefined when failed to fetch a user', async () => { - const { coreSetup } = await setupPlugin({ - currentUserProps: new Error('failed to fetch a user'), - }); - - expect(coreSetup.analytics.registerContextProvider).toHaveBeenCalled(); - - const [{ context$ }] = coreSetup.analytics.registerContextProvider.mock.calls.find( - ([{ name }]) => name === 'cloud_user_id' - )!; - - await expect(firstValueFrom(context$)).resolves.toEqual({ - userId: undefined, - isElasticCloudUser: false, - }); - }); - }); - - describe('setupChat', () => { - let consoleMock: jest.SpyInstance<void, [message?: any, ...optionalParams: any[]]>; - - beforeEach(() => { - consoleMock = jest.spyOn(console, 'debug').mockImplementation(() => {}); - }); - - afterEach(() => { - consoleMock.mockRestore(); - }); - - const setupPlugin = async ({ - config = {}, - securityEnabled = true, - currentUserProps = {}, - isCloudEnabled = true, - failHttp = false, - }: { - config?: Partial<CloudConfigType>; - securityEnabled?: boolean; - currentUserProps?: Record<string, any>; - isCloudEnabled?: boolean; - failHttp?: boolean; - }) => { - const initContext = coreMock.createPluginInitializerContext({ - ...baseConfig, - id: isCloudEnabled ? 'cloud-id' : null, - ...config, - }); - - const plugin = new CloudPlugin(initContext); - - const coreSetup = coreMock.createSetup(); - const coreStart = coreMock.createStart(); - - if (failHttp) { - coreSetup.http.get.mockImplementation(() => { - throw new Error('HTTP request failed'); - }); - } - - coreSetup.getStartServices.mockResolvedValue([coreStart, {}, undefined]); - - const securitySetup = securityMock.createSetup(); - securitySetup.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser(currentUserProps) - ); - - const setup = plugin.setup(coreSetup, securityEnabled ? { security: securitySetup } : {}); - - return { initContext, plugin, setup, coreSetup }; - }; - - it('chatConfig is not retrieved if cloud is not enabled', async () => { - const { coreSetup } = await setupPlugin({ isCloudEnabled: false }); - expect(coreSetup.http.get).not.toHaveBeenCalled(); - }); - - it('chatConfig is not retrieved if security is not enabled', async () => { - const { coreSetup } = await setupPlugin({ securityEnabled: false }); - expect(coreSetup.http.get).not.toHaveBeenCalled(); - }); - - it('chatConfig is not retrieved if chat is enabled but url is not provided', async () => { - // @ts-expect-error 2741 - const { coreSetup } = await setupPlugin({ config: { chat: { enabled: true } } }); - expect(coreSetup.http.get).not.toHaveBeenCalled(); - }); - - it('chatConfig is not retrieved if internal API fails', async () => { - const { coreSetup } = await setupPlugin({ - config: { chat: { enabled: true, chatURL: 'http://chat.elastic.co' } }, - failHttp: true, - }); - expect(coreSetup.http.get).toHaveBeenCalled(); - expect(consoleMock).toHaveBeenCalled(); - }); - - it('chatConfig is retrieved if chat is enabled and url is provided', async () => { - const { coreSetup } = await setupPlugin({ - config: { chat: { enabled: true, chatURL: 'http://chat.elastic.co' } }, - }); - expect(coreSetup.http.get).toHaveBeenCalled(); - }); - }); - describe('interface', () => { const setupPlugin = () => { const initContext = coreMock.createPluginInitializerContext({ @@ -317,7 +27,7 @@ describe('Cloud Plugin', () => { const plugin = new CloudPlugin(initContext); const coreSetup = coreMock.createSetup(); - const setup = plugin.setup(coreSetup, {}); + const setup = plugin.setup(coreSetup); return { setup }; }; @@ -361,49 +71,10 @@ describe('Cloud Plugin', () => { const { setup } = setupPlugin(); expect(setup.cname).toBe('cloud.elastic.co'); }); - }); - - describe('Set up cloudExperiments', () => { - describe('when cloud ID is not provided in the config', () => { - let cloudExperiments: jest.Mocked<CloudExperimentsPluginSetup>; - beforeEach(() => { - const plugin = new CloudPlugin(coreMock.createPluginInitializerContext(baseConfig)); - cloudExperiments = cloudExperimentsMock.createSetupMock(); - plugin.setup(coreMock.createSetup(), { cloudExperiments }); - }); - test('does not call cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser).not.toHaveBeenCalled(); - }); - }); - - describe('when cloud ID is provided in the config', () => { - let cloudExperiments: jest.Mocked<CloudExperimentsPluginSetup>; - beforeEach(() => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext({ ...baseConfig, id: 'cloud test' }) - ); - cloudExperiments = cloudExperimentsMock.createSetupMock(); - plugin.setup(coreMock.createSetup(), { cloudExperiments }); - }); - - test('calls cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser).toHaveBeenCalledTimes(1); - }); - - test('the cloud ID is hashed when calling cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser.mock.calls[0][0]).toEqual( - '1acb4a1cc1c3d672a8d826055d897c2623ceb1d4fb07e46d97986751a36b06cf' - ); - }); - - test('specifies the Kibana version when calling cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser.mock.calls[0][1]).toEqual( - expect.objectContaining({ - kibanaVersion: 'version', - }) - ); - }); + it('exposes registerCloudService', () => { + const { setup } = setupPlugin(); + expect(setup.registerCloudService).toBeDefined(); }); }); }); @@ -426,9 +97,8 @@ describe('Cloud Plugin', () => { }) ); const coreSetup = coreMock.createSetup(); - const homeSetup = homePluginMock.createSetupContract(); - plugin.setup(coreSetup, { home: homeSetup }); + plugin.setup(coreSetup); return { coreSetup, plugin }; }; @@ -437,8 +107,7 @@ describe('Cloud Plugin', () => { const { plugin } = startPlugin(); const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - plugin.start(coreStart, { security: securityStart }); + plugin.start(coreStart); expect(coreStart.chrome.setHelpSupportUrl).toHaveBeenCalledTimes(1); expect(coreStart.chrome.setHelpSupportUrl.mock.calls[0]).toMatchInlineSnapshot(` @@ -447,177 +116,5 @@ describe('Cloud Plugin', () => { ] `); }); - - it('does not register custom nav links on anonymous pages', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - - const securityStart = securityMock.createStart(); - securityStart.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser({ - elastic_cloud_user: true, - }) - ); - - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(coreStart.chrome.setCustomNavLink).not.toHaveBeenCalled(); - expect(securityStart.authc.getCurrentUser).not.toHaveBeenCalled(); - }); - - it('registers a custom nav link for cloud users', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - - securityStart.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser({ - elastic_cloud_user: true, - }) - ); - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(coreStart.chrome.setCustomNavLink).toHaveBeenCalledTimes(1); - expect(coreStart.chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "euiIconType": "logoCloud", - "href": "https://cloud.elastic.co/abc123", - "title": "Manage this deployment", - }, - ] - `); - }); - - it('registers a custom nav link when there is an error retrieving the current user', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - securityStart.authc.getCurrentUser.mockRejectedValue(new Error('something happened')); - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(coreStart.chrome.setCustomNavLink).toHaveBeenCalledTimes(1); - expect(coreStart.chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "euiIconType": "logoCloud", - "href": "https://cloud.elastic.co/abc123", - "title": "Manage this deployment", - }, - ] - `); - }); - - it('does not register a custom nav link for non-cloud users', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - securityStart.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser({ - elastic_cloud_user: false, - }) - ); - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(coreStart.chrome.setCustomNavLink).not.toHaveBeenCalled(); - }); - - it('registers user profile links for cloud users', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - securityStart.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser({ - elastic_cloud_user: true, - }) - ); - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(securityStart.navControlService.addUserMenuLinks).toHaveBeenCalledTimes(1); - expect(securityStart.navControlService.addUserMenuLinks.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "href": "https://cloud.elastic.co/profile/alice", - "iconType": "user", - "label": "Edit profile", - "order": 100, - "setAsProfile": true, - }, - Object { - "href": "https://cloud.elastic.co/org/myOrg", - "iconType": "gear", - "label": "Account & Billing", - "order": 200, - }, - ], - ] - `); - }); - - it('registers profile links when there is an error retrieving the current user', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - securityStart.authc.getCurrentUser.mockRejectedValue(new Error('something happened')); - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(securityStart.navControlService.addUserMenuLinks).toHaveBeenCalledTimes(1); - expect(securityStart.navControlService.addUserMenuLinks.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "href": "https://cloud.elastic.co/profile/alice", - "iconType": "user", - "label": "Edit profile", - "order": 100, - "setAsProfile": true, - }, - Object { - "href": "https://cloud.elastic.co/org/myOrg", - "iconType": "gear", - "label": "Account & Billing", - "order": 200, - }, - ], - ] - `); - }); - - it('does not register profile links for non-cloud users', async () => { - const { plugin } = startPlugin(); - - const coreStart = coreMock.createStart(); - const securityStart = securityMock.createStart(); - securityStart.authc.getCurrentUser.mockResolvedValue( - securityMock.createMockAuthenticatedUser({ - elastic_cloud_user: false, - }) - ); - plugin.start(coreStart, { security: securityStart }); - - await nextTick(); - - expect(securityStart.navControlService.addUserMenuLinks).not.toHaveBeenCalled(); - }); }); }); diff --git a/x-pack/plugins/cloud/public/plugin.tsx b/x-pack/plugins/cloud/public/plugin.tsx index c27668feb09bd..f50f41f3c79cd 100644 --- a/x-pack/plugins/cloud/public/plugin.tsx +++ b/x-pack/plugins/cloud/public/plugin.tsx @@ -6,34 +6,12 @@ */ import React, { FC } from 'react'; -import type { - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, - HttpStart, - IBasePath, - AnalyticsServiceSetup, -} from '@kbn/core/public'; -import { i18n } from '@kbn/i18n'; -import useObservable from 'react-use/lib/useObservable'; -import { BehaviorSubject, catchError, from, map, of } from 'rxjs'; +import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; -import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; -import { HomePublicPluginSetup } from '@kbn/home-plugin/public'; -import { Sha256 } from '@kbn/crypto-browser'; -import type { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; import { registerCloudDeploymentIdAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; -import { - ELASTIC_SUPPORT_LINK, - CLOUD_SNAPSHOTS_PATH, - GET_CHAT_USER_DATA_ROUTE_PATH, -} from '../common/constants'; -import type { GetChatUserDataResponseBody } from '../common/types'; -import { createUserMenuLinks } from './user_menu_links'; +import { ELASTIC_SUPPORT_LINK, CLOUD_SNAPSHOTS_PATH } from '../common/constants'; import { getFullCloudUrl } from './utils'; -import { ChatConfig, ServicesProvider } from './services'; export interface CloudConfigType { id?: string; @@ -47,23 +25,6 @@ export interface CloudConfigType { org_id?: string; eventTypesAllowlist?: string[]; }; - /** Configuration to enable live chat in Cloud-enabled instances of Kibana. */ - chat: { - /** Determines if chat is enabled. */ - enabled: boolean; - /** The URL to the remotely-hosted chat application. */ - chatURL: string; - }; -} - -interface CloudSetupDependencies { - home?: HomePublicPluginSetup; - security?: Pick<SecurityPluginSetup, 'authc'>; - cloudExperiments?: CloudExperimentsPluginSetup; -} - -interface CloudStartDependencies { - security?: SecurityPluginStart; } export interface CloudStart { @@ -71,6 +32,26 @@ export interface CloudStart { * A React component that provides a pre-wired `React.Context` which connects components to Cloud services. */ CloudContextProvider: FC<{}>; + /** + * `true` when Kibana is running on Elastic Cloud. + */ + isCloudEnabled: boolean; + /** + * Cloud ID. Undefined if not running on Cloud. + */ + cloudId?: string; + /** + * The full URL to the deployment management page on Elastic Cloud. Undefined if not running on Cloud. + */ + deploymentUrl?: string; + /** + * The full URL to the user profile page on Elastic Cloud. Undefined if not running on Cloud. + */ + profileUrl?: string; + /** + * The full URL to the organization management page on Elastic Cloud. Undefined if not running on Cloud. + */ + organizationUrl?: string; } export interface CloudSetup { @@ -82,268 +63,93 @@ export interface CloudSetup { organizationUrl?: string; snapshotsUrl?: string; isCloudEnabled: boolean; + registerCloudService: (contextProvider: FC) => void; } -interface SetupFullStoryDeps { - analytics: AnalyticsServiceSetup; - basePath: IBasePath; -} - -interface SetupChatDeps extends Pick<CloudSetupDependencies, 'security'> { - http: CoreSetup['http']; +interface CloudUrls { + deploymentUrl?: string; + profileUrl?: string; + organizationUrl?: string; + snapshotsUrl?: string; } export class CloudPlugin implements Plugin<CloudSetup> { private readonly config: CloudConfigType; private readonly isCloudEnabled: boolean; - private chatConfig$ = new BehaviorSubject<ChatConfig>({ enabled: false }); + private readonly contextProviders: FC[] = []; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get<CloudConfigType>(); this.isCloudEnabled = getIsCloudEnabled(this.config.id); } - public setup(core: CoreSetup, { cloudExperiments, home, security }: CloudSetupDependencies) { - this.setupTelemetryContext(core.analytics, security, this.config.id); - - this.setupFullStory({ analytics: core.analytics, basePath: core.http.basePath }).catch((e) => - // eslint-disable-next-line no-console - console.debug(`Error setting up FullStory: ${e.toString()}`) - ); + public setup(core: CoreSetup): CloudSetup { + registerCloudDeploymentIdAnalyticsContext(core.analytics, this.config.id); - const { - id, - cname, - profile_url: profileUrl, - organization_url: organizationUrl, - deployment_url: deploymentUrl, - base_url: baseUrl, - } = this.config; - - if (this.isCloudEnabled && id) { - // We use the Hashed Cloud Deployment ID as the userId in the Cloud Experiments - cloudExperiments?.identifyUser(sha256(id), { - kibanaVersion: this.initializerContext.env.packageInfo.version, - }); - } - - this.setupChat({ http: core.http, security }).catch((e) => - // eslint-disable-next-line no-console - console.debug(`Error setting up Chat: ${e.toString()}`) - ); - - if (home) { - home.environment.update({ cloud: this.isCloudEnabled }); - if (this.isCloudEnabled) { - home.tutorials.setVariable('cloud', { id, baseUrl, profileUrl, deploymentUrl }); - } - } - - const fullCloudDeploymentUrl = getFullCloudUrl(baseUrl, deploymentUrl); - const fullCloudProfileUrl = getFullCloudUrl(baseUrl, profileUrl); - const fullCloudOrganizationUrl = getFullCloudUrl(baseUrl, organizationUrl); - const fullCloudSnapshotsUrl = `${fullCloudDeploymentUrl}/${CLOUD_SNAPSHOTS_PATH}`; + const { id, cname, base_url: baseUrl } = this.config; return { cloudId: id, cname, baseUrl, - deploymentUrl: fullCloudDeploymentUrl, - profileUrl: fullCloudProfileUrl, - organizationUrl: fullCloudOrganizationUrl, - snapshotsUrl: fullCloudSnapshotsUrl, + ...this.getCloudUrls(), isCloudEnabled: this.isCloudEnabled, + registerCloudService: (contextProvider) => { + this.contextProviders.push(contextProvider); + }, }; } - public start(coreStart: CoreStart, { security }: CloudStartDependencies): CloudStart { - const { deployment_url: deploymentUrl, base_url: baseUrl } = this.config; + public start(coreStart: CoreStart): CloudStart { coreStart.chrome.setHelpSupportUrl(ELASTIC_SUPPORT_LINK); - const setLinks = (authorized: boolean) => { - if (!authorized) return; - - if (baseUrl && deploymentUrl) { - coreStart.chrome.setCustomNavLink({ - title: i18n.translate('xpack.cloud.deploymentLinkLabel', { - defaultMessage: 'Manage this deployment', - }), - euiIconType: 'logoCloud', - href: getFullCloudUrl(baseUrl, deploymentUrl), - }); - } - - if (security && this.isCloudEnabled) { - const userMenuLinks = createUserMenuLinks(this.config); - security.navControlService.addUserMenuLinks(userMenuLinks); - } - }; - - this.checkIfAuthorizedForLinks({ http: coreStart.http, security }) - .then(setLinks) - // In the event of an unexpected error, fail *open*. - // Cloud admin console will always perform the actual authorization checks. - .catch(() => setLinks(true)); - - // There's a risk that the request for chat config will take too much time to complete, and the provider - // will maintain a stale value. To avoid this, we'll use an Observable. + // Nest all the registered context providers under the Cloud Services Provider. + // This way, plugins only need to require Cloud's context provider to have all the enriched Cloud services. const CloudContextProvider: FC = ({ children }) => { - const chatConfig = useObservable(this.chatConfig$, { enabled: false }); - return <ServicesProvider chat={chatConfig}>{children}</ServicesProvider>; + return ( + <> + {this.contextProviders.reduce( + (acc, ContextProvider) => ( + <ContextProvider> {acc} </ContextProvider> + ), + children + )} + </> + ); }; + const { deploymentUrl, profileUrl, organizationUrl } = this.getCloudUrls(); + return { CloudContextProvider, + isCloudEnabled: this.isCloudEnabled, + cloudId: this.config.id, + deploymentUrl, + profileUrl, + organizationUrl, }; } public stop() {} - /** - * Determines if the current user should see links back to Cloud. - * This isn't a true authorization check, but rather a heuristic to - * see if the current user is *likely* a cloud deployment administrator. - * - * At this point, we do not have enough information to reliably make this determination, - * but we do know that all cloud deployment admins are superusers by default. - */ - private async checkIfAuthorizedForLinks({ - http, - security, - }: { - http: HttpStart; - security?: SecurityPluginStart; - }) { - if (http.anonymousPaths.isAnonymous(window.location.pathname)) { - return false; - } - // Security plugin is disabled - if (!security) return true; - - // Otherwise check if user is a cloud user. - // If user is not defined due to an unexpected error, then fail *open*. - // Cloud admin console will always perform the actual authorization checks. - const user = await security.authc.getCurrentUser().catch(() => null); - return user?.elastic_cloud_user ?? true; - } - - /** - * If the right config is provided, register the FullStory shipper to the analytics client. - * @param analytics Core's Analytics service's setup contract. - * @param basePath Core's http.basePath helper. - * @private - */ - private async setupFullStory({ analytics, basePath }: SetupFullStoryDeps) { - const { enabled, org_id: fullStoryOrgId, eventTypesAllowlist } = this.config.full_story; - if (!enabled || !fullStoryOrgId) { - return; // do not load any FullStory code in the browser if not enabled - } - - // Keep this import async so that we do not load any FullStory code into the browser when it is disabled. - const { FullStoryShipper } = await import('@kbn/analytics-shippers-fullstory'); - analytics.registerShipper(FullStoryShipper, { - eventTypesAllowlist, - fullStoryOrgId, - // Load an Elastic-internally audited script. Ideally, it should be hosted on a CDN. - scriptUrl: basePath.prepend( - `/internal/cloud/${this.initializerContext.env.packageInfo.buildNum}/fullstory.js` - ), - namespace: 'FSKibana', - }); - } - - /** - * Set up the Analytics context providers. - * @param analytics Core's Analytics service. The Setup contract. - * @param security The security plugin. - * @param cloudId The Cloud Org ID. - * @private - */ - private setupTelemetryContext( - analytics: AnalyticsServiceSetup, - security?: Pick<SecurityPluginSetup, 'authc'>, - cloudId?: string - ) { - registerCloudDeploymentIdAnalyticsContext(analytics, cloudId); - - if (security) { - analytics.registerContextProvider({ - name: 'cloud_user_id', - context$: from(security.authc.getCurrentUser()).pipe( - map((user) => { - if (user.elastic_cloud_user) { - // If the user is managed by ESS, use the plain username as the user ID: - // The username is expected to be unique for these users, - // and it matches how users are identified in the Cloud UI, so it allows us to correlate them. - return { userId: user.username, isElasticCloudUser: true }; - } - - return { - // For the rest of the authentication providers, we want to add the cloud deployment ID to make it unique. - // Especially in the case of Elasticsearch-backed authentication, where users are commonly repeated - // across multiple deployments (i.e.: `elastic` superuser). - userId: cloudId ? `${cloudId}:${user.username}` : user.username, - isElasticCloudUser: false, - }; - }), - // The hashing here is to keep it at clear as possible in our source code that we do not send literal user IDs - map(({ userId, isElasticCloudUser }) => ({ userId: sha256(userId), isElasticCloudUser })), - catchError(() => of({ userId: undefined, isElasticCloudUser: false })) - ), - schema: { - userId: { - type: 'keyword', - _meta: { description: 'The user id scoped as seen by Cloud (hashed)' }, - }, - isElasticCloudUser: { - type: 'boolean', - _meta: { - description: '`true` if the user is managed by ESS.', - }, - }, - }, - }); - } - } - - private async setupChat({ http, security }: SetupChatDeps) { - if (!this.isCloudEnabled) { - return; - } - - const { enabled, chatURL } = this.config.chat; - - if (!security || !enabled || !chatURL) { - return; - } - - try { - const { - email, - id, - token: jwt, - } = await http.get<GetChatUserDataResponseBody>(GET_CHAT_USER_DATA_ROUTE_PATH); + private getCloudUrls(): CloudUrls { + const { + profile_url: profileUrl, + organization_url: organizationUrl, + deployment_url: deploymentUrl, + base_url: baseUrl, + } = this.config; - if (!email || !id || !jwt) { - return; - } + const fullCloudDeploymentUrl = getFullCloudUrl(baseUrl, deploymentUrl); + const fullCloudProfileUrl = getFullCloudUrl(baseUrl, profileUrl); + const fullCloudOrganizationUrl = getFullCloudUrl(baseUrl, organizationUrl); + const fullCloudSnapshotsUrl = `${fullCloudDeploymentUrl}/${CLOUD_SNAPSHOTS_PATH}`; - this.chatConfig$.next({ - enabled, - chatURL, - user: { - email, - id, - jwt, - }, - }); - } catch (e) { - // eslint-disable-next-line no-console - console.debug(`[cloud.chat] Could not retrieve chat config: ${e.res.status} ${e.message}`, e); - } + return { + deploymentUrl: fullCloudDeploymentUrl, + profileUrl: fullCloudProfileUrl, + organizationUrl: fullCloudOrganizationUrl, + snapshotsUrl: fullCloudSnapshotsUrl, + }; } } - -function sha256(str: string) { - return new Sha256().update(str, 'utf8').digest('hex'); -} diff --git a/x-pack/plugins/cloud/server/config.ts b/x-pack/plugins/cloud/server/config.ts index aebbc65e50f18..512542c756798 100644 --- a/x-pack/plugins/cloud/server/config.ts +++ b/x-pack/plugins/cloud/server/config.ts @@ -18,32 +18,11 @@ const apmConfigSchema = schema.object({ ), }); -const fullStoryConfigSchema = schema.object({ - enabled: schema.boolean({ defaultValue: false }), - org_id: schema.conditional( - schema.siblingRef('enabled'), - true, - schema.string({ minLength: 1 }), - schema.maybe(schema.string()) - ), - eventTypesAllowlist: schema.arrayOf(schema.string(), { - defaultValue: ['Loaded Kibana'], - }), -}); - -const chatConfigSchema = schema.object({ - enabled: schema.boolean({ defaultValue: false }), - chatURL: schema.maybe(schema.string()), -}); - const configSchema = schema.object({ apm: schema.maybe(apmConfigSchema), base_url: schema.maybe(schema.string()), - chat: chatConfigSchema, - chatIdentitySecret: schema.maybe(schema.string()), cname: schema.maybe(schema.string()), deployment_url: schema.maybe(schema.string()), - full_story: fullStoryConfigSchema, id: schema.maybe(schema.string()), organization_url: schema.maybe(schema.string()), profile_url: schema.maybe(schema.string()), @@ -54,10 +33,8 @@ export type CloudConfigType = TypeOf<typeof configSchema>; export const config: PluginConfigDescriptor<CloudConfigType> = { exposeToBrowser: { base_url: true, - chat: true, cname: true, deployment_url: true, - full_story: true, id: true, organization_url: true, profile_url: true, diff --git a/x-pack/plugins/cloud/server/mocks.ts b/x-pack/plugins/cloud/server/mocks.ts new file mode 100644 index 0000000000000..557e64edf6cc1 --- /dev/null +++ b/x-pack/plugins/cloud/server/mocks.ts @@ -0,0 +1,25 @@ +/* + * 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 { CloudSetup } from '.'; + +function createSetupMock(): jest.Mocked<CloudSetup> { + return { + cloudId: 'mock-cloud-id', + instanceSizeMb: 1234, + deploymentId: 'deployment-id', + isCloudEnabled: true, + apm: { + url: undefined, + secretToken: undefined, + }, + }; +} + +export const cloudMock = { + createSetup: createSetupMock, +}; diff --git a/x-pack/plugins/cloud/server/plugin.test.ts b/x-pack/plugins/cloud/server/plugin.test.ts index 05109a4c54816..55be923e98cf8 100644 --- a/x-pack/plugins/cloud/server/plugin.test.ts +++ b/x-pack/plugins/cloud/server/plugin.test.ts @@ -7,111 +7,54 @@ import { coreMock } from '@kbn/core/server/mocks'; import { CloudPlugin } from './plugin'; -import { config } from './config'; -import { securityMock } from '@kbn/security-plugin/server/mocks'; -import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; -import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; -import { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; + +const baseConfig = { + base_url: 'https://cloud.elastic.co', + deployment_url: '/abc123', + profile_url: '/user/settings/', + organization_url: '/account/', +}; describe('Cloud Plugin', () => { describe('#setup', () => { - describe('setupSecurity', () => { - it('properly handles missing optional Security dependency if Cloud ID is NOT set.', async () => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext(config.schema.validate({})) - ); + describe('interface', () => { + const setupPlugin = () => { + const initContext = coreMock.createPluginInitializerContext({ + ...baseConfig, + id: 'cloudId', + cname: 'cloud.elastic.co', + }); + const plugin = new CloudPlugin(initContext); - expect(() => - plugin.setup(coreMock.createSetup(), { - usageCollection: usageCollectionPluginMock.createSetupContract(), - }) - ).not.toThrow(); - }); + const coreSetup = coreMock.createSetup(); + const setup = plugin.setup(coreSetup, {}); - it('properly handles missing optional Security dependency if Cloud ID is set.', async () => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext(config.schema.validate({ id: 'my-cloud' })) - ); + return { setup }; + }; - expect(() => - plugin.setup(coreMock.createSetup(), { - usageCollection: usageCollectionPluginMock.createSetupContract(), - }) - ).not.toThrow(); + it('exposes isCloudEnabled', () => { + const { setup } = setupPlugin(); + expect(setup.isCloudEnabled).toBe(true); }); - it('does not notify Security plugin about Cloud environment if Cloud ID is NOT set.', async () => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext(config.schema.validate({})) - ); - - const securityDependencyMock = securityMock.createSetup(); - plugin.setup(coreMock.createSetup(), { - security: securityDependencyMock, - usageCollection: usageCollectionPluginMock.createSetupContract(), - }); - - expect(securityDependencyMock.setIsElasticCloudDeployment).not.toHaveBeenCalled(); + it('exposes cloudId', () => { + const { setup } = setupPlugin(); + expect(setup.cloudId).toBe('cloudId'); }); - it('properly notifies Security plugin about Cloud environment if Cloud ID is set.', async () => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext(config.schema.validate({ id: 'my-cloud' })) - ); - - const securityDependencyMock = securityMock.createSetup(); - plugin.setup(coreMock.createSetup(), { - security: securityDependencyMock, - usageCollection: usageCollectionPluginMock.createSetupContract(), - }); - - expect(securityDependencyMock.setIsElasticCloudDeployment).toHaveBeenCalledTimes(1); + it('exposes instanceSizeMb', () => { + const { setup } = setupPlugin(); + expect(setup.instanceSizeMb).toBeUndefined(); }); - }); - describe('Set up cloudExperiments', () => { - describe('when cloud ID is not provided in the config', () => { - let cloudExperiments: jest.Mocked<CloudExperimentsPluginSetup>; - beforeEach(() => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext(config.schema.validate({})) - ); - cloudExperiments = cloudExperimentsMock.createSetupMock(); - plugin.setup(coreMock.createSetup(), { cloudExperiments }); - }); - - test('does not call cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser).not.toHaveBeenCalled(); - }); + it('exposes deploymentId', () => { + const { setup } = setupPlugin(); + expect(setup.deploymentId).toBe('abc123'); }); - describe('when cloud ID is provided in the config', () => { - let cloudExperiments: jest.Mocked<CloudExperimentsPluginSetup>; - beforeEach(() => { - const plugin = new CloudPlugin( - coreMock.createPluginInitializerContext(config.schema.validate({ id: 'cloud test' })) - ); - cloudExperiments = cloudExperimentsMock.createSetupMock(); - plugin.setup(coreMock.createSetup(), { cloudExperiments }); - }); - - test('calls cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser).toHaveBeenCalledTimes(1); - }); - - test('the cloud ID is hashed when calling cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser.mock.calls[0][0]).toEqual( - '1acb4a1cc1c3d672a8d826055d897c2623ceb1d4fb07e46d97986751a36b06cf' - ); - }); - - test('specifies the Kibana version when calling cloudExperiments.identifyUser', async () => { - expect(cloudExperiments.identifyUser.mock.calls[0][1]).toEqual( - expect.objectContaining({ - kibanaVersion: 'version', - }) - ); - }); + it('exposes apm', () => { + const { setup } = setupPlugin(); + expect(setup.apm).toStrictEqual({ url: undefined, secretToken: undefined }); }); }); }); diff --git a/x-pack/plugins/cloud/server/plugin.ts b/x-pack/plugins/cloud/server/plugin.ts index d38a57a4d3bab..9cf1a308800a0 100644 --- a/x-pack/plugins/cloud/server/plugin.ts +++ b/x-pack/plugins/cloud/server/plugin.ts @@ -5,24 +5,17 @@ * 2.0. */ -import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; -import { CoreSetup, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server'; -import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; -import type { CloudExperimentsPluginSetup } from '@kbn/cloud-experiments-plugin/common'; -import { createSHA256Hash } from '@kbn/crypto'; +import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/server'; +import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import { registerCloudDeploymentIdAnalyticsContext } from '../common/register_cloud_deployment_id_analytics_context'; -import { CloudConfigType } from './config'; +import type { CloudConfigType } from './config'; import { registerCloudUsageCollector } from './collectors'; import { getIsCloudEnabled } from '../common/is_cloud_enabled'; import { parseDeploymentIdFromDeploymentUrl } from './utils'; -import { registerFullstoryRoute } from './routes/fullstory'; -import { registerChatRoute } from './routes/chat'; import { readInstanceSizeMb } from './env'; interface PluginsSetup { usageCollection?: UsageCollectionSetup; - security?: SecurityPluginSetup; - cloudExperiments?: CloudExperimentsPluginSetup; } export interface CloudSetup { @@ -37,52 +30,17 @@ export interface CloudSetup { } export class CloudPlugin implements Plugin<CloudSetup> { - private readonly logger: Logger; private readonly config: CloudConfigType; - private readonly isDev: boolean; constructor(private readonly context: PluginInitializerContext) { - this.logger = this.context.logger.get(); this.config = this.context.config.get<CloudConfigType>(); - this.isDev = this.context.env.mode.dev; } - public setup( - core: CoreSetup, - { cloudExperiments, usageCollection, security }: PluginsSetup - ): CloudSetup { - this.logger.debug('Setting up Cloud plugin'); + public setup(core: CoreSetup, { usageCollection }: PluginsSetup): CloudSetup { const isCloudEnabled = getIsCloudEnabled(this.config.id); registerCloudDeploymentIdAnalyticsContext(core.analytics, this.config.id); registerCloudUsageCollector(usageCollection, { isCloudEnabled }); - if (isCloudEnabled) { - security?.setIsElasticCloudDeployment(); - } - - if (isCloudEnabled && this.config.id) { - // We use the Cloud ID as the userId in the Cloud Experiments - cloudExperiments?.identifyUser(createSHA256Hash(this.config.id), { - kibanaVersion: this.context.env.packageInfo.version, - }); - } - - if (this.config.full_story.enabled) { - registerFullstoryRoute({ - httpResources: core.http.resources, - packageInfo: this.context.env.packageInfo, - }); - } - - if (this.config.chat.enabled && this.config.chatIdentitySecret) { - registerChatRoute({ - router: core.http.createRouter(), - chatIdentitySecret: this.config.chatIdentitySecret, - security, - isDev: this.isDev, - }); - } - return { cloudId: this.config.id, instanceSizeMb: readInstanceSizeMb(), diff --git a/x-pack/plugins/cloud/tsconfig.json b/x-pack/plugins/cloud/tsconfig.json index d8c8a5c8eca44..ca9ba32ed10b0 100644 --- a/x-pack/plugins/cloud/tsconfig.json +++ b/x-pack/plugins/cloud/tsconfig.json @@ -16,8 +16,5 @@ "references": [ { "path": "../../../src/core/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, - { "path": "../../../src/plugins/home/tsconfig.json" }, - { "path": "../cloud_integrations/cloud_experiments/tsconfig.json" }, - { "path": "../security/tsconfig.json" }, ] } diff --git a/x-pack/plugins/cloud/.storybook/decorator.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/decorator.tsx similarity index 89% rename from x-pack/plugins/cloud/.storybook/decorator.tsx rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/decorator.tsx index 4489b58f75759..3af8d04a598eb 100644 --- a/x-pack/plugins/cloud/.storybook/decorator.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/decorator.tsx @@ -7,12 +7,11 @@ import React from 'react'; import { DecoratorFn } from '@storybook/react'; -import { ServicesProvider, CloudServices } from '../public/services'; +import { ServicesProvider, CloudChatServices } from '../public/services'; // TODO: move to a storybook implementation of the service using parameters. -const services: CloudServices = { +const services: CloudChatServices = { chat: { - enabled: true, chatURL: 'https://elasticcloud-production-chat-us-east-1.s3.amazonaws.com/drift-iframe.html', user: { id: 'user-id', diff --git a/x-pack/plugins/cloud/.storybook/index.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/index.ts similarity index 100% rename from x-pack/plugins/cloud/.storybook/index.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/index.ts diff --git a/x-pack/plugins/cloud/.storybook/main.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/main.ts similarity index 100% rename from x-pack/plugins/cloud/.storybook/main.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/main.ts diff --git a/x-pack/plugins/cloud/.storybook/manager.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/manager.ts similarity index 100% rename from x-pack/plugins/cloud/.storybook/manager.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/manager.ts diff --git a/x-pack/plugins/cloud/.storybook/preview.ts b/x-pack/plugins/cloud_integrations/cloud_chat/.storybook/preview.ts similarity index 100% rename from x-pack/plugins/cloud/.storybook/preview.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/.storybook/preview.ts diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/README.md b/x-pack/plugins/cloud_integrations/cloud_chat/README.md new file mode 100755 index 0000000000000..cee3d9f5a6671 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/README.md @@ -0,0 +1,3 @@ +# Cloud Chat + +Integrates with DriftChat in order to provide live support to our Elastic Cloud users. This plugin should only run on Elastic Cloud. diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts b/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts new file mode 100755 index 0000000000000..d7bd133e5b4f9 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/common/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const GET_CHAT_USER_DATA_ROUTE_PATH = '/internal/cloud/chat_user'; diff --git a/x-pack/plugins/cloud/common/types.ts b/x-pack/plugins/cloud_integrations/cloud_chat/common/types.ts similarity index 100% rename from x-pack/plugins/cloud/common/types.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/common/types.ts diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js b/x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js new file mode 100644 index 0000000000000..44f6f241d44d0 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../', + roots: ['<rootDir>/x-pack/plugins/cloud_integrations/cloud_chat'], + coverageDirectory: + '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/cloud_integrations/cloud_chat', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/cloud_integrations/cloud_chat/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/kibana.json b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.json new file mode 100755 index 0000000000000..76f7e34e71e56 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "cloudChat", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Kibana Core", + "githubTeam": "kibana-core" + }, + "description": "Chat available on Elastic Cloud deployments for quicker assistance.", + "server": true, + "ui": true, + "configPath": ["xpack", "cloud_integrations", "chat"], + "requiredPlugins": ["cloud"], + "optionalPlugins": ["security"] +} diff --git a/x-pack/plugins/cloud/public/components/chat/chat.stories.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.stories.tsx similarity index 99% rename from x-pack/plugins/cloud/public/components/chat/chat.stories.tsx rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.stories.tsx index 7e673e341cec7..295750ee43039 100644 --- a/x-pack/plugins/cloud/public/components/chat/chat.stories.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.stories.tsx @@ -68,7 +68,6 @@ export const Component = ({ id, email, chatURL, jwt }: Params) => { return ( <ServicesProvider chat={{ - enabled: true, chatURL, user: { jwt, diff --git a/x-pack/plugins/cloud/public/components/chat/chat.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.tsx similarity index 94% rename from x-pack/plugins/cloud/public/components/chat/chat.tsx rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.tsx index 6791b9bc625e6..4c13217c7859b 100644 --- a/x-pack/plugins/cloud/public/components/chat/chat.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/chat.tsx @@ -57,7 +57,7 @@ export const Chat = ({ onHide = () => {}, onReady, onResize }: Props) => { }} size="xs" > - {i18n.translate('xpack.cloud.chat.hideChatButtonLabel', { + {i18n.translate('xpack.cloudChat.hideChatButtonLabel', { defaultMessage: 'Hide chat', })} </EuiButtonEmpty> @@ -80,7 +80,7 @@ export const Chat = ({ onHide = () => {}, onReady, onResize }: Props) => { {button} <iframe data-test-subj="cloud-chat-frame" - title={i18n.translate('xpack.cloud.chat.chatFrameTitle', { + title={i18n.translate('xpack.cloudChat.chatFrameTitle', { defaultMessage: 'Chat', })} {...config} diff --git a/x-pack/plugins/cloud/public/components/chat/get_chat_context.test.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/get_chat_context.test.ts similarity index 97% rename from x-pack/plugins/cloud/public/components/chat/get_chat_context.test.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/get_chat_context.test.ts index e3e2675d0291c..ad9c85411643d 100644 --- a/x-pack/plugins/cloud/public/components/chat/get_chat_context.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/get_chat_context.test.ts @@ -23,7 +23,7 @@ const REFERRER = 'referrer'; describe('getChatContext', () => { const url = new URL(HREF); - test('retreive the context', () => { + test('retrieve the context', () => { Object.defineProperty(window, 'location', { value: url }); Object.defineProperty(window, 'navigator', { value: { diff --git a/x-pack/plugins/cloud/public/components/chat/get_chat_context.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/get_chat_context.ts similarity index 100% rename from x-pack/plugins/cloud/public/components/chat/get_chat_context.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/get_chat_context.ts diff --git a/x-pack/plugins/cloud/public/components/chat/index.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/index.ts similarity index 100% rename from x-pack/plugins/cloud/public/components/chat/index.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/index.ts diff --git a/x-pack/plugins/cloud/public/components/chat/use_chat_config.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/use_chat_config.ts similarity index 95% rename from x-pack/plugins/cloud/public/components/chat/use_chat_config.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/use_chat_config.ts index 4e2b7a8b0f4c2..20a2755578bcf 100644 --- a/x-pack/plugins/cloud/public/components/chat/use_chat_config.ts +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/chat/use_chat_config.ts @@ -45,11 +45,7 @@ export const useChatConfig = ({ const handleMessage = (event: MessageEvent): void => { const { current: chatIframe } = ref; - if ( - !chat.enabled || - !chatIframe?.contentWindow || - event.source !== chatIframe?.contentWindow - ) { + if (!chat || !chatIframe?.contentWindow || event.source !== chatIframe?.contentWindow) { return; } @@ -108,7 +104,7 @@ export const useChatConfig = ({ return () => window.removeEventListener('message', handleMessage); }, [chat, style, onReady, onResize, isReady, isResized]); - if (chat.enabled) { + if (chat) { return { enabled: true, src: chat.chatURL, ref, style, isReady, isResized }; } diff --git a/x-pack/plugins/cloud/public/components/index.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/components/index.tsx similarity index 100% rename from x-pack/plugins/cloud/public/components/index.tsx rename to x-pack/plugins/cloud_integrations/cloud_chat/public/components/index.tsx diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/public/index.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/index.ts new file mode 100755 index 0000000000000..4407f19670560 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext } from '@kbn/core/public'; +import { CloudChatPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudChatPlugin(initializerContext); +} + +export { Chat } from './components'; diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts new file mode 100644 index 0000000000000..9e84c86791311 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.test.ts @@ -0,0 +1,103 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; +import { securityMock } from '@kbn/security-plugin/public/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; +import type { CloudChatConfigType } from '../server/config'; +import { CloudChatPlugin } from './plugin'; + +describe('Cloud Chat Plugin', () => { + describe('#setup', () => { + describe('setupChat', () => { + let consoleMock: jest.SpyInstance<void, [message?: any, ...optionalParams: any[]]>; + + beforeEach(() => { + consoleMock = jest.spyOn(console, 'debug').mockImplementation(() => {}); + }); + + afterEach(() => { + consoleMock.mockRestore(); + }); + + const setupPlugin = async ({ + config = {}, + securityEnabled = true, + currentUserProps = {}, + isCloudEnabled = true, + failHttp = false, + }: { + config?: Partial<CloudChatConfigType>; + securityEnabled?: boolean; + currentUserProps?: Record<string, any>; + isCloudEnabled?: boolean; + failHttp?: boolean; + }) => { + const initContext = coreMock.createPluginInitializerContext(config); + + const plugin = new CloudChatPlugin(initContext); + + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createStart(); + + if (failHttp) { + coreSetup.http.get.mockImplementation(() => { + throw new Error('HTTP request failed'); + }); + } + + coreSetup.getStartServices.mockResolvedValue([coreStart, {}, undefined]); + + const securitySetup = securityMock.createSetup(); + securitySetup.authc.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser(currentUserProps) + ); + + const cloud = cloudMock.createSetup(); + + plugin.setup(coreSetup, { + cloud: { ...cloud, isCloudEnabled }, + ...(securityEnabled ? { security: securitySetup } : {}), + }); + + return { initContext, plugin, coreSetup }; + }; + + it('chatConfig is not retrieved if cloud is not enabled', async () => { + const { coreSetup } = await setupPlugin({ isCloudEnabled: false }); + expect(coreSetup.http.get).not.toHaveBeenCalled(); + }); + + it('chatConfig is not retrieved if security is not enabled', async () => { + const { coreSetup } = await setupPlugin({ securityEnabled: false }); + expect(coreSetup.http.get).not.toHaveBeenCalled(); + }); + + it('chatConfig is not retrieved if chat is enabled but url is not provided', async () => { + // @ts-expect-error 2741 + const { coreSetup } = await setupPlugin({ config: { chat: { enabled: true } } }); + expect(coreSetup.http.get).not.toHaveBeenCalled(); + }); + + it('chatConfig is not retrieved if internal API fails', async () => { + const { coreSetup } = await setupPlugin({ + config: { chatURL: 'http://chat.elastic.co' }, + failHttp: true, + }); + expect(coreSetup.http.get).toHaveBeenCalled(); + expect(consoleMock).toHaveBeenCalled(); + }); + + it('chatConfig is retrieved if chat is enabled and url is provided', async () => { + const { coreSetup } = await setupPlugin({ + config: { chatURL: 'http://chat.elastic.co' }, + }); + expect(coreSetup.http.get).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx new file mode 100755 index 0000000000000..24f4e0d8199f1 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/plugin.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { type FC } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import type { CoreSetup, Plugin, PluginInitializerContext } from '@kbn/core/public'; +import type { HttpSetup } from '@kbn/core-http-browser'; +import type { SecurityPluginSetup } from '@kbn/security-plugin/public'; +import type { CloudSetup } from '@kbn/cloud-plugin/public'; +import { ReplaySubject } from 'rxjs'; +import type { GetChatUserDataResponseBody } from '../common/types'; +import { GET_CHAT_USER_DATA_ROUTE_PATH } from '../common/constants'; +import { ChatConfig, ServicesProvider } from './services'; + +interface CloudChatSetupDeps { + cloud: CloudSetup; + security?: SecurityPluginSetup; +} + +interface SetupChatDeps extends CloudChatSetupDeps { + http: HttpSetup; +} + +interface CloudChatConfig { + chatURL?: string; +} + +export class CloudChatPlugin implements Plugin { + private readonly config: CloudChatConfig; + private chatConfig$ = new ReplaySubject<ChatConfig>(1); + + constructor(initializerContext: PluginInitializerContext<CloudChatConfig>) { + this.config = initializerContext.config.get(); + } + + public setup(core: CoreSetup, { cloud, security }: CloudChatSetupDeps) { + this.setupChat({ http: core.http, cloud, security }).catch((e) => + // eslint-disable-next-line no-console + console.debug(`Error setting up Chat: ${e.toString()}`) + ); + + const CloudChatContextProvider: FC = ({ children }) => { + // There's a risk that the request for chat config will take too much time to complete, and the provider + // will maintain a stale value. To avoid this, we'll use an Observable. + const chatConfig = useObservable(this.chatConfig$, undefined); + return <ServicesProvider chat={chatConfig}>{children}</ServicesProvider>; + }; + cloud.registerCloudService(CloudChatContextProvider); + } + + public start() {} + + public stop() {} + + private async setupChat({ cloud, http, security }: SetupChatDeps) { + if (!cloud.isCloudEnabled || !security || !this.config.chatURL) { + return; + } + + try { + const { + email, + id, + token: jwt, + } = await http.get<GetChatUserDataResponseBody>(GET_CHAT_USER_DATA_ROUTE_PATH); + + if (!email || !id || !jwt) { + return; + } + + this.chatConfig$.next({ + chatURL: this.config.chatURL, + user: { + email, + id, + jwt, + }, + }); + } catch (e) { + // eslint-disable-next-line no-console + console.debug(`[cloud.chat] Could not retrieve chat config: ${e.res.status} ${e.message}`, e); + } + } +} diff --git a/x-pack/plugins/cloud/public/services/index.tsx b/x-pack/plugins/cloud_integrations/cloud_chat/public/services/index.tsx similarity index 58% rename from x-pack/plugins/cloud/public/services/index.tsx rename to x-pack/plugins/cloud_integrations/cloud_chat/public/services/index.tsx index 96b80ae308883..a1459a57b8a29 100644 --- a/x-pack/plugins/cloud/public/services/index.tsx +++ b/x-pack/plugins/cloud_integrations/cloud_chat/public/services/index.tsx @@ -7,12 +7,7 @@ import React, { FC, createContext, useContext } from 'react'; -interface WithoutChat { - enabled: false; -} - -interface WithChat { - enabled: true; +export interface ChatConfig { chatURL: string; user: { jwt: string; @@ -21,26 +16,24 @@ interface WithChat { }; } -export type ChatConfig = WithChat | WithoutChat; - -export interface CloudServices { - chat: ChatConfig; +export interface CloudChatServices { + chat?: ChatConfig; } -const ServicesContext = createContext<CloudServices>({ chat: { enabled: false } }); +const ServicesContext = createContext<CloudChatServices>({}); -export const ServicesProvider: FC<CloudServices> = ({ children, ...services }) => ( +export const ServicesProvider: FC<CloudChatServices> = ({ children, ...services }) => ( <ServicesContext.Provider value={services}>{children}</ServicesContext.Provider> ); /** - * React hook for accessing the pre-wired `CloudServices`. + * React hook for accessing the pre-wired `CloudChatServices`. */ export function useServices() { return useContext(ServicesContext); } -export function useChat(): ChatConfig { +export function useChat(): ChatConfig | undefined { const { chat } = useServices(); return chat; } diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/config.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/config.ts new file mode 100644 index 0000000000000..72651e1555784 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/config.ts @@ -0,0 +1,74 @@ +/* + * 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 { get, has } from 'lodash'; +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core/server'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + chatURL: schema.maybe(schema.string()), + chatIdentitySecret: schema.maybe(schema.string()), +}); + +export type CloudChatConfigType = TypeOf<typeof configSchema>; + +export const config: PluginConfigDescriptor<CloudChatConfigType> = { + exposeToBrowser: { + chatURL: true, + }, + schema: configSchema, + deprecations: () => [ + // Silently move the chat configuration from `xpack.cloud` to `xpack.cloud_integrations.chat`. + // No need to emit a deprecation log because it's an internal restructure + (cfg) => { + return { + set: [ + ...copyIfExists({ + cfg, + fromKey: 'xpack.cloud.chat.enabled', + toKey: 'xpack.cloud_integrations.chat.enabled', + }), + ...copyIfExists({ + cfg, + fromKey: 'xpack.cloud.chat.chatURL', + toKey: 'xpack.cloud_integrations.chat.chatURL', + }), + ...copyIfExists({ + cfg, + fromKey: 'xpack.cloud.chatIdentitySecret', + toKey: 'xpack.cloud_integrations.chat.chatIdentitySecret', + }), + ], + unset: [ + { path: 'xpack.cloud.chat.enabled' }, + { path: 'xpack.cloud.chat.chatURL' }, + { path: 'xpack.cloud.chatIdentitySecret' }, + ], + }; + }, + ], +}; + +/** + * Defines the `set` action only if the key exists in the `fromKey` value. + * This is to avoid overwriting actual values with undefined. + * @param cfg The config object + * @param fromKey The key to copy from. + * @param toKey The key where the value should be copied to. + */ +function copyIfExists({ + cfg, + fromKey, + toKey, +}: { + cfg: Readonly<{ [p: string]: unknown }>; + fromKey: string; + toKey: string; +}) { + return has(cfg, fromKey) ? [{ path: toKey, value: get(cfg, fromKey) }] : []; +} diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/index.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/index.ts new file mode 100755 index 0000000000000..71f0c7c23355e --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext } from '@kbn/core/server'; +import { CloudChatPlugin } from './plugin'; + +export { config } from './config'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudChatPlugin(initializerContext); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts new file mode 100755 index 0000000000000..02173d96269b3 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/plugin.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { PluginInitializerContext, CoreSetup, Plugin } from '@kbn/core/server'; + +import { SecurityPluginSetup } from '@kbn/security-plugin/server'; +import { CloudSetup } from '@kbn/cloud-plugin/server'; +import { registerChatRoute } from './routes'; +import { CloudChatConfigType } from './config'; + +interface CloudChatSetupDeps { + cloud: CloudSetup; + security?: SecurityPluginSetup; +} + +export class CloudChatPlugin implements Plugin<void, void, CloudChatSetupDeps> { + private readonly config: CloudChatConfigType; + private readonly isDev: boolean; + + constructor(initializerContext: PluginInitializerContext) { + this.config = initializerContext.config.get(); + this.isDev = initializerContext.env.mode.dev; + } + + public setup(core: CoreSetup, { cloud, security }: CloudChatSetupDeps) { + if (cloud.isCloudEnabled && this.config.chatIdentitySecret) { + registerChatRoute({ + router: core.http.createRouter(), + chatIdentitySecret: this.config.chatIdentitySecret, + security, + isDev: this.isDev, + }); + } + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/plugins/cloud/server/routes/chat.test.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts similarity index 100% rename from x-pack/plugins/cloud/server/routes/chat.test.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.test.ts diff --git a/x-pack/plugins/cloud/server/routes/chat.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts similarity index 100% rename from x-pack/plugins/cloud/server/routes/chat.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/server/routes/chat.ts diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/index.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/index.ts new file mode 100755 index 0000000000000..b70a9d00347de --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/server/routes/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { registerChatRoute } from './chat'; diff --git a/x-pack/plugins/cloud/server/util/generate_jwt.test.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/util/generate_jwt.test.ts similarity index 100% rename from x-pack/plugins/cloud/server/util/generate_jwt.test.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/server/util/generate_jwt.test.ts diff --git a/x-pack/plugins/cloud/server/util/generate_jwt.ts b/x-pack/plugins/cloud_integrations/cloud_chat/server/util/generate_jwt.ts similarity index 100% rename from x-pack/plugins/cloud/server/util/generate_jwt.ts rename to x-pack/plugins/cloud_integrations/cloud_chat/server/util/generate_jwt.ts diff --git a/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json new file mode 100644 index 0000000000000..967962363be2c --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_chat/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + ".storybook/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../../src/core/tsconfig.json" }, + { "path": "../../cloud/tsconfig.json" }, + { "path": "../../security/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/index.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/index.ts index 1078d980d4b82..78874d5e7dda0 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/index.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/index.ts @@ -9,6 +9,5 @@ export type { CloudExperimentsMetric, CloudExperimentsMetricNames, CloudExperimentsPluginStart, - CloudExperimentsPluginSetup, CloudExperimentsFeatureFlagNames, } from './types'; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.ts index ebfa14a074dec..fd18c3ee2420d 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/mocks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { CloudExperimentsPluginSetup, CloudExperimentsPluginStart } from './types'; +import type { CloudExperimentsPluginStart } from './types'; function createStartMock(): jest.Mocked<CloudExperimentsPluginStart> { return { @@ -14,13 +14,6 @@ function createStartMock(): jest.Mocked<CloudExperimentsPluginStart> { }; } -function createSetupMock(): jest.Mocked<CloudExperimentsPluginSetup> { - return { - identifyUser: jest.fn(), - }; -} - export const cloudExperimentsMock = { - createSetupMock, createStartMock, }; diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts index 225f39a11f0c1..4c43def8dab79 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/common/types.ts @@ -7,27 +7,6 @@ import { FEATURE_FLAG_NAMES, METRIC_NAMES } from './constants'; -/** - * The contract of the setup lifecycle method. - * - * @public - */ -export interface CloudExperimentsPluginSetup { - /** - * Identifies the user in the A/B testing service. - * For now, we only rely on the user ID. In the future, we may request further details for more targeted experiments. - * @param userId The unique identifier of the user in the experiment. - * @param userMetadata Additional attributes to the user. Take care to ensure these values do not contain PII. - * - * @deprecated This API will become internal as soon as we reduce the dependency graph of the `cloud` plugin, - * and this plugin depends on it to fetch the data. - */ - identifyUser: ( - userId: string, - userMetadata?: Record<string, string | boolean | number | Array<string | boolean | number>> - ) => void; -} - /** * The names of the feature flags declared in Kibana. * Valid keys are defined in {@link FEATURE_FLAG_NAMES}. When using a new feature flag, add the name to the list. diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json b/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json index 412b16810c8bd..6bbb41f796f94 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/kibana.json @@ -10,6 +10,6 @@ "server": true, "ui": true, "configPath": ["xpack", "cloud_integrations", "experiments"], - "requiredPlugins": [], + "requiredPlugins": ["cloud"], "optionalPlugins": ["usageCollection"] } diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts index 63dd98991d6c6..3c6396d686796 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.test.ts @@ -6,6 +6,7 @@ */ import { coreMock } from '@kbn/core/public/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { ldClientMock } from './plugin.test.mock'; import { CloudExperimentsPlugin } from './plugin'; import { FEATURE_FLAG_NAMES } from '../common/constants'; @@ -61,13 +62,12 @@ describe('Cloud Experiments public plugin', () => { plugin = new CloudExperimentsPlugin(initializerContext); }); - test('returns the contract', () => { - const setupContract = plugin.setup(coreMock.createSetup()); - expect(setupContract).toStrictEqual( - expect.objectContaining({ - identifyUser: expect.any(Function), + test('returns no contract', () => { + expect( + plugin.setup(coreMock.createSetup(), { + cloud: cloudMock.createSetup(), }) - ); + ).toBeUndefined(); }); describe('identifyUser', () => { @@ -76,44 +76,29 @@ describe('Cloud Experiments public plugin', () => { flag_overrides: { my_flag: '1234' }, }); const customPlugin = new CloudExperimentsPlugin(initializerContext); - const setupContract = customPlugin.setup(coreMock.createSetup()); expect(customPlugin).toHaveProperty('launchDarklyClient', undefined); - setupContract.identifyUser('user-id', {}); + customPlugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); expect(customPlugin).toHaveProperty('launchDarklyClient', undefined); }); - test('it initializes the LaunchDarkly client', () => { - const setupContract = plugin.setup(coreMock.createSetup()); + test('it skips creating the client if cloud is not enabled', () => { expect(plugin).toHaveProperty('launchDarklyClient', undefined); - setupContract.identifyUser('user-id', {}); - expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); - expect(ldClientMock.identify).not.toHaveBeenCalled(); - }); - - test('it calls identify if the client already exists', () => { - const setupContract = plugin.setup(coreMock.createSetup()); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: false }, + }); expect(plugin).toHaveProperty('launchDarklyClient', undefined); - setupContract.identifyUser('user-id', {}); - expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); - expect(ldClientMock.identify).not.toHaveBeenCalled(); - ldClientMock.identify.mockResolvedValue({}); // ensure it's a promise - setupContract.identifyUser('user-id', {}); - expect(ldClientMock.identify).toHaveBeenCalledTimes(1); }); - test('it handles identify rejections', async () => { - const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); - const setupContract = plugin.setup(coreMock.createSetup()); + test('it initializes the LaunchDarkly client', async () => { expect(plugin).toHaveProperty('launchDarklyClient', undefined); - setupContract.identifyUser('user-id', {}); - expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); - expect(ldClientMock.identify).not.toHaveBeenCalled(); - const error = new Error('Something went terribly wrong'); - ldClientMock.identify.mockRejectedValue(error); - setupContract.identifyUser('user-id', {}); - expect(ldClientMock.identify).toHaveBeenCalledTimes(1); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); + // await the lazy import await new Promise((resolve) => process.nextTick(resolve)); - expect(consoleWarnSpy).toHaveBeenCalledWith(error); + expect(plugin).toHaveProperty('launchDarklyClient', ldClientMock); }); }); }); @@ -132,7 +117,7 @@ describe('Cloud Experiments public plugin', () => { }); test('returns the contract', () => { - plugin.setup(coreMock.createSetup()); + plugin.setup(coreMock.createSetup(), { cloud: cloudMock.createSetup() }); const startContract = plugin.start(coreMock.createStart()); expect(startContract).toStrictEqual( expect.objectContaining({ @@ -145,8 +130,9 @@ describe('Cloud Experiments public plugin', () => { describe('getVariation', () => { describe('with the user identified', () => { beforeEach(() => { - const setupContract = plugin.setup(coreMock.createSetup()); - setupContract.identifyUser('user-id', {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); }); test('uses the flag overrides to respond early', async () => { @@ -175,7 +161,9 @@ describe('Cloud Experiments public plugin', () => { describe('with the user not identified', () => { beforeEach(() => { - plugin.setup(coreMock.createSetup()); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: false }, + }); }); test('uses the flag overrides to respond early', async () => { @@ -202,8 +190,9 @@ describe('Cloud Experiments public plugin', () => { describe('reportMetric', () => { describe('with the user identified', () => { beforeEach(() => { - const setupContract = plugin.setup(coreMock.createSetup()); - setupContract.identifyUser('user-id', {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); }); test('calls the track API', () => { @@ -224,7 +213,9 @@ describe('Cloud Experiments public plugin', () => { describe('with the user not identified', () => { beforeEach(() => { - plugin.setup(coreMock.createSetup()); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: false }, + }); }); test('calls the track API', () => { @@ -250,8 +241,9 @@ describe('Cloud Experiments public plugin', () => { flag_overrides: { my_flag: '1234' }, }); plugin = new CloudExperimentsPlugin(initializerContext); - const setupContract = plugin.setup(coreMock.createSetup()); - setupContract.identifyUser('user-id', {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); plugin.start(coreMock.createStart()); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts index 6206aadc24c31..45ca45ab08af1 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/public/plugin.ts @@ -6,21 +6,26 @@ */ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/public'; -import LaunchDarkly, { type LDClient } from 'launchdarkly-js-client-sdk'; +import type { LDClient } from 'launchdarkly-js-client-sdk'; import { get, has } from 'lodash'; +import { Sha256 } from '@kbn/crypto-browser'; +import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { CloudExperimentsFeatureFlagNames, CloudExperimentsMetric, - CloudExperimentsPluginSetup, CloudExperimentsPluginStart, } from '../common'; import { FEATURE_FLAG_NAMES, METRIC_NAMES } from '../common/constants'; +interface CloudExperimentsPluginSetupDeps { + cloud: CloudSetup; +} + /** * Browser-side implementation of the Cloud Experiments plugin */ export class CloudExperimentsPlugin - implements Plugin<CloudExperimentsPluginSetup, CloudExperimentsPluginStart> + implements Plugin<void, CloudExperimentsPluginStart, CloudExperimentsPluginSetupDeps> { private launchDarklyClient?: LDClient; private readonly clientId?: string; @@ -53,30 +58,32 @@ export class CloudExperimentsPlugin } /** - * Returns the contract {@link CloudExperimentsPluginSetup} + * Sets up the A/B testing client only if cloud is enabled * @param core {@link CoreSetup} + * @param deps {@link CloudExperimentsPluginSetupDeps} */ - public setup(core: CoreSetup): CloudExperimentsPluginSetup { - return { - identifyUser: (userId, userMetadata) => { - if (!this.clientId) return; // Only applies in dev mode. - - if (!this.launchDarklyClient) { - // If the client has not been initialized, create it with the user data.. + public setup(core: CoreSetup, deps: CloudExperimentsPluginSetupDeps) { + if (deps.cloud.isCloudEnabled && deps.cloud.cloudId && this.clientId) { + import('launchdarkly-js-client-sdk').then( + (LaunchDarkly) => { this.launchDarklyClient = LaunchDarkly.initialize( - this.clientId, - { key: userId, custom: userMetadata }, + this.clientId!, + { + // We use the Hashed Cloud Deployment ID as the userId in the Cloud Experiments + key: sha256(deps.cloud.cloudId!), + custom: { + kibanaVersion: this.kibanaVersion, + }, + }, { application: { id: 'kibana-browser', version: this.kibanaVersion } } ); - } else { - // Otherwise, call the `identify` method. - this.launchDarklyClient - .identify({ key: userId, custom: userMetadata }) - // eslint-disable-next-line no-console - .catch((err) => console.warn(err)); + }, + (err) => { + // eslint-disable-next-line no-console + console.debug(`Error setting up LaunchDarkly: ${err.toString()}`); } - }, - }; + ); + } } /** @@ -125,3 +132,7 @@ export class CloudExperimentsPlugin } }; } + +function sha256(str: string) { + return new Sha256().update(str, 'utf8').digest('hex'); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts index 1369f80779d7f..aa78353b72329 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.test.ts @@ -6,9 +6,10 @@ */ import { coreMock } from '@kbn/core/server/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; +import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; import { ldClientMock } from './plugin.test.mock'; import { CloudExperimentsPlugin } from './plugin'; -import { usageCollectionPluginMock } from '@kbn/usage-collection-plugin/server/mocks'; import { FEATURE_FLAG_NAMES } from '../common/constants'; describe('Cloud Experiments server plugin', () => { @@ -16,6 +17,13 @@ describe('Cloud Experiments server plugin', () => { jest.resetAllMocks(); }); + const ldUser = { + key: '1c2412b751f056aef6e340efa5637d137442d489a4b1e3117071e7c87f8523f2', + custom: { + kibanaVersion: coreMock.createPluginInitializerContext().env.packageInfo.version, + }, + }; + describe('constructor', () => { test('successfully creates a new plugin if provided an empty configuration', () => { const initializerContext = coreMock.createPluginInitializerContext(); @@ -68,17 +76,19 @@ describe('Cloud Experiments server plugin', () => { }); test('returns the contract', () => { - const setupContract = plugin.setup(coreMock.createSetup(), {}); - expect(setupContract).toStrictEqual( - expect.objectContaining({ - identifyUser: expect.any(Function), + expect( + plugin.setup(coreMock.createSetup(), { + cloud: cloudMock.createSetup(), }) - ); + ).toBeUndefined(); }); test('registers the usage collector when available', () => { const usageCollection = usageCollectionPluginMock.createSetupContract(); - plugin.setup(coreMock.createSetup(), { usageCollection }); + plugin.setup(coreMock.createSetup(), { + cloud: cloudMock.createSetup(), + usageCollection, + }); expect(usageCollection.makeUsageCollector).toHaveBeenCalledTimes(1); expect(usageCollection.registerCollector).toHaveBeenCalledTimes(1); }); @@ -86,9 +96,9 @@ describe('Cloud Experiments server plugin', () => { describe('identifyUser', () => { test('sets launchDarklyUser and calls identify', () => { expect(plugin).toHaveProperty('launchDarklyUser', undefined); - const setupContract = plugin.setup(coreMock.createSetup(), {}); - setupContract.identifyUser('user-id', {}); - const ldUser = { key: 'user-id', custom: {} }; + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); expect(plugin).toHaveProperty('launchDarklyUser', ldUser); expect(ldClientMock.identify).toHaveBeenCalledWith(ldUser); }); @@ -110,7 +120,7 @@ describe('Cloud Experiments server plugin', () => { }); test('returns the contract', () => { - plugin.setup(coreMock.createSetup(), {}); + plugin.setup(coreMock.createSetup(), { cloud: cloudMock.createSetup() }); const startContract = plugin.start(coreMock.createStart()); expect(startContract).toStrictEqual( expect.objectContaining({ @@ -123,8 +133,9 @@ describe('Cloud Experiments server plugin', () => { describe('getVariation', () => { describe('with the user identified', () => { beforeEach(() => { - const setupContract = plugin.setup(coreMock.createSetup(), {}); - setupContract.identifyUser('user-id', {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); }); test('uses the flag overrides to respond early', async () => { @@ -146,7 +157,7 @@ describe('Cloud Experiments server plugin', () => { ).resolves.toStrictEqual('12345'); expect(ldClientMock.variation).toHaveBeenCalledWith( undefined, // it couldn't find it in FEATURE_FLAG_NAMES - { key: 'user-id', custom: {} }, + ldUser, 123 ); }); @@ -154,7 +165,9 @@ describe('Cloud Experiments server plugin', () => { describe('with the user not identified', () => { beforeEach(() => { - plugin.setup(coreMock.createSetup(), {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: false }, + }); }); test('uses the flag overrides to respond early', async () => { @@ -181,8 +194,9 @@ describe('Cloud Experiments server plugin', () => { describe('reportMetric', () => { describe('with the user identified', () => { beforeEach(() => { - const setupContract = plugin.setup(coreMock.createSetup(), {}); - setupContract.identifyUser('user-id', {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); }); test('calls the track API', () => { @@ -195,7 +209,7 @@ describe('Cloud Experiments server plugin', () => { }); expect(ldClientMock.track).toHaveBeenCalledWith( undefined, // it couldn't find it in METRIC_NAMES - { key: 'user-id', custom: {} }, + ldUser, {}, 1 ); @@ -204,7 +218,9 @@ describe('Cloud Experiments server plugin', () => { describe('with the user not identified', () => { beforeEach(() => { - plugin.setup(coreMock.createSetup(), {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: false }, + }); }); test('calls the track API', () => { @@ -231,7 +247,9 @@ describe('Cloud Experiments server plugin', () => { }); ldClientMock.waitForInitialization.mockResolvedValue(ldClientMock); plugin = new CloudExperimentsPlugin(initializerContext); - plugin.setup(coreMock.createSetup(), {}); + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); plugin.start(coreMock.createStart()); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts index 2ffc0d0a464f5..782159dc12ab9 100755 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/server/plugin.ts @@ -14,36 +14,33 @@ import type { } from '@kbn/core/server'; import { get, has } from 'lodash'; import LaunchDarkly, { type LDClient, type LDUser } from 'launchdarkly-node-server-sdk'; +import { createSHA256Hash } from '@kbn/crypto'; import type { LogMeta } from '@kbn/logging'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; import { registerUsageCollector } from './usage'; import type { CloudExperimentsConfigType } from './config'; import type { CloudExperimentsFeatureFlagNames, CloudExperimentsMetric, - CloudExperimentsPluginSetup, CloudExperimentsPluginStart, } from '../common'; import { FEATURE_FLAG_NAMES, METRIC_NAMES } from '../common/constants'; interface CloudExperimentsPluginSetupDeps { + cloud: CloudSetup; usageCollection?: UsageCollectionSetup; } export class CloudExperimentsPlugin - implements - Plugin< - CloudExperimentsPluginSetup, - CloudExperimentsPluginStart, - CloudExperimentsPluginSetupDeps - > + implements Plugin<void, CloudExperimentsPluginStart, CloudExperimentsPluginSetupDeps> { private readonly logger: Logger; private readonly launchDarklyClient?: LDClient; private readonly flagOverrides?: Record<string, unknown>; private launchDarklyUser: LDUser | undefined; - constructor(initializerContext: PluginInitializerContext) { + constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); const config = initializerContext.config.get<CloudExperimentsConfigType>(); if (config.flag_overrides) { @@ -73,10 +70,7 @@ export class CloudExperimentsPlugin } } - public setup( - core: CoreSetup, - deps: CloudExperimentsPluginSetupDeps - ): CloudExperimentsPluginSetup { + public setup(core: CoreSetup, deps: CloudExperimentsPluginSetupDeps) { if (deps.usageCollection) { registerUsageCollector(deps.usageCollection, () => ({ launchDarklyClient: this.launchDarklyClient, @@ -84,12 +78,17 @@ export class CloudExperimentsPlugin })); } - return { - identifyUser: (userId, userMetadata) => { - this.launchDarklyUser = { key: userId, custom: userMetadata }; - this.launchDarklyClient?.identify(this.launchDarklyUser!); - }, - }; + if (deps.cloud.isCloudEnabled && deps.cloud.cloudId) { + this.launchDarklyUser = { + // We use the Cloud ID as the userId in the Cloud Experiments + key: createSHA256Hash(deps.cloud.cloudId), + custom: { + // This list of deployment metadata will likely grow in future versions + kibanaVersion: this.initializerContext.env.packageInfo.version, + }, + }; + this.launchDarklyClient?.identify(this.launchDarklyUser); + } } public start(core: CoreStart) { diff --git a/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json index de8c2e7bb26a7..917c8c0c9fc53 100644 --- a/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json +++ b/x-pack/plugins/cloud_integrations/cloud_experiments/tsconfig.json @@ -16,5 +16,6 @@ "references": [ { "path": "../../../../src/core/tsconfig.json" }, { "path": "../../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../cloud/tsconfig.json" }, ] } diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/.i18nrc.json b/x-pack/plugins/cloud_integrations/cloud_full_story/.i18nrc.json new file mode 100755 index 0000000000000..aa690dec41fc1 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/.i18nrc.json @@ -0,0 +1,7 @@ +{ + "prefix": "cloudFullStory", + "paths": { + "cloudFullStory": "." + }, + "translations": ["translations/ja-JP.json"] +} diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/README.md b/x-pack/plugins/cloud_integrations/cloud_full_story/README.md new file mode 100755 index 0000000000000..53e2bb66e8239 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/README.md @@ -0,0 +1,3 @@ +# Cloud FullStory + +Integrates with FullStory in order to provide better product analytics, so we can understand how our users make use of Kibana. This plugin should only run on Elastic Cloud. diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/jest.config.js b/x-pack/plugins/cloud_integrations/cloud_full_story/jest.config.js new file mode 100644 index 0000000000000..2aad10a598754 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../', + roots: ['<rootDir>/x-pack/plugins/cloud_integrations/cloud_full_story'], + coverageDirectory: + '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/cloud_integrations/cloud_full_story', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/cloud_integrations/cloud_full_story/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.json b/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.json new file mode 100755 index 0000000000000..39e31a89bc631 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/kibana.json @@ -0,0 +1,15 @@ +{ + "id": "cloudFullStory", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Kibana Core", + "githubTeam": "kibana-core" + }, + "description": "When Kibana runs on Elastic Cloud, this plugin registers FullStory as a shipper for telemetry.", + "server": true, + "ui": true, + "configPath": ["xpack", "cloud_integrations", "full_story"], + "requiredPlugins": ["cloud"], + "optionalPlugins": ["security"] +} diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/public/index.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/public/index.ts new file mode 100755 index 0000000000000..814eb6fca446b --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/public/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext } from '@kbn/core/public'; +import { CloudFullStoryPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudFullStoryPlugin(initializerContext); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.test.ts new file mode 100644 index 0000000000000..5f241f3ee7f9b --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.test.ts @@ -0,0 +1,70 @@ +/* + * 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 { coreMock } from '@kbn/core/public/mocks'; +import type { CloudFullStoryConfigType } from '../server/config'; +import { CloudFullStoryPlugin } from './plugin'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; + +describe('Cloud Plugin', () => { + describe('#setup', () => { + describe('setupFullStory', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const setupPlugin = async ({ + config = {}, + isCloudEnabled = true, + }: { + config?: Partial<CloudFullStoryConfigType>; + isCloudEnabled?: boolean; + }) => { + const initContext = coreMock.createPluginInitializerContext(config); + + const plugin = new CloudFullStoryPlugin(initContext); + + const coreSetup = coreMock.createSetup(); + + const cloud = { ...cloudMock.createSetup(), isCloudEnabled }; + + plugin.setup(coreSetup, { cloud }); + + // Wait for FullStory dynamic import to resolve + await new Promise((r) => setImmediate(r)); + + return { initContext, plugin, coreSetup }; + }; + + test('register the shipper FullStory with correct args when enabled and org_id are set', async () => { + const { coreSetup } = await setupPlugin({ + config: { org_id: 'foo' }, + }); + + expect(coreSetup.analytics.registerShipper).toHaveBeenCalled(); + expect(coreSetup.analytics.registerShipper).toHaveBeenCalledWith(expect.anything(), { + fullStoryOrgId: 'foo', + scriptUrl: '/internal/cloud/100/fullstory.js', + namespace: 'FSKibana', + }); + }); + + it('does not call initializeFullStory when isCloudEnabled=false', async () => { + const { coreSetup } = await setupPlugin({ + config: { org_id: 'foo' }, + isCloudEnabled: false, + }); + expect(coreSetup.analytics.registerShipper).not.toHaveBeenCalled(); + }); + + it('does not call initializeFullStory when org_id is undefined', async () => { + const { coreSetup } = await setupPlugin({ config: {} }); + expect(coreSetup.analytics.registerShipper).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts new file mode 100755 index 0000000000000..66adac5f5fc22 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/public/plugin.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + AnalyticsServiceSetup, + IBasePath, + PluginInitializerContext, + CoreSetup, + Plugin, +} from '@kbn/core/public'; +import type { CloudSetup } from '@kbn/cloud-plugin/public'; + +interface SetupFullStoryDeps { + analytics: AnalyticsServiceSetup; + basePath: IBasePath; +} + +interface CloudFullStoryConfig { + org_id?: string; + eventTypesAllowlist: string[]; +} + +interface CloudFullStorySetupDeps { + cloud: CloudSetup; +} + +export class CloudFullStoryPlugin implements Plugin { + private readonly config: CloudFullStoryConfig; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.config = this.initializerContext.config.get<CloudFullStoryConfig>(); + } + + public setup(core: CoreSetup, { cloud }: CloudFullStorySetupDeps) { + if (cloud.isCloudEnabled) { + this.setupFullStory({ analytics: core.analytics, basePath: core.http.basePath }).catch((e) => + // eslint-disable-next-line no-console + console.debug(`Error setting up FullStory: ${e.toString()}`) + ); + } + } + + public start() {} + + public stop() {} + + /** + * If the right config is provided, register the FullStory shipper to the analytics client. + * @param analytics Core's Analytics service's setup contract. + * @param basePath Core's http.basePath helper. + * @private + */ + private async setupFullStory({ analytics, basePath }: SetupFullStoryDeps) { + const { org_id: fullStoryOrgId, eventTypesAllowlist } = this.config; + if (!fullStoryOrgId) { + return; // do not load any FullStory code in the browser if not enabled + } + + // Keep this import async so that we do not load any FullStory code into the browser when it is disabled. + const { FullStoryShipper } = await import('@kbn/analytics-shippers-fullstory'); + analytics.registerShipper(FullStoryShipper, { + eventTypesAllowlist, + fullStoryOrgId, + // Load an Elastic-internally audited script. Ideally, it should be hosted on a CDN. + scriptUrl: basePath.prepend( + `/internal/cloud/${this.initializerContext.env.packageInfo.buildNum}/fullstory.js` + ), + namespace: 'FSKibana', + }); + } +} diff --git a/x-pack/plugins/cloud/server/assets/fullstory_library.js b/x-pack/plugins/cloud_integrations/cloud_full_story/server/assets/fullstory_library.js similarity index 100% rename from x-pack/plugins/cloud/server/assets/fullstory_library.js rename to x-pack/plugins/cloud_integrations/cloud_full_story/server/assets/fullstory_library.js diff --git a/x-pack/plugins/cloud/server/config.test.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.test.ts similarity index 50% rename from x-pack/plugins/cloud/server/config.test.ts rename to x-pack/plugins/cloud_integrations/cloud_full_story/server/config.test.ts index 9581bf8fd48b8..d17b9f7555cd3 100644 --- a/x-pack/plugins/cloud/server/config.test.ts +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.test.ts @@ -10,28 +10,22 @@ import { config } from './config'; describe('xpack.cloud config', () => { describe('full_story', () => { it('allows org_id when enabled: false', () => { - expect(() => - config.schema.validate({ full_story: { enabled: false, org_id: 'asdf' } }) - ).not.toThrow(); + expect(() => config.schema.validate({ enabled: false, org_id: 'asdf' })).not.toThrow(); }); it('rejects undefined or empty org_id when enabled: true', () => { - expect(() => - config.schema.validate({ full_story: { enabled: true } }) - ).toThrowErrorMatchingInlineSnapshot( - `"[full_story.org_id]: expected value of type [string] but got [undefined]"` + expect(() => config.schema.validate({ enabled: true })).toThrowErrorMatchingInlineSnapshot( + `"[org_id]: expected value of type [string] but got [undefined]"` ); expect(() => - config.schema.validate({ full_story: { enabled: true, org_id: '' } }) + config.schema.validate({ enabled: true, org_id: '' }) ).toThrowErrorMatchingInlineSnapshot( - `"[full_story.org_id]: value has length [0] but it must have a minimum length of [1]."` + `"[org_id]: value has length [0] but it must have a minimum length of [1]."` ); }); it('accepts org_id when enabled: true', () => { - expect(() => - config.schema.validate({ full_story: { enabled: true, org_id: 'asdf' } }) - ).not.toThrow(); + expect(() => config.schema.validate({ enabled: true, org_id: 'asdf' })).not.toThrow(); }); }); }); diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts new file mode 100644 index 0000000000000..53e619b0d50be --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core/server'; +import { get, has } from 'lodash'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + org_id: schema.conditional( + schema.siblingRef('enabled'), + true, + schema.string({ minLength: 1 }), + schema.maybe(schema.string()) + ), + eventTypesAllowlist: schema.arrayOf(schema.string(), { + defaultValue: ['Loaded Kibana'], + }), +}); + +export type CloudFullStoryConfigType = TypeOf<typeof configSchema>; + +export const config: PluginConfigDescriptor<CloudFullStoryConfigType> = { + exposeToBrowser: { + org_id: true, + eventTypesAllowlist: true, + }, + schema: configSchema, + deprecations: () => [ + // Silently move the chat configuration from `xpack.cloud` to `xpack.cloud_integrations.full_story`. + // No need to emit a deprecation log because it's an internal restructure + (cfg) => { + return { + set: [ + ...copyIfExists({ + cfg, + fromKey: 'xpack.cloud.full_story.enabled', + toKey: 'xpack.cloud_integrations.full_story.enabled', + }), + ...copyIfExists({ + cfg, + fromKey: 'xpack.cloud.full_story.org_id', + toKey: 'xpack.cloud_integrations.full_story.org_id', + }), + ...copyIfExists({ + cfg, + fromKey: 'xpack.cloud.full_story.eventTypesAllowlist', + toKey: 'xpack.cloud_integrations.full_story.eventTypesAllowlist', + }), + ], + unset: [ + { path: 'xpack.cloud.full_story.enabled' }, + { path: 'xpack.cloud.full_story.org_id' }, + { path: 'xpack.cloud.full_story.eventTypesAllowlist' }, + ], + }; + }, + ], +}; + +/** + * Defines the `set` action only if the key exists in the `fromKey` value. + * This is to avoid overwriting actual values with undefined. + * @param cfg The config object + * @param fromKey The key to copy from. + * @param toKey The key where the value should be copied to. + */ +function copyIfExists({ + cfg, + fromKey, + toKey, +}: { + cfg: Readonly<{ [p: string]: unknown }>; + fromKey: string; + toKey: string; +}) { + return has(cfg, fromKey) ? [{ path: toKey, value: get(cfg, fromKey) }] : []; +} diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/index.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/index.ts new file mode 100755 index 0000000000000..fe50e3d2944c3 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { PluginInitializerContext } from '@kbn/core/server'; +import { CloudFullStoryPlugin } from './plugin'; + +export { config } from './config'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new CloudFullStoryPlugin(initializerContext); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.mock.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.mock.ts new file mode 100644 index 0000000000000..c28c5e039d7db --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.mock.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const registerFullStoryRouteMock = jest.fn(); + +jest.doMock('./routes', () => ({ + registerFullStoryRoute: registerFullStoryRouteMock, +})); diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.ts new file mode 100644 index 0000000000000..972437932bea9 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { coreMock } from '@kbn/core/server/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; +import { registerFullStoryRouteMock } from './plugin.test.mock'; +import { CloudFullStoryPlugin } from './plugin'; + +describe('Cloud FullStory plugin', () => { + let plugin: CloudFullStoryPlugin; + beforeEach(() => { + registerFullStoryRouteMock.mockReset(); + plugin = new CloudFullStoryPlugin(coreMock.createPluginInitializerContext()); + }); + + test('registers route when cloud is enabled', () => { + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: true }, + }); + expect(registerFullStoryRouteMock).toHaveBeenCalledTimes(1); + }); + + test('does not register the route when cloud is disabled', () => { + plugin.setup(coreMock.createSetup(), { + cloud: { ...cloudMock.createSetup(), isCloudEnabled: false }, + }); + expect(registerFullStoryRouteMock).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.ts new file mode 100755 index 0000000000000..52c3b32afa7fc --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/plugin.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PluginInitializerContext, CoreSetup, Plugin } from '@kbn/core/server'; +import type { CloudSetup } from '@kbn/cloud-plugin/server'; + +import { registerFullStoryRoute } from './routes'; + +interface CloudFullStorySetupDeps { + cloud: CloudSetup; +} + +export class CloudFullStoryPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { cloud }: CloudFullStorySetupDeps) { + if (cloud.isCloudEnabled) { + registerFullStoryRoute({ + httpResources: core.http.resources, + packageInfo: this.initializerContext.env.packageInfo, + }); + } + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/plugins/cloud/server/routes/fullstory.test.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/fullstory.test.ts similarity index 100% rename from x-pack/plugins/cloud/server/routes/fullstory.test.ts rename to x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/fullstory.test.ts diff --git a/x-pack/plugins/cloud/server/routes/fullstory.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/fullstory.ts similarity index 98% rename from x-pack/plugins/cloud/server/routes/fullstory.ts rename to x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/fullstory.ts index e9f4835494a4e..03e38baee4e91 100644 --- a/x-pack/plugins/cloud/server/routes/fullstory.ts +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/fullstory.ts @@ -40,7 +40,7 @@ export const renderFullStoryLibraryFactory = (dist = true) => } ); -export const registerFullstoryRoute = ({ +export const registerFullStoryRoute = ({ httpResources, packageInfo, }: { diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/index.ts b/x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/index.ts new file mode 100755 index 0000000000000..1ace8f41a7951 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/server/routes/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { registerFullStoryRoute } from './fullstory'; diff --git a/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json new file mode 100644 index 0000000000000..e81bf47099981 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_full_story/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + ".storybook/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../../src/core/tsconfig.json" }, + { "path": "../../cloud/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/cloud_integrations/cloud_links/README.md b/x-pack/plugins/cloud_integrations/cloud_links/README.md new file mode 100755 index 0000000000000..e42ce0af4ae6c --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/README.md @@ -0,0 +1,3 @@ +# Cloud Links + +Adds all the links to the Elastic Cloud console. diff --git a/x-pack/plugins/cloud_integrations/cloud_links/jest.config.js b/x-pack/plugins/cloud_integrations/cloud_links/jest.config.js new file mode 100644 index 0000000000000..1cec558302f27 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../../', + roots: ['<rootDir>/x-pack/plugins/cloud_integrations/cloud_links'], + coverageDirectory: + '<rootDir>/target/kibana-coverage/jest/x-pack/plugins/cloud_integrations/cloud_links', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '<rootDir>/x-pack/plugins/cloud_integrations/cloud_links/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/cloud_integrations/cloud_links/kibana.json b/x-pack/plugins/cloud_integrations/cloud_links/kibana.json new file mode 100755 index 0000000000000..5e3faef59fd66 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/kibana.json @@ -0,0 +1,14 @@ +{ + "id": "cloudLinks", + "version": "1.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Kibana Core", + "githubTeam": "@kibana-core" + }, + "description": "Adds the links to the Elastic Cloud console", + "server": false, + "ui": true, + "requiredPlugins": [], + "optionalPlugins": ["cloud", "security"] +} diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/index.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/index.ts new file mode 100755 index 0000000000000..edb43cd0405ca --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { CloudLinksPlugin } from './plugin'; + +export function plugin() { + return new CloudLinksPlugin(); +} diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/index.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/index.ts new file mode 100644 index 0000000000000..02508f434a008 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { maybeAddCloudLinks } from './maybe_add_cloud_links'; diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts new file mode 100644 index 0000000000000..4bc8edd057b6e --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.test.ts @@ -0,0 +1,134 @@ +/* + * 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 { cloudMock } from '@kbn/cloud-plugin/public/mocks'; +import { coreMock } from '@kbn/core/public/mocks'; +import { securityMock } from '@kbn/security-plugin/public/mocks'; + +import { maybeAddCloudLinks } from './maybe_add_cloud_links'; + +describe('maybeAddCloudLinks', () => { + it('should skip if cloud is disabled', async () => { + const security = securityMock.createStart(); + maybeAddCloudLinks({ + security, + chrome: coreMock.createStart().chrome, + cloud: { ...cloudMock.createStart(), isCloudEnabled: false }, + }); + // Since there's a promise, let's wait for the next tick + await new Promise((resolve) => process.nextTick(resolve)); + expect(security.authc.getCurrentUser).not.toHaveBeenCalled(); + }); + + it('when cloud enabled and the user is an Elastic Cloud user, it sets the links', async () => { + const security = securityMock.createStart(); + security.authc.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser({ elastic_cloud_user: true }) + ); + const chrome = coreMock.createStart().chrome; + maybeAddCloudLinks({ + security, + chrome, + cloud: { ...cloudMock.createStart(), isCloudEnabled: true }, + }); + // Since there's a promise, let's wait for the next tick + await new Promise((resolve) => process.nextTick(resolve)); + expect(security.authc.getCurrentUser).toHaveBeenCalledTimes(1); + expect(chrome.setCustomNavLink).toHaveBeenCalledTimes(1); + expect(chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "euiIconType": "logoCloud", + "href": "deployment-url", + "title": "Manage this deployment", + }, + ] + `); + expect(security.navControlService.addUserMenuLinks).toHaveBeenCalledTimes(1); + expect(security.navControlService.addUserMenuLinks.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "href": "profile-url", + "iconType": "user", + "label": "Edit profile", + "order": 100, + "setAsProfile": true, + }, + Object { + "href": "organization-url", + "iconType": "gear", + "label": "Account & Billing", + "order": 200, + }, + ], + ] + `); + }); + + it('when cloud enabled and it fails to fetch the user, it sets the links', async () => { + const security = securityMock.createStart(); + security.authc.getCurrentUser.mockRejectedValue(new Error('Something went terribly wrong')); + const chrome = coreMock.createStart().chrome; + maybeAddCloudLinks({ + security, + chrome, + cloud: { ...cloudMock.createStart(), isCloudEnabled: true }, + }); + // Since there's a promise, let's wait for the next tick + await new Promise((resolve) => process.nextTick(resolve)); + expect(security.authc.getCurrentUser).toHaveBeenCalledTimes(1); + expect(chrome.setCustomNavLink).toHaveBeenCalledTimes(1); + expect(chrome.setCustomNavLink.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "euiIconType": "logoCloud", + "href": "deployment-url", + "title": "Manage this deployment", + }, + ] + `); + expect(security.navControlService.addUserMenuLinks).toHaveBeenCalledTimes(1); + expect(security.navControlService.addUserMenuLinks.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "href": "profile-url", + "iconType": "user", + "label": "Edit profile", + "order": 100, + "setAsProfile": true, + }, + Object { + "href": "organization-url", + "iconType": "gear", + "label": "Account & Billing", + "order": 200, + }, + ], + ] + `); + }); + + it('when cloud enabled and the user is NOT an Elastic Cloud user, it does not set the links', async () => { + const security = securityMock.createStart(); + security.authc.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser({ elastic_cloud_user: false }) + ); + const chrome = coreMock.createStart().chrome; + maybeAddCloudLinks({ + security, + chrome, + cloud: { ...cloudMock.createStart(), isCloudEnabled: true }, + }); + // Since there's a promise, let's wait for the next tick + await new Promise((resolve) => process.nextTick(resolve)); + expect(security.authc.getCurrentUser).toHaveBeenCalledTimes(1); + expect(chrome.setCustomNavLink).not.toHaveBeenCalled(); + expect(security.navControlService.addUserMenuLinks).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts new file mode 100644 index 0000000000000..383709e1e7e8c --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/maybe_add_cloud_links.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { catchError, defer, filter, map, of } from 'rxjs'; + +import { i18n } from '@kbn/i18n'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; +import type { ChromeStart } from '@kbn/core/public'; +import type { SecurityPluginStart } from '@kbn/security-plugin/public'; + +import { createUserMenuLinks } from './user_menu_links'; + +export interface MaybeAddCloudLinksDeps { + security: SecurityPluginStart; + chrome: ChromeStart; + cloud: CloudStart; +} + +export function maybeAddCloudLinks({ security, chrome, cloud }: MaybeAddCloudLinksDeps): void { + if (cloud.isCloudEnabled) { + defer(() => security.authc.getCurrentUser()) + .pipe( + // Check if user is a cloud user. + map((user) => user.elastic_cloud_user), + // If user is not defined due to an unexpected error, then fail *open*. + catchError(() => of(true)), + filter((isElasticCloudUser) => isElasticCloudUser === true), + map(() => { + if (cloud.deploymentUrl) { + chrome.setCustomNavLink({ + title: i18n.translate('xpack.cloudLinks.deploymentLinkLabel', { + defaultMessage: 'Manage this deployment', + }), + euiIconType: 'logoCloud', + href: cloud.deploymentUrl, + }); + } + const userMenuLinks = createUserMenuLinks(cloud); + security.navControlService.addUserMenuLinks(userMenuLinks); + }) + ) + .subscribe(); + } +} diff --git a/x-pack/plugins/cloud/public/user_menu_links.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/user_menu_links.ts similarity index 50% rename from x-pack/plugins/cloud/public/user_menu_links.ts rename to x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/user_menu_links.ts index e29736e215e0d..9b0ab8faac5a5 100644 --- a/x-pack/plugins/cloud/public/user_menu_links.ts +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/maybe_add_cloud_links/user_menu_links.ts @@ -6,33 +6,32 @@ */ import { i18n } from '@kbn/i18n'; -import { UserMenuLink } from '@kbn/security-plugin/public'; -import { CloudConfigType } from '.'; -import { getFullCloudUrl } from './utils'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; +import type { UserMenuLink } from '@kbn/security-plugin/public'; -export const createUserMenuLinks = (config: CloudConfigType): UserMenuLink[] => { - const { profile_url: profileUrl, organization_url: organizationUrl, base_url: baseUrl } = config; +export const createUserMenuLinks = (cloud: CloudStart): UserMenuLink[] => { + const { profileUrl, organizationUrl } = cloud; const userMenuLinks = [] as UserMenuLink[]; - if (baseUrl && profileUrl) { + if (profileUrl) { userMenuLinks.push({ - label: i18n.translate('xpack.cloud.userMenuLinks.profileLinkText', { + label: i18n.translate('xpack.cloudLinks.userMenuLinks.profileLinkText', { defaultMessage: 'Edit profile', }), iconType: 'user', - href: getFullCloudUrl(baseUrl, profileUrl), + href: profileUrl, order: 100, setAsProfile: true, }); } - if (baseUrl && organizationUrl) { + if (organizationUrl) { userMenuLinks.push({ - label: i18n.translate('xpack.cloud.userMenuLinks.accountLinkText', { + label: i18n.translate('xpack.cloudLinks.userMenuLinks.accountLinkText', { defaultMessage: 'Account & Billing', }), iconType: 'gear', - href: getFullCloudUrl(baseUrl, organizationUrl), + href: organizationUrl, order: 200, }); } diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.mocks.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.mocks.ts new file mode 100644 index 0000000000000..3e3075ff23862 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.mocks.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const maybeAddCloudLinksMock = jest.fn(); + +jest.doMock('./maybe_add_cloud_links', () => ({ + maybeAddCloudLinks: maybeAddCloudLinksMock, +})); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts new file mode 100644 index 0000000000000..e6107874b6d5d --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.test.ts @@ -0,0 +1,77 @@ +/* + * 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 { maybeAddCloudLinksMock } from './plugin.test.mocks'; +import { CloudLinksPlugin } from './plugin'; +import { coreMock } from '@kbn/core/public/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; +import { securityMock } from '@kbn/security-plugin/public/mocks'; + +describe('Cloud Links Plugin - public', () => { + let plugin: CloudLinksPlugin; + + beforeEach(() => { + plugin = new CloudLinksPlugin(); + }); + + afterEach(() => { + maybeAddCloudLinksMock.mockReset(); + }); + + describe('start', () => { + beforeEach(() => { + plugin.setup(); + }); + + afterEach(() => { + plugin.stop(); + }); + + test('calls maybeAddCloudLinks when cloud and security are enabled and it is an authenticated page', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(1); + }); + + test('does not call maybeAddCloudLinks when security is disabled', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + plugin.start(coreStart, { cloud }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when the page is anonymous', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: true }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when cloud is disabled', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const security = securityMock.createStart(); + plugin.start(coreStart, { security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + + test('does not call maybeAddCloudLinks when isCloudEnabled is false', () => { + const coreStart = coreMock.createStart(); + coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); + const cloud = { ...cloudMock.createStart(), isCloudEnabled: false }; + const security = securityMock.createStart(); + plugin.start(coreStart, { cloud, security }); + expect(maybeAddCloudLinksMock).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts new file mode 100755 index 0000000000000..7fbf7a8a65064 --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/public/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart, Plugin } from '@kbn/core/public'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; +import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; +import { maybeAddCloudLinks } from './maybe_add_cloud_links'; + +interface CloudLinksDepsSetup { + cloud?: CloudSetup; + security?: SecurityPluginSetup; +} + +interface CloudLinksDepsStart { + cloud?: CloudStart; + security?: SecurityPluginStart; +} + +export class CloudLinksPlugin + implements Plugin<void, void, CloudLinksDepsSetup, CloudLinksDepsStart> +{ + public setup() {} + + public start(core: CoreStart, { cloud, security }: CloudLinksDepsStart) { + if ( + cloud?.isCloudEnabled && + security && + !core.http.anonymousPaths.isAnonymous(window.location.pathname) + ) { + maybeAddCloudLinks({ security, chrome: core.chrome, cloud }); + } + } + + public stop() {} +} diff --git a/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json new file mode 100644 index 0000000000000..967962363be2c --- /dev/null +++ b/x-pack/plugins/cloud_integrations/cloud_links/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + ".storybook/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../../src/core/tsconfig.json" }, + { "path": "../../cloud/tsconfig.json" }, + { "path": "../../security/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/data_visualizer/kibana.json b/x-pack/plugins/data_visualizer/kibana.json index 65b1035640405..afc9fe34be640 100644 --- a/x-pack/plugins/data_visualizer/kibana.json +++ b/x-pack/plugins/data_visualizer/kibana.json @@ -31,7 +31,7 @@ "fieldFormats", "uiActions", "lens", - "cloud" + "cloudChat" ], "owner": { "name": "Machine Learning UI", diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js index b9766e65a3555..b1378769efc92 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js @@ -30,7 +30,7 @@ import { processResults, } from '../../../common/components/utils'; -import { Chat } from '@kbn/cloud-plugin/public'; +import { Chat } from '@kbn/cloud-chat-plugin/public'; import { MODE } from './constants'; diff --git a/x-pack/plugins/data_visualizer/tsconfig.json b/x-pack/plugins/data_visualizer/tsconfig.json index 504d3fbd9d8aa..2168eaa5696c5 100644 --- a/x-pack/plugins/data_visualizer/tsconfig.json +++ b/x-pack/plugins/data_visualizer/tsconfig.json @@ -28,6 +28,7 @@ { "path": "../../../src/plugins/embeddable/tsconfig.json" }, { "path": "../../../src/plugins/unified_search/tsconfig.json" }, { "path": "../cloud/tsconfig.json" }, + { "path": "../cloud_integrations/cloud_chat/tsconfig.json" }, { "path": "../../../src/plugins/embeddable/tsconfig.json" } ] } diff --git a/x-pack/plugins/enterprise_search/kibana.json b/x-pack/plugins/enterprise_search/kibana.json index 4556e5dc96106..7cd1e2e71ee7d 100644 --- a/x-pack/plugins/enterprise_search/kibana.json +++ b/x-pack/plugins/enterprise_search/kibana.json @@ -4,10 +4,10 @@ "kibanaVersion": "kibana", "requiredPlugins": ["features", "spaces", "security", "licensing", "data", "charts", "infra", "cloud", "esUiShared"], "configPath": ["enterpriseSearch"], - "optionalPlugins": ["usageCollection", "home", "cloud", "customIntegrations"], + "optionalPlugins": ["usageCollection", "home", "customIntegrations"], "server": true, "ui": true, - "requiredBundles": ["kibanaReact"], + "requiredBundles": ["kibanaReact", "cloudChat"], "owner": { "name": "Enterprise Search", "githubTeam": "enterprise-search-frontend" diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts index a6345775b1a9c..9921dd7d82dc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts @@ -6,6 +6,7 @@ */ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks'; import { Capabilities } from '@kbn/core/public'; @@ -18,6 +19,7 @@ export const mockKibanaValues = { config: { host: 'http://localhost:3002' }, charts: chartPluginMock.createStartContract(), cloud: { + ...cloudMock.createSetup(), isCloudEnabled: false, deployment_url: 'https://cloud.elastic.co/deployments/some-id', }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx index 1bb6bb3777d81..01997959ec413 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx @@ -19,7 +19,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { Chat } from '@kbn/cloud-plugin/public'; +import { Chat } from '@kbn/cloud-chat-plugin/public'; import { i18n } from '@kbn/i18n'; import { diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json index ce288f8b4b97d..10fcc3b8c0d58 100644 --- a/x-pack/plugins/enterprise_search/tsconfig.json +++ b/x-pack/plugins/enterprise_search/tsconfig.json @@ -21,6 +21,7 @@ { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, { "path": "../cloud/tsconfig.json" }, + { "path": "../cloud_integrations/cloud_chat/tsconfig.json" }, { "path": "../infra/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, diff --git a/x-pack/plugins/fleet/.storybook/context/cloud.ts b/x-pack/plugins/fleet/.storybook/context/cloud.ts index eccb41d6aa8c0..1bd63d673bb3e 100644 --- a/x-pack/plugins/fleet/.storybook/context/cloud.ts +++ b/x-pack/plugins/fleet/.storybook/context/cloud.ts @@ -17,6 +17,7 @@ export const getCloud = ({ isCloudEnabled }: { isCloudEnabled: boolean }) => { organizationUrl: 'https://organization.url', profileUrl: 'https://profile.url', snapshotsUrl: 'https://snapshots.url', + registerCloudService: () => {}, }; return cloud; diff --git a/x-pack/plugins/fleet/kibana.json b/x-pack/plugins/fleet/kibana.json index 6ab87283e0b26..79d8bbd40644e 100644 --- a/x-pack/plugins/fleet/kibana.json +++ b/x-pack/plugins/fleet/kibana.json @@ -11,5 +11,5 @@ "requiredPlugins": ["licensing", "data", "encryptedSavedObjects", "navigation", "customIntegrations", "share", "spaces", "security", "unifiedSearch", "savedObjectsTagging", "taskManager"], "optionalPlugins": ["features", "cloud", "usageCollection", "home", "globalSearch", "telemetry", "discover", "ingestPipelines"], "extraPublicDirs": ["common"], - "requiredBundles": ["kibanaReact", "cloud", "esUiShared", "infra", "kibanaUtils", "usageCollection", "unifiedSearch"] + "requiredBundles": ["kibanaReact", "cloudChat", "esUiShared", "infra", "kibanaUtils", "usageCollection", "unifiedSearch"] } diff --git a/x-pack/plugins/fleet/public/applications/integrations/app.tsx b/x-pack/plugins/fleet/public/applications/integrations/app.tsx index ad037652a78a3..3d47db8963d3f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/app.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/app.tsx @@ -14,7 +14,7 @@ import useObservable from 'react-use/lib/useObservable'; import { KibanaContextProvider, RedirectAppLinks } from '@kbn/kibana-react-plugin/public'; import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; -import { Chat } from '@kbn/cloud-plugin/public'; +import { Chat } from '@kbn/cloud-chat-plugin/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 7cc16fe654268..320843546a305 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -36,6 +36,7 @@ { "path": "../../../src/plugins/home/tsconfig.json" }, // requiredBundles from ./kibana.json + { "path": "../cloud_integrations/cloud_chat/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, { "path": "../infra/tsconfig.json" }, diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts index 820f8a4f9100a..75db772ec0926 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/node_allocation/cloud_aware_behavior.test.ts @@ -11,6 +11,7 @@ import { CloudNodeAllocationTestBed, setupCloudNodeAllocation, } from './cloud_aware_behavior.helpers'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; describe('<EditPolicy /> node allocation cloud-aware behavior', () => { let testBed: CloudNodeAllocationTestBed; @@ -28,7 +29,7 @@ describe('<EditPolicy /> node allocation cloud-aware behavior', () => { await act(async () => { if (Boolean(isOnCloud)) { testBed = await setupCloudNodeAllocation(httpSetup, { - appServicesContext: { cloud: { isCloudEnabled: true } }, + appServicesContext: { cloud: { ...cloudMock.createSetup(), isCloudEnabled: true } }, }); } else { testBed = await setupCloudNodeAllocation(httpSetup); diff --git a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts index 68e74e23a781c..fbe724c881af3 100644 --- a/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts +++ b/x-pack/plugins/index_lifecycle_management/integration_tests/edit_policy/features/searchable_snapshots.test.ts @@ -15,6 +15,7 @@ import { SearchableSnapshotsTestBed, setupSearchableSnapshotsTestBed, } from './searchable_snapshots.helpers'; +import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; describe('<EditPolicy /> searchable snapshots', () => { let testBed: SearchableSnapshotsTestBed; @@ -142,7 +143,7 @@ describe('<EditPolicy /> searchable snapshots', () => { await act(async () => { testBed = await setupSearchableSnapshotsTestBed(httpSetup, { - appServicesContext: { cloud: { isCloudEnabled: true } }, + appServicesContext: { cloud: { ...cloudMock.createSetup(), isCloudEnabled: true } }, }); }); @@ -171,7 +172,7 @@ describe('<EditPolicy /> searchable snapshots', () => { await act(async () => { testBed = await setupSearchableSnapshotsTestBed(httpSetup, { - appServicesContext: { cloud: { isCloudEnabled: true } }, + appServicesContext: { cloud: { ...cloudMock.createSetup(), isCloudEnabled: true } }, }); }); diff --git a/x-pack/plugins/security/kibana.json b/x-pack/plugins/security/kibana.json index 881f67dcf22cb..6804a5ba52253 100644 --- a/x-pack/plugins/security/kibana.json +++ b/x-pack/plugins/security/kibana.json @@ -9,7 +9,7 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "security"], "requiredPlugins": ["features", "licensing", "taskManager"], - "optionalPlugins": ["dataViews", "home", "management", "usageCollection", "spaces", "share"], + "optionalPlugins": ["cloud", "dataViews", "home", "management", "usageCollection", "spaces", "share"], "server": true, "ui": true, "enabledOnAnonymousPages": true, diff --git a/x-pack/plugins/security/public/analytics/analytics_service.test.ts b/x-pack/plugins/security/public/analytics/analytics_service.test.ts index 8174fffc250d9..28a272c12f9ec 100644 --- a/x-pack/plugins/security/public/analytics/analytics_service.test.ts +++ b/x-pack/plugins/security/public/analytics/analytics_service.test.ts @@ -11,6 +11,8 @@ import { coreMock } from '@kbn/core/public/mocks'; import { nextTick } from '@kbn/test-jest-helpers'; import { licenseMock } from '../../common/licensing/index.mock'; +import { authenticationMock } from '../authentication/index.mock'; +import { securityMock } from '../mocks'; import { AnalyticsService } from './analytics_service'; describe('AnalyticsService', () => { @@ -29,7 +31,14 @@ describe('AnalyticsService', () => { expect(localStorage.getItem(AnalyticsService.AuthTypeInfoStorageKey)).toBeNull(); - analyticsService.setup({ securityLicense: licenseMock.create({ allowLogin: true }) }); + const authc = authenticationMock.createSetup(); + authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + + analyticsService.setup({ + authc, + analytics: coreMock.createSetup().analytics, + securityLicense: licenseMock.create({ allowLogin: true }), + }); analyticsService.start({ http: mockCore.http }); await nextTick(); @@ -51,7 +60,12 @@ describe('AnalyticsService', () => { mockCore.http.post.mockResolvedValue({ signature: 'some-signature', timestamp: 1234 }); const licenseFeatures$ = new BehaviorSubject({ allowLogin: true }); + const authc = authenticationMock.createSetup(); + authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + analyticsService.setup({ + authc, + analytics: coreMock.createSetup().analytics, securityLicense: licenseMock.create(licenseFeatures$.asObservable()), }); analyticsService.start({ http: mockCore.http }); @@ -99,7 +113,14 @@ describe('AnalyticsService', () => { }); localStorage.setItem(AnalyticsService.AuthTypeInfoStorageKey, mockCurrentAuthTypeInfo); - analyticsService.setup({ securityLicense: licenseMock.create({ allowLogin: true }) }); + const authc = authenticationMock.createSetup(); + authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + + analyticsService.setup({ + authc, + analytics: coreMock.createSetup().analytics, + securityLicense: licenseMock.create({ allowLogin: true }), + }); analyticsService.start({ http: mockCore.http }); await nextTick(); @@ -117,7 +138,14 @@ describe('AnalyticsService', () => { it('does not report authentication type if security is not available', async () => { const mockCore = coreMock.createStart(); - analyticsService.setup({ securityLicense: licenseMock.create({ allowLogin: false }) }); + const authc = authenticationMock.createSetup(); + authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + + analyticsService.setup({ + authc, + analytics: coreMock.createSetup().analytics, + securityLicense: licenseMock.create({ allowLogin: false }), + }); analyticsService.start({ http: mockCore.http }); await nextTick(); @@ -136,7 +164,14 @@ describe('AnalyticsService', () => { }); localStorage.setItem(AnalyticsService.AuthTypeInfoStorageKey, mockCurrentAuthTypeInfo); - analyticsService.setup({ securityLicense: licenseMock.create({ allowLogin: true }) }); + const authc = authenticationMock.createSetup(); + authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + + analyticsService.setup({ + authc, + analytics: coreMock.createSetup().analytics, + securityLicense: licenseMock.create({ allowLogin: true }), + }); analyticsService.start({ http: mockCore.http }); await nextTick(); diff --git a/x-pack/plugins/security/public/analytics/analytics_service.ts b/x-pack/plugins/security/public/analytics/analytics_service.ts index 1db3a9566f6fd..87c402e9983a0 100644 --- a/x-pack/plugins/security/public/analytics/analytics_service.ts +++ b/x-pack/plugins/security/public/analytics/analytics_service.ts @@ -9,12 +9,20 @@ import type { Subscription } from 'rxjs'; import { filter } from 'rxjs'; import { throttleTime } from 'rxjs/operators'; -import type { HttpStart } from '@kbn/core/public'; +import type { + AnalyticsServiceSetup as CoreAnalyticsServiceSetup, + HttpStart, +} from '@kbn/core/public'; +import type { AuthenticationServiceSetup } from '..'; import type { SecurityLicense } from '../../common'; +import { registerUserContext } from './register_user_context'; interface AnalyticsServiceSetupParams { securityLicense: SecurityLicense; + analytics: CoreAnalyticsServiceSetup; + authc: AuthenticationServiceSetup; + cloudId?: string; } interface AnalyticsServiceStartParams { @@ -35,8 +43,9 @@ export class AnalyticsService { private securityLicense!: SecurityLicense; private securityFeaturesSubscription?: Subscription; - public setup({ securityLicense }: AnalyticsServiceSetupParams) { + public setup({ analytics, authc, cloudId, securityLicense }: AnalyticsServiceSetupParams) { this.securityLicense = securityLicense; + registerUserContext(analytics, authc, cloudId); } public start({ http }: AnalyticsServiceStartParams) { diff --git a/x-pack/plugins/security/public/analytics/register_user_context.test.ts b/x-pack/plugins/security/public/analytics/register_user_context.test.ts new file mode 100644 index 0000000000000..0654042059649 --- /dev/null +++ b/x-pack/plugins/security/public/analytics/register_user_context.test.ts @@ -0,0 +1,126 @@ +/* + * 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 { firstValueFrom } from 'rxjs'; + +import type { AnalyticsServiceSetup } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; +import { Sha256 } from '@kbn/crypto-browser'; + +import type { AuthenticationServiceSetup } from '..'; +import { authenticationMock } from '../authentication/index.mock'; +import { securityMock } from '../mocks'; +import { registerUserContext } from './register_user_context'; + +describe('registerUserContext', () => { + const username = '1234'; + const expectedHashedPlainUsername = new Sha256().update(username, 'utf8').digest('hex'); + + let analytics: jest.Mocked<AnalyticsServiceSetup>; + let authentication: jest.Mocked<AuthenticationServiceSetup>; + + beforeEach(() => { + jest.clearAllMocks(); + analytics = coreMock.createSetup().analytics; + authentication = authenticationMock.createSetup(); + authentication.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser()); + }); + + test('register the context provider for the cloud user with hashed user ID when security is available', async () => { + registerUserContext(analytics, authentication, 'cloudId'); + + expect(analytics.registerContextProvider).toHaveBeenCalled(); + + const [{ context$ }] = analytics.registerContextProvider.mock.calls.find( + ([{ name }]) => name === 'user_id' + )!; + + await expect(firstValueFrom(context$)).resolves.toEqual({ + userId: '7a3e98632e2c878671da5d5c49e625dd84fb4ba85758feae9a5fd5ec57724753', + isElasticCloudUser: false, + }); + }); + + it('user hash includes cloud id', async () => { + authentication.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser({ username }) + ); + const analytics1 = coreMock.createSetup().analytics; + registerUserContext(analytics1, authentication, 'esOrg1'); + + const [{ context$: context1$ }] = analytics1.registerContextProvider.mock.calls.find( + ([{ name }]) => name === 'user_id' + )!; + + const { userId: hashId1 } = (await firstValueFrom(context1$)) as { userId: string }; + expect(hashId1).not.toEqual(expectedHashedPlainUsername); + + const analytics2 = coreMock.createSetup().analytics; + registerUserContext(analytics2, authentication, 'esOrg2'); + const [{ context$: context2$ }] = analytics2.registerContextProvider.mock.calls.find( + ([{ name }]) => name === 'user_id' + )!; + + const { userId: hashId2 } = (await firstValueFrom(context2$)) as { userId: string }; + expect(hashId2).not.toEqual(expectedHashedPlainUsername); + + expect(hashId1).not.toEqual(hashId2); + }); + + test('user hash does not include cloudId when user is an Elastic Cloud user', async () => { + authentication.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser({ username, elastic_cloud_user: true }) + ); + registerUserContext(analytics, authentication, 'cloudDeploymentId'); + + expect(analytics.registerContextProvider).toHaveBeenCalled(); + + const [{ context$ }] = analytics.registerContextProvider.mock.calls.find( + ([{ name }]) => name === 'user_id' + )!; + + await expect(firstValueFrom(context$)).resolves.toEqual({ + userId: expectedHashedPlainUsername, + isElasticCloudUser: true, + }); + }); + + test('user hash does not include cloudId when not provided', async () => { + authentication.getCurrentUser.mockResolvedValue( + securityMock.createMockAuthenticatedUser({ username }) + ); + registerUserContext(analytics, authentication); + + expect(analytics.registerContextProvider).toHaveBeenCalled(); + + const [{ context$ }] = analytics.registerContextProvider.mock.calls.find( + ([{ name }]) => name === 'user_id' + )!; + + await expect(firstValueFrom(context$)).resolves.toEqual({ + userId: expectedHashedPlainUsername, + isElasticCloudUser: false, + }); + }); + + test('user hash is undefined when failed to fetch a user', async () => { + authentication.getCurrentUser.mockRejectedValue(new Error('failed to fetch a user')); + + registerUserContext(analytics, authentication); + + expect(analytics.registerContextProvider).toHaveBeenCalled(); + + const [{ context$ }] = analytics.registerContextProvider.mock.calls.find( + ([{ name }]) => name === 'user_id' + )!; + + await expect(firstValueFrom(context$)).resolves.toEqual({ + userId: undefined, + isElasticCloudUser: false, + }); + }); +}); diff --git a/x-pack/plugins/security/public/analytics/register_user_context.ts b/x-pack/plugins/security/public/analytics/register_user_context.ts new file mode 100644 index 0000000000000..bc48846913d02 --- /dev/null +++ b/x-pack/plugins/security/public/analytics/register_user_context.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { catchError, from, map, of } from 'rxjs'; + +import type { AnalyticsServiceSetup } from '@kbn/core/public'; +import { Sha256 } from '@kbn/crypto-browser'; + +import type { AuthenticationServiceSetup } from '..'; + +/** + * Set up the Analytics context provider for the User information. + * @param analytics Core's Analytics service. The Setup contract. + * @param authc {@link AuthenticationServiceSetup} used to get the current user's information + * @param cloudId The Cloud Org ID. + * @private + */ +export function registerUserContext( + analytics: AnalyticsServiceSetup, + authc: AuthenticationServiceSetup, + cloudId?: string +) { + analytics.registerContextProvider({ + name: 'user_id', + context$: from(authc.getCurrentUser()).pipe( + map((user) => { + if (user.elastic_cloud_user) { + // If the user is managed by ESS, use the plain username as the user ID: + // The username is expected to be unique for these users, + // and it matches how users are identified in the Cloud UI, so it allows us to correlate them. + return { userId: user.username, isElasticCloudUser: true }; + } + + return { + // For the rest of the authentication providers, we want to add the cloud deployment ID to make it unique. + // Especially in the case of Elasticsearch-backed authentication, where users are commonly repeated + // across multiple deployments (i.e.: `elastic` superuser). + userId: cloudId ? `${cloudId}:${user.username}` : user.username, + isElasticCloudUser: false, + }; + }), + // The hashing here is to keep it at clear as possible in our source code that we do not send literal user IDs + map(({ userId, isElasticCloudUser }) => ({ userId: sha256(userId), isElasticCloudUser })), + catchError(() => of({ userId: undefined, isElasticCloudUser: false })) + ), + schema: { + userId: { + type: 'keyword', + _meta: { description: 'The user id scoped as seen by Cloud (hashed)' }, + }, + isElasticCloudUser: { + type: 'boolean', + _meta: { + description: '`true` if the user is managed by ESS.', + }, + }, + }, + }); +} + +function sha256(str: string) { + return new Sha256().update(str, 'utf8').digest('hex'); +} diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index a7ce4e855962f..2a91479824062 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { CoreSetup, CoreStart, @@ -43,6 +44,7 @@ export interface PluginSetupDependencies { home?: HomePublicPluginSetup; management?: ManagementSetup; share?: SharePluginSetup; + cloud?: CloudSetup; } export interface PluginStartDependencies { @@ -51,6 +53,7 @@ export interface PluginStartDependencies { management?: ManagementStart; spaces?: SpacesPluginStart; share?: SharePluginStart; + cloud?: CloudStart; } export class SecurityPlugin @@ -81,7 +84,7 @@ export class SecurityPlugin public setup( core: CoreSetup<PluginStartDependencies>, - { home, licensing, management, share }: PluginSetupDependencies + { cloud, home, licensing, management, share }: PluginSetupDependencies ): SecurityPluginSetup { const { license } = this.securityLicenseService.setup({ license$: licensing.license$ }); @@ -106,7 +109,12 @@ export class SecurityPlugin securityApiClients: this.securityApiClients, }); - this.analyticsService.setup({ securityLicense: license }); + this.analyticsService.setup({ + analytics: core.analytics, + authc: this.authc, + cloudId: cloud?.cloudId, + securityLicense: license, + }); accountManagementApp.create({ authc: this.authc, diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 6c8b97b1e4b9b..10e3b99484f52 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -124,18 +124,9 @@ describe('Security Plugin', () => { "privilegeDeprecationsService": Object { "getKibanaRolesByFeatureId": [Function], }, - "setIsElasticCloudDeployment": [Function], } `); }); - - it('#setIsElasticCloudDeployment cannot be called twice', () => { - const { setIsElasticCloudDeployment } = plugin.setup(mockCoreSetup, mockSetupDependencies); - setIsElasticCloudDeployment(); - expect(() => setIsElasticCloudDeployment()).toThrowErrorMatchingInlineSnapshot( - `"The Elastic Cloud deployment flag has been set already!"` - ); - }); }); describe('start()', () => { diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 682c2eba56da9..9e5724b6a393b 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -8,6 +8,7 @@ import type { Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; +import type { CloudStart } from '@kbn/cloud-plugin/public'; import type { TypeOf } from '@kbn/config-schema'; import type { CoreSetup, @@ -88,12 +89,6 @@ export interface SecurityPluginSetup { * Exposes services to access kibana roles per feature id with the GetDeprecationsContext */ privilegeDeprecationsService: PrivilegeDeprecationsService; - - /** - * Sets the flag to indicate that Kibana is running inside an Elastic Cloud deployment. This flag is supposed to be - * set by the Cloud plugin and can be only once. - */ - setIsElasticCloudDeployment: () => void; } /** @@ -123,6 +118,7 @@ export interface PluginSetupDependencies { } export interface PluginStartDependencies { + cloud?: CloudStart; features: FeaturesPluginStart; licensing: LicensingPluginStart; taskManager: TaskManagerStartContract; @@ -206,21 +202,6 @@ export class SecurityPlugin return this.userProfileStart; }; - /** - * Indicates whether Kibana is running inside an Elastic Cloud deployment. Since circular plugin dependencies are - * forbidden, this flag is supposed to be set by the Cloud plugin that already depends on the Security plugin. - * @private - */ - private isElasticCloudDeployment?: boolean; - private readonly getIsElasticCloudDeployment = () => this.isElasticCloudDeployment === true; - private readonly setIsElasticCloudDeployment = () => { - if (this.isElasticCloudDeployment !== undefined) { - throw new Error(`The Elastic Cloud deployment flag has been set already!`); - } - - this.isElasticCloudDeployment = true; - }; - constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -374,13 +355,12 @@ export class SecurityPlugin license, logger: this.logger.get('deprecations'), }), - setIsElasticCloudDeployment: this.setIsElasticCloudDeployment, }); } public start( core: CoreStart, - { features, licensing, taskManager, spaces }: PluginStartDependencies + { cloud, features, licensing, taskManager, spaces }: PluginStartDependencies ) { this.logger.debug('Starting plugin'); @@ -413,7 +393,7 @@ export class SecurityPlugin session, applicationName: this.authorizationSetup!.applicationName, kibanaFeatures: features.getKibanaFeatures(), - isElasticCloudDeployment: this.getIsElasticCloudDeployment, + isElasticCloudDeployment: () => cloud?.isCloudEnabled === true, }); this.authorizationService.start({ diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index e4566248efc46..68c43cf64e6b6 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -8,6 +8,7 @@ }, "include": ["common/**/*", "public/**/*", "server/**/*"], "references": [ + { "path": "../cloud/tsconfig.json" }, { "path": "../features/tsconfig.json" }, { "path": "../licensing/tsconfig.json" }, { "path": "../spaces/tsconfig.json" }, diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index da2072ab3d605..a5b6ae699c723 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -33811,11 +33811,6 @@ "visTypeTagCloud.visParams.orientationsLabel": "Orientations", "visTypeTagCloud.visParams.showLabelToggleLabel": "Afficher l'Ă©tiquette", "visTypeTagCloud.visParams.textScaleLabel": "Échelle de texte", - "xpack.cloud.chat.chatFrameTitle": "Chat", - "xpack.cloud.chat.hideChatButtonLabel": "Masquer le chat", - "xpack.cloud.deploymentLinkLabel": "GĂ©rer ce dĂ©ploiement", - "xpack.cloud.userMenuLinks.accountLinkText": "Compte et facturation", - "xpack.cloud.userMenuLinks.profileLinkText": "Modifier le profil", "xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard": "Choisir le tableau de bord de destination", "xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab": "Ouvrir le tableau de bord dans un nouvel onglet", "xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange": "Utiliser la plage de dates du tableau de bord d'origine", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0238b9e9add28..72e20dc6efa37 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -33785,11 +33785,6 @@ "visTypeTagCloud.visParams.orientationsLabel": "æ–č搑", "visTypeTagCloud.visParams.showLabelToggleLabel": "ăƒ©ăƒ™ăƒ«ă‚’èĄšç€ș", "visTypeTagCloud.visParams.textScaleLabel": "テキă‚čトă‚čă‚±ăƒŒăƒ«", - "xpack.cloud.chat.chatFrameTitle": "チャット", - "xpack.cloud.chat.hideChatButtonLabel": "ă‚°ăƒ©ăƒ•ă‚’éžèĄšç€ș", - "xpack.cloud.deploymentLinkLabel": "ă“ăźăƒ‡ăƒ—ăƒ­ă‚€ăźçźĄç†", - "xpack.cloud.userMenuLinks.accountLinkText": "äŒšèšˆăƒ»è«‹æ±‚", - "xpack.cloud.userMenuLinks.profileLinkText": "ăƒ—ăƒ­ăƒ•ă‚ŁăƒŒăƒ«ă‚’ç·šé›†", "xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard": "ćŻŸè±Ąăƒ€ăƒƒă‚·ăƒ„ăƒœăƒŒăƒ‰ă‚’éžæŠž", "xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab": "æ–°ă—ă„ă‚żăƒ–ă§ăƒ€ăƒƒă‚·ăƒ„ăƒœăƒŒăƒ‰ă‚’é–‹ă", "xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange": "ć…ƒăźăƒ€ăƒƒă‚·ăƒ„ăƒœăƒŒăƒ‰ă‹ă‚‰æ—„ä»˜çŻ„ć›Čă‚’äœżç”š", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d555235d7ad76..bf90d943ab06b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -33822,11 +33822,6 @@ "visTypeTagCloud.visParams.orientationsLabel": "æ–č搑", "visTypeTagCloud.visParams.showLabelToggleLabel": "星ç€ș标筟", "visTypeTagCloud.visParams.textScaleLabel": "æ–‡æœŹæŻ”äŸ‹", - "xpack.cloud.chat.chatFrameTitle": "èŠć€©", - "xpack.cloud.chat.hideChatButtonLabel": "éšè—èŠć€©", - "xpack.cloud.deploymentLinkLabel": "çźĄç†æ­€éƒšçœČ", - "xpack.cloud.userMenuLinks.accountLinkText": "ćžæˆ·ć’Œćžć•", - "xpack.cloud.userMenuLinks.profileLinkText": "çŒ–èŸ‘é…çœźæ–‡ä»¶", "xpack.dashboard.components.DashboardDrilldownConfig.chooseDestinationDashboard": "é€‰æ‹©ç›źæ ‡ä»ȘèĄšæż", "xpack.dashboard.components.DashboardDrilldownConfig.openInNewTab": "ćœšæ–°é€‰éĄčćĄäž­æ‰“ćŒ€ä»ȘèĄšæż", "xpack.dashboard.components.DashboardDrilldownConfig.useCurrentDateRange": "äœżç”šæșä»ȘèĄšæżçš„æ—„æœŸèŒƒć›Ž", From b8c17b022704f146be8f066edc5fcea39a39cfc2 Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:56:28 +0200 Subject: [PATCH 021/174] [Fleet] Allow agent force upgrading to a newer patch release (#142450) --- .../routes/agent/upgrade_handler.test.ts | 14 ++++++ .../server/routes/agent/upgrade_handler.ts | 46 ++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts index 5eafcf1a94104..aefcbfc5cd87f 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.test.ts @@ -22,5 +22,19 @@ describe('upgrade handler', () => { it('should not throw if upgrade version is equal to kibana version with snapshot', () => { expect(() => checkKibanaVersion('8.4.0', '8.4.0-SNAPSHOT')).not.toThrowError(); }); + + it('should not throw if force is specified and patch is newer', () => { + expect(() => checkKibanaVersion('8.4.1', '8.4.0', true)).not.toThrowError(); + expect(() => checkKibanaVersion('8.4.1-SNAPSHOT', '8.4.0', true)).not.toThrowError(); + }); + + it('should throw if force is specified and minor is newer', () => { + expect(() => checkKibanaVersion('8.5.0', '8.4.0', true)).toThrowError(); + }); + + it('should not throw if force is specified and major and minor is newer', () => { + expect(() => checkKibanaVersion('7.5.0', '8.4.0', true)).not.toThrowError(); + expect(() => checkKibanaVersion('8.4.0', '8.4.0', true)).not.toThrowError(); + }); }); }); diff --git a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts index a79edbaa36856..d3fffac7d9050 100644 --- a/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts +++ b/x-pack/plugins/fleet/server/routes/agent/upgrade_handler.ts @@ -10,6 +10,8 @@ import type { TypeOf } from '@kbn/config-schema'; import semverCoerce from 'semver/functions/coerce'; import semverGt from 'semver/functions/gt'; +import semverMajor from 'semver/functions/major'; +import semverMinor from 'semver/functions/minor'; import type { PostAgentUpgradeResponse, GetCurrentUpgradesResponse } from '../../../common/types'; import type { PostAgentUpgradeRequestSchema, PostBulkAgentUpgradeRequestSchema } from '../../types'; @@ -34,7 +36,7 @@ export const postAgentUpgradeHandler: RequestHandler< const { version, source_uri: sourceUri, force } = request.body; const kibanaVersion = appContextService.getKibanaVersion(); try { - checkKibanaVersion(version, kibanaVersion); + checkKibanaVersion(version, kibanaVersion, force); } catch (err) { return response.customError({ statusCode: 400, @@ -114,9 +116,9 @@ export const postBulkAgentsUpgradeHandler: RequestHandler< } = request.body; const kibanaVersion = appContextService.getKibanaVersion(); try { - checkKibanaVersion(version, kibanaVersion); + checkKibanaVersion(version, kibanaVersion, force); const fleetServerAgents = await getAllFleetServerAgents(soClient, esClient); - checkFleetServerVersion(version, fleetServerAgents); + checkFleetServerVersion(version, fleetServerAgents, force); } catch (err) { return response.customError({ statusCode: 400, @@ -158,7 +160,7 @@ export const getCurrentUpgradesHandler: RequestHandler = async (context, request } }; -export const checkKibanaVersion = (version: string, kibanaVersion: string) => { +export const checkKibanaVersion = (version: string, kibanaVersion: string, force = false) => { // get version number only in case "-SNAPSHOT" is in it const kibanaVersionNumber = semverCoerce(kibanaVersion)?.version; if (!kibanaVersionNumber) throw new Error(`kibanaVersion ${kibanaVersionNumber} is not valid`); @@ -166,14 +168,31 @@ export const checkKibanaVersion = (version: string, kibanaVersion: string) => { if (!versionToUpgradeNumber) throw new Error(`version to upgrade ${versionToUpgradeNumber} is not valid`); - if (semverGt(versionToUpgradeNumber, kibanaVersionNumber)) + if (!force && semverGt(versionToUpgradeNumber, kibanaVersionNumber)) { throw new Error( `cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the installed kibana version ${kibanaVersionNumber}` ); + } + + const kibanaMajorGt = semverMajor(kibanaVersionNumber) > semverMajor(versionToUpgradeNumber); + const kibanaMajorEqMinorGte = + semverMajor(kibanaVersionNumber) === semverMajor(versionToUpgradeNumber) && + semverMinor(kibanaVersionNumber) >= semverMinor(versionToUpgradeNumber); + + // When force is enabled, only the major and minor versions are checked + if (force && !(kibanaMajorGt || kibanaMajorEqMinorGte)) { + throw new Error( + `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisfy the major and minor of the installed kibana version ${kibanaVersionNumber}` + ); + } }; // Check the installed fleet server version -const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgents: Agent[]) => { +const checkFleetServerVersion = ( + versionToUpgradeNumber: string, + fleetServerAgents: Agent[], + force = false +) => { const fleetServerVersions = fleetServerAgents.map( (agent) => agent.local_metadata.elastic.agent.version ) as string[]; @@ -184,9 +203,22 @@ const checkFleetServerVersion = (versionToUpgradeNumber: string, fleetServerAgen return; } - if (semverGt(versionToUpgradeNumber, maxFleetServerVersion)) { + if (!force && semverGt(versionToUpgradeNumber, maxFleetServerVersion)) { throw new Error( `cannot upgrade agent to ${versionToUpgradeNumber} because it is higher than the latest fleet server version ${maxFleetServerVersion}` ); } + + const fleetServerMajorGt = + semverMajor(maxFleetServerVersion) > semverMajor(versionToUpgradeNumber); + const fleetServerMajorEqMinorGte = + semverMajor(maxFleetServerVersion) === semverMajor(versionToUpgradeNumber) && + semverMinor(maxFleetServerVersion) >= semverMinor(versionToUpgradeNumber); + + // When force is enabled, only the major and minor versions are checked + if (force && !(fleetServerMajorGt || fleetServerMajorEqMinorGte)) { + throw new Error( + `cannot force upgrade agent to ${versionToUpgradeNumber} because it does not satisfy the major and minor of the latest fleet server version ${maxFleetServerVersion}` + ); + } }; From a43f235c28de70d3bb11dde99e7fb4c76a99cb0e Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid <awahab07@yahoo.com> Date: Tue, 4 Oct 2022 12:58:35 +0200 Subject: [PATCH 022/174] Fix: Render 404 step screenshots as "Image not available". (#142320) By default, mark `allStepsLoading = true` where steps loading isn't important. --- .../monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx index 4cee2eb9bfca8..ed3f5499ba0ab 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx @@ -41,7 +41,7 @@ export const PingTimestamp = ({ label, checkGroup, stepStatus, - allStepsLoaded, + allStepsLoaded = true, initialStepNo = 1, }: Props) => { const [stepNumber, setStepNumber] = useState(initialStepNo); From d60acf8774ac210e842e8ee39336a54205dae3de Mon Sep 17 00:00:00 2001 From: Milton Hultgren <milton.hultgren@elastic.co> Date: Tue, 4 Oct 2022 13:11:11 +0200 Subject: [PATCH 023/174] [Infra] Make nav react to Hosts view enabled flag changing (#142477) * [Infra] Make nav react to Hosts view enabled flag changing (#140996) * Move comment to more relevant location Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/infra/public/plugin.ts | 50 +++++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/infra/public/plugin.ts b/x-pack/plugins/infra/public/plugin.ts index 6eb6663b10eeb..c14a13d1a7ea1 100644 --- a/x-pack/plugins/infra/public/plugin.ts +++ b/x-pack/plugins/infra/public/plugin.ts @@ -5,12 +5,17 @@ * 2.0. */ +import { + AppMountParameters, + AppUpdater, + CoreStart, + DEFAULT_APP_CATEGORIES, + PluginInitializerContext, +} from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { AppMountParameters, PluginInitializerContext } from '@kbn/core/public'; -import { from } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { enableInfrastructureHostsView } from '@kbn/observability-plugin/public'; +import { BehaviorSubject, combineLatest, from } from 'rxjs'; +import { map } from 'rxjs/operators'; import { defaultLogViewsStaticConfig } from '../common/log_views'; import { InfraPublicConfig } from '../common/plugin_config_types'; import { createInventoryMetricRuleType } from './alerting/inventory'; @@ -38,6 +43,7 @@ import { getLogsHasDataFetcher, getLogsOverviewDataFetcher } from './utils/logs_ export class Plugin implements InfraClientPluginClass { public config: InfraPublicConfig; private logViews: LogViewsService; + private readonly appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({})); constructor(context: PluginInitializerContext<InfraPublicConfig>) { this.config = context.config.get(); @@ -74,6 +80,11 @@ export class Plugin implements InfraClientPluginClass { fetchData: createMetricsFetchData(core.getStartServices), }); + const startDep$AndHostViewFlag$ = combineLatest([ + from(core.getStartServices()), + core.uiSettings.get$<boolean>(enableInfrastructureHostsView), + ]); + /** !! Need to be kept in sync with the deepLinks in x-pack/plugins/infra/public/plugin.ts */ const infraEntries = [ { label: 'Inventory', app: 'metrics', path: '/inventory' }, @@ -81,12 +92,15 @@ export class Plugin implements InfraClientPluginClass { ]; const hostInfraEntry = { label: 'Hosts', app: 'metrics', path: '/hosts' }; pluginsSetup.observability.navigation.registerSections( - from(core.getStartServices()).pipe( + startDep$AndHostViewFlag$.pipe( map( ([ - { - application: { capabilities }, - }, + [ + { + application: { capabilities }, + }, + ], + isInfrastructureHostsViewEnabled, ]) => [ ...(capabilities.logs.show ? [ @@ -106,7 +120,7 @@ export class Plugin implements InfraClientPluginClass { { label: 'Infrastructure', sortKey: 300, - entries: core.uiSettings.get(enableInfrastructureHostsView) + entries: isInfrastructureHostsViewEnabled ? [hostInfraEntry, ...infraEntries] : infraEntries, }, @@ -171,6 +185,7 @@ export class Plugin implements InfraClientPluginClass { }, }); + // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx const infraDeepLinks = [ { id: 'inventory', @@ -210,8 +225,8 @@ export class Plugin implements InfraClientPluginClass { order: 8200, appRoute: '/app/metrics', category: DEFAULT_APP_CATEGORIES.observability, - // !! Need to be kept in sync with the routes in x-pack/plugins/infra/public/pages/metrics/index.tsx - deepLinks: core.uiSettings.get(enableInfrastructureHostsView) + updater$: this.appUpdater$, + deepLinks: core.uiSettings.get<boolean>(enableInfrastructureHostsView) ? [hostInfraDeepLink, ...infraDeepLinks] : infraDeepLinks, mount: async (params: AppMountParameters) => { @@ -223,6 +238,19 @@ export class Plugin implements InfraClientPluginClass { }, }); + startDep$AndHostViewFlag$.subscribe( + ([_startServices, isInfrastructureHostsViewEnabled]: [ + [CoreStart, InfraClientStartDeps, InfraClientStartExports], + boolean + ]) => { + this.appUpdater$.next(() => ({ + deepLinks: isInfrastructureHostsViewEnabled + ? [hostInfraDeepLink, ...infraDeepLinks] + : infraDeepLinks, + })); + } + ); + /* This exists purely to facilitate URL redirects from the old App ID ("infra"), to our new App IDs ("metrics" and "logs"). With version 8.0.0 we can remove this. */ core.application.register({ From 0e6fa006c183ffdcd89ef25aacd6b4a04fdf2267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Loix?= <sebastien.loix@elastic.co> Date: Tue, 4 Oct 2022 12:19:22 +0100 Subject: [PATCH 024/174] [SavedObjectClient] Add support to exclude references when searching (#142479) --- docs/api/saved-objects/find.asciidoc | 9 + .../src/lib/repository.test.ts | 4 + .../src/lib/repository.ts | 4 + .../src/lib/search_dsl/query_params.test.ts | 51 ++- .../src/lib/search_dsl/query_params.ts | 33 +- .../lib/search_dsl/references_filter.test.ts | 347 +++++++++++++----- .../src/lib/search_dsl/references_filter.ts | 40 +- .../src/lib/search_dsl/search_dsl.test.ts | 9 +- .../src/lib/search_dsl/search_dsl.ts | 11 +- .../src/apis/find.ts | 12 + .../src/saved_objects_client.test.ts | 2 + .../src/saved_objects_client.ts | 5 + .../src/routes/find.ts | 6 + .../saved_objects/routes/find.test.ts | 68 ++++ .../apis/saved_objects/find.ts | 125 +++++++ 15 files changed, 601 insertions(+), 125 deletions(-) diff --git a/docs/api/saved-objects/find.asciidoc b/docs/api/saved-objects/find.asciidoc index 43c7f4cde8fa8..275bd1c21f9ed 100644 --- a/docs/api/saved-objects/find.asciidoc +++ b/docs/api/saved-objects/find.asciidoc @@ -52,6 +52,15 @@ experimental[] Retrieve a paginated set of {kib} saved objects by various condit `has_reference`:: (Optional, object) Filters to objects that have a relationship with the type and ID combination. +`has_reference_operator`:: + (Optional, string) The operator to use for the `has_reference` parameter. Either `OR` or `AND`. Defaults to `OR`. + +`has_no_reference`:: + (Optional, object) Filters to objects that do not have a relationship with the type and ID combination. + +`has_no_reference_operator`:: + (Optional, string) The operator to use for the `has_no_reference` parameter. Either `OR` or `AND`. Defaults to `OR`. + `filter`:: (Optional, string) The filter is a KQL string with the caveat that if you filter with an attribute from your saved object type, it should look like that: `savedObjectType.attributes.title: "myTitle"`. However, If you use a root attribute of a saved diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index 0739c9acab8f5..d9a65f984c222 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -4182,6 +4182,10 @@ describe('SavedObjectsRepository', () => { type: 'foo', id: '1', }, + hasNoReference: { + type: 'bar', + id: '1', + }, }; it(`passes mappings, registry, and search options to getSearchDsl`, async () => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 5569141c7fa0e..f48e031bd23c4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -1129,6 +1129,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { rootSearchFields, hasReference, hasReferenceOperator, + hasNoReference, + hasNoReferenceOperator, page = FIND_DEFAULT_PAGE, perPage = FIND_DEFAULT_PER_PAGE, pit, @@ -1235,6 +1237,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { typeToNamespacesMap, hasReference, hasReferenceOperator, + hasNoReference, + hasNoReferenceOperator, kueryNode, }), }, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts index c502665468e6c..20ce3a2f46b2e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.test.ts @@ -195,17 +195,18 @@ describe('#getQueryParams', () => { }); }); - describe('reference filter clause', () => { - describe('`hasReference` parameter', () => { - it('does not call `getReferencesFilter` when `hasReference` is not specified', () => { - getQueryParams({ - registry, - hasReference: undefined, - }); - - expect(getReferencesFilterMock).not.toHaveBeenCalled(); + describe('reference/noreference filter clause', () => { + it('does not call `getReferencesFilter` when neither `hasReference` nor `hasNoReference` are specified', () => { + getQueryParams({ + registry, + hasReference: undefined, + hasNoReference: undefined, }); + expect(getReferencesFilterMock).not.toHaveBeenCalled(); + }); + + describe('`hasReference` parameter', () => { it('calls `getReferencesFilter` with the correct parameters', () => { const hasReference = { id: 'foo', type: 'bar' }; getQueryParams({ @@ -235,6 +236,38 @@ describe('#getQueryParams', () => { expect(filters.some((filter) => filter.references_filter === true)).toBeDefined(); }); }); + + describe('`hasNoReference` parameter', () => { + it('calls `getReferencesFilter` with the correct parameters', () => { + const hasNoReference = { id: 'noFoo', type: 'bar' }; + getQueryParams({ + registry, + hasNoReference, + hasNoReferenceOperator: 'AND', + }); + + expect(getReferencesFilterMock).toHaveBeenCalledTimes(1); + expect(getReferencesFilterMock).toHaveBeenCalledWith({ + must: false, + references: [hasNoReference], + operator: 'AND', + }); + }); + + it('includes the return of `getReferencesFilter` in the `filter` clause', () => { + getReferencesFilterMock.mockReturnValue({ references_filter: true }); + + const hasNoReference = { id: 'noFoo', type: 'bar' }; + const result = getQueryParams({ + registry, + hasNoReference, + hasReferenceOperator: 'AND', + }); + + const filters: any[] = result.query.bool.filter; + expect(filters.some((filter) => filter.references_filter === true)).toBeDefined(); + }); + }); }); describe('type filter clauses', () => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts index 669f2a273569b..896b934c90b80 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts @@ -7,6 +7,7 @@ */ import * as esKuery from '@kbn/es-query'; +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; type KueryNode = any; @@ -123,11 +124,6 @@ function getClauseForType( }; } -export interface HasReferenceQueryParams { - type: string; - id: string; -} - export type SearchOperator = 'AND' | 'OR'; interface QueryParams { @@ -139,8 +135,10 @@ interface QueryParams { defaultSearchOperator?: SearchOperator; searchFields?: string[]; rootSearchFields?: string[]; - hasReference?: HasReferenceQueryParams | HasReferenceQueryParams[]; + hasReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[]; hasReferenceOperator?: SearchOperator; + hasNoReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[]; + hasNoReferenceOperator?: SearchOperator; kueryNode?: KueryNode; } @@ -148,6 +146,13 @@ interface QueryParams { const uniqNamespaces = (namespacesToNormalize?: string[]) => namespacesToNormalize ? Array.from(new Set(namespacesToNormalize)) : undefined; +const toArray = (val: unknown) => { + if (typeof val === 'undefined') { + return val; + } + return !Array.isArray(val) ? [val] : val; +}; + /** * Get the "query" related keys for the search body */ @@ -162,6 +167,8 @@ export function getQueryParams({ defaultSearchOperator, hasReference, hasReferenceOperator, + hasNoReference, + hasNoReferenceOperator, kueryNode, }: QueryParams) { const types = getTypes( @@ -169,9 +176,8 @@ export function getQueryParams({ typeToNamespacesMap ? Array.from(typeToNamespacesMap.keys()) : type ); - if (hasReference && !Array.isArray(hasReference)) { - hasReference = [hasReference]; - } + hasReference = toArray(hasReference); + hasNoReference = toArray(hasNoReference); const bool: any = { filter: [ @@ -184,6 +190,15 @@ export function getQueryParams({ }), ] : []), + ...(hasNoReference?.length + ? [ + getReferencesFilter({ + references: hasNoReference, + operator: hasNoReferenceOperator, + must: false, + }), + ] + : []), { bool: { should: types.map((shouldType) => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts index 9a042579c8e8f..127f3a94edd21 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.test.ts @@ -20,29 +20,141 @@ describe('getReferencesFilter', () => { }, }); - describe('when using the `OR` operator', () => { - it('generates one `should` clause per type of reference', () => { + describe('for "must" match clauses', () => { + describe('when using the `OR` operator', () => { + it('generates one `should` clause per type of reference', () => { + const references = [ + { type: 'foo', id: 'foo-1' }, + { type: 'foo', id: 'foo-2' }, + { type: 'foo', id: 'foo-3' }, + { type: 'bar', id: 'bar-1' }, + { type: 'bar', id: 'bar-2' }, + ]; + const clause = getReferencesFilter({ + references, + operator: 'OR', + }); + + expect(clause).toEqual({ + bool: { + should: [ + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-1', 'foo-2', 'foo-3'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['bar-1', 'bar-2'] } }, + { term: { 'references.type': 'bar' } }, + ]), + ], + minimum_should_match: 1, + }, + }); + }); + + it('does not include more than `maxTermsPerClause` per `terms` clauses', () => { + const references = [ + { type: 'foo', id: 'foo-1' }, + { type: 'foo', id: 'foo-2' }, + { type: 'foo', id: 'foo-3' }, + { type: 'foo', id: 'foo-4' }, + { type: 'foo', id: 'foo-5' }, + { type: 'bar', id: 'bar-1' }, + { type: 'bar', id: 'bar-2' }, + { type: 'bar', id: 'bar-3' }, + { type: 'dolly', id: 'dolly-1' }, + ]; + const clause = getReferencesFilter({ + references, + operator: 'OR', + maxTermsPerClause: 2, + }); + + expect(clause).toEqual({ + bool: { + should: [ + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-1', 'foo-2'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-3', 'foo-4'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-5'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['bar-1', 'bar-2'] } }, + { term: { 'references.type': 'bar' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['bar-3'] } }, + { term: { 'references.type': 'bar' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['dolly-1'] } }, + { term: { 'references.type': 'dolly' } }, + ]), + ], + minimum_should_match: 1, + }, + }); + }); + }); + + describe('when using the `AND` operator', () => { + it('generates one `must` clause per reference', () => { + const references = [ + { type: 'foo', id: 'foo-1' }, + { type: 'foo', id: 'foo-2' }, + { type: 'bar', id: 'bar-1' }, + ]; + + const clause = getReferencesFilter({ + references, + operator: 'AND', + }); + + expect(clause).toEqual({ + bool: { + must: references.map((ref) => ({ + nested: { + path: 'references', + query: { + bool: { + must: [ + { term: { 'references.id': ref.id } }, + { term: { 'references.type': ref.type } }, + ], + }, + }, + }, + })), + }, + }); + }); + }); + + it('defaults to using the `OR` operator', () => { const references = [ { type: 'foo', id: 'foo-1' }, - { type: 'foo', id: 'foo-2' }, - { type: 'foo', id: 'foo-3' }, { type: 'bar', id: 'bar-1' }, - { type: 'bar', id: 'bar-2' }, ]; const clause = getReferencesFilter({ references, - operator: 'OR', }); expect(clause).toEqual({ bool: { should: [ nestedRefMustClauses([ - { terms: { 'references.id': ['foo-1', 'foo-2', 'foo-3'] } }, + { terms: { 'references.id': ['foo-1'] } }, { term: { 'references.type': 'foo' } }, ]), nestedRefMustClauses([ - { terms: { 'references.id': ['bar-1', 'bar-2'] } }, + { terms: { 'references.id': ['bar-1'] } }, { term: { 'references.type': 'bar' } }, ]), ], @@ -50,115 +162,156 @@ describe('getReferencesFilter', () => { }, }); }); + }); + + describe('for "must_not" match clauses', () => { + describe('when using the `OR` operator', () => { + it('generates one `must_not` clause per type of reference', () => { + const references = [ + { type: 'foo', id: 'foo-1' }, + { type: 'foo', id: 'foo-2' }, + { type: 'foo', id: 'foo-3' }, + { type: 'bar', id: 'bar-1' }, + { type: 'bar', id: 'bar-2' }, + ]; + const clause = getReferencesFilter({ + references, + operator: 'OR', + must: false, + }); + + expect(clause).toEqual({ + bool: { + must_not: [ + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-1', 'foo-2', 'foo-3'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['bar-1', 'bar-2'] } }, + { term: { 'references.type': 'bar' } }, + ]), + ], + }, + }); + }); + + it('does not include more than `maxTermsPerClause` per `terms` clauses', () => { + const references = [ + { type: 'foo', id: 'foo-1' }, + { type: 'foo', id: 'foo-2' }, + { type: 'foo', id: 'foo-3' }, + { type: 'foo', id: 'foo-4' }, + { type: 'foo', id: 'foo-5' }, + { type: 'bar', id: 'bar-1' }, + { type: 'bar', id: 'bar-2' }, + { type: 'bar', id: 'bar-3' }, + { type: 'dolly', id: 'dolly-1' }, + ]; + const clause = getReferencesFilter({ + references, + operator: 'OR', + maxTermsPerClause: 2, + must: false, + }); + + expect(clause).toEqual({ + bool: { + must_not: [ + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-1', 'foo-2'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-3', 'foo-4'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['foo-5'] } }, + { term: { 'references.type': 'foo' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['bar-1', 'bar-2'] } }, + { term: { 'references.type': 'bar' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['bar-3'] } }, + { term: { 'references.type': 'bar' } }, + ]), + nestedRefMustClauses([ + { terms: { 'references.id': ['dolly-1'] } }, + { term: { 'references.type': 'dolly' } }, + ]), + ], + }, + }); + }); + }); - it('does not include mode than `maxTermsPerClause` per `terms` clauses', () => { + describe('when using the `AND` operator', () => { + it('generates one `must` clause per reference', () => { + const references = [ + { type: 'foo', id: 'foo-1' }, + { type: 'foo', id: 'foo-2' }, + { type: 'bar', id: 'bar-1' }, + ]; + + const clause = getReferencesFilter({ + references, + operator: 'AND', + must: false, + }); + + expect(clause).toEqual({ + bool: { + must_not: [ + { + bool: { + must: references.map((ref) => ({ + nested: { + path: 'references', + query: { + bool: { + must: [ + { term: { 'references.id': ref.id } }, + { term: { 'references.type': ref.type } }, + ], + }, + }, + }, + })), + }, + }, + ], + }, + }); + }); + }); + + it('defaults to using the `OR` operator', () => { const references = [ { type: 'foo', id: 'foo-1' }, - { type: 'foo', id: 'foo-2' }, - { type: 'foo', id: 'foo-3' }, - { type: 'foo', id: 'foo-4' }, - { type: 'foo', id: 'foo-5' }, { type: 'bar', id: 'bar-1' }, - { type: 'bar', id: 'bar-2' }, - { type: 'bar', id: 'bar-3' }, - { type: 'dolly', id: 'dolly-1' }, ]; const clause = getReferencesFilter({ references, - operator: 'OR', - maxTermsPerClause: 2, + must: false, }); expect(clause).toEqual({ bool: { - should: [ - nestedRefMustClauses([ - { terms: { 'references.id': ['foo-1', 'foo-2'] } }, - { term: { 'references.type': 'foo' } }, - ]), + must_not: [ nestedRefMustClauses([ - { terms: { 'references.id': ['foo-3', 'foo-4'] } }, + { terms: { 'references.id': ['foo-1'] } }, { term: { 'references.type': 'foo' } }, ]), nestedRefMustClauses([ - { terms: { 'references.id': ['foo-5'] } }, - { term: { 'references.type': 'foo' } }, - ]), - nestedRefMustClauses([ - { terms: { 'references.id': ['bar-1', 'bar-2'] } }, + { terms: { 'references.id': ['bar-1'] } }, { term: { 'references.type': 'bar' } }, ]), - nestedRefMustClauses([ - { terms: { 'references.id': ['bar-3'] } }, - { term: { 'references.type': 'bar' } }, - ]), - nestedRefMustClauses([ - { terms: { 'references.id': ['dolly-1'] } }, - { term: { 'references.type': 'dolly' } }, - ]), ], - minimum_should_match: 1, - }, - }); - }); - }); - - describe('when using the `AND` operator', () => { - it('generates one `must` clause per reference', () => { - const references = [ - { type: 'foo', id: 'foo-1' }, - { type: 'foo', id: 'foo-2' }, - { type: 'bar', id: 'bar-1' }, - ]; - - const clause = getReferencesFilter({ - references, - operator: 'AND', - }); - - expect(clause).toEqual({ - bool: { - must: references.map((ref) => ({ - nested: { - path: 'references', - query: { - bool: { - must: [ - { term: { 'references.id': ref.id } }, - { term: { 'references.type': ref.type } }, - ], - }, - }, - }, - })), }, }); }); }); - - it('defaults to using the `OR` operator', () => { - const references = [ - { type: 'foo', id: 'foo-1' }, - { type: 'bar', id: 'bar-1' }, - ]; - const clause = getReferencesFilter({ - references, - }); - - expect(clause).toEqual({ - bool: { - should: [ - nestedRefMustClauses([ - { terms: { 'references.id': ['foo-1'] } }, - { term: { 'references.type': 'foo' } }, - ]), - nestedRefMustClauses([ - { terms: { 'references.id': ['bar-1'] } }, - { term: { 'references.type': 'bar' } }, - ]), - ], - minimum_should_match: 1, - }, - }); - }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts index b0849560d2e43..4dd6bc640f174 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts @@ -6,35 +6,61 @@ * Side Public License, v 1. */ -import type { HasReferenceQueryParams, SearchOperator } from './query_params'; +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; + +import type { SearchOperator } from './query_params'; export function getReferencesFilter({ references, operator = 'OR', maxTermsPerClause = 1000, + must = true, }: { - references: HasReferenceQueryParams[]; + references: SavedObjectTypeIdTuple[]; operator?: SearchOperator; maxTermsPerClause?: number; + must?: boolean; }) { if (operator === 'AND') { + if (must) { + return { + bool: { + must: references.map(getNestedTermClauseForReference), + }, + }; + } + return { bool: { - must: references.map(getNestedTermClauseForReference), + must_not: [ + { + bool: { + must: references.map(getNestedTermClauseForReference), + }, + }, + ], }, }; } else { + if (must) { + return { + bool: { + should: getAggregatedTermsClauses(references, maxTermsPerClause), + minimum_should_match: 1, + }, + }; + } + return { bool: { - should: getAggregatedTermsClauses(references, maxTermsPerClause), - minimum_should_match: 1, + must_not: getAggregatedTermsClauses(references, maxTermsPerClause), }, }; } } const getAggregatedTermsClauses = ( - references: HasReferenceQueryParams[], + references: SavedObjectTypeIdTuple[], maxTermsPerClause: number ) => { const refTypeToIds = references.reduce((map, { type, id }) => { @@ -58,7 +84,7 @@ const createChunks = <T>(array: T[], chunkSize: number): T[][] => { return chunks; }; -export const getNestedTermClauseForReference = (reference: HasReferenceQueryParams) => { +export const getNestedTermClauseForReference = (reference: SavedObjectTypeIdTuple) => { return { nested: { path: 'references', diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts index d1ed7251b2414..84ef7c232d775 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.test.ts @@ -49,7 +49,7 @@ describe('getSearchDsl', () => { }); describe('passes control', () => { - it('passes (mappings, schema, namespaces, type, typeToNamespacesMap, search, searchFields, rootSearchFields, hasReference, hasReferenceOperator) to getQueryParams', () => { + it('passes (mappings, schema, namespaces, type, typeToNamespacesMap, search, searchFields, rootSearchFields, hasReference, hasReferenceOperator, hasNoReference, hasNoReferenceOperator) to getQueryParams', () => { const opts = { namespaces: ['foo-namespace'], type: 'foo', @@ -63,6 +63,11 @@ describe('getSearchDsl', () => { id: '1', }, hasReferenceOperator: 'AND' as queryParamsNS.SearchOperator, + hasNoReference: { + type: 'noBar', + id: '1', + }, + hasNoReferenceOperator: 'AND' as queryParamsNS.SearchOperator, }; getSearchDsl(mappings, registry, opts); @@ -78,6 +83,8 @@ describe('getSearchDsl', () => { defaultSearchOperator: opts.defaultSearchOperator, hasReference: opts.hasReference, hasReferenceOperator: opts.hasReferenceOperator, + hasNoReference: opts.hasNoReference, + hasNoReferenceOperator: opts.hasNoReferenceOperator, }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts index 980bf800755b9..381f20069d25a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts @@ -12,7 +12,8 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import { getQueryParams, HasReferenceQueryParams, SearchOperator } from './query_params'; +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { getQueryParams, SearchOperator } from './query_params'; import { getPitParams } from './pit_params'; import { getSortingParams } from './sorting_params'; @@ -30,8 +31,10 @@ interface GetSearchDslOptions { namespaces?: string[]; pit?: SavedObjectsPitParams; typeToNamespacesMap?: Map<string, string[] | undefined>; - hasReference?: HasReferenceQueryParams | HasReferenceQueryParams[]; + hasReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[]; hasReferenceOperator?: SearchOperator; + hasNoReference?: SavedObjectTypeIdTuple | SavedObjectTypeIdTuple[]; + hasNoReferenceOperator?: SearchOperator; kueryNode?: KueryNode; } @@ -54,6 +57,8 @@ export function getSearchDsl( typeToNamespacesMap, hasReference, hasReferenceOperator, + hasNoReference, + hasNoReferenceOperator, kueryNode, } = options; @@ -77,6 +82,8 @@ export function getSearchDsl( defaultSearchOperator, hasReference, hasReferenceOperator, + hasNoReference, + hasNoReferenceOperator, kueryNode, }), ...getSortingParams(mappings, type, sortField, sortOrder), diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts index 49042029f334b..a50506c96c8e5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts @@ -66,11 +66,23 @@ export interface SavedObjectsFindOptions { * Use `hasReferenceOperator` to specify the operator to use when searching for multiple references. */ hasReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[]; + /** * The operator to use when searching by multiple references using the `hasReference` option. Defaults to `OR` */ hasReferenceOperator?: 'AND' | 'OR'; + /** + * Search for documents *not* having a reference to the specified objects. + * Use `hasNoReferenceOperator` to specify the operator to use when searching for multiple references. + */ + hasNoReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[]; + + /** + * The operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR` + */ + hasNoReferenceOperator?: 'AND' | 'OR'; + /** * The search operator to use with the provided filter. Defaults to `OR` */ diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts index 6c2966ee9775f..7825b09cf29bd 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.test.ts @@ -612,6 +612,7 @@ describe('SavedObjectsClient', () => { defaultSearchOperator: 'OR' as const, fields: ['title'], hasReference: { id: '1', type: 'reference' }, + hasNoReference: { id: '1', type: 'reference' }, page: 10, perPage: 100, search: 'what is the meaning of life?|life', @@ -633,6 +634,7 @@ describe('SavedObjectsClient', () => { "fields": Array [ "title", ], + "has_no_reference": "{\\"id\\":\\"1\\",\\"type\\":\\"reference\\"}", "has_reference": "{\\"id\\":\\"1\\",\\"type\\":\\"reference\\"}", "page": 10, "per_page": 100, diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts index dd2feed58123f..1fd111186f551 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts @@ -292,6 +292,8 @@ export class SavedObjectsClient implements SavedObjectsClientContract { fields: 'fields', hasReference: 'has_reference', hasReferenceOperator: 'has_reference_operator', + hasNoReference: 'has_no_reference', + hasNoReferenceOperator: 'has_no_reference_operator', page: 'page', perPage: 'per_page', search: 'search', @@ -315,6 +317,9 @@ export class SavedObjectsClient implements SavedObjectsClientContract { if (query.has_reference) { query.has_reference = JSON.stringify(query.has_reference); } + if (query.has_no_reference) { + query.has_no_reference = JSON.stringify(query.has_no_reference); + } // `aggs` is a structured object. we need to stringify it before sending it, as `fetch` // is not doing it implicitly. diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts index 4587cb1ebeb09..983b31caf7a2b 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/find.ts @@ -45,6 +45,10 @@ export const registerFindRoute = ( schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)]) ), has_reference_operator: searchOperatorSchema, + has_no_reference: schema.maybe( + schema.oneOf([referenceSchema, schema.arrayOf(referenceSchema)]) + ), + has_no_reference_operator: searchOperatorSchema, fields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), filter: schema.maybe(schema.string()), aggs: schema.maybe(schema.string()), @@ -88,6 +92,8 @@ export const registerFindRoute = ( sortField: query.sort_field, hasReference: query.has_reference, hasReferenceOperator: query.has_reference_operator, + hasNoReference: query.has_no_reference, + hasNoReferenceOperator: query.has_no_reference_operator, fields: typeof query.fields === 'string' ? [query.fields] : query.fields, filter: query.filter, aggs, diff --git a/src/core/server/integration_tests/saved_objects/routes/find.test.ts b/src/core/server/integration_tests/saved_objects/routes/find.test.ts index ab3ca6c459dae..2c7b1c9838b50 100644 --- a/src/core/server/integration_tests/saved_objects/routes/find.test.ts +++ b/src/core/server/integration_tests/saved_objects/routes/find.test.ts @@ -123,6 +123,7 @@ describe('GET /api/saved_objects/_find', () => { type: ['foo', 'bar'], defaultSearchOperator: 'OR', hasReferenceOperator: 'OR', + hasNoReferenceOperator: 'OR', }); }); @@ -213,6 +214,73 @@ describe('GET /api/saved_objects/_find', () => { ); }); + it('accepts the query parameter has_no_reference as an object', async () => { + const references = querystring.escape( + JSON.stringify({ + id: '1', + type: 'reference', + }) + ); + await supertest(httpSetup.server.listener) + .get(`/api/saved_objects/_find?type=foo&has_no_reference=${references}`) + .expect(200); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); + + const options = savedObjectsClient.find.mock.calls[0][0]; + expect(options.hasNoReference).toEqual({ + id: '1', + type: 'reference', + }); + }); + + it('accepts the query parameter has_no_reference as an array', async () => { + const references = querystring.escape( + JSON.stringify([ + { + id: '1', + type: 'reference', + }, + { + id: '2', + type: 'reference', + }, + ]) + ); + await supertest(httpSetup.server.listener) + .get(`/api/saved_objects/_find?type=foo&has_no_reference=${references}`) + .expect(200); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); + + const options = savedObjectsClient.find.mock.calls[0][0]; + expect(options.hasNoReference).toEqual([ + { + id: '1', + type: 'reference', + }, + { + id: '2', + type: 'reference', + }, + ]); + }); + + it('accepts the query parameter has_no_reference_operator', async () => { + await supertest(httpSetup.server.listener) + .get('/api/saved_objects/_find?type=foo&has_no_reference_operator=AND') + .expect(200); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); + + const options = savedObjectsClient.find.mock.calls[0][0]; + expect(options).toEqual( + expect.objectContaining({ + hasNoReferenceOperator: 'AND', + }) + ); + }); + it('accepts the query parameter search_fields', async () => { await supertest(httpSetup.server.listener) .get('/api/saved_objects/_find?type=foo&search_fields=title') diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts index 4afcc4f162a62..5c11b6f74d7ae 100644 --- a/test/api_integration/apis/saved_objects/find.ts +++ b/test/api_integration/apis/saved_objects/find.ts @@ -338,6 +338,131 @@ export default function ({ getService }: FtrProviderContext) { }); }); + describe('`has_no_reference` and `has_no_reference_operator` parameters', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json', + { space: SPACE_ID } + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json', + { space: SPACE_ID } + ); + }); + + it('search for objects not containing a reference', async () => { + await supertest + .get(`/s/${SPACE_ID}/api/saved_objects/_find`) + .query({ + type: 'visualization', + has_no_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }), + }) + .expect(200) + .then((resp) => { + const objects = resp.body.saved_objects; + const ids = objects.map((obj: SavedObject) => obj.id); + expect(ids).to.contain('only-ref-2'); + expect(ids).to.contain('only-ref-3'); + expect(ids).not.to.contain('only-ref-1'); + expect(ids).not.to.contain('ref-1-and-ref-2'); + }); + }); + + it('search for multiple references with OR operator', async () => { + await supertest + .get(`/s/${SPACE_ID}/api/saved_objects/_find`) + .query({ + type: 'visualization', + has_no_reference: JSON.stringify([ + { type: 'ref-type', id: 'ref-1' }, + { type: 'ref-type', id: 'ref-2' }, + ]), + has_no_reference_operator: 'OR', + }) + .expect(200) + .then((resp) => { + const objects = resp.body.saved_objects; + const ids = objects.map((obj: SavedObject) => obj.id); + + expect(ids).to.contain('only-ref-3'); + expect(ids).not.to.contain('only-ref-1'); + expect(ids).not.to.contain('only-ref-2'); + expect(ids).not.to.contain('ref-1-and-ref-2'); + }); + }); + + it('search for multiple references with AND operator', async () => { + await supertest + .get(`/s/${SPACE_ID}/api/saved_objects/_find`) + .query({ + type: 'visualization', + has_no_reference: JSON.stringify([ + { type: 'ref-type', id: 'ref-1' }, + { type: 'ref-type', id: 'ref-2' }, + ]), + has_no_reference_operator: 'AND', + }) + .expect(200) + .then((resp) => { + const objects = resp.body.saved_objects; + const ids = objects.map((obj: SavedObject) => obj.id); + expect(ids).to.contain('only-ref-1'); + expect(ids).to.contain('only-ref-2'); + expect(ids).to.contain('only-ref-3'); + expect(ids).not.to.contain('ref-1-and-ref-2'); + }); + }); + }); + + describe('with both `has_reference` and `has_no_reference` parameters', () => { + before(async () => { + await kibanaServer.importExport.load( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json', + { space: SPACE_ID } + ); + }); + after(async () => { + await kibanaServer.importExport.unload( + 'test/api_integration/fixtures/kbn_archiver/saved_objects/references.json', + { space: SPACE_ID } + ); + }); + + it('search for objects containing a reference and excluding another reference', async () => { + await supertest + .get(`/s/${SPACE_ID}/api/saved_objects/_find`) + .query({ + type: 'visualization', + has_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }), + has_no_reference: JSON.stringify({ type: 'ref-type', id: 'ref-2' }), + }) + .expect(200) + .then((resp) => { + const objects = resp.body.saved_objects; + const ids = objects.map((obj: SavedObject) => obj.id); + expect(ids).to.eql(['only-ref-1']); + }); + }); + + it('search for objects with same reference passed to `has_reference` and `has_no_reference`', async () => { + await supertest + .get(`/s/${SPACE_ID}/api/saved_objects/_find`) + .query({ + type: 'visualization', + has_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }), + has_no_reference: JSON.stringify({ type: 'ref-type', id: 'ref-1' }), + }) + .expect(200) + .then((resp) => { + const objects = resp.body.saved_objects; + const ids = objects.map((obj: SavedObject) => obj.id); + expect(ids).to.eql([]); + }); + }); + }); + describe('searching for special characters', () => { before(async () => { await kibanaServer.importExport.load( From b66d12a40c5aed0335d76b417c73cb3bb946764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= <efeguerkan.yalaman@elastic.co> Date: Tue, 4 Oct 2022 13:26:50 +0200 Subject: [PATCH 025/174] [Enterprise Search] Add ml doc links (#141921) * Add documentation links for ml inference card and modal * Fix link --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../pipelines/ml_inference/configure_pipeline.tsx | 8 +++----- .../search_index/pipelines/ml_inference/no_models.tsx | 5 +++-- .../components/search_index/pipelines/pipelines.tsx | 2 +- .../public/applications/shared/doc_links/doc_links.ts | 3 +++ 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 8ef5a68a3f98c..445bf9458d457 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -130,6 +130,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { crawlerGettingStarted: `${ENTERPRISE_SEARCH_DOCS}crawler-getting-started.html`, crawlerManaging: `${ENTERPRISE_SEARCH_DOCS}crawler-managing.html`, crawlerOverview: `${ENTERPRISE_SEARCH_DOCS}crawler.html`, + deployTrainedModels: `${MACHINE_LEARNING_DOCS}ml-nlp-deploy-models.html`, documentLevelSecurity: `${ELASTICSEARCH_DOCS}document-level-security.html`, ingestPipelines: `${ENTERPRISE_SEARCH_DOCS}ingest-pipelines.html`, languageAnalyzers: `${ELASTICSEARCH_DOCS}analysis-lang-analyzer.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index aed1b552bdb30..d9902a7b11de3 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -115,6 +115,7 @@ export interface DocLinks { readonly crawlerGettingStarted: string; readonly crawlerManaging: string; readonly crawlerOverview: string; + readonly deployTrainedModels: string; readonly documentLevelSecurity: string; readonly ingestPipelines: string; readonly languageAnalyzers: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx index b1d8fd4d074a8..bd895dcf45704 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/configure_pipeline.tsx @@ -23,6 +23,8 @@ import { import { i18n } from '@kbn/i18n'; +import { docLinks } from '../../../../../shared/doc_links'; + import { MLInferenceLogic } from './ml_inference_logic'; export const ConfigurePipeline: React.FC = () => { @@ -50,11 +52,7 @@ export const ConfigurePipeline: React.FC = () => { } )} </p> - <EuiLink - // TODO replace with docs link - href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-models.html" - target="_blank" - > + <EuiLink href={docLinks.deployTrainedModels} target="_blank"> {i18n.translate( 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.docsLink', { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx index 084fb4244cb7a..66ffbe45c1777 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/no_models.tsx @@ -14,6 +14,8 @@ import { i18n } from '@kbn/i18n'; import noMlModelsGraphicDark from '../../../../../../assets/images/no_ml_models_dark.svg'; import noMlModelsGraphicLight from '../../../../../../assets/images/no_ml_models_light.svg'; +import { docLinks } from '../../../../../shared/doc_links'; + export const NoModelsPanel: React.FC = () => { const { colorMode } = useEuiTheme(); @@ -43,8 +45,7 @@ export const NoModelsPanel: React.FC = () => { </> } footer={ - // TODO: insert correct docsLink here - <EuiLink href="#" target="_blank"> + <EuiLink href={docLinks.deployTrainedModels} target="_blank"> {i18n.translate( 'xpack.enterpriseSearch.appSearch.crawler.crawlRequestsTable.emptyPrompt.docsLink', { diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx index 07be63b54f3b5..9cab24190a2de 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx @@ -87,7 +87,7 @@ export const SearchIndexPipelines: React.FC = () => { <DataPanel hasBorder footerDocLink={ - <EuiLink href="" external color="subdued"> + <EuiLink href={docLinks.deployTrainedModels} target="_blank" color="subdued"> {i18n.translate( 'xpack.enterpriseSearch.content.indices.pipelines.mlInferencePipelines.docLink', { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts index 17ee2230b2fb7..975e7981829f2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/doc_links/doc_links.ts @@ -65,6 +65,7 @@ class DocLinks { public crawlerGettingStarted: string; public crawlerManaging: string; public crawlerOverview: string; + public deployTrainedModels: string; public documentLevelSecurity: string; public elasticsearchCreateIndex: string; public elasticsearchGettingStarted: string; @@ -178,6 +179,7 @@ class DocLinks { this.crawlerGettingStarted = ''; this.crawlerManaging = ''; this.crawlerOverview = ''; + this.deployTrainedModels = ''; this.documentLevelSecurity = ''; this.elasticsearchCreateIndex = ''; this.elasticsearchGettingStarted = ''; @@ -293,6 +295,7 @@ class DocLinks { this.crawlerGettingStarted = docLinks.links.enterpriseSearch.crawlerGettingStarted; this.crawlerManaging = docLinks.links.enterpriseSearch.crawlerManaging; this.crawlerOverview = docLinks.links.enterpriseSearch.crawlerOverview; + this.deployTrainedModels = docLinks.links.enterpriseSearch.deployTrainedModels; this.documentLevelSecurity = docLinks.links.enterpriseSearch.documentLevelSecurity; this.elasticsearchCreateIndex = docLinks.links.elasticsearch.createIndex; this.elasticsearchGettingStarted = docLinks.links.elasticsearch.gettingStarted; From 4753d7c170ea47e3bc178c2b48fd6507a150594d Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger <walter.rafelsberger@elastic.co> Date: Tue, 4 Oct 2022 13:50:55 +0200 Subject: [PATCH 026/174] [ML] Explain Log Rate Spikes: Fix error handling. (#142047) - Fixes error handling that before was not providing enough information for debugging purposes and support. This will now output more fine grained error information to the Kibana server log. The analysis is now more resilient to errors for individual queries. For example, we don't stop the analysis anymore if individual queries for p-values or histograms fail. - Moves the error callout above all other possible elements like empty prompts when the analysis doesn't return results. --- .../api/explain_log_rate_spikes/actions.ts | 10 + .../api/explain_log_rate_spikes/index.ts | 1 + .../explain_log_rate_spikes_analysis.tsx | 54 +- .../server/routes/explain_log_rate_spikes.ts | 556 +++++++++++------- .../queries/fetch_change_point_p_values.ts | 16 +- .../routes/queries/fetch_frequent_items.ts | 23 +- .../apis/aiops/explain_log_rate_spikes.ts | 4 +- 7 files changed, 405 insertions(+), 259 deletions(-) diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts index e050946a489be..7c4e3a47f8b79 100644 --- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts +++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/actions.ts @@ -18,6 +18,7 @@ export const API_ACTION_NAME = { ADD_CHANGE_POINTS_GROUP: 'add_change_point_group', ADD_CHANGE_POINTS_GROUP_HISTOGRAM: 'add_change_point_group_histogram', ADD_ERROR: 'add_error', + PING: 'ping', RESET: 'reset', UPDATE_LOADING_STATE: 'update_loading_state', } as const; @@ -89,6 +90,14 @@ export function addErrorAction(payload: ApiActionAddError['payload']): ApiAction }; } +interface ApiActionPing { + type: typeof API_ACTION_NAME.PING; +} + +export function pingAction(): ApiActionPing { + return { type: API_ACTION_NAME.PING }; +} + interface ApiActionReset { type: typeof API_ACTION_NAME.RESET; } @@ -121,5 +130,6 @@ export type AiopsExplainLogRateSpikesApiAction = | ApiActionAddChangePointsHistogram | ApiActionAddChangePointsGroupHistogram | ApiActionAddError + | ApiActionPing | ApiActionReset | ApiActionUpdateLoadingState; diff --git a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts index 5628b509980ad..c092b34c8b2b6 100644 --- a/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts +++ b/x-pack/plugins/aiops/common/api/explain_log_rate_spikes/index.ts @@ -11,6 +11,7 @@ export { addChangePointsGroupHistogramAction, addChangePointsHistogramAction, addErrorAction, + pingAction, resetAction, updateLoadingStateAction, API_ACTION_NAME, diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx index 2425161615915..9949ec537b77a 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_analysis.tsx @@ -172,6 +172,33 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps> onCancel={cancel} shouldRerunAnalysis={shouldRerunAnalysis} /> + {errors.length > 0 ? ( + <> + <EuiCallOut + title={i18n.translate('xpack.aiops.analysis.errorCallOutTitle', { + defaultMessage: + 'The following {errorCount, plural, one {error} other {errors}} occurred running the analysis.', + values: { errorCount: errors.length }, + })} + color="warning" + iconType="alert" + size="s" + > + <EuiText size="s"> + {errors.length === 1 ? ( + <p>{errors[0]}</p> + ) : ( + <ul> + {errors.map((e, i) => ( + <li key={i}>{e}</li> + ))} + </ul> + )} + </EuiText> + </EuiCallOut> + <EuiSpacer size="xs" /> + </> + ) : null} {showSpikeAnalysisTable && foundGroups && ( <EuiFormRow display="columnCompressedSwitch" label={groupResultsMessage}> <EuiSwitch @@ -207,33 +234,6 @@ export const ExplainLogRateSpikesAnalysis: FC<ExplainLogRateSpikesAnalysisProps> } /> )} - {errors.length > 0 && ( - <> - <EuiCallOut - title={i18n.translate('xpack.aiops.analysis.errorCallOutTitle', { - defaultMessage: - 'The following {errorCount, plural, one {error} other {errors}} occurred running the analysis.', - values: { errorCount: errors.length }, - })} - color="warning" - iconType="alert" - size="s" - > - <EuiText size="s"> - {errors.length === 1 ? ( - <p>{errors[0]}</p> - ) : ( - <ul> - {errors.map((e, i) => ( - <li key={i}>{e}</li> - ))} - </ul> - )} - </EuiText> - </EuiCallOut> - <EuiSpacer size="xs" /> - </> - )} {showSpikeAnalysisTable && groupResults && foundGroups ? ( <SpikeAnalysisGroupsTable changePoints={data.changePoints} diff --git a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts index 8a8a7372f80e2..949b535ca16fb 100644 --- a/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts +++ b/x-pack/plugins/aiops/server/routes/explain_log_rate_spikes.ts @@ -27,6 +27,7 @@ import { addChangePointsHistogramAction, aiopsExplainLogRateSpikesSchema, addErrorAction, + pingAction, resetAction, updateLoadingStateAction, AiopsExplainLogRateSpikesApiAction, @@ -74,6 +75,15 @@ export const defineExplainLogRateSpikesRoute = ( return response.forbidden(); } + let logMessageCounter = 1; + + function logInfoMessage(msg: string) { + logger.info(`Explain Log Rate Spikes #${logMessageCounter}: ${msg}`); + logMessageCounter++; + } + + logInfoMessage('Starting analysis.'); + const groupingEnabled = !!request.body.grouping; const client = (await context.core).elasticsearch.client.asCurrentUser; @@ -83,19 +93,33 @@ export const defineExplainLogRateSpikesRoute = ( let loaded = 0; let shouldStop = false; request.events.aborted$.subscribe(() => { + logInfoMessage('aborted$ subscription trigger.'); shouldStop = true; controller.abort(); }); request.events.completed$.subscribe(() => { + logInfoMessage('completed$ subscription trigger.'); shouldStop = true; controller.abort(); }); - const { end, push, responseWithHeaders } = streamFactory<AiopsExplainLogRateSpikesApiAction>( - request.headers, - logger, - true - ); + const { + end: streamEnd, + push, + responseWithHeaders, + } = streamFactory<AiopsExplainLogRateSpikesApiAction>(request.headers, logger, true); + + function pushPing() { + push(pingAction()); + } + + const pingInterval = setInterval(pushPing, 1000); + + function end() { + logInfoMessage('Ending analysis.'); + clearInterval(pingInterval); + streamEnd(); + } function endWithUpdatedLoadingState() { push( @@ -114,9 +138,16 @@ export const defineExplainLogRateSpikesRoute = ( end(); } + function pushError(m: string) { + logInfoMessage('Push error.'); + push(addErrorAction(m)); + } + // Async IIFE to run the analysis while not blocking returning `responseWithHeaders`. (async () => { + logInfoMessage('Reset.'); push(resetAction()); + logInfoMessage('Load field candidates.'); push( updateLoadingStateAction({ ccsWarning: false, @@ -134,7 +165,8 @@ export const defineExplainLogRateSpikesRoute = ( try { fieldCandidates = await fetchFieldCandidates(client, request.body); } catch (e) { - push(addErrorAction(e.toString())); + logger.error(`Failed to fetch field candidates, got: \n${e.toString()}`); + pushError(`Failed to fetch field candidates.`); end(); return; } @@ -168,17 +200,33 @@ export const defineExplainLogRateSpikesRoute = ( const changePoints: ChangePoint[] = []; const fieldsToSample = new Set<string>(); const chunkSize = 10; + let chunkCount = 0; const fieldCandidatesChunks = chunk(fieldCandidates, chunkSize); + logInfoMessage('Fetch p-values.'); + for (const fieldCandidatesChunk of fieldCandidatesChunks) { + chunkCount++; + logInfoMessage(`Fetch p-values. Chunk ${chunkCount} of ${fieldCandidatesChunks.length}`); let pValues: Awaited<ReturnType<typeof fetchChangePointPValues>>; try { - pValues = await fetchChangePointPValues(client, request.body, fieldCandidatesChunk); + pValues = await fetchChangePointPValues( + client, + request.body, + fieldCandidatesChunk, + logger, + pushError + ); } catch (e) { - push(addErrorAction(e.toString())); - end(); - return; + logger.error( + `Failed to fetch p-values for ${JSON.stringify( + fieldCandidatesChunk + )}, got: \n${e.toString()}` + ); + pushError(`Failed to fetch p-values for ${JSON.stringify(fieldCandidatesChunk)}.`); + // Still continue the analysis even if chunks of p-value queries fail. + continue; } if (pValues.length > 0) { @@ -210,12 +258,15 @@ export const defineExplainLogRateSpikesRoute = ( ); if (shouldStop) { + logInfoMessage('shouldStop fetching p-values.'); + end(); return; } } if (changePoints?.length === 0) { + logInfoMessage('Stopping analysis, did not find change points.'); endWithUpdatedLoadingState(); return; } @@ -224,16 +275,27 @@ export const defineExplainLogRateSpikesRoute = ( { fieldName: request.body.timeFieldName, type: KBN_FIELD_TYPES.DATE }, ]; - const [overallTimeSeries] = (await fetchHistogramsForFields( - client, - request.body.index, - { match_all: {} }, - // fields - histogramFields, - // samplerShardSize - -1, - undefined - )) as [NumericChartData]; + logInfoMessage('Fetch overall histogram.'); + + let overallTimeSeries: NumericChartData | undefined; + try { + overallTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + { match_all: {} }, + // fields + histogramFields, + // samplerShardSize + -1, + undefined + )) as [NumericChartData] + )[0]; + } catch (e) { + logger.error(`Failed to fetch the overall histogram data, got: \n${e.toString()}`); + pushError(`Failed to fetch overall histogram data.`); + // Still continue the analysis even if loading the overall histogram fails. + } function pushHistogramDataLoadingState() { push( @@ -251,6 +313,8 @@ export const defineExplainLogRateSpikesRoute = ( } if (groupingEnabled) { + logInfoMessage('Group results.'); + push( updateLoadingStateAction({ ccsWarning: false, @@ -283,208 +347,242 @@ export const defineExplainLogRateSpikesRoute = ( (g) => g.group.length > 1 ); - const { fields, df } = await fetchFrequentItems( - client, - request.body.index, - JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer, - deduplicatedChangePoints, - request.body.timeFieldName, - request.body.deviationMin, - request.body.deviationMax - ); - - // The way the `frequent_items` aggregations works could return item sets that include - // field/value pairs that are not part of the original list of significant change points. - // This cleans up groups and removes those unrelated field/value pairs. - const filteredDf = df - .map((fi) => { - fi.set = Object.entries(fi.set).reduce<ItemsetResult['set']>( - (set, [field, value]) => { - if ( - changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value) - ) { - set[field] = value; + try { + const { fields, df } = await fetchFrequentItems( + client, + request.body.index, + JSON.parse(request.body.searchQuery) as estypes.QueryDslQueryContainer, + deduplicatedChangePoints, + request.body.timeFieldName, + request.body.deviationMin, + request.body.deviationMax, + logger, + pushError + ); + + if (fields.length > 0 && df.length > 0) { + // The way the `frequent_items` aggregations works could return item sets that include + // field/value pairs that are not part of the original list of significant change points. + // This cleans up groups and removes those unrelated field/value pairs. + const filteredDf = df + .map((fi) => { + fi.set = Object.entries(fi.set).reduce<ItemsetResult['set']>( + (set, [field, value]) => { + if ( + changePoints.some((cp) => cp.fieldName === field && cp.fieldValue === value) + ) { + set[field] = value; + } + return set; + }, + {} + ); + fi.size = Object.keys(fi.set).length; + return fi; + }) + .filter((fi) => fi.size > 1); + + // `frequent_items` returns lot of different small groups of field/value pairs that co-occur. + // The following steps analyse these small groups, identify overlap between these groups, + // and then summarize them in larger groups where possible. + + // Get a tree structure based on `frequent_items`. + const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields); + + // Each leave of the tree will be a summarized group of co-occuring field/value pairs. + const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []); + + // To be able to display a more cleaned up results table in the UI, we identify field/value pairs + // that occur in multiple groups. This will allow us to highlight field/value pairs that are + // unique to a group in a better way. This step will also re-add duplicates we identified in the + // beginning and didn't pass on to the `frequent_items` agg. + const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves); + const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map( + (g) => { + const group = [...g.group]; + + for (const groupItem of g.group) { + const { duplicate } = groupItem; + const duplicates = groupedChangePoints.find((d) => + d.group.some( + (dg) => + dg.fieldName === groupItem.fieldName && + dg.fieldValue === groupItem.fieldValue + ) + ); + + if (duplicates !== undefined) { + group.push( + ...duplicates.group.map((d) => { + return { + fieldName: d.fieldName, + fieldValue: d.fieldValue, + duplicate, + }; + }) + ); + } } - return set; - }, - {} - ); - fi.size = Object.keys(fi.set).length; - return fi; - }) - .filter((fi) => fi.size > 1); - - // `frequent_items` returns lot of different small groups of field/value pairs that co-occur. - // The following steps analyse these small groups, identify overlap between these groups, - // and then summarize them in larger groups where possible. - - // Get a tree structure based on `frequent_items`. - const { root } = getSimpleHierarchicalTree(filteredDf, true, false, fields); - - // Each leave of the tree will be a summarized group of co-occuring field/value pairs. - const treeLeaves = getSimpleHierarchicalTreeLeaves(root, []); - - // To be able to display a more cleaned up results table in the UI, we identify field/value pairs - // that occur in multiple groups. This will allow us to highlight field/value pairs that are - // unique to a group in a better way. This step will also re-add duplicates we identified in the - // beginning and didn't pass on to the `frequent_items` agg. - const fieldValuePairCounts = getFieldValuePairCounts(treeLeaves); - const changePointGroups = markDuplicates(treeLeaves, fieldValuePairCounts).map((g) => { - const group = [...g.group]; - - for (const groupItem of g.group) { - const { duplicate } = groupItem; - const duplicates = groupedChangePoints.find((d) => - d.group.some( - (dg) => - dg.fieldName === groupItem.fieldName && dg.fieldValue === groupItem.fieldValue - ) - ); - - if (duplicates !== undefined) { - group.push( - ...duplicates.group.map((d) => { - return { - fieldName: d.fieldName, - fieldValue: d.fieldValue, - duplicate, - }; - }) - ); - } - } - return { - ...g, - group, - }; - }); - - // Some field/value pairs might not be part of the `frequent_items` result set, for example - // because they don't co-occur with other field/value pairs or because of the limits we set on the query. - // In this next part we identify those missing pairs and add them as individual groups. - const missingChangePoints = deduplicatedChangePoints.filter((cp) => { - return !changePointGroups.some((cpg) => { - return cpg.group.some( - (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue + return { + ...g, + group, + }; + } ); - }); - }); - changePointGroups.push( - ...missingChangePoints.map(({ fieldName, fieldValue, doc_count: docCount, pValue }) => { - const duplicates = groupedChangePoints.find((d) => - d.group.some((dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue) + // Some field/value pairs might not be part of the `frequent_items` result set, for example + // because they don't co-occur with other field/value pairs or because of the limits we set on the query. + // In this next part we identify those missing pairs and add them as individual groups. + const missingChangePoints = deduplicatedChangePoints.filter((cp) => { + return !changePointGroups.some((cpg) => { + return cpg.group.some( + (d) => d.fieldName === cp.fieldName && d.fieldValue === cp.fieldValue + ); + }); + }); + + changePointGroups.push( + ...missingChangePoints.map( + ({ fieldName, fieldValue, doc_count: docCount, pValue }) => { + const duplicates = groupedChangePoints.find((d) => + d.group.some( + (dg) => dg.fieldName === fieldName && dg.fieldValue === fieldValue + ) + ); + if (duplicates !== undefined) { + return { + id: `${stringHash( + JSON.stringify( + duplicates.group.map((d) => ({ + fieldName: d.fieldName, + fieldValue: d.fieldValue, + })) + ) + )}`, + group: duplicates.group.map((d) => ({ + fieldName: d.fieldName, + fieldValue: d.fieldValue, + duplicate: false, + })), + docCount, + pValue, + }; + } else { + return { + id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`, + group: [ + { + fieldName, + fieldValue, + duplicate: false, + }, + ], + docCount, + pValue, + }; + } + } + ) ); - if (duplicates !== undefined) { - return { - id: `${stringHash( - JSON.stringify( - duplicates.group.map((d) => ({ - fieldName: d.fieldName, - fieldValue: d.fieldValue, - })) - ) - )}`, - group: duplicates.group.map((d) => ({ - fieldName: d.fieldName, - fieldValue: d.fieldValue, - duplicate: false, - })), - docCount, - pValue, - }; - } else { - return { - id: `${stringHash(JSON.stringify({ fieldName, fieldValue }))}`, - group: [ - { - fieldName, - fieldValue, - duplicate: false, - }, - ], - docCount, - pValue, - }; - } - }) - ); - - // Finally, we'll find out if there's at least one group with at least two items, - // only then will we return the groups to the clients and make the grouping option available. - const maxItems = Math.max(...changePointGroups.map((g) => g.group.length)); - if (maxItems > 1) { - push(addChangePointsGroupAction(changePointGroups)); - } + // Finally, we'll find out if there's at least one group with at least two items, + // only then will we return the groups to the clients and make the grouping option available. + const maxItems = Math.max(...changePointGroups.map((g) => g.group.length)); - loaded += PROGRESS_STEP_GROUPING; + if (maxItems > 1) { + push(addChangePointsGroupAction(changePointGroups)); + } - pushHistogramDataLoadingState(); + loaded += PROGRESS_STEP_GROUPING; - if (changePointGroups) { - await asyncForEach(changePointGroups, async (cpg, index) => { - const histogramQuery = { - bool: { - filter: cpg.group.map((d) => ({ - term: { [d.fieldName]: d.fieldValue }, - })), - }, - }; + pushHistogramDataLoadingState(); - const [cpgTimeSeries] = (await fetchHistogramsForFields( - client, - request.body.index, - histogramQuery, - // fields - [ - { - fieldName: request.body.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - // samplerShardSize - -1, - undefined - )) as [NumericChartData]; + logInfoMessage('Fetch group histograms.'); - const histogram = - overallTimeSeries.data.map((o, i) => { - const current = cpgTimeSeries.data.find( - (d1) => d1.key_as_string === o.key_as_string - ) ?? { - doc_count: 0, - }; - return { - key: o.key, - key_as_string: o.key_as_string ?? '', - doc_count_change_point: current.doc_count, - doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + await asyncForEach(changePointGroups, async (cpg) => { + if (overallTimeSeries !== undefined) { + const histogramQuery = { + bool: { + filter: cpg.group.map((d) => ({ + term: { [d.fieldName]: d.fieldValue }, + })), + }, }; - }) ?? []; - push( - addChangePointsGroupHistogramAction([ - { - id: cpg.id, - histogram, - }, - ]) - ); - }); + let cpgTimeSeries: NumericChartData; + try { + cpgTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined + )) as [NumericChartData] + )[0]; + } catch (e) { + logger.error( + `Failed to fetch the histogram data for group #${ + cpg.id + }, got: \n${e.toString()}` + ); + pushError(`Failed to fetch the histogram data for group #${cpg.id}.`); + return; + } + const histogram = + overallTimeSeries.data.map((o, i) => { + const current = cpgTimeSeries.data.find( + (d1) => d1.key_as_string === o.key_as_string + ) ?? { + doc_count: 0, + }; + return { + key: o.key, + key_as_string: o.key_as_string ?? '', + doc_count_change_point: current.doc_count, + doc_count_overall: Math.max(0, o.doc_count - current.doc_count), + }; + }) ?? []; + + push( + addChangePointsGroupHistogramAction([ + { + id: cpg.id, + histogram, + }, + ]) + ); + } + }); + } + } catch (e) { + logger.error( + `Failed to transform field/value pairs into groups, got: \n${e.toString()}` + ); + pushError(`Failed to transform field/value pairs into groups.`); } } loaded += PROGRESS_STEP_HISTOGRAMS_GROUPS; + logInfoMessage('Fetch field/value histograms.'); + // time series filtered by fields - if (changePoints) { - await asyncForEach(changePoints, async (cp, index) => { - if (changePoints) { + if (changePoints && overallTimeSeries !== undefined) { + await asyncForEach(changePoints, async (cp) => { + if (overallTimeSeries !== undefined) { const histogramQuery = { bool: { filter: [ @@ -495,24 +593,40 @@ export const defineExplainLogRateSpikesRoute = ( }, }; - const [cpTimeSeries] = (await fetchHistogramsForFields( - client, - request.body.index, - histogramQuery, - // fields - [ - { - fieldName: request.body.timeFieldName, - type: KBN_FIELD_TYPES.DATE, - interval: overallTimeSeries.interval, - min: overallTimeSeries.stats[0], - max: overallTimeSeries.stats[1], - }, - ], - // samplerShardSize - -1, - undefined - )) as [NumericChartData]; + let cpTimeSeries: NumericChartData; + + try { + cpTimeSeries = ( + (await fetchHistogramsForFields( + client, + request.body.index, + histogramQuery, + // fields + [ + { + fieldName: request.body.timeFieldName, + type: KBN_FIELD_TYPES.DATE, + interval: overallTimeSeries.interval, + min: overallTimeSeries.stats[0], + max: overallTimeSeries.stats[1], + }, + ], + // samplerShardSize + -1, + undefined + )) as [NumericChartData] + )[0]; + } catch (e) { + logger.error( + `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${ + cp.fieldValue + }", got: \n${e.toString()}` + ); + pushError( + `Failed to fetch the histogram data for field/value pair "${cp.fieldName}:${cp.fieldValue}".` + ); + return; + } const histogram = overallTimeSeries.data.map((o, i) => { diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts index 03242a4bc8ae5..0fb7f90c89c12 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_change_point_p_values.ts @@ -8,6 +8,7 @@ import { uniqBy } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ElasticsearchClient } from '@kbn/core/server'; +import type { Logger } from '@kbn/logging'; import { ChangePoint } from '@kbn/ml-agg-utils'; import { SPIKE_ANALYSIS_THRESHOLD } from '../../../common/constants'; import type { AiopsExplainLogRateSpikesSchema } from '../../../common/api/explain_log_rate_spikes'; @@ -92,7 +93,9 @@ interface Aggs extends estypes.AggregationsSignificantLongTermsAggregate { export const fetchChangePointPValues = async ( esClient: ElasticsearchClient, params: AiopsExplainLogRateSpikesSchema, - fieldNames: string[] + fieldNames: string[], + logger: Logger, + emitError: (m: string) => void ): Promise<ChangePoint[]> => { const result: ChangePoint[] = []; @@ -101,7 +104,16 @@ export const fetchChangePointPValues = async ( const resp = await esClient.search<unknown, { change_point_p_value: Aggs }>(request); if (resp.aggregations === undefined) { - throw new Error('fetchChangePoint failed, did not return aggregations.'); + logger.error( + `Failed to fetch p-value aggregation for fieldName "${fieldName}", got: \n${JSON.stringify( + resp, + null, + 2 + )}` + ); + emitError(`Failed to fetch p-value aggregation for fieldName "${fieldName}".`); + // Still continue the analysis even if individual p-value queries fail. + continue; } const overallResult = resp.aggregations.change_point_p_value; diff --git a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts index 055c22397064f..c9444aaca22af 100644 --- a/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts +++ b/x-pack/plugins/aiops/server/routes/queries/fetch_frequent_items.ts @@ -10,6 +10,7 @@ import { uniq, uniqWith, pick, isEqual } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { Logger } from '@kbn/logging'; import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils'; interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregation { @@ -53,9 +54,11 @@ export async function fetchFrequentItems( changePoints: ChangePoint[], timeFieldName: string, deviationMin: number, - deviationMax: number + deviationMax: number, + logger: Logger, + emitError: (m: string) => void ) { - // get unique fields that are left + // get unique fields from change points const fields = [...new Set(changePoints.map((t) => t.fieldName))]; // TODO add query params @@ -91,6 +94,8 @@ export async function fetchFrequentItems( sampleProbability = Math.min(0.5, minDocCount / totalDocCount); } + logger.debug(`frequent_items sample probability: ${sampleProbability}`); + // frequent items can be slow, so sample and use 10% min_support const aggs: Record<string, estypes.AggregationsAggregationContainer> = { sample: { @@ -103,7 +108,7 @@ export async function fetchFrequentItems( frequent_items: { minimum_set_size: 2, size: 200, - minimum_support: 0.01, + minimum_support: 0.1, fields: aggFields, }, }, @@ -125,12 +130,18 @@ export async function fetchFrequentItems( { maxRetries: 0 } ); - const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value; - if (body.aggregations === undefined) { - throw new Error('fetchFrequentItems failed, did not return aggregations.'); + logger.error(`Failed to fetch frequent_items, got: \n${JSON.stringify(body, null, 2)}`); + emitError(`Failed to fetch frequent_items.`); + return { + fields: [], + df: [], + totalDocCount: 0, + }; } + const totalDocCountFi = (body.hits.total as estypes.SearchTotalHits).value; + const shape = body.aggregations.sample.fi.buckets.length; let maximum = shape; if (maximum > 50000) { diff --git a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts index b4bdf9f50beb5..a2e1f158a73e2 100644 --- a/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts +++ b/x-pack/test/api_integration/apis/aiops/explain_log_rate_spikes.ts @@ -222,9 +222,7 @@ export default ({ getService }: FtrProviderContext) => { const errorActions = data.filter((d) => d.type === expected.errorFilter); expect(errorActions.length).to.be(1); - expect(errorActions[0].payload).to.be( - 'ResponseError: index_not_found_exception: [index_not_found_exception] Reason: no such index [does_not_exist]' - ); + expect(errorActions[0].payload).to.be('Failed to fetch field candidates.'); }); }); }; From 87dc1fa82f4654defcac1834bd722d6330e91036 Mon Sep 17 00:00:00 2001 From: Ashokaditya <1849116+ashokaditya@users.noreply.github.com> Date: Tue, 4 Oct 2022 13:57:00 +0200 Subject: [PATCH 027/174] [Security Solution][Endpoint][Response Actions] Add license check to actions log management RBAC (#142482) * Add license check to actions log management RBAC fixes elastic/security-team/issues/5118 refs elastic/kibana/pull/142470 * useUSerPrivileges instead review changes (@paul-tavares) * Don't register route if no access review changes (@paul-tavares) * reset mocked privilege review changes (@paul-tavares) --- .../common/endpoint/service/authz/authz.ts | 2 +- .../common/components/navigation/types.ts | 2 +- .../index.test.tsx | 28 +++++++++++++++++++ .../use_navigation_items.tsx | 8 +++++- .../public/management/links.test.ts | 19 ++++++++++++- .../public/management/links.ts | 17 +++++++---- .../public/management/pages/index.tsx | 13 +++++---- 7 files changed, 75 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts index dde2a7f92b1e0..d25fd440d1c24 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts @@ -160,7 +160,7 @@ export const calculateEndpointAuthz = ( canWritePolicyManagement, canReadPolicyManagement, canWriteActionsLogManagement, - canReadActionsLogManagement, + canReadActionsLogManagement: canReadActionsLogManagement && isPlatinumPlusLicense, // Response Actions canIsolateHost: canIsolateHost && isPlatinumPlusLicense, canUnIsolateHost: canIsolateHost, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts index ebfae21d5a5e5..5a2d192b9fd48 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/types.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/types.ts @@ -68,7 +68,6 @@ export interface NavTab { } export const securityNavKeys = [ SecurityPageName.alerts, - SecurityPageName.responseActionsHistory, SecurityPageName.blocklist, SecurityPageName.detectionAndResponse, SecurityPageName.case, @@ -81,6 +80,7 @@ export const securityNavKeys = [ SecurityPageName.hosts, SecurityPageName.network, SecurityPageName.overview, + SecurityPageName.responseActionsHistory, SecurityPageName.rules, SecurityPageName.timelines, SecurityPageName.trustedApps, diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index 1055c98835d56..5a99df01e5328 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -17,6 +17,7 @@ import { TestProviders } from '../../../mock'; import { CASES_FEATURE_ID } from '../../../../../common/constants'; import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks'; import { useTourContext } from '../../guided_onboarding'; +import { useUserPrivileges } from '../../user_privileges'; import { noCasesPermissions, readCasesCapabilities, @@ -38,6 +39,9 @@ jest.mock('../../../hooks/use_experimental_features'); jest.mock('../../../utils/route/use_route_spy'); jest.mock('../../../../management/pages/host_isolation_exceptions/view/hooks'); jest.mock('../../guided_onboarding'); +jest.mock('../../user_privileges'); + +const mockUseUserPrivileges = useUserPrivileges as jest.Mock; describe('useSecuritySolutionNavigation', () => { const mockRouteSpy = [ @@ -56,6 +60,9 @@ describe('useSecuritySolutionNavigation', () => { (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); (useRouteSpy as jest.Mock).mockReturnValue(mockRouteSpy); (useCanSeeHostIsolationExceptionsMenu as jest.Mock).mockReturnValue(true); + mockUseUserPrivileges.mockImplementation(() => ({ + endpointPrivileges: { canReadActionsLogManagement: true }, + })); (useTourContext as jest.Mock).mockReturnValue({ isTourShown: false }); const cases = mockCasesContract(); @@ -83,6 +90,10 @@ describe('useSecuritySolutionNavigation', () => { }); }); + afterEach(() => { + mockUseUserPrivileges.mockReset(); + }); + it('should create navigation config', async () => { const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( () => useSecuritySolutionNavigation(), @@ -117,6 +128,23 @@ describe('useSecuritySolutionNavigation', () => { ).toBeUndefined(); }); + it('should omit response actions history if hook reports false', () => { + mockUseUserPrivileges.mockImplementation(() => ({ + endpointPrivileges: { canReadActionsLogManagement: false }, + })); + const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( + () => useSecuritySolutionNavigation(), + { wrapper: TestProviders } + ); + const items = result.current?.items; + expect(items).toBeDefined(); + expect( + items! + .find((item) => item.id === 'manage') + ?.items?.find((item) => item.id === 'response_actions_history') + ).toBeUndefined(); + }); + describe('Permission gated routes', () => { describe('cases', () => { it('should display the cases navigation item when the user has read permissions', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index dc15e371ba630..a4364c8564529 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -21,6 +21,7 @@ import { SecurityPageName } from '../../../../../common/constants'; import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks'; import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; import { useGlobalQueryString } from '../../../utils/global_query_string'; +import { useUserPrivileges } from '../../user_privileges'; export const usePrimaryNavigationItems = ({ navTabs, @@ -71,6 +72,8 @@ export const usePrimaryNavigationItems = ({ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) { const hasCasesReadPermissions = useGetUserCasesPermissions().read; const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu(); + const canSeeResponseActionsHistory = + useUserPrivileges().endpointPrivileges.canReadActionsLogManagement; const isPolicyListEnabled = useIsExperimentalFeatureEnabled('policyListEnabled'); const uiCapabilities = useKibana().services.application.capabilities; @@ -138,7 +141,9 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) { ? [navTabs[SecurityPageName.hostIsolationExceptions]] : []), navTabs[SecurityPageName.blocklist], - navTabs[SecurityPageName.responseActionsHistory], + ...(canSeeResponseActionsHistory + ? [navTabs[SecurityPageName.responseActionsHistory]] + : []), navTabs[SecurityPageName.cloudSecurityPostureBenchmarks], ], }, @@ -156,6 +161,7 @@ function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) { navTabs, hasCasesReadPermissions, canSeeHostIsolationExceptions, + canSeeResponseActionsHistory, isPolicyListEnabled, ] ); diff --git a/x-pack/plugins/security_solution/public/management/links.test.ts b/x-pack/plugins/security_solution/public/management/links.test.ts index 09c47bc70095c..c8166563428ab 100644 --- a/x-pack/plugins/security_solution/public/management/links.test.ts +++ b/x-pack/plugins/security_solution/public/management/links.test.ts @@ -80,13 +80,30 @@ describe('links', () => { expect(filteredLinks).toEqual(links); }); + it('it returns all but response actions history when no access privilege to either response actions history or HIE but have at least one HIE entry', async () => { + fakeHttpServices.get.mockResolvedValue({ total: 1 }); + const filteredLinks = await getManagementFilteredLinks( + coreMockStarted, + getPlugins(['superuser']) + ); + (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); + expect(filteredLinks).toEqual({ + ...links, + links: links.links?.filter((link) => link.id !== SecurityPageName.responseActionsHistory), + }); + }); + it('it returns filtered links when not having isolation permissions and no host isolation exceptions entry', async () => { fakeHttpServices.get.mockResolvedValue({ total: 0 }); (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); const filteredLinks = await getManagementFilteredLinks(coreMockStarted, getPlugins([])); expect(filteredLinks).toEqual({ ...links, - links: links.links?.filter((link) => link.id !== SecurityPageName.hostIsolationExceptions), + links: links.links?.filter( + (link) => + link.id !== SecurityPageName.hostIsolationExceptions && + link.id !== SecurityPageName.responseActionsHistory + ), }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/links.ts b/x-pack/plugins/security_solution/public/management/links.ts index 03cfee736def3..12a904201a9c5 100644 --- a/x-pack/plugins/security_solution/public/management/links.ts +++ b/x-pack/plugins/security_solution/public/management/links.ts @@ -226,7 +226,7 @@ export const links: LinkItem = { ], }; -const getFilteredLinks = (linkIds: SecurityPageName[]) => ({ +const excludeLinks = (linkIds: SecurityPageName[]) => ({ ...links, links: links.links?.filter((link) => !linkIds.includes(link.id)), }); @@ -249,19 +249,26 @@ export const getManagementFilteredLinks = async ( ) : getEndpointAuthzInitialState(); if (!privileges.canAccessEndpointManagement) { - return getFilteredLinks([SecurityPageName.hostIsolationExceptions]); + return excludeLinks([ + SecurityPageName.hostIsolationExceptions, + SecurityPageName.responseActionsHistory, + ]); } - if (!privileges.canIsolateHost) { + if (!privileges.canIsolateHost || !privileges.canReadActionsLogManagement) { const hostIsolationExceptionsApiClientInstance = HostIsolationExceptionsApiClient.getInstance( core.http ); const summaryResponse = await hostIsolationExceptionsApiClientInstance.summary(); if (!summaryResponse.total) { - return getFilteredLinks([SecurityPageName.hostIsolationExceptions]); + return excludeLinks([ + SecurityPageName.hostIsolationExceptions, + SecurityPageName.responseActionsHistory, + ]); } + return excludeLinks([SecurityPageName.responseActionsHistory]); } } catch { - return getFilteredLinks([SecurityPageName.hostIsolationExceptions]); + return excludeLinks([SecurityPageName.hostIsolationExceptions]); } return links; diff --git a/x-pack/plugins/security_solution/public/management/pages/index.tsx b/x-pack/plugins/security_solution/public/management/pages/index.tsx index dd06a838a26cb..590b3786ece15 100644 --- a/x-pack/plugins/security_solution/public/management/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/index.tsx @@ -76,7 +76,8 @@ const ResponseActionsTelemetry = () => ( ); export const ManagementContainer = memo(() => { - const { loading, canAccessEndpointManagement } = useUserPrivileges().endpointPrivileges; + const { loading, canAccessEndpointManagement, canReadActionsLogManagement } = + useUserPrivileges().endpointPrivileges; // Lets wait until we can verify permissions if (loading) { @@ -103,10 +104,12 @@ export const ManagementContainer = memo(() => { component={HostIsolationExceptionsTelemetry} /> <Route path={MANAGEMENT_ROUTING_BLOCKLIST_PATH} component={BlocklistContainer} /> - <Route - path={MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH} - component={ResponseActionsTelemetry} - /> + {canReadActionsLogManagement && ( + <Route + path={MANAGEMENT_ROUTING_RESPONSE_ACTIONS_HISTORY_PATH} + component={ResponseActionsTelemetry} + /> + )} <Route path={MANAGEMENT_PATH} exact> <Redirect to={getEndpointListPath({ name: 'endpointList' })} /> </Route> From 8be7668d208073a80812397050298a6954c51a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20R=C3=BChsen?= <tim.ruhsen@elastic.co> Date: Tue, 4 Oct 2022 13:59:28 +0200 Subject: [PATCH 028/174] [Profiling] Show Top 1000 functions (#142391) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/profiling/public/components/topn_functions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/profiling/public/components/topn_functions.tsx b/x-pack/plugins/profiling/public/components/topn_functions.tsx index 3ad540983d903..4d8522913a245 100644 --- a/x-pack/plugins/profiling/public/components/topn_functions.tsx +++ b/x-pack/plugins/profiling/public/components/topn_functions.tsx @@ -219,7 +219,7 @@ export const TopNFunctionsTable = ({ : row[sortField]; }, [sortDirection] - ).slice(0, 100); + ); return ( <> From 8e770bb6080b99b5437b22b22fc0d07c68e4c504 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa <vlad.lasitsa@gmail.com> Date: Tue, 4 Oct 2022 15:00:59 +0300 Subject: [PATCH 029/174] [TSVB][Lens]Fix conversion from static value in timeseries to reference line in lens (#142453) * Fix conversion static value in timeseries to reference line in lens * Doesn't allow convert static value with split * Fix condition * Ignore axis position from model for top n * Added tests Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lib/configurations/xy/layers.test.ts | 131 ++++++++++++------ .../lib/configurations/xy/layers.ts | 37 +++-- .../convert_to_lens/timeseries/index.test.ts | 13 ++ .../convert_to_lens/timeseries/index.ts | 12 +- .../public/convert_to_lens/top_n/index.ts | 2 +- 5 files changed, 138 insertions(+), 57 deletions(-) diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts index 6c94971397d3e..46e9d9e1fae2a 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.test.ts @@ -24,6 +24,33 @@ jest.mock('uuid', () => ({ v4: () => 'test-id', })); +const mockedIndices = [ + { + id: 'test', + title: 'test', + timeFieldName: 'test_field', + getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }), + }, +] as unknown as DataView[]; + +const indexPatternsService = { + getDefault: jest.fn(() => + Promise.resolve({ + id: 'default', + title: 'index', + getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }), + }) + ), + get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })), + find: jest.fn((search: string, size: number) => { + if (size !== 1) { + // shouldn't request more than one data view since there is a significant performance penalty + throw new Error('trying to fetch too many data views'); + } + return Promise.resolve(mockedIndices || []); + }), +} as unknown as DataViewsPublicPluginStart; + describe('getLayers', () => { const dataSourceLayers: Record<number, Layer> = [ { @@ -331,10 +358,16 @@ describe('getLayers', () => { series: [createSeries({ metrics: staticValueMetric })], }); - test.each<[string, [Record<number, Layer>, Panel], Array<Partial<XYLayerConfig>>]>([ + test.each< + [ + string, + [Record<number, Layer>, Panel, DataViewsPublicPluginStart, boolean], + Array<Partial<XYLayerConfig>> + ] + >([ [ 'data layer if columns do not include static column', - [dataSourceLayers, panel], + [dataSourceLayers, panel, indexPatternsService, false], [ { layerType: 'data', @@ -353,9 +386,30 @@ describe('getLayers', () => { }, ], ], + [ + 'data layer with "left" axisMode if isSingleAxis is provided', + [dataSourceLayers, panel, indexPatternsService, true], + [ + { + layerType: 'data', + accessors: ['column-id-1'], + xAccessor: 'column-id-2', + splitAccessor: 'column-id-3', + seriesType: 'area', + layerId: 'test-layer-1', + yConfig: [ + { + forAccessor: 'column-id-1', + axisMode: 'left', + color: '#68BC00', + }, + ], + }, + ], + ], [ 'reference line layer if columns include static column', - [dataSourceLayersWithStatic, panelWithStaticValue], + [dataSourceLayersWithStatic, panelWithStaticValue, indexPatternsService, false], [ { layerType: 'referenceLine', @@ -364,9 +418,10 @@ describe('getLayers', () => { yConfig: [ { forAccessor: 'column-id-1', - axisMode: 'right', + axisMode: 'left', color: '#68BC00', fill: 'below', + lineWidth: 1, }, ], }, @@ -374,7 +429,7 @@ describe('getLayers', () => { ], [ 'correct colors if columns include percentile columns', - [dataSourceLayersWithPercentile, panelWithPercentileMetric], + [dataSourceLayersWithPercentile, panelWithPercentileMetric, indexPatternsService, false], [ { yConfig: [ @@ -394,7 +449,12 @@ describe('getLayers', () => { ], [ 'correct colors if columns include percentile rank columns', - [dataSourceLayersWithPercentileRank, panelWithPercentileRankMetric], + [ + dataSourceLayersWithPercentileRank, + panelWithPercentileRankMetric, + indexPatternsService, + false, + ], [ { yConfig: [ @@ -414,7 +474,7 @@ describe('getLayers', () => { ], [ 'annotation layer gets correct params and converts color, extraFields and icons', - [dataSourceLayersWithStatic, panelWithSingleAnnotation], + [dataSourceLayersWithStatic, panelWithSingleAnnotation, indexPatternsService, false], [ { layerType: 'referenceLine', @@ -423,9 +483,10 @@ describe('getLayers', () => { yConfig: [ { forAccessor: 'column-id-1', - axisMode: 'right', + axisMode: 'left', color: '#68BC00', fill: 'below', + lineWidth: 1, }, ], }, @@ -459,7 +520,12 @@ describe('getLayers', () => { ], [ 'annotation layer should gets correct default params', - [dataSourceLayersWithStatic, panelWithSingleAnnotationWithoutQueryStringAndTimefield], + [ + dataSourceLayersWithStatic, + panelWithSingleAnnotationWithoutQueryStringAndTimefield, + indexPatternsService, + false, + ], [ { layerType: 'referenceLine', @@ -468,9 +534,10 @@ describe('getLayers', () => { yConfig: [ { forAccessor: 'column-id-1', - axisMode: 'right', + axisMode: 'left', color: '#68BC00', fill: 'below', + lineWidth: 1, }, ], }, @@ -504,7 +571,7 @@ describe('getLayers', () => { ], [ 'multiple annotations with different data views create separate layers', - [dataSourceLayersWithStatic, panelWithMultiAnnotations], + [dataSourceLayersWithStatic, panelWithMultiAnnotations, indexPatternsService, false], [ { layerType: 'referenceLine', @@ -513,9 +580,10 @@ describe('getLayers', () => { yConfig: [ { forAccessor: 'column-id-1', - axisMode: 'right', + axisMode: 'left', color: '#68BC00', fill: 'below', + lineWidth: 1, }, ], }, @@ -598,7 +666,12 @@ describe('getLayers', () => { ], [ 'annotation layer gets correct dataView when none is defined', - [dataSourceLayersWithStatic, panelWithSingleAnnotationDefaultDataView], + [ + dataSourceLayersWithStatic, + panelWithSingleAnnotationDefaultDataView, + indexPatternsService, + false, + ], [ { layerType: 'referenceLine', @@ -607,9 +680,10 @@ describe('getLayers', () => { yConfig: [ { forAccessor: 'column-id-1', - axisMode: 'right', + axisMode: 'left', color: '#68BC00', fill: 'below', + lineWidth: 1, }, ], }, @@ -642,34 +716,7 @@ describe('getLayers', () => { ], ], ])('should return %s', async (_, input, expected) => { - const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart); + const layers = await getLayers(...input); expect(layers).toEqual(expected.map(expect.objectContaining)); }); }); - -const mockedIndices = [ - { - id: 'test', - title: 'test', - timeFieldName: 'test_field', - getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }), - }, -] as unknown as DataView[]; - -const indexPatternsService = { - getDefault: jest.fn(() => - Promise.resolve({ - id: 'default', - title: 'index', - getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }), - }) - ), - get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })), - find: jest.fn((search: string, size: number) => { - if (size !== 1) { - // shouldn't request more than one data view since there is a significant performance penalty - throw new Error('trying to fetch too many data views'); - } - return Promise.resolve(mockedIndices || []); - }), -} as unknown as DataViewsPublicPluginStart; diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts index ec0e24e2db873..8784c2952807d 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/lib/configurations/xy/layers.ts @@ -24,7 +24,7 @@ import { getDefaultQueryLanguage } from '../../../../application/components/lib/ import { fetchIndexPattern } from '../../../../../common/index_patterns_utils'; import { ICON_TYPES_MAP } from '../../../../application/visualizations/constants'; import { SUPPORTED_METRICS } from '../../metrics'; -import type { Annotation, Metric, Panel } from '../../../../../common/types'; +import type { Annotation, Metric, Panel, Series } from '../../../../../common/types'; import { getSeriesAgg } from '../../series'; import { isPercentileRanksColumnWithMeta, @@ -44,6 +44,10 @@ function getPalette(palette: PaletteOutput): PaletteOutput { : palette; } +function getAxisMode(series: Series, model: Panel): YAxisMode { + return (series.separate_axis ? series.axis_position : model.axis_position) as YAxisMode; +} + function getColor( metricColumn: Column, metric: Metric, @@ -69,7 +73,8 @@ function nonNullable<T>(value: T): value is NonNullable<T> { export const getLayers = async ( dataSourceLayers: Record<number, Layer>, model: Panel, - dataViews: DataViewsPublicPluginStart + dataViews: DataViewsPublicPluginStart, + isSingleAxis: boolean = false ): Promise<XYLayerConfig[] | null> => { const nonAnnotationsLayers: XYLayerConfig[] = Object.keys(dataSourceLayers).map((key) => { const series = model.series[parseInt(key, 10)]; @@ -84,13 +89,13 @@ export const getLayers = async ( const metricColumns = dataSourceLayer.columns.filter( (l) => !l.isBucketed && l.columnId !== referenceColumnId ); - const isReferenceLine = metrics.length === 1 && metrics[0].type === 'static'; + const isReferenceLine = + metricColumns.length === 1 && metricColumns[0].operationType === 'static_value'; const splitAccessor = dataSourceLayer.columns.find( (column) => column.isBucketed && column.isSplit )?.columnId; const chartType = getChartType(series, model.type); const commonProps = { - seriesType: chartType, layerId: dataSourceLayer.layerId, accessors: metricColumns.map((metricColumn) => { return metricColumn.columnId; @@ -102,19 +107,19 @@ export const getLayers = async ( return { forAccessor: metricColumn.columnId, color: getColor(metricColumn, metric!, series.color, splitAccessor), - axisMode: (series.separate_axis - ? series.axis_position - : model.axis_position) as YAxisMode, + axisMode: isReferenceLine // reference line should be assigned to axis with real data + ? model.series.some((s) => s.id !== series.id && getAxisMode(s, model) === 'right') + ? 'right' + : 'left' + : isSingleAxis + ? 'left' + : getAxisMode(series, model), ...(isReferenceLine && { - fill: chartType === 'area' ? FillTypes.BELOW : FillTypes.NONE, + fill: chartType.includes('area') ? FillTypes.BELOW : FillTypes.NONE, + lineWidth: series.line_width, }), }; }), - xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit) - ?.columnId, - splitAccessor, - collapseFn: seriesAgg, - palette: getPalette(series.palette as PaletteOutput), }; if (isReferenceLine) { return { @@ -123,8 +128,14 @@ export const getLayers = async ( }; } else { return { + seriesType: chartType, layerType: 'data', ...commonProps, + xAccessor: dataSourceLayer.columns.find((column) => column.isBucketed && !column.isSplit) + ?.columnId, + splitAccessor, + collapseFn: seriesAgg, + palette: getPalette(series.palette as PaletteOutput), }; } }); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts index 50aa1a6c6f7f4..c81db38e05384 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.test.ts @@ -112,6 +112,19 @@ describe('convertToLens', () => { expect(mockGetBucketsColumns).toBeCalledTimes(1); }); + test('should return null for static value with buckets', async () => { + mockGetBucketsColumns.mockReturnValue([{}]); + mockGetMetricsColumns.mockReturnValue([ + { + operationType: 'static_value', + }, + ]); + const result = await convertToLens(model); + expect(result).toBeNull(); + expect(mockGetMetricsColumns).toBeCalledTimes(1); + expect(mockGetBucketsColumns).toBeCalledTimes(1); + }); + test('should return state for valid model', async () => { const result = await convertToLens(model); expect(result).toBeDefined(); diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts index 8cbbbf0f9e739..ef678fcc2dab4 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/timeseries/index.ts @@ -98,11 +98,21 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model: Panel return null; } + const isReferenceLine = + metricsColumns.length === 1 && metricsColumns[0].operationType === 'static_value'; + + // only static value without split is supported + if (isReferenceLine && bucketsColumns.length) { + return null; + } + const layerId = uuid(); extendedLayers[layerIdx] = { indexPatternId, layerId, - columns: [...metricsColumns, dateHistogramColumn, ...bucketsColumns], + columns: isReferenceLine + ? [...metricsColumns] + : [...metricsColumns, dateHistogramColumn, ...bucketsColumns], columnOrder: [], }; } diff --git a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts index 020aaec28f573..130646f72f127 100644 --- a/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts +++ b/src/plugins/vis_types/timeseries/public/convert_to_lens/top_n/index.ts @@ -86,7 +86,7 @@ export const convertToLens: ConvertTsvbToLensVisualization = async (model, timeR }; } - const configLayers = await getLayers(extendedLayers, model, dataViews); + const configLayers = await getLayers(extendedLayers, model, dataViews, true); if (configLayers === null) { return null; } From bcfa351f06037532dbc7d9ca6f06eaa5193a60d2 Mon Sep 17 00:00:00 2001 From: Byron Hulcher <byronhulcher@gmail.com> Date: Tue, 4 Oct 2022 08:17:17 -0400 Subject: [PATCH 030/174] Allow null description field value to round trip from server (#142540) --- .../update_connector_name_and_description_api_logic.ts | 8 +++----- .../connector_name_and_description.tsx | 2 +- .../server/routes/enterprise_search/connectors.ts | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts index caf19f80f040a..639bd56208546 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/connector/update_connector_name_and_description_api_logic.ts @@ -16,17 +16,15 @@ export type PutConnectorNameAndDescriptionArgs = Partial< indexName: string; }; -export type PutConnectorNameAndDescriptionResponse = Partial< - Pick<Connector, 'name' | 'description'> -> & { +export type PutConnectorNameAndDescriptionResponse = Pick<Connector, 'name' | 'description'> & { indexName: string; }; export const putConnectorNameAndDescription = async ({ connectorId, - description, + description = null, indexName, - name, + name = '', }: PutConnectorNameAndDescriptionArgs) => { const route = `/internal/enterprise_search/connectors/${connectorId}/name_and_description`; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx index d75482e25e784..9f6c96ce75e0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_name_and_description/connector_name_and_description.tsx @@ -65,7 +65,7 @@ export const ConnectorNameAndDescription: React.FC = () => { title: NAME_LABEL, }, { - description: description ?? '--', + description: description || '--', title: DESCRIPTION_LABEL, }, ]} diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts index 0aaf30ef126d4..9663b216ec91c 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts @@ -254,8 +254,8 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) { connectorId: schema.string(), }), body: schema.object({ - name: schema.maybe(schema.string()), - description: schema.maybe(schema.string()), + name: schema.string(), + description: schema.nullable(schema.string()), }), }, }, From 24ce456ec1ebd7713dcfccd569f0397a6e393999 Mon Sep 17 00:00:00 2001 From: doakalexi <109488926+doakalexi@users.noreply.github.com> Date: Tue, 4 Oct 2022 08:22:38 -0400 Subject: [PATCH 031/174] Fixing flaky test (#142498) --- .../group2/tests/telemetry/alerting_and_actions_telemetry.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts index c89e5b48b236b..b4cb36ab59d85 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/telemetry/alerting_and_actions_telemetry.ts @@ -235,8 +235,9 @@ export default function createAlertingAndActionsTelemetryTests({ getService }: F // number of action executions broken down by connector type expect(telemetry.count_actions_executions_by_type_per_day['test.throw'] > 0).to.be(true); - // average execution time - just checking for non-zero as we can't set an exact number - expect(telemetry.avg_execution_time_per_day > 0).to.be(true); + // average execution time - just checking for a positive number as we can't set an exact number + // if the time is less than 1ms it will round down to 0 + expect(telemetry.avg_execution_time_per_day >= 0).to.be(true); // average execution time broken down by rule type expect(telemetry.avg_execution_time_by_type_per_day['test.throw'] > 0).to.be(true); From 6f8e758f113d7f22cc1df1ec4449b3d0f01da50d Mon Sep 17 00:00:00 2001 From: Ying Mao <ying.mao@elastic.co> Date: Tue, 4 Oct 2022 08:25:32 -0400 Subject: [PATCH 032/174] =?UTF-8?q?Fixing=20Failing=20test:=20X-Pack=20Ale?= =?UTF-8?q?rting=20API=20Integration=20Tests.x-pack/test/alerting=5Fapi=5F?= =?UTF-8?q?integration/security=5Fand=5Fspaces/group1/tests/alerting/disab?= =?UTF-8?q?le=C2=B7ts=20-=20alerting=20api=20integration=20security=20and?= =?UTF-8?q?=20spaces=20enabled=20Alerts=20-=20Group=201=20alerts=20disable?= =?UTF-8?q?=20superuser=20at=20space1=20should=20still=20be=20able=20to=20?= =?UTF-8?q?disable=20alert=20when=20AAD=20is=20broken=20(#142483)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Unskipping test * Adding retries --- .../group1/tests/alerting/disable.ts | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts index 860576f806e38..df9fc34e17014 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts @@ -26,8 +26,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); - // Failing: See https://github.com/elastic/kibana/issues/140797 - describe.skip('disable', () => { + describe('disable', () => { const objectRemover = new ObjectRemover(supertest); after(() => objectRemover.removeAll()); @@ -110,21 +109,23 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.body).to.eql(''); // task should still exist but be disabled - const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id); - expect(taskRecord2.type).to.eql('task'); - expect(taskRecord2.task.taskType).to.eql('alerting:test.noop'); - expect(JSON.parse(taskRecord2.task.params)).to.eql({ - alertId: createdAlert.id, - spaceId: space.id, - consumer: 'alertsFixture', - }); - expect(taskRecord2.task.enabled).to.eql(false); - // Ensure AAD isn't broken - await checkAAD({ - supertest, - spaceId: space.id, - type: 'alert', - id: createdAlert.id, + await retry.try(async () => { + const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id); + expect(taskRecord2.type).to.eql('task'); + expect(taskRecord2.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord2.task.params)).to.eql({ + alertId: createdAlert.id, + spaceId: space.id, + consumer: 'alertsFixture', + }); + expect(taskRecord2.task.enabled).to.eql(false); + // Ensure AAD isn't broken + await checkAAD({ + supertest, + spaceId: space.id, + type: 'alert', + id: createdAlert.id, + }); }); break; default: @@ -295,15 +296,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); // task should still exist but be disabled - const taskRecord = await getScheduledTask(createdAlert.scheduled_task_id); - expect(taskRecord.type).to.eql('task'); - expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); - expect(JSON.parse(taskRecord.task.params)).to.eql({ - alertId: createdAlert.id, - spaceId: space.id, - consumer: 'alerts', + await retry.try(async () => { + const taskRecord = await getScheduledTask(createdAlert.scheduled_task_id); + expect(taskRecord.type).to.eql('task'); + expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord.task.params)).to.eql({ + alertId: createdAlert.id, + spaceId: space.id, + consumer: 'alerts', + }); + expect(taskRecord.task.enabled).to.eql(false); }); - expect(taskRecord.task.enabled).to.eql(false); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -366,15 +369,17 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); // task should still exist but be disabled - const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id); - expect(taskRecord2.type).to.eql('task'); - expect(taskRecord2.task.taskType).to.eql('alerting:test.noop'); - expect(JSON.parse(taskRecord2.task.params)).to.eql({ - alertId: createdAlert.id, - spaceId: space.id, - consumer: 'alertsFixture', + await retry.try(async () => { + const taskRecord2 = await getScheduledTask(createdAlert.scheduled_task_id); + expect(taskRecord2.type).to.eql('task'); + expect(taskRecord2.task.taskType).to.eql('alerting:test.noop'); + expect(JSON.parse(taskRecord2.task.params)).to.eql({ + alertId: createdAlert.id, + spaceId: space.id, + consumer: 'alertsFixture', + }); + expect(taskRecord2.task.enabled).to.eql(false); }); - expect(taskRecord2.task.enabled).to.eql(false); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); From fb6b10e23210a820f19e6e69684ba0b90730099e Mon Sep 17 00:00:00 2001 From: Tiago Costa <tiago.costa@elastic.co> Date: Tue, 4 Oct 2022 13:29:00 +0100 Subject: [PATCH 033/174] skip flaky suite (#142110) --- .../api_integration/apis/uptime/rest/add_monitor_project.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts index 9dce7e7d8fdaa..a8eec4c568dc9 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts @@ -19,7 +19,8 @@ import { PrivateLocationTestService } from './services/private_location_test_ser import { comparePolicies, getTestProjectSyntheticsPolicy } from './sample_data/test_policy'; export default function ({ getService }: FtrProviderContext) { - describe('AddProjectMonitors', function () { + // FLAKY: https://github.com/elastic/kibana/issues/142110 + describe.skip('AddProjectMonitors', function () { this.tags('skipCloud'); const supertest = getService('supertest'); From b62c0f98cf8b82d4124518983abedf76babf5843 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang <rickyangwyn@gmail.com> Date: Tue, 4 Oct 2022 05:33:39 -0700 Subject: [PATCH 034/174] added beta tag for linux config (#142171) --- .../policy_forms/components/policy_form_layout.test.tsx | 7 +++++++ .../pages/policy/view/policy_forms/events/linux.tsx | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx index 49a2e8173476a..60dc7bd29895a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx @@ -135,6 +135,13 @@ describe('Policy Form Layout', () => { expect(saveButton).toHaveLength(1); expect(saveButton.text()).toEqual('Save'); }); + it('should display beta badge', async () => { + await asyncActions; + policyFormLayoutView.update(); + const saveButton = policyFormLayoutView.find('EuiBetaBadge'); + expect(saveButton).toHaveLength(1); + expect(saveButton.text()).toEqual('beta'); + }); describe('when the save button is clicked', () => { let saveButton: FindReactWrapperResponse; let confirmModal: FindReactWrapperResponse; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx index 1b8e4f2040150..984bc53a014e3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx @@ -88,7 +88,7 @@ const SUPPLEMENTAL_OPTIONS: ReadonlyArray<SupplementalEventFormOption<OperatingS isDisabled: (config: UIPolicyConfig) => { return !config.linux.events.session_data; }, - beta: false, + beta: true, }, ]; From 6824718c0a15347395d8e49db23b934b475b142d Mon Sep 17 00:00:00 2001 From: Shahzad <shahzad.muhammad@elastic.co> Date: Tue, 4 Oct 2022 14:46:53 +0200 Subject: [PATCH 035/174] [Synthetics] Increase project API payload limit (#142140) --- src/plugins/bfetch/server/plugin.ts | 8 ++++++-- .../server/routes/monitor_cruds/add_monitor_project.ts | 7 +++++++ x-pack/plugins/synthetics/server/server.ts | 5 +++-- .../plugins/synthetics/server/synthetics_route_wrapper.ts | 1 + 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index 0f51f5da62353..85720480cf9a0 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -20,6 +20,7 @@ import { } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { map$ } from '@kbn/std'; +import { RouteConfigOptions } from '@kbn/core-http-server'; import { StreamingResponseHandler, BatchRequestData, @@ -54,7 +55,8 @@ export interface BfetchServerSetup { context: RequestHandlerContext ) => StreamingResponseHandler<Payload, Response>, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', - pluginRouter?: ReturnType<CoreSetup['http']['createRouter']> + pluginRouter?: ReturnType<CoreSetup['http']['createRouter']>, + options?: RouteConfigOptions<'get' | 'post' | 'put' | 'delete'> ) => void; } @@ -117,14 +119,16 @@ export class BfetchServerPlugin router: ReturnType<CoreSetup['http']['createRouter']>; logger: Logger; }): BfetchServerSetup['addStreamingResponseRoute'] => - (path, handler, method = 'POST', pluginRouter) => { + (path, handler, method = 'POST', pluginRouter, options) => { const httpRouter = pluginRouter || router; + const routeDefinition = { path: `/${removeLeadingSlash(path)}`, validate: { body: schema.any(), query: schema.object({ compress: schema.boolean({ defaultValue: false }) }), }, + options, }; const routeHandler: RequestHandler<unknown, Query> = async ( context: RequestHandlerContext, diff --git a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts index 668d97a0819e3..ea269d87413e7 100644 --- a/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts +++ b/x-pack/plugins/synthetics/server/routes/monitor_cruds/add_monitor_project.ts @@ -13,6 +13,8 @@ import { API_URLS } from '../../../common/constants'; import { getAllLocations } from '../../synthetics_service/get_all_locations'; import { ProjectMonitorFormatter } from '../../synthetics_service/project_monitor/project_monitor_formatter'; +const MAX_PAYLOAD_SIZE = 1048576 * 20; // 20MiB + export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory = ( libs: UMServerLibs ) => ({ @@ -25,6 +27,11 @@ export const addSyntheticsProjectMonitorRoute: SyntheticsStreamingRouteFactory = monitors: schema.arrayOf(schema.any()), }), }, + options: { + body: { + maxBytes: MAX_PAYLOAD_SIZE, + }, + }, handler: async ({ request, savedObjectsClient, diff --git a/x-pack/plugins/synthetics/server/server.ts b/x-pack/plugins/synthetics/server/server.ts index 12844c9cb9223..7f667e0fb264d 100644 --- a/x-pack/plugins/synthetics/server/server.ts +++ b/x-pack/plugins/synthetics/server/server.ts @@ -57,7 +57,7 @@ export const initSyntheticsServer = ( }); syntheticsAppStreamingApiRoutes.forEach((route) => { - const { method, streamHandler, path } = syntheticsRouteWrapper( + const { method, streamHandler, path, options } = syntheticsRouteWrapper( createSyntheticsRouteWithAuth(libs, route), server, syntheticsMonitorClient @@ -82,7 +82,8 @@ export const initSyntheticsServer = ( }; }, method, - server.router + server.router, + options ); }); }; diff --git a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts index 8706735fa9256..fc1376e157607 100644 --- a/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts +++ b/x-pack/plugins/synthetics/server/synthetics_route_wrapper.ts @@ -19,6 +19,7 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = ( ...uptimeRoute, options: { tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])], + ...(uptimeRoute.options ?? {}), }, streamHandler: async (context, request, subject) => { const coreContext = await context.core; From 6875d18d0f1340d684e234ef866970bcb9ed087b Mon Sep 17 00:00:00 2001 From: Kurt <kc13greiner@users.noreply.github.com> Date: Tue, 4 Oct 2022 08:50:12 -0400 Subject: [PATCH 036/174] Removing esArchiver in favor of testDataLoader for `bulk_get` Saved Objects integration tests (#140998) * Removing esArchiver in favor of testDataLoader * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Adding test data for loader * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Adding generic TestDataLoader * Importing just the type per PR feedback * Changing testDataLoader function names to be more descriptive Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Larry Gregory <larry.gregory@elastic.co> Co-authored-by: Thomas Watson <watson@elastic.co> --- .../common/lib/test_data_loader.ts | 46 ++--- .../fixtures/kbn_archiver/default_space.json | 163 ++++++++++++++++++ .../common/fixtures/kbn_archiver/space_1.json | 72 ++++++++ .../common/fixtures/kbn_archiver/space_2.json | 58 +++++++ .../common/suites/bulk_get.ts | 43 +++-- .../security_and_spaces/apis/bulk_get.ts | 11 +- .../spaces_only/apis/bulk_get.ts | 7 +- .../common/suites/copy_to_space.ts | 34 +++- .../suites/resolve_copy_to_space_conflicts.ts | 29 +++- 9 files changed, 401 insertions(+), 62 deletions(-) rename x-pack/test/{spaces_api_integration => }/common/lib/test_data_loader.ts (79%) create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json create mode 100644 x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json diff --git a/x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts b/x-pack/test/common/lib/test_data_loader.ts similarity index 79% rename from x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts rename to x-pack/test/common/lib/test_data_loader.ts index 4b25c722603c8..61c8ff4c1bf52 100644 --- a/x-pack/test/spaces_api_integration/common/lib/test_data_loader.ts +++ b/x-pack/test/common/lib/test_data_loader.ts @@ -5,16 +5,14 @@ * 2.0. */ -import { FtrProviderContext } from '../ftr_provider_context'; - -const SPACE_1 = { +export const SPACE_1 = { id: 'space_1', name: 'Space 1', description: 'This is the first test space', disabledFeatures: [], }; -const SPACE_2 = { +export const SPACE_2 = { id: 'space_2', name: 'Space 2', description: 'This is the second test space', @@ -64,36 +62,38 @@ const OBJECTS_TO_SHARE: Array<{ }, ]; -export function getTestDataLoader({ getService }: FtrProviderContext) { +// @ts-ignore +export function getTestDataLoader({ getService }) { const spacesService = getService('spaces'); const kbnServer = getService('kibanaServer'); const supertest = getService('supertest'); const log = getService('log'); return { - before: async () => { + createFtrSpaces: async () => { await Promise.all([await spacesService.create(SPACE_1), await spacesService.create(SPACE_2)]); }, - after: async () => { + deleteFtrSpaces: async () => { await Promise.all([spacesService.delete(SPACE_1.id), spacesService.delete(SPACE_2.id)]); }, - beforeEach: async () => { + createFtrSavedObjectsData: async ( + spaceData: Array<{ spaceName: string | null; dataUrl: string }> + ) => { log.debug('Loading test data for the following spaces: default, space_1 and space_2'); - await Promise.all([ - kbnServer.importExport.load( - 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json' - ), - kbnServer.importExport.load( - 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json', - { space: SPACE_1.id } - ), - kbnServer.importExport.load( - 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json', - { space: SPACE_2.id } - ), - ]); + + await Promise.all( + spaceData.map((spaceDataObj) => { + if (spaceDataObj.spaceName) { + return kbnServer.importExport.load(spaceDataObj.dataUrl, { + space: spaceDataObj.spaceName, + }); + } else { + return kbnServer.importExport.load(spaceDataObj.dataUrl); + } + }) + ); // Adjust spaces for the imported saved objects. for (const { objects, spacesToAdd = [], spacesToRemove = [] } of OBJECTS_TO_SHARE) { @@ -111,9 +111,9 @@ export function getTestDataLoader({ getService }: FtrProviderContext) { } }, - afterEach: async () => { + deleteFtrSavedObjectsData: async () => { const allSpacesIds = [ - ...(await spacesService.getAll()).map((space) => space.id), + ...(await spacesService.getAll()).map((space: { id: string }) => space.id), 'non_existent_space', ]; log.debug(`Removing data from the following spaces: ${allSpacesIds.join(', ')}`); diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json new file mode 100644 index 0000000000000..9a2713fc61872 --- /dev/null +++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json @@ -0,0 +1,163 @@ +{ + "attributes": { + "title": "logstash-*" + }, + "coreMigrationVersion": "8.4.0", + "id": "defaultspace-index-pattern-id", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "originId": "cts_ip_1", + "references": [], + "type": "index-pattern", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzUyOCwxXQ==" +} + +{ + "attributes": { + "title": "Count of requests", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "version": 1, + "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}", + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"defaultspace-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "id": "defaultspace-isolatedtype-id", + "references": [], + "type": "isolatedtype", + "updated_at": "2017-09-21T18:51:23.794Z", + "version": "WzQ4NywxXQ==" +} + +{ + "attributes": { + "title": "Requests", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + } + }, + "coreMigrationVersion": "8.4.0", + "id": "defaultspace-dashboard-id", + "migrationVersion": { + "dashboard": "8.4.0" + }, + "type": "dashboard", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzUyMCwxXQ==" +} + +{ + "attributes": { + "title": "A share-capable (isolated) saved-object only in the default space" + }, + "id": "only_default_space", + "type": "sharecapabletype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A shared saved-object in all spaces" + }, + "id": "all_spaces", + "type": "sharedtype", + "references": [], + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ5NywxXQ==" +} + +{ + "attributes": { + "title": "My favorite global object" + }, + "id": "globaltype-id", + "references": [], + "type": "globaltype", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzQ4NywxXQ==" +} + +{ + "attributes": { + "title": "A shared saved-object in the default and space_1 spaces" + }, + "id": "default_and_space_1", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A sharedtype saved-object with id: conflict_1" + }, + "id": "conflict_1", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A sharedtype saved-object with id: conflict_2a" + }, + "id": "conflict_2a", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A sharedtype saved-object with id: conflict_2b" + }, + "id": "conflict_2b", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A sharedtype saved-object with id: conflict_3" + }, + "id": "conflict_3", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A sharedtype saved-object with id: conflict_4a" + }, + "id": "conflict_4a", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "Resolve outcome exactMatch" + }, + "id": "exact-match", + "type": "resolvetype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "Resolve outcome aliasMatch" + }, + "id": "alias-match-newid", + "type": "resolvetype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json new file mode 100644 index 0000000000000..6356d5c01989b --- /dev/null +++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json @@ -0,0 +1,72 @@ + + +{ + "attributes": { + "title": "logstash-*" + }, + "coreMigrationVersion": "8.4.0", + "id": "space1-index-pattern-id", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzUyOSwxXQ==" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"space1-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + }, + "title": "Count of requests", + "uiStateJSON": "{\"spy\":{\"mode\":{\"name\":null,\"fill\":false}}}", + "version": 1, + "visState": "{\"title\":\"Count of requests\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}]}" + }, + "id": "space1-isolatedtype-id", + "references": [], + "type": "isolatedtype", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzQ4NywxXQ==" +} + +{ + "attributes": { + "title": "Requests", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + }, + "version": 1 + }, + "coreMigrationVersion": "8.4.0", + "id": "space1-dashboard-id", + "migrationVersion": { + "dashboard": "8.4.0" + }, + "type": "dashboard", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzUyMCwxXQ==" +} + +{ + "attributes": { + "title": "A shared saved-object only in space_1" + }, + "id": "only_space_1", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} + +{ + "attributes": { + "title": "A share-capable (isolated) saved-object only in space_1" + }, + "id": "only_space_1", + "type": "sharecapabletype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json new file mode 100644 index 0000000000000..9715a5f54d2b4 --- /dev/null +++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json @@ -0,0 +1,58 @@ +{ + "attributes": { + "title": "logstash-*" + }, + "coreMigrationVersion": "8.4.0", + "id": "space2-index-pattern-id", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzUyOSwxXQ==" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"space2-index-pattern-id\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + }, + "title": "Count of requests", + "version": 1 + }, + "id": "space2-isolatedtype-id", + "references": [], + "type": "isolatedtype", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzQ4NywxXQ==" +} + +{ + "attributes": { + "title": "Requests", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + }, + "version": 1 + }, + "coreMigrationVersion": "8.4.0", + "id": "space2-dashboard-id", + "migrationVersion": { + "dashboard": "8.4.0" + }, + "type": "dashboard", + "updated_at": "2017-09-21T18:49:16.270Z", + "version": "WzUyMCwxXQ==" +} + +{ + "attributes": { + "title": "A shared saved-object only in space_2" + }, + "id": "only_space_2", + "type": "sharedtype", + "updated_at": "2017-09-21T18:59:16.270Z", + "version": "WzQ4OCwxXQ==" +} diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index 10709a6f20916..c9cb3b9739eee 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -6,11 +6,12 @@ */ import expect from '@kbn/expect'; -import { SuperTest } from 'supertest'; +import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader'; import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; import { SPACES } from '../lib/spaces'; import { expectResponses, getUrlPrefix, getTestTitle } from '../lib/saved_object_test_utils'; import { ExpectResponseBody, TestCase, TestDefinition, TestSuite } from '../lib/types'; +import type { FtrProviderContext } from '../ftr_provider_context'; export interface BulkGetTestDefinition extends TestDefinition { request: Array<{ type: string; id: string }>; @@ -33,7 +34,10 @@ const createRequest = ({ type, id, namespaces }: BulkGetTestCase) => ({ ...(namespaces && { namespaces }), // individual "object namespaces" string array }); -export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) { +export function bulkGetTestSuiteFactory(context: FtrProviderContext) { + const testDataLoader = getTestDataLoader(context); + const supertest = context.getService('supertestWithoutAuth'); + const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_get'); const expectResponseBody = (testCases: BulkGetTestCase | BulkGetTestCase[], statusCode: 200 | 403): ExpectResponseBody => @@ -91,16 +95,31 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest<an const { user, spaceId = SPACES.DEFAULT.spaceId, tests } = definition; describeFn(description, () => { - before(() => - esArchiver.load( - 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces' - ) - ); - after(() => - esArchiver.unload( - 'x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces' - ) - ); + before(async () => { + await testDataLoader.createFtrSpaces(); + await testDataLoader.createFtrSavedObjectsData([ + { + spaceName: null, + dataUrl: + 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json', + }, + { + spaceName: SPACE_1.id, + dataUrl: + 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_1.json', + }, + { + spaceName: SPACE_2.id, + dataUrl: + 'x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/space_2.json', + }, + ]); + }); + + after(async () => { + await testDataLoader.deleteFtrSpaces(); + await testDataLoader.deleteFtrSavedObjectsData(); + }); for (const test of tests) { it(`should return ${test.responseStatusCode} ${test.title}`, async () => { diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts index 2c1fbf442b0ec..ed251440d361a 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts @@ -67,14 +67,9 @@ const createTestCases = (spaceId: string) => { return { normalTypes, crossNamespace, hiddenType, allTypes }; }; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertestWithoutAuth'); - const esArchiver = getService('esArchiver'); - - const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkGetTestSuiteFactory( - esArchiver, - supertest - ); +export default function (context: FtrProviderContext) { + const { addTests, createTestDefinitions, expectSavedObjectForbidden } = + bulkGetTestSuiteFactory(context); const createTests = (spaceId: string) => { const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts index 41fa4749cc48e..30ed220ea9ae3 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_get.ts @@ -55,11 +55,8 @@ const createTestCases = (spaceId: string) => [ { ...CASES.MULTI_NAMESPACE_ONLY_SPACE_1, namespaces: [ALL_SPACES_ID] }, ]; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const { addTests, createTestDefinitions } = bulkGetTestSuiteFactory(esArchiver, supertest); +export default function (context: FtrProviderContext) { + const { addTests, createTestDefinitions } = bulkGetTestSuiteFactory(context); const createTests = (spaceId: string) => { const testCases = createTestCases(spaceId); return createTestDefinitions(testCases, false, { singleRequest: true }); diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index c781eff6d3272..4c5ae878bbf6e 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -14,8 +14,8 @@ import { } from '@kbn/core/server'; import { getAggregatedSpaceData, getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; -import { getTestDataLoader } from '../lib/test_data_loader'; -import { FtrProviderContext } from '../ftr_provider_context'; +import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader'; +import type { FtrProviderContext } from '../ftr_provider_context'; type TestResponse = Record<string, any>; @@ -74,6 +74,21 @@ const UUID_PATTERN = new RegExp( /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i ); +const SPACE_DATA_TO_LOAD: Array<{ spaceName: string | null; dataUrl: string }> = [ + { + spaceName: null, + dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json', + }, + { + spaceName: SPACE_1.id, + dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json', + }, + { + spaceName: SPACE_2.id, + dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json', + }, +]; + const getDestinationWithoutConflicts = () => 'space_2'; const getDestinationWithConflicts = (originSpaceId?: string) => !originSpaceId || originSpaceId === DEFAULT_SPACE_ID ? 'space_1' : DEFAULT_SPACE_ID; @@ -748,16 +763,19 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { // test data only allows for the following spaces as the copy origin expect(['default', 'space_1']).to.contain(spaceId); - await testDataLoader.before(); + await testDataLoader.createFtrSpaces(); }); after(async () => { - await testDataLoader.after(); + await testDataLoader.deleteFtrSpaces(); }); describe('single-namespace types', () => { - beforeEach(async () => await testDataLoader.beforeEach()); - afterEach(async () => await testDataLoader.afterEach()); + beforeEach(async () => { + await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD); + }); + + afterEach(async () => await testDataLoader.deleteFtrSavedObjectsData()); const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` }; @@ -898,8 +916,8 @@ export function copyToSpaceTestSuiteFactory(context: FtrProviderContext) { const spaces = ['space_2']; const includeReferences = false; describe(`multi-namespace types with overwrite=${overwrite} and createNewCopies=${createNewCopies}`, () => { - before(async () => await testDataLoader.beforeEach()); - after(async () => await testDataLoader.afterEach()); + before(async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD)); + after(async () => await testDataLoader.deleteFtrSavedObjectsData()); const testCases = tests.multiNamespaceTestCases(overwrite, createNewCopies); testCases.forEach(({ testTitle, objects, statusCode, response }) => { diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 58a434bd0ca91..5f2c361714c49 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -11,8 +11,8 @@ import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common/constants'; import { CopyResponse } from '@kbn/spaces-plugin/server/lib/copy_to_spaces'; import { getUrlPrefix } from '../lib/space_test_utils'; import { DescribeFn, TestDefinitionAuthentication } from '../lib/types'; -import { FtrProviderContext } from '../ftr_provider_context'; -import { getTestDataLoader } from '../lib/test_data_loader'; +import type { FtrProviderContext } from '../ftr_provider_context'; +import { getTestDataLoader, SPACE_1, SPACE_2 } from '../../../common/lib/test_data_loader'; type TestResponse = Record<string, any>; @@ -44,6 +44,21 @@ interface ResolveCopyToSpaceTestDefinition { const NON_EXISTENT_SPACE_ID = 'non_existent_space'; +const SPACE_DATA_TO_LOAD: Array<{ spaceName: string | null; dataUrl: string }> = [ + { + spaceName: null, + dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/default_space.json', + }, + { + spaceName: SPACE_1.id, + dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_1.json', + }, + { + spaceName: SPACE_2.id, + dataUrl: 'x-pack/test/spaces_api_integration/common/fixtures/kbn_archiver/space_2.json', + }, +]; + const getDestinationSpace = (originSpaceId?: string) => { if (!originSpaceId || originSpaceId === DEFAULT_SPACE_ID) { return 'space_1'; @@ -487,8 +502,10 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { }); describe('single-namespace types', () => { - beforeEach(async () => await testDataLoader.beforeEach()); - afterEach(async () => await testDataLoader.afterEach()); + beforeEach( + async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD) + ); + afterEach(async () => await testDataLoader.deleteFtrSavedObjectsData()); const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` }; const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` }; @@ -630,8 +647,8 @@ export function resolveCopyToSpaceConflictsSuite(context: FtrProviderContext) { const includeReferences = false; const createNewCopies = false; describe(`multi-namespace types with "overwrite" retry`, () => { - before(async () => await testDataLoader.beforeEach()); - after(async () => await testDataLoader.afterEach()); + before(async () => await testDataLoader.createFtrSavedObjectsData(SPACE_DATA_TO_LOAD)); + after(async () => await testDataLoader.deleteFtrSavedObjectsData()); const testCases = tests.multiNamespaceTestCases(); testCases.forEach(({ testTitle, objects, retries, statusCode, response }) => { From f5f60b640b935b86ee76e6c808a65b56be93edb1 Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Tue, 4 Oct 2022 18:04:28 +0500 Subject: [PATCH 037/174] [Console] Refactor Console settings toggles to follow best practices (#140902) * Refactor settings modal labels * Fix checks * Update related test case * Migrate old settings to new ones * Refactor migrate fn to be more generic Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../application/components/settings_modal.tsx | 28 ++++----- .../editor/legacy/console_editor/editor.tsx | 4 +- .../use_send_current_request.test.tsx | 4 +- .../use_send_current_request.ts | 6 +- .../console/public/services/settings.ts | 62 ++++++++++++------- .../translations/translations/fr-FR.json | 2 - .../translations/translations/ja-JP.json | 2 - .../translations/translations/zh-CN.json | 2 - 8 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/plugins/console/public/application/components/settings_modal.tsx b/src/plugins/console/public/application/components/settings_modal.tsx index 095dde1c29507..67b0e2c0d957a 100644 --- a/src/plugins/console/public/application/components/settings_modal.tsx +++ b/src/plugins/console/public/application/components/settings_modal.tsx @@ -77,9 +77,9 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { const [polling, setPolling] = useState(props.settings.polling); const [pollInterval, setPollInterval] = useState(props.settings.pollInterval); const [tripleQuotes, setTripleQuotes] = useState(props.settings.tripleQuotes); - const [isHistoryDisabled, setIsHistoryDisabled] = useState(props.settings.isHistoryDisabled); - const [isKeyboardShortcutsDisabled, setIsKeyboardShortcutsDisabled] = useState( - props.settings.isKeyboardShortcutsDisabled + const [isHistoryEnabled, setIsHistoryEnabled] = useState(props.settings.isHistoryEnabled); + const [isKeyboardShortcutsEnabled, setIsKeyboardShortcutsEnabled] = useState( + props.settings.isKeyboardShortcutsEnabled ); const autoCompleteCheckboxes = [ @@ -140,8 +140,8 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { polling, pollInterval, tripleQuotes, - isHistoryDisabled, - isKeyboardShortcutsDisabled, + isHistoryEnabled, + isKeyboardShortcutsEnabled, }); } @@ -153,17 +153,17 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { }, []); const toggleKeyboardShortcuts = useCallback( - (isDisabled: boolean) => { + (isEnabled: boolean) => { if (props.editorInstance) { unregisterCommands(props.editorInstance); - setIsKeyboardShortcutsDisabled(isDisabled); + setIsKeyboardShortcutsEnabled(isEnabled); } }, [props.editorInstance] ); const toggleSavingToHistory = useCallback( - (isDisabled: boolean) => setIsHistoryDisabled(isDisabled), + (isEnabled: boolean) => setIsHistoryEnabled(isEnabled), [] ); @@ -289,11 +289,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { } > <EuiSwitch - checked={isHistoryDisabled} + checked={isHistoryEnabled} label={ <FormattedMessage - defaultMessage="Disable saving requests to history" - id="console.settingsPage.savingRequestsToHistoryMessage" + defaultMessage="Save requests to history" + id="console.settingsPage.saveRequestsToHistoryLabel" /> } onChange={(e) => toggleSavingToHistory(e.target.checked)} @@ -309,11 +309,11 @@ export const DevToolsSettingsModal = (props: DevToolsSettingsModalProps) => { } > <EuiSwitch - checked={isKeyboardShortcutsDisabled} + checked={isKeyboardShortcutsEnabled} label={ <FormattedMessage - defaultMessage="Disable keyboard shortcuts" - id="console.settingsPage.disableKeyboardShortcutsMessage" + defaultMessage="Enable keyboard shortcuts" + id="console.settingsPage.enableKeyboardShortcutsLabel" /> } onChange={(e) => toggleKeyboardShortcuts(e.target.checked)} diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index 74a052646e198..ed8c87b5df147 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -259,8 +259,8 @@ function EditorUI({ initialTextValue, setEditorInstance }: EditorProps) { }, [settings]); useEffect(() => { - const { isKeyboardShortcutsDisabled } = settings; - if (!isKeyboardShortcutsDisabled) { + const { isKeyboardShortcutsEnabled } = settings; + if (isKeyboardShortcutsEnabled) { registerCommands({ senseEditor: editorInstanceRef.current!, sendCurrentRequest, diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx index 0c7e4c46d95a6..e895ddc135db8 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx +++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.test.tsx @@ -106,7 +106,9 @@ describe('useSendCurrentRequest', () => { (sendRequest as jest.Mock).mockReturnValue( [{ request: {} }, { request: {} }] /* two responses to save history */ ); - (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({}); + (mockContextValue.services.settings.toJSON as jest.Mock).mockReturnValue({ + isHistoryEnabled: true, + }); (mockContextValue.services.history.addToHistory as jest.Mock).mockImplementation(() => { // Mock throwing throw new Error('cannot save!'); diff --git a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts index 87f72571a63e6..28d875c246ca3 100644 --- a/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts +++ b/src/plugins/console/public/application/hooks/use_send_current_request/use_send_current_request.ts @@ -52,9 +52,9 @@ export const useSendCurrentRequest = () => { const results = await sendRequest({ http, requests }); let saveToHistoryError: undefined | Error; - const { isHistoryDisabled } = settings.toJSON(); + const { isHistoryEnabled } = settings.toJSON(); - if (!isHistoryDisabled) { + if (isHistoryEnabled) { results.forEach(({ request: { path, method, data } }) => { try { history.addToHistory(path, method, data); @@ -84,7 +84,7 @@ export const useSendCurrentRequest = () => { notifications.toasts.remove(toast); }, onDisableSavingToHistory: () => { - settings.setIsHistoryDisabled(true); + settings.setIsHistoryEnabled(false); notifications.toasts.remove(toast); }, }), diff --git a/src/plugins/console/public/services/settings.ts b/src/plugins/console/public/services/settings.ts index aa2280f06064f..e4731dd3f3a31 100644 --- a/src/plugins/console/public/services/settings.ts +++ b/src/plugins/console/public/services/settings.ts @@ -15,8 +15,8 @@ export const DEFAULT_SETTINGS = Object.freeze({ tripleQuotes: true, wrapMode: true, autocomplete: Object.freeze({ fields: true, indices: true, templates: true, dataStreams: true }), - isHistoryDisabled: false, - isKeyboardShortcutsDisabled: false, + isHistoryEnabled: true, + isKeyboardShortcutsEnabled: true, }); export interface DevToolsSettings { @@ -31,8 +31,8 @@ export interface DevToolsSettings { polling: boolean; pollInterval: number; tripleQuotes: boolean; - isHistoryDisabled: boolean; - isKeyboardShortcutsDisabled: boolean; + isHistoryEnabled: boolean; + isKeyboardShortcutsEnabled: boolean; } enum SettingKeys { @@ -42,12 +42,32 @@ enum SettingKeys { AUTOCOMPLETE_SETTINGS = 'autocomplete_settings', CONSOLE_POLLING = 'console_polling', POLL_INTERVAL = 'poll_interval', - IS_HISTORY_DISABLED = 'is_history_disabled', - IS_KEYBOARD_SHORTCUTS_DISABLED = 'is_keyboard_shortcuts_disabled', + IS_HISTORY_ENABLED = 'is_history_enabled', + IS_KEYBOARD_SHORTCUTS_ENABLED = 'is_keyboard_shortcuts_enabled', } export class Settings { - constructor(private readonly storage: Storage) {} + constructor(private readonly storage: Storage) { + // Migration from old settings to new ones + this.addMigrationRule('is_history_disabled', SettingKeys.IS_HISTORY_ENABLED, (value: any) => { + return !value; + }); + this.addMigrationRule( + 'is_keyboard_shortcuts_disabled', + SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, + (value: any) => { + return !value; + } + ); + } + + private addMigrationRule(previousKey: string, newKey: string, migration: (value: any) => any) { + const value = this.storage.get(previousKey); + if (value !== undefined) { + this.storage.set(newKey, migration(value)); + this.storage.delete(previousKey); + } + } getFontSize() { return this.storage.get(SettingKeys.FONT_SIZE, DEFAULT_SETTINGS.fontSize); @@ -94,13 +114,13 @@ export class Settings { return true; } - setIsHistoryDisabled(isDisabled: boolean) { - this.storage.set(SettingKeys.IS_HISTORY_DISABLED, isDisabled); + setIsHistoryEnabled(isEnabled: boolean) { + this.storage.set(SettingKeys.IS_HISTORY_ENABLED, isEnabled); return true; } - getIsHistoryDisabled() { - return this.storage.get(SettingKeys.IS_HISTORY_DISABLED, DEFAULT_SETTINGS.isHistoryDisabled); + getIsHistoryEnabled() { + return this.storage.get(SettingKeys.IS_HISTORY_ENABLED, DEFAULT_SETTINGS.isHistoryEnabled); } setPollInterval(interval: number) { @@ -111,15 +131,15 @@ export class Settings { return this.storage.get(SettingKeys.POLL_INTERVAL, DEFAULT_SETTINGS.pollInterval); } - setIsKeyboardShortcutsDisabled(disable: boolean) { - this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, disable); + setIsKeyboardShortcutsEnabled(isEnabled: boolean) { + this.storage.set(SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, isEnabled); return true; } getIsKeyboardShortcutsDisabled() { return this.storage.get( - SettingKeys.IS_KEYBOARD_SHORTCUTS_DISABLED, - DEFAULT_SETTINGS.isKeyboardShortcutsDisabled + SettingKeys.IS_KEYBOARD_SHORTCUTS_ENABLED, + DEFAULT_SETTINGS.isKeyboardShortcutsEnabled ); } @@ -131,8 +151,8 @@ export class Settings { fontSize: parseFloat(this.getFontSize()), polling: Boolean(this.getPolling()), pollInterval: this.getPollInterval(), - isHistoryDisabled: Boolean(this.getIsHistoryDisabled()), - isKeyboardShortcutsDisabled: Boolean(this.getIsKeyboardShortcutsDisabled()), + isHistoryEnabled: Boolean(this.getIsHistoryEnabled()), + isKeyboardShortcutsEnabled: Boolean(this.getIsKeyboardShortcutsDisabled()), }; } @@ -143,8 +163,8 @@ export class Settings { autocomplete, polling, pollInterval, - isHistoryDisabled, - isKeyboardShortcutsDisabled, + isHistoryEnabled, + isKeyboardShortcutsEnabled, }: DevToolsSettings) { this.setFontSize(fontSize); this.setWrapMode(wrapMode); @@ -152,8 +172,8 @@ export class Settings { this.setAutocomplete(autocomplete); this.setPolling(polling); this.setPollInterval(pollInterval); - this.setIsHistoryDisabled(isHistoryDisabled); - this.setIsKeyboardShortcutsDisabled(isKeyboardShortcutsDisabled); + this.setIsHistoryEnabled(isHistoryEnabled); + this.setIsKeyboardShortcutsEnabled(isKeyboardShortcutsEnabled); } } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index a5b6ae699c723..006c2744d4eb0 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -260,7 +260,6 @@ "console.settingsPage.autocompleteLabel": "Saisie semi-automatique", "console.settingsPage.cancelButtonLabel": "Annuler", "console.settingsPage.dataStreamsLabelText": "Flux de donnĂ©es", - "console.settingsPage.disableKeyboardShortcutsMessage": "DĂ©sactiver les raccourcis clavier", "console.settingsPage.fieldsLabelText": "Champs", "console.settingsPage.fontSizeLabel": "Taille de la police", "console.settingsPage.historyLabel": "Historique", @@ -274,7 +273,6 @@ "console.settingsPage.refreshInterval.everyHourTimeInterval": "Toutes les heures", "console.settingsPage.refreshInterval.onceTimeInterval": "Une fois, au chargement de la console", "console.settingsPage.saveButtonLabel": "Enregistrer", - "console.settingsPage.savingRequestsToHistoryMessage": "DĂ©sactiver l'enregistrement des requĂȘtes dans l'historique", "console.settingsPage.templatesLabelText": "ModĂšles", "console.settingsPage.tripleQuotesMessage": "Utiliser des guillemets triples dans le volet de sortie", "console.settingsPage.wrapLongLinesLabelText": "Formater les longues lignes", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 72e20dc6efa37..4601cc9bb3919 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -260,7 +260,6 @@ "console.settingsPage.autocompleteLabel": "è‡Ș拕慄抛", "console.settingsPage.cancelButtonLabel": "ă‚­ăƒŁăƒłă‚»ăƒ«", "console.settingsPage.dataStreamsLabelText": "ăƒ‡ăƒŒă‚żă‚čトăƒȘăƒŒăƒ ", - "console.settingsPage.disableKeyboardShortcutsMessage": "ă‚­ăƒŒăƒœăƒŒăƒ‰ă‚·ăƒ§ăƒŒăƒˆă‚«ăƒƒăƒˆă‚’ç„ĄćŠčにする", "console.settingsPage.fieldsLabelText": "ăƒ•ă‚ŁăƒŒăƒ«ăƒ‰", "console.settingsPage.fontSizeLabel": "ăƒ•ă‚©ăƒłăƒˆă‚”ă‚€ă‚ș", "console.settingsPage.historyLabel": "ć±„æ­Ž", @@ -274,7 +273,6 @@ "console.settingsPage.refreshInterval.everyHourTimeInterval": "æŻŽæ™‚", "console.settingsPage.refreshInterval.onceTimeInterval": "ă‚łăƒłă‚œăƒŒăƒ«ăźèȘ­ăżèŸŒăżæ™‚に1曞", "console.settingsPage.saveButtonLabel": "保歘", - "console.settingsPage.savingRequestsToHistoryMessage": "ć±„æ­ŽăžăźăƒȘクスă‚čトぼ保歘を無ćŠčă«ă—ăŠăă ă•ă„", "console.settingsPage.templatesLabelText": "ăƒ†ăƒłăƒ—ăƒŹăƒŒăƒˆ", "console.settingsPage.tripleQuotesMessage": "ć‡șćŠ›ă‚Šă‚Łăƒłăƒ‰ă‚Šă§ăŻäž‰é‡ćŒ•ç”šçŹŠă‚’äœżç”šă—ăŠăă ă•ă„", "console.settingsPage.wrapLongLinesLabelText": "é•·ă„èĄŒă‚’æ”čèĄŒ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index bf90d943ab06b..3181f9602038b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -260,7 +260,6 @@ "console.settingsPage.autocompleteLabel": "è‡ȘćŠšćźŒæˆ", "console.settingsPage.cancelButtonLabel": "ć–æ¶ˆ", "console.settingsPage.dataStreamsLabelText": "æ•°æźæ”", - "console.settingsPage.disableKeyboardShortcutsMessage": "çŠç”šé”źç›˜ćż«æ·é”ź", "console.settingsPage.fieldsLabelText": "ć­—æź”", "console.settingsPage.fontSizeLabel": "ć­—äœ“ć€§ć°", "console.settingsPage.historyLabel": "掆ćČèź°ćœ•", @@ -274,7 +273,6 @@ "console.settingsPage.refreshInterval.everyHourTimeInterval": "æŻć°æ—¶", "console.settingsPage.refreshInterval.onceTimeInterval": "äž€æŹĄïŒŒæŽ§ćˆ¶ć°ćŠ èœœæ—¶", "console.settingsPage.saveButtonLabel": "保歘", - "console.settingsPage.savingRequestsToHistoryMessage": "çŠæ­ąć°†èŻ·æ±‚äżć­˜ćˆ°ćŽ†ćČèź°ćœ•", "console.settingsPage.templatesLabelText": "æšĄæż", "console.settingsPage.tripleQuotesMessage": "ćœšèŸ“ć‡șçȘ—æ Œäž­äœżç”šäž‰é‡ćŒ•ć·", "console.settingsPage.wrapLongLinesLabelText": "é•żèĄŒæąèĄŒ", From 890bf7430cd9a62ee1d2f46c16f486bfb6aebd59 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:09:55 -0400 Subject: [PATCH 038/174] [Security Solution][Analyzer] Make all analyzer apis have time range as optional (#142536) --- .../common/endpoint/schema/resolver.ts | 10 ++- .../e2e/detection_alerts/resolver.cy.ts | 4 +- .../resolver/data_access_layer/factory.ts | 75 +++++++++---------- .../data_access_layer/mocks/generator_tree.ts | 8 +- .../mocks/no_ancestors_two_children.ts | 6 +- ..._children_in_index_called_awesome_index.ts | 6 +- ..._children_with_related_events_on_origin.ts | 6 +- .../one_node_with_paginated_related_events.ts | 6 +- .../current_related_event_fetcher.ts | 5 +- .../store/middleware/node_data_fetcher.ts | 4 +- .../middleware/related_events_fetcher.ts | 4 +- .../store/middleware/resolver_tree_fetcher.ts | 6 +- .../public/resolver/types.ts | 8 +- .../routes/resolver/queries/events.ts | 55 +++----------- .../routes/resolver/tree/queries/base.ts | 16 ++-- 15 files changed, 98 insertions(+), 121 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts index 15c89c8cd9c28..6de81d3e95a55 100644 --- a/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts +++ b/x-pack/plugins/security_solution/common/endpoint/schema/resolver.ts @@ -58,10 +58,12 @@ export const validateEvents = { afterEvent: schema.maybe(schema.string()), }), body: schema.object({ - timeRange: schema.object({ - from: schema.string(), - to: schema.string(), - }), + timeRange: schema.maybe( + schema.object({ + from: schema.string(), + to: schema.string(), + }) + ), indexPatterns: schema.arrayOf(schema.string()), filter: schema.maybe(schema.string()), entityType: schema.maybe(schema.string()), diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts index aa2263b9b518c..c2436f3f2de9a 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_alerts/resolver.cy.ts @@ -28,12 +28,12 @@ describe('Analyze events view for alerts', () => { waitForAlertsToPopulate(); }); - it('should render analyzer when button is clicked', () => { + it('should render when button is clicked', () => { openAnalyzerForFirstAlertInTimeline(); cy.get(ANALYZER_NODE).first().should('be.visible'); }); - it(`should render an analyzer view and display + it(`should display a toast indicating the date range of found events when a time range has 0 events in it`, () => { const dateContainingZeroEvents = 'Jul 27, 2022 @ 00:00:00.000'; setStartDate(dateContainingZeroEvents); diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts index 04e694b2cedbb..719fdedb73546 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/factory.ts @@ -17,6 +17,17 @@ import type { ResolverSchema, } from '../../../common/endpoint/types'; +function getRangeFilter(timeRange: TimeRange | undefined) { + return timeRange + ? { + timeRange: { + from: timeRange.from, + to: timeRange.to, + }, + } + : []; +} + /** * The data access layer for resolver. All communication with the Kibana server is done through this object. This object is provided to Resolver. In tests, a mock data access layer can be used instead. */ @@ -34,7 +45,7 @@ export function dataAccessLayerFactory( indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverRelatedEvents> { const response: ResolverPaginatedEvents = await context.services.http.post( @@ -43,10 +54,7 @@ export function dataAccessLayerFactory( query: {}, body: JSON.stringify({ indexPatterns, - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), filter: JSON.stringify({ bool: { filter: [ @@ -76,16 +84,13 @@ export function dataAccessLayerFactory( entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverPaginatedEvents> { const commonFields = { query: { afterEvent: after, limit: 25 }, body: { - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), indexPatterns, }, }; @@ -127,30 +132,28 @@ export function dataAccessLayerFactory( limit, }: { ids: string[]; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; limit: number; }): Promise<SafeResolverEvent[]> { - const response: ResolverPaginatedEvents = await context.services.http.post( - '/api/endpoint/resolver/events', - { - query: { limit }, - body: JSON.stringify({ - timeRange: { - from: timeRange.from, - to: timeRange.to, + const query = { + query: { limit }, + body: JSON.stringify({ + indexPatterns, + ...getRangeFilter(timeRange), + filter: JSON.stringify({ + bool: { + filter: [ + { terms: { 'process.entity_id': ids } }, + { term: { 'event.category': 'process' } }, + ], }, - indexPatterns, - filter: JSON.stringify({ - bool: { - filter: [ - { terms: { 'process.entity_id': ids } }, - { term: { 'event.category': 'process' } }, - ], - }, - }), }), - } + }), + }; + const response: ResolverPaginatedEvents = await context.services.http.post( + '/api/endpoint/resolver/events', + query ); return response.events; }, @@ -172,7 +175,7 @@ export function dataAccessLayerFactory( eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<SafeResolverEvent | null> { /** @description - eventID isn't provided by winlog. This can be removed once runtime fields are available */ @@ -200,10 +203,7 @@ export function dataAccessLayerFactory( query: { limit: 1 }, body: JSON.stringify({ indexPatterns, - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), filter: JSON.stringify(filter), }), } @@ -217,10 +217,7 @@ export function dataAccessLayerFactory( query: { limit: 1 }, body: JSON.stringify({ indexPatterns, - timeRange: { - from: timeRange.from, - to: timeRange.to, - }, + ...getRangeFilter(timeRange), entityType: 'alertDetail', eventID, }), @@ -250,7 +247,7 @@ export function dataAccessLayerFactory( }: { dataId: string; schema: ResolverSchema; - timeRange: TimeRange; + timeRange?: TimeRange; indices: string[]; ancestors: number; descendants: number; diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts index 130b81c5622b2..6b833c93704b4 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/generator_tree.ts @@ -63,7 +63,7 @@ export function generateTreeWithDAL( indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverRelatedEvents> { const node = allNodes.get(entityID); @@ -88,7 +88,7 @@ export function generateTreeWithDAL( entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> { const node = allNodes.get(entityID); @@ -119,7 +119,7 @@ export function generateTreeWithDAL( eventCategory: string[]; eventTimestamp: string; eventID?: string | number; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<SafeResolverEvent | null> { return null; @@ -135,7 +135,7 @@ export function generateTreeWithDAL( limit, }: { ids: string[]; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; limit: number; }): Promise<SafeResolverEvent[]> { diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts index 000d08b4e15c7..e883a96b162e8 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children.ts @@ -59,7 +59,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverRelatedEvents> { return Promise.resolve({ @@ -83,7 +83,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; @@ -110,7 +110,7 @@ export function noAncestorsTwoChildren(): { dataAccessLayer: DataAccessLayer; me eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<SafeResolverEvent | null> { return null; diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts index 808c4463f3a89..c4c7fda097e8f 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_in_index_called_awesome_index.ts @@ -64,7 +64,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverRelatedEvents> { return Promise.resolve({ @@ -90,7 +90,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; @@ -121,7 +121,7 @@ export function noAncestorsTwoChildenInIndexCalledAwesomeIndex(): { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<SafeResolverEvent | null> { return mockEndpointEvent({ diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts index 774111baf165d..30f7e07bf041a 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/no_ancestors_two_children_with_related_events_on_origin.ts @@ -67,7 +67,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverRelatedEvents> { /** @@ -97,7 +97,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> { const events = @@ -129,7 +129,7 @@ export function noAncestorsTwoChildrenWithRelatedEventsOnOrigin(): { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<SafeResolverEvent | null> { return relatedEvents.events.find((event) => eventModel.eventID(event) === eventID) ?? null; diff --git a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts index 7eb8c28a433e3..dc7031acdbd91 100644 --- a/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts +++ b/x-pack/plugins/security_solution/public/resolver/data_access_layer/mocks/one_node_with_paginated_related_events.ts @@ -58,7 +58,7 @@ export function oneNodeWithPaginatedEvents(): { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<ResolverRelatedEvents> { /** @@ -86,7 +86,7 @@ export function oneNodeWithPaginatedEvents(): { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<{ events: SafeResolverEvent[]; nextEvent: string | null }> { let events: SafeResolverEvent[] = []; @@ -121,7 +121,7 @@ export function oneNodeWithPaginatedEvents(): { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }): Promise<SafeResolverEvent | null> { return mockTree.events.find((event) => eventModel.eventID(event) === eventID) ?? null; diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts index 6b58dd4e8e62e..cd4119f9569e7 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/current_related_event_fetcher.ts @@ -48,8 +48,9 @@ export function CurrentRelatedEventFetcher( api.dispatch({ type: 'appRequestedCurrentRelatedEventData', }); - const timeRangeFilters = selectors.timeRangeFilters(state); - + const detectedBounds = selectors.detectedBounds(state); + const timeRangeFilters = + detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state); let result: SafeResolverEvent | null = null; try { result = await dataAccessLayer.event({ diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts index c3173b3238737..9a3a9eb3450fd 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/node_data_fetcher.ts @@ -60,7 +60,9 @@ export function NodeDataFetcher( let results: SafeResolverEvent[] | undefined; try { - const timeRangeFilters = selectors.timeRangeFilters(state); + const detectedBounds = selectors.detectedBounds(state); + const timeRangeFilters = + detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state); results = await dataAccessLayer.nodeData({ ids: Array.from(newIDsToRequest), timeRange: timeRangeFilters, diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts index ec0f068b5425c..ab8f71940104e 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/related_events_fetcher.ts @@ -30,7 +30,9 @@ export function RelatedEventsFetcher( const indices = selectors.eventIndices(state); const oldParams = last; - const timeRangeFilters = selectors.timeRangeFilters(state); + const detectedBounds = selectors.detectedBounds(state); + const timeRangeFilters = + detectedBounds !== undefined ? undefined : selectors.timeRangeFilters(state); // Update this each time before fetching data (or even if we don't fetch data) so that subsequent actions that call this (concurrently) will have up to date info. last = newParams; diff --git a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts index e4da1af5f4d79..61319158fccc2 100644 --- a/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts +++ b/x-pack/plugins/security_solution/public/resolver/store/middleware/resolver_tree_fetcher.ts @@ -93,9 +93,9 @@ export function ResolverTreeFetcher( descendants: descendantsRequestAmount(), }); if (unboundedTree.length > 0) { - const timestamps = unboundedTree.map((event) => - firstNonNullValue(event.data['@timestamp']) - ); + const timestamps = unboundedTree + .map((event) => firstNonNullValue(event.data['@timestamp'])) + .sort(); const oldestTimestamp = timestamps[0]; const newestTimestamp = timestamps.slice(-1); api.dispatch({ diff --git a/x-pack/plugins/security_solution/public/resolver/types.ts b/x-pack/plugins/security_solution/public/resolver/types.ts index 00ecd995176eb..88e97f416dc49 100644 --- a/x-pack/plugins/security_solution/public/resolver/types.ts +++ b/x-pack/plugins/security_solution/public/resolver/types.ts @@ -692,7 +692,7 @@ export interface DataAccessLayer { indexPatterns, }: { entityID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }) => Promise<ResolverRelatedEvents>; @@ -710,7 +710,7 @@ export interface DataAccessLayer { entityID: string; category: string; after?: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }) => Promise<ResolverPaginatedEvents>; @@ -725,7 +725,7 @@ export interface DataAccessLayer { limit, }: { ids: string[]; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; limit: number; }): Promise<SafeResolverEvent[]>; @@ -747,7 +747,7 @@ export interface DataAccessLayer { eventTimestamp: string; eventID?: string | number; winlogRecordID: string; - timeRange: TimeRange; + timeRange?: TimeRange; indexPatterns: string[]; }) => Promise<SafeResolverEvent | null>; diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts index ba4f682423670..869ae911ad890 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/queries/events.ts @@ -11,31 +11,22 @@ import type { JsonObject, JsonValue } from '@kbn/utility-types'; import { parseFilterQuery } from '../../../../utils/serialized_query'; import type { SafeResolverEvent } from '../../../../../common/endpoint/types'; import type { PaginationBuilder } from '../utils/pagination'; - -interface TimeRange { - from: string; - to: string; -} +import { BaseResolverQuery } from '../tree/queries/base'; +import type { ResolverQueryParams } from '../tree/queries/base'; /** * Builds a query for retrieving events. */ -export class EventsQuery { - private readonly pagination: PaginationBuilder; - private readonly indexPatterns: string | string[]; - private readonly timeRange: TimeRange; +export class EventsQuery extends BaseResolverQuery { + readonly pagination: PaginationBuilder; constructor({ - pagination, indexPatterns, timeRange, - }: { - pagination: PaginationBuilder; - indexPatterns: string | string[]; - timeRange: TimeRange; - }) { + isInternalRequest, + pagination, + }: ResolverQueryParams & { pagination: PaginationBuilder }) { + super({ indexPatterns, timeRange, isInternalRequest }); this.pagination = pagination; - this.indexPatterns = indexPatterns; - this.timeRange = timeRange; } private query(filters: JsonObject[]): JsonObject { @@ -44,15 +35,7 @@ export class EventsQuery { bool: { filter: [ ...filters, - { - range: { - '@timestamp': { - gte: this.timeRange.from, - lte: this.timeRange.to, - format: 'strict_date_optional_time', - }, - }, - }, + ...this.getRangeFilter(), { term: { 'event.kind': 'event' }, }, @@ -71,15 +54,7 @@ export class EventsQuery { { term: { 'event.id': id }, }, - { - range: { - '@timestamp': { - gte: this.timeRange.from, - lte: this.timeRange.to, - format: 'strict_date_optional_time', - }, - }, - }, + ...this.getRangeFilter(), ], }, }, @@ -97,15 +72,7 @@ export class EventsQuery { { term: { 'process.entity_id': id }, }, - { - range: { - '@timestamp': { - gte: this.timeRange.from, - lte: this.timeRange.to, - format: 'strict_date_optional_time', - }, - }, - }, + ...this.getRangeFilter(), ], }, }, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts index 6637e7931b056..256f2b58b6864 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/tree/queries/base.ts @@ -11,10 +11,10 @@ import type { TimeRange } from '../utils'; import { resolverFields } from '../utils'; export interface ResolverQueryParams { - readonly schema: ResolverSchema; + readonly schema?: ResolverSchema; readonly indexPatterns: string | string[]; readonly timeRange: TimeRange | undefined; - readonly isInternalRequest: boolean; + readonly isInternalRequest?: boolean; readonly resolverFields?: JsonValue[]; getRangeFilter?: () => Array<{ range: { '@timestamp': { gte: string; lte: string; format: string } }; @@ -25,12 +25,18 @@ export class BaseResolverQuery implements ResolverQueryParams { readonly schema: ResolverSchema; readonly indexPatterns: string | string[]; readonly timeRange: TimeRange | undefined; - readonly isInternalRequest: boolean; + readonly isInternalRequest?: boolean; readonly resolverFields?: JsonValue[]; constructor({ schema, indexPatterns, timeRange, isInternalRequest }: ResolverQueryParams) { - this.resolverFields = resolverFields(schema); - this.schema = schema; + const schemaOrDefault = schema + ? schema + : { + id: 'process.entity_id', + parent: 'process.parent.entity_id', + }; + this.resolverFields = resolverFields(schemaOrDefault); + this.schema = schemaOrDefault; this.indexPatterns = indexPatterns; this.timeRange = timeRange; this.isInternalRequest = isInternalRequest; From f147fe8b98ae94558411fd43d822c329c688a33a Mon Sep 17 00:00:00 2001 From: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:28:15 +0200 Subject: [PATCH 039/174] [Fleet] Update unenroll logic to account for new API key fields (#142579) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/fleet/common/types/models/agent.ts | 5 +++++ .../fleet/server/services/agents/unenroll.test.ts | 13 +++++++++++++ .../services/agents/unenroll_action_runner.ts | 10 ++++++++++ 3 files changed, 28 insertions(+) diff --git a/x-pack/plugins/fleet/common/types/models/agent.ts b/x-pack/plugins/fleet/common/types/models/agent.ts index fa54f8c943e27..ea2ad78dc10cd 100644 --- a/x-pack/plugins/fleet/common/types/models/agent.ts +++ b/x-pack/plugins/fleet/common/types/models/agent.ts @@ -94,7 +94,12 @@ interface AgentBase { export interface Agent extends AgentBase { id: string; access_api_key?: string; + // @deprecated default_api_key_history?: FleetServerAgent['default_api_key_history']; + outputs?: Array<{ + api_key_id: string; + to_retire_api_key_ids?: FleetServerAgent['default_api_key_history']; + }>; status?: AgentStatus; packages: string[]; sort?: Array<number | string | null>; diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts index 5beb5c0a9ac00..9169df19fbcfb 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll.test.ts @@ -331,6 +331,15 @@ describe('invalidateAPIKeysForAgents', () => { id: 'defaultApiKeyHistory2', }, ], + outputs: [ + { + api_key_id: 'outputApiKey1', + to_retire_api_key_ids: [{ id: 'outputApiKeyRetire1' }, { id: 'outputApiKeyRetire2' }], + }, + { + api_key_id: 'outputApiKey2', + }, + ], } as any, ]); @@ -340,6 +349,10 @@ describe('invalidateAPIKeysForAgents', () => { 'defaultApiKey1', 'defaultApiKeyHistory1', 'defaultApiKeyHistory2', + 'outputApiKey1', + 'outputApiKeyRetire1', + 'outputApiKeyRetire2', + 'outputApiKey2', ]); }); }); diff --git a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts index c735254f18256..fed5d44fe98e8 100644 --- a/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts +++ b/x-pack/plugins/fleet/server/services/agents/unenroll_action_runner.ts @@ -215,6 +215,16 @@ export async function invalidateAPIKeysForAgents(agents: Agent[]) { if (agent.default_api_key_history) { agent.default_api_key_history.forEach((apiKey) => keys.push(apiKey.id)); } + if (agent.outputs) { + agent.outputs.forEach((output) => { + if (output.api_key_id) { + keys.push(output.api_key_id); + } + if (output.to_retire_api_key_ids) { + output.to_retire_api_key_ids.forEach((apiKey) => keys.push(apiKey.id)); + } + }); + } return keys; }, []); From 5add1f9b76d1a60eea521bea880aec84c594a72d Mon Sep 17 00:00:00 2001 From: Philippe Oberti <philippe.oberti@elastic.co> Date: Tue, 4 Oct 2022 08:45:11 -0500 Subject: [PATCH 040/174] [TIP] Add full screen feature for indicators table (#142519) [TIP] Add full screen feature for indicators table --- .../use_toolbar_options.test.tsx.snap | 120 ++++++++++++++++++ .../hooks/use_toolbar_options.test.tsx | 119 +---------------- .../hooks/use_toolbar_options.tsx | 2 +- 3 files changed, 124 insertions(+), 117 deletions(-) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap new file mode 100644 index 0000000000000..4b58689023333 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap @@ -0,0 +1,120 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`useToolbarOptions() should return correct value for 0 indicators total 1`] = ` +Object { + "additionalControls": Object { + "left": Object { + "append": <IndicatorsFieldBrowser + browserFields={Object {}} + columnIds={Array []} + onResetColumns={[Function]} + onToggleColumn={[Function]} + />, + "prepend": <EuiText + size="xs" + style={ + Object { + "display": "inline", + } + } + > + <React.Fragment> + - + </React.Fragment> + </EuiText>, + }, + "right": <EuiButtonIcon + data-test-subj="tiIndicatorsGridInspect" + iconType="inspect" + onClick={[Function]} + title="Inspect" + />, + }, + "showDisplaySelector": false, + "showFullScreenSelector": true, +} +`; + +exports[`useToolbarOptions() should return correct value for 25 indicators total 1`] = ` +Object { + "additionalControls": Object { + "left": Object { + "append": <IndicatorsFieldBrowser + browserFields={Object {}} + columnIds={Array []} + onResetColumns={[Function]} + onToggleColumn={[Function]} + />, + "prepend": <EuiText + size="xs" + style={ + Object { + "display": "inline", + } + } + > + <React.Fragment> + Showing + 1 + - + 25 + of + + 25 + indicators + </React.Fragment> + </EuiText>, + }, + "right": <EuiButtonIcon + data-test-subj="tiIndicatorsGridInspect" + iconType="inspect" + onClick={[Function]} + title="Inspect" + />, + }, + "showDisplaySelector": false, + "showFullScreenSelector": true, +} +`; + +exports[`useToolbarOptions() should return correct value for 50 indicators total 1`] = ` +Object { + "additionalControls": Object { + "left": Object { + "append": <IndicatorsFieldBrowser + browserFields={Object {}} + columnIds={Array []} + onResetColumns={[Function]} + onToggleColumn={[Function]} + />, + "prepend": <EuiText + size="xs" + style={ + Object { + "display": "inline", + } + } + > + <React.Fragment> + Showing + 26 + - + 50 + of + + 50 + indicators + </React.Fragment> + </EuiText>, + }, + "right": <EuiButtonIcon + data-test-subj="tiIndicatorsGridInspect" + iconType="inspect" + onClick={[Function]} + title="Inspect" + />, + }, + "showDisplaySelector": false, + "showFullScreenSelector": true, +} +`; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx index 084279fe8353a..ecf1cbf0a477a 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx @@ -25,40 +25,7 @@ describe('useToolbarOptions()', () => { { wrapper: TestProvidersComponent } ); - expect(result.result.current).toMatchInlineSnapshot(` - Object { - "additionalControls": Object { - "left": Object { - "append": <IndicatorsFieldBrowser - browserFields={Object {}} - columnIds={Array []} - onResetColumns={[Function]} - onToggleColumn={[Function]} - />, - "prepend": <EuiText - size="xs" - style={ - Object { - "display": "inline", - } - } - > - <React.Fragment> - - - </React.Fragment> - </EuiText>, - }, - "right": <EuiButtonIcon - data-test-subj="tiIndicatorsGridInspect" - iconType="inspect" - onClick={[Function]} - title="Inspect" - />, - }, - "showDisplaySelector": false, - "showFullScreenSelector": false, - } - `); + expect(result.result.current).toMatchSnapshot(); }); it('should return correct value for 25 indicators total', () => { @@ -76,47 +43,7 @@ describe('useToolbarOptions()', () => { { wrapper: TestProvidersComponent } ); - expect(result.result.current).toMatchInlineSnapshot(` - Object { - "additionalControls": Object { - "left": Object { - "append": <IndicatorsFieldBrowser - browserFields={Object {}} - columnIds={Array []} - onResetColumns={[Function]} - onToggleColumn={[Function]} - />, - "prepend": <EuiText - size="xs" - style={ - Object { - "display": "inline", - } - } - > - <React.Fragment> - Showing - 1 - - - 25 - of - - 25 - indicators - </React.Fragment> - </EuiText>, - }, - "right": <EuiButtonIcon - data-test-subj="tiIndicatorsGridInspect" - iconType="inspect" - onClick={[Function]} - title="Inspect" - />, - }, - "showDisplaySelector": false, - "showFullScreenSelector": false, - } - `); + expect(result.result.current).toMatchSnapshot(); }); it('should return correct value for 50 indicators total', () => { @@ -134,46 +61,6 @@ describe('useToolbarOptions()', () => { { wrapper: TestProvidersComponent } ); - expect(result.result.current).toMatchInlineSnapshot(` - Object { - "additionalControls": Object { - "left": Object { - "append": <IndicatorsFieldBrowser - browserFields={Object {}} - columnIds={Array []} - onResetColumns={[Function]} - onToggleColumn={[Function]} - />, - "prepend": <EuiText - size="xs" - style={ - Object { - "display": "inline", - } - } - > - <React.Fragment> - Showing - 26 - - - 50 - of - - 50 - indicators - </React.Fragment> - </EuiText>, - }, - "right": <EuiButtonIcon - data-test-subj="tiIndicatorsGridInspect" - iconType="inspect" - onClick={[Function]} - title="Inspect" - />, - }, - "showDisplaySelector": false, - "showFullScreenSelector": false, - } - `); + expect(result.result.current).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx index b19d6df71463e..12bd94951e33c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx @@ -41,7 +41,7 @@ export const useToolbarOptions = ({ return useMemo( () => ({ showDisplaySelector: false, - showFullScreenSelector: false, + showFullScreenSelector: true, additionalControls: { left: { prepend: ( From 796dcb99153f3872c2f220cf0687620425731db2 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:58:43 -0600 Subject: [PATCH 041/174] skip failing test suite (#142548) --- .../instrumented_events/from_the_browser/loaded_dashboard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts index bc04d60c3fb54..7b21a5637d167 100644 --- a/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts +++ b/test/analytics/tests/instrumented_events/from_the_browser/loaded_dashboard.ts @@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardAddPanel = getService('dashboardAddPanel'); const queryBar = getService('queryBar'); - describe('Loaded Dashboard', () => { + // Failing: See https://github.com/elastic/kibana/issues/142548 + describe.skip('Loaded Dashboard', () => { let fromTimestamp: string | undefined; const getEvents = async (count: number, options?: GetEventsOptions) => From b798f9f627c780cebea2221db38dbb5db8a5e089 Mon Sep 17 00:00:00 2001 From: Shahzad <shahzad.muhammad@elastic.co> Date: Tue, 4 Oct 2022 16:23:42 +0200 Subject: [PATCH 042/174] [Uptime] Unskip flaky api test (#142595) --- x-pack/test/api_integration/apis/uptime/feature_controls.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/test/api_integration/apis/uptime/feature_controls.ts b/x-pack/test/api_integration/apis/uptime/feature_controls.ts index 8185cb0f03a20..39d7406636353 100644 --- a/x-pack/test/api_integration/apis/uptime/feature_controls.ts +++ b/x-pack/test/api_integration/apis/uptime/feature_controls.ts @@ -145,8 +145,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) } }); - // FLAKY: https://github.com/elastic/kibana/issues/136542 - describe.skip('spaces', () => { + describe('spaces', () => { // the following tests create a user_1 which has uptime read access to space_1 and dashboard all access to space_2 const space1Id = 'space_1'; const space2Id = 'space_2'; From d95e690e9e633c5cff79c6f9b847a8bb6fb16b5f Mon Sep 17 00:00:00 2001 From: jennypavlova <dzheni.pavlova@elastic.co> Date: Tue, 4 Oct 2022 16:37:19 +0200 Subject: [PATCH 043/174] [Infrastructure UI] Use same no data messaging on hosts view (#142063) * [WIP] Implement No Data message * Implement refetch * Render lens component and hide when there is no data * Add onLoading hook and conditional rendering Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../metrics/hosts/components/hosts_table.tsx | 35 ++++++++++++++++++- .../pages/metrics/hosts/hosts_content.tsx | 19 ++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx index 74f2468eb4c45..e92ac801e8612 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_table.tsx @@ -8,8 +8,10 @@ import { useKibana } from '@kbn/kibana-react-plugin/public'; import { TypedLensByValueInput } from '@kbn/lens-plugin/public'; import type { Query, TimeRange } from '@kbn/es-query'; -import React from 'react'; +import React, { useState } from 'react'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { i18n } from '@kbn/i18n'; +import { NoData } from '../../../../components/empty_states'; import { InfraClientStartDeps } from '../../../../types'; const getLensHostsTable = ( @@ -498,23 +500,54 @@ interface Props { timeRange: TimeRange; query: Query; searchSessionId: string; + onRefetch: () => void; + onLoading: (isLoading: boolean) => void; + isLensLoading: boolean; } export const HostsTable: React.FunctionComponent<Props> = ({ dataView, timeRange, query, searchSessionId, + onRefetch, + onLoading, + isLensLoading, }) => { const { services: { lens }, } = useKibana<InfraClientStartDeps>(); const LensComponent = lens?.EmbeddableComponent; + const [noData, setNoData] = useState(false); + + if (noData && !isLensLoading) { + return ( + <NoData + titleText={i18n.translate('xpack.infra.metrics.emptyViewTitle', { + defaultMessage: 'There is no data to display.', + })} + bodyText={i18n.translate('xpack.infra.metrics.emptyViewDescription', { + defaultMessage: 'Try adjusting your time or filter.', + })} + refetchText={i18n.translate('xpack.infra.metrics.refetchButtonLabel', { + defaultMessage: 'Check for new data', + })} + onRefetch={onRefetch} + testString="metricsEmptyViewState" + /> + ); + } return ( <LensComponent id="hostsView" timeRange={timeRange} attributes={getLensHostsTable(dataView, query)} searchSessionId={searchSessionId} + onLoad={(isLoading, adapters) => { + if (!isLoading && adapters?.tables) { + setNoData(adapters?.tables.tables.default?.rows.length === 0); + onLoading(false); + } + }} /> ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx index 63e95a19f1c7b..7bf087db39eb5 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx @@ -27,6 +27,7 @@ export const HostsContent: React.FunctionComponent = () => { useMetricsDataViewContext(); // needed to refresh the lens table when filters havent changed const [searchSessionId, setSearchSessionId] = useState(data.search.session.start()); + const [isLensLoading, setIsLensLoading] = useState(false); const onQuerySubmit = useCallback( (payload: { dateRange: TimeRange; query?: Query }) => { @@ -34,11 +35,26 @@ export const HostsContent: React.FunctionComponent = () => { if (payload.query) { setQuery(payload.query); } + setIsLensLoading(true); setSearchSessionId(data.search.session.start()); }, [setDateRange, setQuery, data.search.session] ); + const onLoading = useCallback( + (isLoading: boolean) => { + if (isLensLoading) { + setIsLensLoading(isLoading); + } + }, + [setIsLensLoading, isLensLoading] + ); + + const onRefetch = useCallback(() => { + setIsLensLoading(true); + setSearchSessionId(data.search.session.start()); + }, [data.search.session]); + return ( <div> {metricsDataView ? ( @@ -61,6 +77,9 @@ export const HostsContent: React.FunctionComponent = () => { timeRange={dateRange} query={query} searchSessionId={searchSessionId} + onRefetch={onRefetch} + onLoading={onLoading} + isLensLoading={isLensLoading} /> </> ) : hasFailedCreatingDataView || hasFailedFetchingDataView ? ( From 001d44cb028df385afe7ff01d5ea3ca5e432efed Mon Sep 17 00:00:00 2001 From: Luke Gmys <lgmys@users.noreply.github.com> Date: Tue, 4 Oct 2022 16:41:56 +0200 Subject: [PATCH 044/174] [TIP] Add update status component (#142560) --- .../cypress/e2e/indicators.cy.ts | 4 +- .../cypress/screens/indicators.ts | 2 +- .../public/components/layout/layout.tsx | 16 ++++- .../public/components/update_status/index.ts | 8 +++ .../update_status/update_status.test.tsx | 63 +++++++++++++++++++ .../update_status/update_status.tsx | 43 +++++++++++++ .../indicators/hooks/use_indicators.test.tsx | 1 + .../indicators/hooks/use_indicators.ts | 5 +- .../indicators/indicators_page.test.tsx | 1 + .../modules/indicators/indicators_page.tsx | 8 ++- 10 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/index.ts create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx create mode 100644 x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx diff --git a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts index e52effa09ab3b..c5d67894aa0ff 100644 --- a/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts +++ b/x-pack/plugins/threat_intelligence/cypress/e2e/indicators.cy.ts @@ -185,9 +185,7 @@ describe('Indicators', () => { it('should render the inspector flyout', () => { cy.get(INSPECTOR_BUTTON).last().click({ force: true }); - cy.get(INSPECTOR_PANEL).should('be.visible'); - - cy.get(INSPECTOR_PANEL).contains('Index patterns'); + cy.get(INSPECTOR_PANEL).contains('Indicators search requests'); }); }); }); diff --git a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts index 2bc1b704e8159..0464e57c6749b 100644 --- a/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts +++ b/x-pack/plugins/threat_intelligence/cypress/screens/indicators.ts @@ -31,7 +31,7 @@ export const FILTERS_GLOBAL_CONTAINER = '[data-test-subj="filters-global-contain export const TIME_RANGE_PICKER = `[data-test-subj="superDatePickerToggleQuickMenuButton"]`; -export const QUERY_INPUT = `[data-test-subj="iocListPageQueryInput"]`; +export const QUERY_INPUT = `[data-test-subj="queryInput"]`; export const EMPTY_STATE = '[data-test-subj="indicatorsTableEmptyState"]'; diff --git a/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx b/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx index 6c7621977b8dc..04ee12819d988 100644 --- a/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx +++ b/x-pack/plugins/threat_intelligence/public/components/layout/layout.tsx @@ -6,17 +6,23 @@ */ import { EuiPageHeader, EuiPageHeaderSection, EuiSpacer, EuiText } from '@elastic/eui'; -import React, { FC } from 'react'; +import React, { FC, ReactNode } from 'react'; import { SecuritySolutionPageWrapper } from '../../containers/security_solution_page_wrapper'; export interface LayoutProps { pageTitle?: string; border?: boolean; + subHeader?: ReactNode; } export const TITLE_TEST_ID = 'tiDefaultPageLayoutTitle'; -export const DefaultPageLayout: FC<LayoutProps> = ({ children, pageTitle, border = true }) => { +export const DefaultPageLayout: FC<LayoutProps> = ({ + children, + pageTitle, + border = true, + subHeader, +}) => { return ( <SecuritySolutionPageWrapper> <EuiPageHeader alignItems="center" bottomBorder={border}> @@ -26,6 +32,12 @@ export const DefaultPageLayout: FC<LayoutProps> = ({ children, pageTitle, border <h2 data-test-subj={TITLE_TEST_ID}>{pageTitle}</h2> </EuiText> )} + {subHeader ? ( + <> + <EuiSpacer size="m" /> + {subHeader} + </> + ) : null} </EuiPageHeaderSection> </EuiPageHeader> <EuiSpacer size="l" /> diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts b/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts new file mode 100644 index 0000000000000..f83c0e64fda23 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/components/update_status/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './update_status'; diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx new file mode 100644 index 0000000000000..2ed1503d89a78 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.test.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { TestProvidersComponent } from '../../common/mocks/test_providers'; +import { UpdateStatus } from './update_status'; + +describe('<UpdateStatus />', () => { + it('should render Updated now', () => { + const result = render(<UpdateStatus updatedAt={Date.now()} isUpdating={false} />, { + wrapper: TestProvidersComponent, + }); + + expect(result.asFragment()).toMatchInlineSnapshot(` + <DocumentFragment> + <div + class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive" + > + <div + class="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + class="euiText emotion-euiText-xs-euiTextColor-subdued" + data-test-subj="updateStatus" + > + Updated now + </div> + </div> + </div> + </DocumentFragment> + `); + }); + + it('should render Updating when isUpdating', () => { + const result = render(<UpdateStatus updatedAt={Date.now()} isUpdating={true} />, { + wrapper: TestProvidersComponent, + }); + + expect(result.asFragment()).toMatchInlineSnapshot(` + <DocumentFragment> + <div + class="euiFlexGroup euiFlexGroup--gutterLarge euiFlexGroup--directionRow euiFlexGroup--responsive" + > + <div + class="euiFlexItem euiFlexItem--flexGrowZero" + > + <div + class="euiText emotion-euiText-xs-euiTextColor-subdued" + data-test-subj="updateStatus" + > + Updating... + </div> + </div> + </div> + </DocumentFragment> + `); + }); +}); diff --git a/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx new file mode 100644 index 0000000000000..02f43481186dd --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/components/update_status/update_status.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedRelative } from '@kbn/i18n-react'; + +interface UpdateStatusProps { + updatedAt: number; + isUpdating: boolean; +} + +const UPDATING = i18n.translate('xpack.threatIntelligence.updateStatus.updating', { + defaultMessage: 'Updating...', +}); + +const UPDATED = i18n.translate('xpack.threatIntelligence.updateStatus.updated', { + defaultMessage: 'Updated', +}); + +export const UpdateStatus: React.FC<UpdateStatusProps> = ({ isUpdating, updatedAt }) => ( + <EuiFlexGroup> + <EuiFlexItem grow={false}> + <EuiText size="xs" color="subdued" data-test-subj="updateStatus"> + {isUpdating ? ( + UPDATING + ) : ( + <> + {UPDATED} +   + <FormattedRelative value={new Date(updatedAt)} /> + </> + )} + </EuiText> + </EuiFlexItem> + </EuiFlexGroup> +); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx index 42f6a4eb1fdb7..40d64636fa346 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx @@ -105,6 +105,7 @@ describe('useIndicators()', () => { expect(hookResult.result.current).toMatchInlineSnapshot(` Object { + "dataUpdatedAt": 0, "handleRefresh": [Function], "indicatorCount": 0, "indicators": Array [], diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts index 2352f302a1d4d..e2e0aaddf07aa 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.ts @@ -47,6 +47,8 @@ export interface UseIndicatorsValue { * Data loading is in progress (see docs on `isFetching` here: https://tanstack.com/query/v4/docs/guides/queries) */ isFetching: boolean; + + dataUpdatedAt: number; } export const useIndicators = ({ @@ -95,7 +97,7 @@ export const useIndicators = ({ [inspectorAdapters, searchService] ); - const { isLoading, isFetching, data, refetch } = useQuery( + const { isLoading, isFetching, data, refetch, dataUpdatedAt } = useQuery( [ 'indicatorsTable', { @@ -132,5 +134,6 @@ export const useIndicators = ({ isLoading, isFetching, handleRefresh, + dataUpdatedAt, }; }; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx index e46c605d1a90a..7f4db9fa75262 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx @@ -42,6 +42,7 @@ describe('<IndicatorsPage />', () => { onChangeItemsPerPage: stub, onChangePage: stub, handleRefresh: stub, + dataUpdatedAt: Date.now(), }); (useFilters as jest.MockedFunction<typeof useFilters>).mockReturnValue({ diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx index 511faaa73a7a0..fcf690631d740 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx @@ -20,6 +20,7 @@ import { useColumnSettings } from './components/indicators_table/hooks/use_colum import { useAggregatedIndicators } from './hooks/use_aggregated_indicators'; import { IndicatorsFilters } from './containers/indicators_filters'; import { useSecurityContext } from '../../hooks/use_security_context'; +import { UpdateStatus } from '../../components/update_status'; const queryClient = new QueryClient(); @@ -48,6 +49,7 @@ const IndicatorsPageContent: VFC = () => { pagination, isLoading: isLoadingIndicators, isFetching: isFetchingIndicators, + dataUpdatedAt, } = useIndicators({ filters, filterQuery, @@ -72,10 +74,14 @@ const IndicatorsPageContent: VFC = () => { return ( <FieldTypesProvider> - <DefaultPageLayout pageTitle="Indicators"> + <DefaultPageLayout + pageTitle="Indicators" + subHeader={<UpdateStatus isUpdating={isFetchingIndicators} updatedAt={dataUpdatedAt} />} + > <FiltersGlobal> <SiemSearchBar indexPattern={indexPattern} id="global" /> </FiltersGlobal> + <IndicatorsBarChartWrapper dateRange={dateRange} series={series} From 9e88bc978fc8b19dc78575c48d3efbf489cb5363 Mon Sep 17 00:00:00 2001 From: Giorgos Bamparopoulos <georgios.bamparopoulos@elastic.co> Date: Tue, 4 Oct 2022 15:51:26 +0100 Subject: [PATCH 045/174] Add getByTestSubj command (#142591) --- .../power_user/feature_flag/comparison.cy.ts | 12 ++--- .../integration_policy.cy.ts | 12 ++--- .../settings/agent_configurations.cy.ts | 11 ++-- .../power_user/settings/custom_links.cy.ts | 10 ++-- .../storage_explorer/storage_explorer.cy.ts | 14 ++--- .../e2e/read_only_user/deep_links.cy.ts | 8 +-- .../e2e/read_only_user/dependencies.cy.ts | 6 +-- .../read_only_user/errors/error_details.cy.ts | 4 +- .../read_only_user/errors/errors_page.cy.ts | 6 +-- .../cypress/e2e/read_only_user/home.cy.ts | 2 +- .../header_filters/header_filters.cy.ts | 2 +- .../service_inventory/service_inventory.cy.ts | 4 +- .../service_overview/errors_table.cy.ts | 8 +-- .../service_overview/header_filters.cy.ts | 28 ++++------ .../service_overview/instances_table.cy.ts | 16 ++---- .../service_overview/service_overview.cy.ts | 42 +++++++-------- .../service_overview/time_comparison.cy.ts | 40 ++++++-------- .../transaction_details/span_links.cy.ts | 52 +++++++++---------- .../transaction_details.cy.ts | 12 ++--- .../transactions_overview.cy.ts | 8 +-- .../apm/ftr_e2e/cypress/support/commands.ts | 18 ++++--- .../apm/ftr_e2e/cypress/support/types.d.ts | 1 + 22 files changed, 144 insertions(+), 172 deletions(-) diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts index d1159efd0fc90..7d40105db192e 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/feature_flag/comparison.cy.ts @@ -36,19 +36,19 @@ describe('Comparison feature flag', () => { it('shows the comparison feature enabled in services overview', () => { cy.visitKibana('/app/apm/services'); cy.get('input[type="checkbox"]#comparison').should('be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled'); + cy.getByTestSubj('comparisonSelect').should('not.be.disabled'); }); it('shows the comparison feature enabled in dependencies overview', () => { cy.visitKibana('/app/apm/dependencies'); cy.get('input[type="checkbox"]#comparison').should('be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled'); + cy.getByTestSubj('comparisonSelect').should('not.be.disabled'); }); it('shows the comparison feature disabled in service map overview page', () => { cy.visitKibana('/app/apm/service-map'); cy.get('input[type="checkbox"]#comparison').should('be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('not.be.disabled'); + cy.getByTestSubj('comparisonSelect').should('not.be.disabled'); }); }); @@ -71,7 +71,7 @@ describe('Comparison feature flag', () => { it('shows the comparison feature disabled in services overview', () => { cy.visitKibana('/app/apm/services'); cy.get('input[type="checkbox"]#comparison').should('not.be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); it('shows the comparison feature disabled in dependencies overview page', () => { @@ -81,13 +81,13 @@ describe('Comparison feature flag', () => { cy.visitKibana('/app/apm/dependencies'); cy.wait('@topDependenciesRequest'); cy.get('input[type="checkbox"]#comparison').should('not.be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); it('shows the comparison feature disabled in service map overview page', () => { cy.visitKibana('/app/apm/service-map'); cy.get('input[type="checkbox"]#comparison').should('not.be.checked'); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts index c25e6a6800311..5d275770e462d 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/integration_settings/integration_policy.cy.ts @@ -60,21 +60,19 @@ describe('when navigating to integration page', () => { cy.visitKibana(integrationsPath); // open integration policy form - cy.get('[data-test-subj="integration-card:epr:apm:featured').click(); + cy.getByTestSubj('integration-card:epr:apm:featured').click(); cy.contains('Elastic APM in Fleet').click(); cy.contains('a', 'APM integration').click(); - cy.get('[data-test-subj="addIntegrationPolicyButton"]').click(); + cy.getByTestSubj('addIntegrationPolicyButton').click(); }); it('checks validators for required fields', () => { const requiredFields = policyFormFields.filter((field) => field.required); requiredFields.map((field) => { - cy.get(`[data-test-subj="${field.selector}"`).clear(); - cy.get('[data-test-subj="createPackagePolicySaveButton"').should( - 'be.disabled' - ); - cy.get(`[data-test-subj="${field.selector}"`).type(field.value); + cy.getByTestSubj(field.selector).clear(); + cy.getByTestSubj('createPackagePolicySaveButton').should('be.disabled'); + cy.getByTestSubj(field.selector).type(field.value); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts index 5be39b4f082dc..47f8c537b100c 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/agent_configurations.cy.ts @@ -90,7 +90,7 @@ describe('Agent configuration', () => { '/api/apm/settings/agent-configuration/environments?*' ).as('serviceEnvironmentApi'); cy.contains('Create configuration').click(); - cy.get('[data-test-subj="serviceNameComboBox"]') + cy.getByTestSubj('serviceNameComboBox') .click() .type('opbeans-node') .type('{enter}'); @@ -98,7 +98,7 @@ describe('Agent configuration', () => { cy.contains('opbeans-node').realClick(); cy.wait('@serviceEnvironmentApi'); - cy.get('[data-test-subj="serviceEnviromentComboBox"]') + cy.getByTestSubj('serviceEnviromentComboBox') .click({ force: true }) .type('prod') .type('{enter}'); @@ -115,14 +115,11 @@ describe('Agent configuration', () => { '/api/apm/settings/agent-configuration/environments' ).as('serviceEnvironmentApi'); cy.contains('Create configuration').click(); - cy.get('[data-test-subj="serviceNameComboBox"]') - .click() - .type('All') - .type('{enter}'); + cy.getByTestSubj('serviceNameComboBox').click().type('All').type('{enter}'); cy.contains('All').realClick(); cy.wait('@serviceEnvironmentApi'); - cy.get('[data-test-subj="serviceEnviromentComboBox"]') + cy.getByTestSubj('serviceEnviromentComboBox') .click({ force: true }) .type('All'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts index 615ff2b49a85a..b680f745609bc 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/settings/custom_links.cy.ts @@ -52,7 +52,7 @@ describe('Custom links', () => { it('creates custom link', () => { cy.visitKibana(basePath); - const emptyPrompt = cy.get('[data-test-subj="customLinksEmptyPrompt"]'); + const emptyPrompt = cy.getByTestSubj('customLinksEmptyPrompt'); cy.contains('Create custom link').click(); cy.contains('Create link'); cy.contains('Save').should('be.disabled'); @@ -63,7 +63,7 @@ describe('Custom links', () => { emptyPrompt.should('not.exist'); cy.contains('foo'); cy.contains('https://foo.com'); - cy.get('[data-test-subj="editCustomLink"]').click(); + cy.getByTestSubj('editCustomLink').click(); cy.contains('Delete').click(); }); @@ -71,14 +71,14 @@ describe('Custom links', () => { cy.visitKibana(basePath); // wait for empty prompt - cy.get('[data-test-subj="customLinksEmptyPrompt"]').should('be.visible'); + cy.getByTestSubj('customLinksEmptyPrompt').should('be.visible'); cy.contains('Create custom link').click(); - cy.get('[data-test-subj="filter-0"]').select('service.name'); + cy.getByTestSubj('filter-0').select('service.name'); cy.get( '[data-test-subj="service.name.value"] [data-test-subj="comboBoxSearchInput"]' ).type('foo'); - cy.get('[data-test-subj="filter-0"]').select('service.environment'); + cy.getByTestSubj('filter-0').select('service.environment'); cy.get( '[data-test-subj="service.environment.value"] [data-test-subj="comboBoxInput"]' ).should('not.contain', 'foo'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index e989ea5cf0faf..20577f8bf5793 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -85,7 +85,7 @@ describe('Storage Explorer', () => { }); it('renders the storage timeseries chart', () => { - cy.get('[data-test-subj="storageExplorerTimeseriesChart"]'); + cy.getByTestSubj('storageExplorerTimeseriesChart'); }); it('has a list of services and environments', () => { @@ -115,7 +115,7 @@ describe('Storage Explorer', () => { it('with the correct environment when changing the environment', () => { cy.wait(mainAliasNames); - cy.get('[data-test-subj="environmentFilter"]').type('production'); + cy.getByTestSubj('environmentFilter').type('production'); cy.contains('button', 'production').click({ force: true }); @@ -148,7 +148,7 @@ describe('Storage Explorer', () => { it('with the correct lifecycle phase when changing the lifecycle phase', () => { cy.wait(mainAliasNames); - cy.get('[data-test-subj="storageExplorerLifecyclePhaseSelect"]').click(); + cy.getByTestSubj('storageExplorerLifecyclePhaseSelect').click(); cy.contains('button', 'Warm').click(); cy.expectAPIsToHaveBeenCalledWith({ @@ -180,13 +180,13 @@ describe('Storage Explorer', () => { cy.wait(mainAliasNames); cy.contains('opbeans-node'); - cy.get('[data-test-subj="storageDetailsButton_opbeans-node"]').click(); - cy.get('[data-test-subj="loadingSpinner"]').should('be.visible'); + cy.getByTestSubj('storageDetailsButton_opbeans-node').click(); + cy.getByTestSubj('loadingSpinner').should('be.visible'); cy.wait('@storageDetailsRequest'); cy.contains('Service storage details'); - cy.get('[data-test-subj="storageExplorerTimeseriesChart"]'); - cy.get('[data-test-subj="serviceStorageDetailsTable"]'); + cy.getByTestSubj('storageExplorerTimeseriesChart'); + cy.getByTestSubj('serviceStorageDetailsTable'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts index cfcabe85b5b2a..00b842f3265c7 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/deep_links.cy.ts @@ -11,7 +11,7 @@ describe('APM deep links', () => { }); it('navigates to apm links on search elastic', () => { cy.visitKibana('/'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); cy.contains('APM'); cy.contains('APM / Services'); cy.contains('APM / Traces'); @@ -23,17 +23,17 @@ describe('APM deep links', () => { cy.contains('APM').click({ force: true }); cy.url().should('include', '/apm/services'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); // navigates to services page cy.contains('APM / Services').click({ force: true }); cy.url().should('include', '/apm/services'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); // navigates to traces page cy.contains('APM / Traces').click({ force: true }); cy.url().should('include', '/apm/traces'); - cy.get('[data-test-subj="nav-search-input"]').type('APM'); + cy.getByTestSubj('nav-search-input').type('APM'); // navigates to service maps cy.contains('APM / Service Map').click({ force: true }); cy.url().should('include', '/apm/service-map'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts index 653809a8e04d3..2ef3ae42b1aac 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/dependencies.cy.ts @@ -66,9 +66,9 @@ describe('Dependencies', () => { })}` ); - cy.get('[data-test-subj="latencyChart"]'); - cy.get('[data-test-subj="throughputChart"]'); - cy.get('[data-test-subj="errorRateChart"]'); + cy.getByTestSubj('latencyChart'); + cy.getByTestSubj('throughputChart'); + cy.getByTestSubj('errorRateChart'); cy.contains('opbeans-java').click({ force: true }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts index 19de523c7ab1f..d00d8036df3bb 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/error_details.cy.ts @@ -68,13 +68,13 @@ describe('Error details', () => { it('shows errors distribution chart', () => { cy.visitKibana(errorDetailsPageHref); cy.contains('Error group 00000'); - cy.get('[data-test-subj="errorDistribution"]').contains('Occurrences'); + cy.getByTestSubj('errorDistribution').contains('Occurrences'); }); it('shows top erroneous transactions table', () => { cy.visitKibana(errorDetailsPageHref); cy.contains('Top 5 affected transactions'); - cy.get('[data-test-subj="topErroneousTransactionsTable"]') + cy.getByTestSubj('topErroneousTransactionsTable') .contains('a', 'GET /apple 🍎') .click(); cy.url().should('include', 'opbeans-java/transactions/view'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts index 301b3384ee2eb..8ac95d509d0bd 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/errors/errors_page.cy.ts @@ -81,14 +81,14 @@ describe('Errors page', () => { it('clicking on type adds a filter in the kuerybar', () => { cy.visitKibana(javaServiceErrorsPageHref); - cy.get('[data-test-subj="headerFilterKuerybar"]') + cy.getByTestSubj('headerFilterKuerybar') .invoke('val') .should('be.empty'); // `force: true` because Cypress says the element is 0x0 cy.contains('exception 0').click({ force: true, }); - cy.get('[data-test-subj="headerFilterKuerybar"]') + cy.getByTestSubj('headerFilterKuerybar') .its('length') .should('be.gt', 0); cy.get('table') @@ -158,7 +158,7 @@ describe('Check detailed statistics API with multiple errors', () => { ]) ); }); - cy.get('[data-test-subj="pagination-button-1"]').click(); + cy.getByTestSubj('pagination-button-1').click(); cy.wait('@errorsDetailedStatistics').then((payload) => { expect(payload.request.body.groupIds).eql( JSON.stringify([ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts index 2ee2f4f019b12..e0c4a3aedd2b3 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/home.cy.ts @@ -69,7 +69,7 @@ describe('Home page', () => { cy.contains('Services'); cy.contains('opbeans-rum').click({ force: true }); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'page-load' ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts index c4e87ac15fbe1..4f72e968d81f8 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/header_filters/header_filters.cy.ts @@ -44,7 +44,7 @@ describe('Service inventory - header filters', () => { cy.contains('Services'); cy.contains('opbeans-node'); cy.contains('service 1'); - cy.get('[data-test-subj="headerFilterKuerybar"]') + cy.getByTestSubj('headerFilterKuerybar') .type(`service.name: "${specialServiceName}"`) .type('{enter}'); cy.contains('service 1'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts index 015df91d792e9..2d40c690a8c92 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_inventory/service_inventory.cy.ts @@ -93,7 +93,7 @@ describe('Service inventory', () => { it('with the correct environment when changing the environment', () => { cy.wait(mainAliasNames); - cy.get('[data-test-subj="environmentFilter"]').type('production'); + cy.getByTestSubj('environmentFilter').type('production'); cy.contains('button', 'production').click(); @@ -175,7 +175,7 @@ describe('Service inventory', () => { ]) ); }); - cy.get('[data-test-subj="pagination-button-1"]').click(); + cy.getByTestSubj('pagination-button-1').click(); cy.wait('@detailedStatisticsRequest').then((payload) => { expect(payload.request.body.serviceNames).eql( JSON.stringify([ diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts index b175eb0430ed4..d693148010c7e 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/errors_table.cy.ts @@ -50,16 +50,12 @@ describe('Errors table', () => { it('clicking on type adds a filter in the kuerybar and navigates to errors page', () => { cy.visitKibana(serviceOverviewHref); - cy.get('[data-test-subj="headerFilterKuerybar"]') - .invoke('val') - .should('be.empty'); + cy.getByTestSubj('headerFilterKuerybar').invoke('val').should('be.empty'); // `force: true` because Cypress says the element is 0x0 cy.contains('Exception').click({ force: true, }); - cy.get('[data-test-subj="headerFilterKuerybar"]') - .its('length') - .should('be.gt', 0); + cy.getByTestSubj('headerFilterKuerybar').its('length').should('be.gt', 0); cy.get('table').find('td:contains("Exception")').should('have.length', 1); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts index 6376d544821aa..8a25024506696 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/header_filters.cy.ts @@ -77,13 +77,13 @@ describe('Service overview - header filters', () => { cy.visitKibana(serviceOverviewHref); cy.contains('opbeans-node'); cy.url().should('not.include', 'transactionType'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); cy.url().should('include', 'transactionType=Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -94,7 +94,7 @@ describe('Service overview - header filters', () => { cy.intercept('GET', endpoint).as(name); }); cy.visitKibana(serviceOverviewHref); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); @@ -104,9 +104,9 @@ describe('Service overview - header filters', () => { value: 'transactionType=request', }); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); cy.url().should('include', 'transactionType=Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -129,18 +129,12 @@ describe('Service overview - header filters', () => { }) ); cy.contains('opbeans-java'); - cy.get('[data-test-subj="headerFilterKuerybar"]').type('transaction.n'); + cy.getByTestSubj('headerFilterKuerybar').type('transaction.n'); cy.contains('transaction.name'); - cy.get('[data-test-subj="suggestionContainer"]') - .find('li') - .first() - .click(); - cy.get('[data-test-subj="headerFilterKuerybar"]').type(':'); - cy.get('[data-test-subj="suggestionContainer"]') - .find('li') - .first() - .click(); - cy.get('[data-test-subj="headerFilterKuerybar"]').type('{enter}'); + cy.getByTestSubj('suggestionContainer').find('li').first().click(); + cy.getByTestSubj('headerFilterKuerybar').type(':'); + cy.getByTestSubj('suggestionContainer').find('li').first().click(); + cy.getByTestSubj('headerFilterKuerybar').type('{enter}'); cy.url().should('include', '&kuery=transaction.name'); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts index 03653df2b0bb6..578b116a10592 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/instances_table.cy.ts @@ -63,7 +63,7 @@ describe('Instances table', () => { it('shows empty message', () => { cy.visitKibana(testServiveHref); cy.contains('test-service'); - cy.get('[data-test-subj="serviceInstancesTableContainer"]').contains( + cy.getByTestSubj('serviceInstancesTableContainer').contains( 'No instances found' ); }); @@ -77,9 +77,7 @@ describe('Instances table', () => { it('hides instances table', () => { cy.visitKibana(serviceRumOverviewHref); cy.contains('opbeans-rum'); - cy.get('[data-test-subj="serviceInstancesTableContainer"]').should( - 'not.exist' - ); + cy.getByTestSubj('serviceInstancesTableContainer').should('not.exist'); }); }); @@ -109,10 +107,8 @@ describe('Instances table', () => { cy.contains(serviceNodeName); cy.wait('@instancesDetailsRequest'); - cy.get( - `[data-test-subj="instanceDetailsButton_${serviceNodeName}"]` - ).realClick(); - cy.get('[data-test-subj="loadingSpinner"]').should('be.visible'); + cy.getByTestSubj(`instanceDetailsButton_${serviceNodeName}`).realClick(); + cy.getByTestSubj('loadingSpinner').should('be.visible'); cy.wait('@instanceDetailsRequest').then(() => { cy.contains('Service'); }); @@ -130,9 +126,7 @@ describe('Instances table', () => { cy.contains(serviceNodeName); cy.wait('@instancesDetailsRequest'); - cy.get( - `[data-test-subj="instanceActionsButton_${serviceNodeName}"]` - ).click(); + cy.getByTestSubj(`instanceActionsButton_${serviceNodeName}`).click(); cy.contains('Pod logs'); cy.contains('Pod metrics'); // cy.contains('Container logs'); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts index e8319c8efafeb..8173e94557b29 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/service_overview.cy.ts @@ -109,13 +109,13 @@ describe('Service Overview', () => { cy.contains('opbeans-node'); // set skipFailures to true to not fail the test when there are accessibility failures checkA11y({ skipFailures: true }); - cy.get('[data-test-subj="latencyChart"]'); - cy.get('[data-test-subj="throughput"]'); - cy.get('[data-test-subj="transactionsGroupTable"]'); - cy.get('[data-test-subj="serviceOverviewErrorsTable"]'); - cy.get('[data-test-subj="dependenciesTable"]'); - cy.get('[data-test-subj="instancesLatencyDistribution"]'); - cy.get('[data-test-subj="serviceOverviewInstancesTable"]'); + cy.getByTestSubj('latencyChart'); + cy.getByTestSubj('throughput'); + cy.getByTestSubj('transactionsGroupTable'); + cy.getByTestSubj('serviceOverviewErrorsTable'); + cy.getByTestSubj('dependenciesTable'); + cy.getByTestSubj('instancesLatencyDistribution'); + cy.getByTestSubj('serviceOverviewInstancesTable'); }); }); @@ -134,17 +134,17 @@ describe('Service Overview', () => { cy.wait('@transactionTypesRequest'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); cy.contains('Transactions').click(); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -159,18 +159,18 @@ describe('Service Overview', () => { cy.visitKibana(baseUrl); cy.wait('@transactionTypesRequest'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); cy.contains('View transactions').click(); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); @@ -226,7 +226,7 @@ describe('Service Overview', () => { 'suggestionsRequest' ); - cy.get('[data-test-subj="environmentFilter"] input').type('production', { + cy.getByTestSubj('environmentFilter').find('input').type('production', { force: true, }); @@ -235,9 +235,7 @@ describe('Service Overview', () => { value: 'fieldValue=production', }); - cy.get( - '[data-test-subj="comboBoxOptionsList environmentFilter-optionsList"]' - ) + cy.getByTestSubj('comboBoxOptionsList environmentFilter-optionsList') .contains('production') .click({ force: true }); @@ -271,11 +269,11 @@ describe('Service Overview', () => { }); it('when selecting a different comparison window', () => { - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); // selects another comparison type - cy.get('[data-test-subj="comparisonSelect"]').select('1w'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w'); + cy.getByTestSubj('comparisonSelect').select('1w'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1w'); cy.expectAPIsToHaveBeenCalledWith({ apisIntercepted: aliasNamesWithComparison, value: 'offset', diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts index 718a2a4a06cf7..bce3da42d5a3f 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/time_comparison.cy.ts @@ -101,18 +101,18 @@ describe('Service overview: Time Comparison', () => { cy.visitKibana(serviceOverviewPath); cy.contains('opbeans-java'); // opens the page with "Day before" selected - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); // selects another comparison type - cy.get('[data-test-subj="comparisonSelect"]').select('1w'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w'); + cy.getByTestSubj('comparisonSelect').select('1w'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1w'); }); it('changes comparison type when a new time range is selected', () => { cy.visitKibana(serviceOverviewHref); cy.contains('opbeans-java'); // Time comparison default value - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); cy.contains('Day before'); cy.contains('Week before'); @@ -121,17 +121,14 @@ describe('Service overview: Time Comparison', () => { '2021-10-20T00:00:00.000Z' ); - cy.get('[data-test-subj="superDatePickerApplyTimeButton"]').click(); + cy.getByTestSubj('superDatePickerApplyTimeButton').click(); - cy.get('[data-test-subj="comparisonSelect"]').should( - 'have.value', - '864000000ms' - ); - cy.get('[data-test-subj="comparisonSelect"]').should( + cy.getByTestSubj('comparisonSelect').should('have.value', '864000000ms'); + cy.getByTestSubj('comparisonSelect').should( 'not.contain.text', 'Day before' ); - cy.get('[data-test-subj="comparisonSelect"]').should( + cy.getByTestSubj('comparisonSelect').should( 'not.contain.text', 'Week before' ); @@ -141,17 +138,14 @@ describe('Service overview: Time Comparison', () => { cy.contains('Week before'); cy.changeTimeRange('Last 24 hours'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1d'); + cy.getByTestSubj('comparisonSelect').should('have.value', '1d'); cy.contains('Day before'); cy.contains('Week before'); cy.changeTimeRange('Last 7 days'); - cy.get('[data-test-subj="comparisonSelect"]').should('have.value', '1w'); - cy.get('[data-test-subj="comparisonSelect"]').should( - 'contain.text', - 'Week before' - ); - cy.get('[data-test-subj="comparisonSelect"]').should( + cy.getByTestSubj('comparisonSelect').should('have.value', '1w'); + cy.getByTestSubj('comparisonSelect').should('contain.text', 'Week before'); + cy.getByTestSubj('comparisonSelect').should( 'not.contain.text', 'Day before' ); @@ -170,7 +164,7 @@ describe('Service overview: Time Comparison', () => { ); cy.contains('opbeans-java'); cy.wait('@throughputChartRequest'); - cy.get('[data-test-subj="throughput"]') + cy.getByTestSubj('throughput') .get('#echHighlighterClipPath__throughput') .realHover({ position: 'center' }); cy.contains('Week before'); @@ -186,17 +180,17 @@ describe('Service overview: Time Comparison', () => { cy.contains('opbeans-java'); // Comparison is enabled by default - cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled'); + cy.getByTestSubj('comparisonSelect').should('be.enabled'); // toggles off comparison cy.contains('Comparison').click(); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); }); it('calls APIs without comparison time range', () => { cy.visitKibana(serviceOverviewHref); - cy.get('[data-test-subj="comparisonSelect"]').should('be.enabled'); + cy.getByTestSubj('comparisonSelect').should('be.enabled'); const offset = `offset=1d`; // When the page loads it fetches all APIs with comparison time range @@ -212,7 +206,7 @@ describe('Service overview: Time Comparison', () => { // toggles off comparison cy.contains('Comparison').click(); - cy.get('[data-test-subj="comparisonSelect"]').should('be.disabled'); + cy.getByTestSubj('comparisonSelect').should('be.disabled'); // When comparison is disabled APIs are called withou comparison time range cy.wait(apisToIntercept.map(({ name }) => `@${name}`)).then( (interceptions) => { diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts index cddba048e8a18..60b36b10ee4a3 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/span_links.cy.ts @@ -50,8 +50,8 @@ describe('Span links', () => { ); cy.contains('Transaction A').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerInternalOnlyIds.spanAId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('2 incoming'); @@ -64,8 +64,8 @@ describe('Span links', () => { ); cy.contains('Transaction B').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerExternalOnlyIds.spanBId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('1 incoming'); @@ -78,8 +78,8 @@ describe('Span links', () => { ); cy.contains('Transaction C').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.transactionCId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerConsumerIds.transactionCId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('1 incoming'); @@ -92,8 +92,8 @@ describe('Span links', () => { ); cy.contains('Transaction C').click(); cy.contains('1 Span link'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerConsumerIds.spanCId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerConsumerIds.spanCId}` ).realHover(); cy.contains('1 Span link found'); cy.contains('1 incoming'); @@ -106,8 +106,8 @@ describe('Span links', () => { ); cy.contains('Transaction D').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.transactionDId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerMultipleIds.transactionDId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('0 incoming'); @@ -120,8 +120,8 @@ describe('Span links', () => { ); cy.contains('Transaction D').click(); cy.contains('2 Span links'); - cy.get( - `[data-test-subj="spanLinksBadge_${ids.producerMultipleIds.spanEId}"]` + cy.getByTestSubj( + `spanLinksBadge_${ids.producerMultipleIds.spanEId}` ).realHover(); cy.contains('2 Span links found'); cy.contains('0 incoming'); @@ -136,7 +136,7 @@ describe('Span links', () => { ); cy.contains('Transaction A').click(); cy.contains('Span A').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('producer-consumer') .should('have.attr', 'href') .and('include', '/services/producer-consumer/overview'); @@ -155,7 +155,7 @@ describe('Span links', () => { 'include', `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Outgoing links (0)' ); @@ -167,7 +167,7 @@ describe('Span links', () => { ); cy.contains('Transaction B').click(); cy.contains('Span B').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('consumer-multiple') .should('have.attr', 'href') @@ -178,9 +178,7 @@ describe('Span links', () => { 'include', `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').select( - 'Outgoing links (1)' - ); + cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)'); cy.contains('Unknown'); cy.contains('trace#1-span#1'); }); @@ -193,7 +191,7 @@ describe('Span links', () => { cy.get( `[aria-controls="${ids.producerConsumerIds.transactionCId}"]` ).click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('consumer-multiple') .should('have.attr', 'href') @@ -205,9 +203,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.spanEId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').select( - 'Outgoing links (1)' - ); + cy.getByTestSubj('spanLinkTypeSelect').select('Outgoing links (1)'); cy.contains('producer-internal-only') .should('have.attr', 'href') .and('include', '/services/producer-internal-only/overview'); @@ -225,7 +221,7 @@ describe('Span links', () => { ); cy.contains('Transaction C').click(); cy.contains('Span C').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('consumer-multiple') .should('have.attr', 'href') @@ -237,7 +233,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerMultipleIds.transactionDId}?waterfallItemId=${ids.producerMultipleIds.transactionDId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Outgoing links (0)' ); @@ -251,7 +247,7 @@ describe('Span links', () => { cy.get( `[aria-controls="${ids.producerMultipleIds.transactionDId}"]` ).click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('producer-consumer') .should('have.attr', 'href') @@ -273,7 +269,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerInternalOnlyIds.transactionAId}?waterfallItemId=${ids.producerInternalOnlyIds.spanAId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Incoming links (0)' ); @@ -285,7 +281,7 @@ describe('Span links', () => { ); cy.contains('Transaction D').click(); cy.contains('Span E').click(); - cy.get('[data-test-subj="spanLinksTab"]').click(); + cy.getByTestSubj('spanLinksTab').click(); cy.contains('producer-external-only') .should('have.attr', 'href') @@ -307,7 +303,7 @@ describe('Span links', () => { `link-to/transaction/${ids.producerConsumerIds.transactionCId}?waterfallItemId=${ids.producerConsumerIds.transactionCId}` ); - cy.get('[data-test-subj="spanLinkTypeSelect"]').should( + cy.getByTestSubj('spanLinkTypeSelect').should( 'contain.text', 'Incoming links (0)' ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts index 5172a5f167fc9..09bd37f5b0b6c 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transaction_details/transaction_details.cy.ts @@ -42,15 +42,15 @@ describe('Transaction details', () => { it('shows transaction name and transaction charts', () => { cy.contains('h2', 'GET /api/product'); - cy.get('[data-test-subj="latencyChart"]'); - cy.get('[data-test-subj="throughput"]'); - cy.get('[data-test-subj="transactionBreakdownChart"]'); - cy.get('[data-test-subj="errorRate"]'); + cy.getByTestSubj('latencyChart'); + cy.getByTestSubj('throughput'); + cy.getByTestSubj('transactionBreakdownChart'); + cy.getByTestSubj('errorRate'); }); it('shows top errors table', () => { cy.contains('Top 5 errors'); - cy.get('[data-test-subj="topErrorsForTransactionTable"]') + cy.getByTestSubj('topErrorsForTransactionTable') .contains('a', '[MockError] Foo') .click(); cy.url().should('include', 'opbeans-java/errors'); @@ -58,7 +58,7 @@ describe('Transaction details', () => { describe('when navigating to a trace sample', () => { it('keeps the same trace sample after reloading the page', () => { - cy.get('[data-test-subj="pagination-button-last"]').click(); + cy.getByTestSubj('pagination-button-last').click(); cy.url().then((url) => { cy.reload(); cy.url().should('eq', url); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts index 83753b7fe2595..2e7e0d336cd5d 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/transactions_overview/transactions_overview.cy.ts @@ -49,17 +49,17 @@ describe('Transactions Overview', () => { it('persists transaction type selected when navigating to Overview tab', () => { cy.visitKibana(serviceTransactionsHref); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'request' ); - cy.get('[data-test-subj="headerFilterTransactionType"]').select('Worker'); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').select('Worker'); + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); cy.get('a[href*="/app/apm/services/opbeans-node/overview"]').click(); - cy.get('[data-test-subj="headerFilterTransactionType"]').should( + cy.getByTestSubj('headerFilterTransactionType').should( 'have.value', 'Worker' ); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 7830e791c3655..9e6e0189e636c 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -52,15 +52,19 @@ Cypress.Commands.add( } ); +Cypress.Commands.add('getByTestSubj', (selector: string) => { + return cy.get(`[data-test-subj="${selector}"]`); +}); + Cypress.Commands.add('changeTimeRange', (value: string) => { - cy.get('[data-test-subj="superDatePickerToggleQuickMenuButton"]').click(); + cy.getByTestSubj('superDatePickerToggleQuickMenuButton').click(); cy.contains(value).click(); }); Cypress.Commands.add('visitKibana', (url: string) => { cy.visit(url); - cy.get('[data-test-subj="kbnLoadingMessage"]').should('exist'); - cy.get('[data-test-subj="kbnLoadingMessage"]').should('not.exist', { + cy.getByTestSubj('kbnLoadingMessage').should('exist'); + cy.getByTestSubj('kbnLoadingMessage').should('not.exist', { timeout: 50000, }); }); @@ -70,13 +74,13 @@ Cypress.Commands.add( (start: string, end: string) => { const format = 'MMM D, YYYY @ HH:mm:ss.SSS'; - cy.get('[data-test-subj="superDatePickerstartDatePopoverButton"]').click(); - cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]') + cy.getByTestSubj('superDatePickerstartDatePopoverButton').click(); + cy.getByTestSubj('superDatePickerAbsoluteDateInput') .eq(0) .clear({ force: true }) .type(moment(start).format(format), { force: true }); - cy.get('[data-test-subj="superDatePickerendDatePopoverButton"]').click(); - cy.get('[data-test-subj="superDatePickerAbsoluteDateInput"]') + cy.getByTestSubj('superDatePickerendDatePopoverButton').click(); + cy.getByTestSubj('superDatePickerAbsoluteDateInput') .eq(1) .clear({ force: true }) .type(moment(end).format(format), { force: true }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts index 2235847e584a4..5d59d4691820a 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/types.d.ts @@ -22,5 +22,6 @@ declare namespace Cypress { value: string; }): void; updateAdvancedSettings(settings: Record<string, unknown>): void; + getByTestSubj(selector: string): Chainable<JQuery<Element>>; } } From d7700a609ff6d720b33c477c62f84038eb3e5021 Mon Sep 17 00:00:00 2001 From: Byron Hulcher <byronhulcher@gmail.com> Date: Tue, 4 Oct 2022 11:01:21 -0400 Subject: [PATCH 046/174] Removing link from native connector advanced configuration steps (#142541) --- .../native_connector_advanced_configuration.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx index fba38e958163a..3e3582bb619fa 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/native_connector_configuration/native_connector_advanced_configuration.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { generateEncodedPath } from '../../../../../shared/encode_path_params'; -import { EuiLinkTo, EuiButtonTo } from '../../../../../shared/react_router_helpers'; +import { EuiButtonTo } from '../../../../../shared/react_router_helpers'; import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; import { IndexNameLogic } from '../../index_name_logic'; @@ -31,19 +31,7 @@ export const NativeConnectorAdvancedConfiguration: React.FC = () => { <EuiText size="s"> <FormattedMessage id="xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.description" - defaultMessage="Finalize your connector by triggering a one time sync, or setting a {schedulingLink}." - values={{ - schedulingLink: ( - <EuiLinkTo to={'' /* TODO docLinks */}> - {i18n.translate( - 'xpack.enterpriseSearch.content.indices.configurationConnector.nativeConnectorAdvancedConfiguration.recurringScheduleLinkLabel', - { - defaultMessage: 'recurring sync schedule', - } - )} - </EuiLinkTo> - ), - }} + defaultMessage="Finalize your connector by triggering a one time sync, or setting a recurring sync schedule." /> </EuiText> </EuiFlexItem> From 53bf927a6fe28545d5b8d7513591f3d8100f8b30 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet <nicolas.chaulet@elastic.co> Date: Tue, 4 Oct 2022 11:03:39 -0400 Subject: [PATCH 047/174] [Fleet] Bulk install packages before creating agent and package policy (#142471) --- .../hooks/devtools_request.tsx | 74 ++++ .../single_page_layout/hooks/form.tsx | 318 ++++++++++++++ .../single_page_layout/hooks/index.tsx | 9 + .../single_page_layout/index.tsx | 414 ++++-------------- .../fleet/public/hooks/use_request/epm.ts | 10 + 5 files changed, 487 insertions(+), 338 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx new file mode 100644 index 0000000000000..55e91154060b7 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx @@ -0,0 +1,74 @@ +/* + * 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 { useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { ExperimentalFeaturesService } from '../../../../../services'; +import { + generateCreatePackagePolicyDevToolsRequest, + generateCreateAgentPolicyDevToolsRequest, +} from '../../../services'; +import { + FLEET_SYSTEM_PACKAGE, + HIDDEN_API_REFERENCE_PACKAGES, +} from '../../../../../../../../common/constants'; +import type { PackageInfo, NewAgentPolicy, NewPackagePolicy } from '../../../../../types'; +import { SelectedPolicyTab } from '../../components'; + +export function useDevToolsRequest({ + newAgentPolicy, + packagePolicy, + packageInfo, + selectedPolicyTab, + withSysMonitoring, +}: { + withSysMonitoring: boolean; + selectedPolicyTab: SelectedPolicyTab; + newAgentPolicy: NewAgentPolicy; + packagePolicy: NewPackagePolicy; + packageInfo?: PackageInfo; +}) { + const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = + ExperimentalFeaturesService.get(); + + const showDevtoolsRequest = + !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') && + isShowDevtoolRequestExperimentEnabled; + + const [devtoolRequest, devtoolRequestDescription] = useMemo(() => { + if (selectedPolicyTab === SelectedPolicyTab.NEW) { + const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; + return [ + `${generateCreateAgentPolicyDevToolsRequest( + newAgentPolicy, + withSysMonitoring && !packagePolicyIsSystem + )}\n\n${generateCreatePackagePolicyDevToolsRequest({ + ...packagePolicy, + })}`, + i18n.translate( + 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription', + { + defaultMessage: + 'These Kibana requests creates a new agent policy and a new package policy.', + } + ), + ]; + } + + return [ + generateCreatePackagePolicyDevToolsRequest({ + ...packagePolicy, + }), + i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', { + defaultMessage: 'This Kibana request creates a new package policy.', + }), + ]; + }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]); + + return { showDevtoolsRequest, devtoolRequest, devtoolRequestDescription }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx new file mode 100644 index 0000000000000..e0f206ef612a8 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/form.tsx @@ -0,0 +1,318 @@ +/* + * 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 { useCallback, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { safeLoad } from 'js-yaml'; + +import type { + AgentPolicy, + NewPackagePolicy, + NewAgentPolicy, + CreatePackagePolicyRequest, + PackagePolicy, + PackageInfo, +} from '../../../../../types'; +import { + useStartServices, + sendCreateAgentPolicy, + sendCreatePackagePolicy, + sendBulkInstallPackages, +} from '../../../../../hooks'; +import { isVerificationError } from '../../../../../services'; +import { FLEET_ELASTIC_AGENT_PACKAGE, FLEET_SYSTEM_PACKAGE } from '../../../../../../../../common'; +import { useConfirmForceInstall } from '../../../../../../integrations/hooks'; +import { validatePackagePolicy, validationHasErrors } from '../../services'; +import type { PackagePolicyValidationResults } from '../../services'; +import type { PackagePolicyFormState } from '../../types'; +import { SelectedPolicyTab } from '../../components'; +import { useOnSaveNavigate } from '../../hooks'; + +async function createAgentPolicy({ + packagePolicy, + newAgentPolicy, + withSysMonitoring, +}: { + packagePolicy: NewPackagePolicy; + newAgentPolicy: NewAgentPolicy; + withSysMonitoring: boolean; +}): Promise<AgentPolicy> { + // do not create agent policy with system integration if package policy already is for system package + const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; + const resp = await sendCreateAgentPolicy(newAgentPolicy, { + withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem, + }); + if (resp.error) { + throw resp.error; + } + if (!resp.data) { + throw new Error('Invalid agent policy creation no data'); + } + return resp.data.item; +} + +async function savePackagePolicy(pkgPolicy: CreatePackagePolicyRequest['body']) { + const result = await sendCreatePackagePolicy(pkgPolicy); + + return result; +} + +export function useOnSubmit({ + agentCount, + selectedPolicyTab, + newAgentPolicy, + withSysMonitoring, + queryParamsPolicyId, + packageInfo, +}: { + packageInfo?: PackageInfo; + newAgentPolicy: NewAgentPolicy; + withSysMonitoring: boolean; + selectedPolicyTab: SelectedPolicyTab; + agentCount: number; + queryParamsPolicyId: string | undefined; +}) { + const { notifications } = useStartServices(); + const confirmForceInstall = useConfirmForceInstall(); + // only used to store the resulting package policy once saved + const [savedPackagePolicy, setSavedPackagePolicy] = useState<PackagePolicy>(); + // Form state + const [formState, setFormState] = useState<PackagePolicyFormState>('VALID'); + + const [agentPolicy, setAgentPolicy] = useState<AgentPolicy | undefined>(); + // New package policy state + const [packagePolicy, setPackagePolicy] = useState<NewPackagePolicy>({ + name: '', + description: '', + namespace: 'default', + policy_id: '', + enabled: true, + inputs: [], + }); + + // Validation state + const [validationResults, setValidationResults] = useState<PackagePolicyValidationResults>(); + const [hasAgentPolicyError, setHasAgentPolicyError] = useState<boolean>(false); + const hasErrors = validationResults ? validationHasErrors(validationResults) : false; + + // Update agent policy method + const updateAgentPolicy = useCallback( + (updatedAgentPolicy: AgentPolicy | undefined) => { + if (updatedAgentPolicy) { + setAgentPolicy(updatedAgentPolicy); + if (packageInfo) { + setHasAgentPolicyError(false); + } + } else { + setHasAgentPolicyError(true); + setAgentPolicy(undefined); + } + + // eslint-disable-next-line no-console + console.debug('Agent policy updated', updatedAgentPolicy); + }, + [packageInfo, setAgentPolicy] + ); + // Update package policy validation + const updatePackagePolicyValidation = useCallback( + (newPackagePolicy?: NewPackagePolicy) => { + if (packageInfo) { + const newValidationResult = validatePackagePolicy( + newPackagePolicy || packagePolicy, + packageInfo, + safeLoad + ); + setValidationResults(newValidationResult); + // eslint-disable-next-line no-console + console.debug('Package policy validation results', newValidationResult); + + return newValidationResult; + } + }, + [packagePolicy, packageInfo] + ); + // Update package policy method + const updatePackagePolicy = useCallback( + (updatedFields: Partial<NewPackagePolicy>) => { + const newPackagePolicy = { + ...packagePolicy, + ...updatedFields, + }; + setPackagePolicy(newPackagePolicy); + + // eslint-disable-next-line no-console + console.debug('Package policy updated', newPackagePolicy); + const newValidationResults = updatePackagePolicyValidation(newPackagePolicy); + const hasPackage = newPackagePolicy.package; + const hasValidationErrors = newValidationResults + ? validationHasErrors(newValidationResults) + : false; + const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== ''; + if ( + hasPackage && + (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) && + !hasValidationErrors + ) { + setFormState('VALID'); + } else { + setFormState('INVALID'); + } + }, + [packagePolicy, setFormState, updatePackagePolicyValidation, selectedPolicyTab] + ); + + const onSaveNavigate = useOnSaveNavigate({ + packagePolicy, + queryParamsPolicyId, + }); + + const navigateAddAgent = (policy?: PackagePolicy) => + onSaveNavigate(policy, ['openEnrollmentFlyout']); + + const navigateAddAgentHelp = (policy?: PackagePolicy) => + onSaveNavigate(policy, ['showAddAgentHelp']); + + const onSubmit = useCallback( + async ({ + force, + overrideCreatedAgentPolicy, + }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => { + if (formState === 'VALID' && hasErrors) { + setFormState('INVALID'); + return; + } + if (agentCount !== 0 && formState !== 'CONFIRM') { + setFormState('CONFIRM'); + return; + } + let createdPolicy = overrideCreatedAgentPolicy; + if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) { + try { + setFormState('LOADING'); + if ((withSysMonitoring || newAgentPolicy.monitoring_enabled?.length) ?? 0 > 0) { + const packagesToPreinstall: string[] = []; + if (packageInfo) { + packagesToPreinstall.push(packageInfo.name); + } + if (withSysMonitoring) { + packagesToPreinstall.push(FLEET_SYSTEM_PACKAGE); + } + if (newAgentPolicy.monitoring_enabled?.length ?? 0 > 0) { + packagesToPreinstall.push(FLEET_ELASTIC_AGENT_PACKAGE); + } + + if (packagesToPreinstall.length > 0) { + await sendBulkInstallPackages([...new Set(packagesToPreinstall)]); + } + } + + createdPolicy = await createAgentPolicy({ + newAgentPolicy, + packagePolicy, + withSysMonitoring, + }); + setAgentPolicy(createdPolicy); + updatePackagePolicy({ policy_id: createdPolicy.id }); + } catch (e) { + setFormState('VALID'); + notifications.toasts.addError(e, { + title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { + defaultMessage: 'Unable to create agent policy', + }), + }); + return; + } + } + + setFormState('LOADING'); + // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately + const { error, data } = await savePackagePolicy({ + ...packagePolicy, + policy_id: createdPolicy?.id ?? packagePolicy.policy_id, + force, + }); + setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS'); + if (!error) { + setSavedPackagePolicy(data!.item); + + const hasAgentsAssigned = agentCount && agentPolicy; + if (!hasAgentsAssigned) { + setFormState('SUBMITTED_NO_AGENTS'); + return; + } + onSaveNavigate(data!.item); + + notifications.toasts.addSuccess({ + title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', { + defaultMessage: `'{packagePolicyName}' integration added.`, + values: { + packagePolicyName: packagePolicy.name, + }, + }), + text: hasAgentsAssigned + ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', { + defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`, + values: { + agentPolicyName: agentPolicy!.name, + }, + }) + : undefined, + 'data-test-subj': 'packagePolicyCreateSuccessToast', + }); + } else { + if (isVerificationError(error)) { + setFormState('VALID'); // don't show the add agent modal + const forceInstall = await confirmForceInstall(packagePolicy.package!); + + if (forceInstall) { + // skip creating the agent policy because it will have already been successfully created + onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true }); + } + return; + } + notifications.toasts.addError(error, { + title: 'Error', + }); + setFormState('VALID'); + } + }, + [ + formState, + hasErrors, + agentCount, + selectedPolicyTab, + packagePolicy, + notifications.toasts, + agentPolicy, + onSaveNavigate, + confirmForceInstall, + newAgentPolicy, + updatePackagePolicy, + withSysMonitoring, + packageInfo, + ] + ); + + return { + agentPolicy, + updateAgentPolicy, + packagePolicy, + updatePackagePolicy, + savedPackagePolicy, + onSubmit, + formState, + setFormState, + hasErrors, + validationResults, + setValidationResults, + hasAgentPolicyError, + setHasAgentPolicyError, + // TODO check + navigateAddAgent, + navigateAddAgentHelp, + }; +} diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx new file mode 100644 index 0000000000000..33d1cee841590 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/index.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { useDevToolsRequest } from './devtools_request'; +export { useOnSubmit } from './form'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx index fae3c84f21268..02f36e2cadcfe 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/index.tsx @@ -21,35 +21,16 @@ import { EuiErrorBoundary, } from '@elastic/eui'; import type { EuiStepProps } from '@elastic/eui/src/components/steps/step'; -import { safeLoad } from 'js-yaml'; -import { useCancelAddPackagePolicy, useOnSaveNavigate } from '../hooks'; -import type { CreatePackagePolicyRequest } from '../../../../../../../common/types'; +import { useCancelAddPackagePolicy } from '../hooks'; import { splitPkgKey } from '../../../../../../../common/services'; -import { - dataTypes, - FLEET_SYSTEM_PACKAGE, - HIDDEN_API_REFERENCE_PACKAGES, -} from '../../../../../../../common/constants'; -import { useConfirmForceInstall } from '../../../../../integrations/hooks'; -import type { - AgentPolicy, - NewAgentPolicy, - NewPackagePolicy, - PackagePolicy, -} from '../../../../types'; -import { - sendCreatePackagePolicy, - useStartServices, - useConfig, - sendGetAgentStatus, - useGetPackageInfoByKey, - sendCreateAgentPolicy, -} from '../../../../hooks'; +import { dataTypes } from '../../../../../../../common/constants'; +import type { NewAgentPolicy } from '../../../../types'; +import { useConfig, sendGetAgentStatus, useGetPackageInfoByKey } from '../../../../hooks'; import { Loading, - Error, + Error as ErrorComponent, ExtensionWrapper, DevtoolsRequestFlyoutButton, } from '../../../../components'; @@ -57,34 +38,21 @@ import { import { agentPolicyFormValidation, ConfirmDeployAgentPolicyModal } from '../../components'; import { useUIExtension } from '../../../../hooks'; import type { PackagePolicyEditExtensionComponentProps } from '../../../../types'; -import { - pkgKeyFromPackageInfo, - isVerificationError, - ExperimentalFeaturesService, -} from '../../../../services'; +import { pkgKeyFromPackageInfo } from '../../../../services'; -import type { - PackagePolicyFormState, - AddToPolicyParams, - CreatePackagePolicyParams, -} from '../types'; +import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types'; import { IntegrationBreadcrumb } from '../components'; -import type { PackagePolicyValidationResults } from '../services'; -import { validatePackagePolicy, validationHasErrors } from '../services'; import { StepConfigurePackagePolicy, StepDefinePackagePolicy, SelectedPolicyTab, StepSelectHosts, } from '../components'; -import { - generateCreatePackagePolicyDevToolsRequest, - generateCreateAgentPolicyDevToolsRequest, -} from '../../services'; import { CreatePackagePolicySinglePageLayout, PostInstallAddAgentModal } from './components'; +import { useDevToolsRequest, useOnSubmit } from './hooks'; const StepsWithLessPadding = styled(EuiSteps)` .euiStep__content { @@ -106,12 +74,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ from, queryParamsPolicyId, }) => { - const { notifications } = useStartServices(); const { agents: { enabled: isFleetEnabled }, } = useConfig(); const { params } = useRouteMatch<AddToPolicyParams>(); - const [agentPolicy, setAgentPolicy] = useState<AgentPolicy | undefined>(); const [newAgentPolicy, setNewAgentPolicy] = useState<NewAgentPolicy>({ name: 'Agent policy 1', @@ -123,64 +89,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ const [withSysMonitoring, setWithSysMonitoring] = useState<boolean>(true); const validation = agentPolicyFormValidation(newAgentPolicy); - // only used to store the resulting package policy once saved - const [savedPackagePolicy, setSavedPackagePolicy] = useState<PackagePolicy>(); - - // Retrieve agent count - const agentPolicyId = agentPolicy?.id; - - const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({ - from, - pkgkey: params.pkgkey, - agentPolicyId, - }); - useEffect(() => { - const getAgentCount = async () => { - const { data } = await sendGetAgentStatus({ policyId: agentPolicyId }); - if (data?.results.total !== undefined) { - setAgentCount(data.results.total); - } - }; - - if (isFleetEnabled && agentPolicyId) { - getAgentCount(); - } - }, [agentPolicyId, isFleetEnabled]); - const [agentCount, setAgentCount] = useState<number>(0); - const [selectedPolicyTab, setSelectedPolicyTab] = useState<SelectedPolicyTab>( queryParamsPolicyId ? SelectedPolicyTab.EXISTING : SelectedPolicyTab.NEW ); - // New package policy state - const [packagePolicy, setPackagePolicy] = useState<NewPackagePolicy>({ - name: '', - description: '', - namespace: 'default', - policy_id: '', - enabled: true, - inputs: [], - }); - - const onSaveNavigate = useOnSaveNavigate({ - packagePolicy, - queryParamsPolicyId, - }); - const navigateAddAgent = (policy?: PackagePolicy) => - onSaveNavigate(policy, ['openEnrollmentFlyout']); - - const navigateAddAgentHelp = (policy?: PackagePolicy) => - onSaveNavigate(policy, ['showAddAgentHelp']); - - const confirmForceInstall = useConfirmForceInstall(); - - // Validation state - const [validationResults, setValidationResults] = useState<PackagePolicyValidationResults>(); - const [hasAgentPolicyError, setHasAgentPolicyError] = useState<boolean>(false); - - // Form state - const [formState, setFormState] = useState<PackagePolicyFormState>('VALID'); - const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey); // Fetch package info const { @@ -194,43 +106,50 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ } }, [packageInfoData]); - // Update agent policy method - const updateAgentPolicy = useCallback( - (updatedAgentPolicy: AgentPolicy | undefined) => { - if (updatedAgentPolicy) { - setAgentPolicy(updatedAgentPolicy); - if (packageInfo) { + const [agentCount, setAgentCount] = useState<number>(0); + + // Save package policy + const { + onSubmit, + updatePackagePolicy, + packagePolicy, + agentPolicy, + updateAgentPolicy, + savedPackagePolicy, + formState, + setFormState, + navigateAddAgent, + navigateAddAgentHelp, + setHasAgentPolicyError, + validationResults, + hasAgentPolicyError, + } = useOnSubmit({ + agentCount, + packageInfo, + newAgentPolicy, + selectedPolicyTab, + withSysMonitoring, + queryParamsPolicyId, + }); + + const setPolicyValidation = useCallback( + (selectedTab: SelectedPolicyTab, updatedAgentPolicy: NewAgentPolicy) => { + if (selectedTab === SelectedPolicyTab.NEW) { + if ( + !updatedAgentPolicy.name || + updatedAgentPolicy.name.trim() === '' || + !updatedAgentPolicy.namespace || + updatedAgentPolicy.namespace.trim() === '' + ) { + setHasAgentPolicyError(true); + } else { setHasAgentPolicyError(false); } - } else { - setHasAgentPolicyError(true); - setAgentPolicy(undefined); } - - // eslint-disable-next-line no-console - console.debug('Agent policy updated', updatedAgentPolicy); }, - [packageInfo, setAgentPolicy] + [setHasAgentPolicyError] ); - const setPolicyValidation = ( - selectedTab: SelectedPolicyTab, - updatedAgentPolicy: NewAgentPolicy - ) => { - if (selectedTab === SelectedPolicyTab.NEW) { - if ( - !updatedAgentPolicy.name || - updatedAgentPolicy.name.trim() === '' || - !updatedAgentPolicy.namespace || - updatedAgentPolicy.namespace.trim() === '' - ) { - setHasAgentPolicyError(true); - } else { - setHasAgentPolicyError(false); - } - } - }; - const updateNewAgentPolicy = useCallback( (updatedFields: Partial<NewAgentPolicy>) => { const updatedAgentPolicy = { @@ -240,7 +159,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ setNewAgentPolicy(updatedAgentPolicy); setPolicyValidation(selectedPolicyTab, updatedAgentPolicy); }, - [setNewAgentPolicy, newAgentPolicy, selectedPolicyTab] + [setNewAgentPolicy, setPolicyValidation, newAgentPolicy, selectedPolicyTab] ); const updateSelectedPolicyTab = useCallback( @@ -248,58 +167,29 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ setSelectedPolicyTab(selectedTab); setPolicyValidation(selectedTab, newAgentPolicy); }, - [setSelectedPolicyTab, newAgentPolicy] + [setSelectedPolicyTab, setPolicyValidation, newAgentPolicy] ); - const hasErrors = validationResults ? validationHasErrors(validationResults) : false; - // Update package policy validation - const updatePackagePolicyValidation = useCallback( - (newPackagePolicy?: NewPackagePolicy) => { - if (packageInfo) { - const newValidationResult = validatePackagePolicy( - newPackagePolicy || packagePolicy, - packageInfo, - safeLoad - ); - setValidationResults(newValidationResult); - // eslint-disable-next-line no-console - console.debug('Package policy validation results', newValidationResult); - - return newValidationResult; - } - }, - [packagePolicy, packageInfo] - ); + // Retrieve agent count + const agentPolicyId = agentPolicy?.id; - // Update package policy method - const updatePackagePolicy = useCallback( - (updatedFields: Partial<NewPackagePolicy>) => { - const newPackagePolicy = { - ...packagePolicy, - ...updatedFields, - }; - setPackagePolicy(newPackagePolicy); - - // eslint-disable-next-line no-console - console.debug('Package policy updated', newPackagePolicy); - const newValidationResults = updatePackagePolicyValidation(newPackagePolicy); - const hasPackage = newPackagePolicy.package; - const hasValidationErrors = newValidationResults - ? validationHasErrors(newValidationResults) - : false; - const hasAgentPolicy = newPackagePolicy.policy_id && newPackagePolicy.policy_id !== ''; - if ( - hasPackage && - (hasAgentPolicy || selectedPolicyTab === SelectedPolicyTab.NEW) && - !hasValidationErrors - ) { - setFormState('VALID'); - } else { - setFormState('INVALID'); + const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({ + from, + pkgkey: params.pkgkey, + agentPolicyId, + }); + useEffect(() => { + const getAgentCount = async () => { + const { data } = await sendGetAgentStatus({ policyId: agentPolicyId }); + if (data?.results.total !== undefined) { + setAgentCount(data.results.total); } - }, - [packagePolicy, updatePackagePolicyValidation, selectedPolicyTab] - ); + }; + + if (isFleetEnabled && agentPolicyId) { + getAgentCount(); + } + }, [agentPolicyId, isFleetEnabled]); const handleExtensionViewOnChange = useCallback< PackagePolicyEditExtensionComponentProps['onChange'] @@ -313,132 +203,16 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ return prevState; }); }, - [updatePackagePolicy] - ); - - // Save package policy - const savePackagePolicy = useCallback( - async (pkgPolicy: CreatePackagePolicyRequest['body']) => { - setFormState('LOADING'); - const result = await sendCreatePackagePolicy(pkgPolicy); - setFormState(agentCount ? 'SUBMITTED' : 'SUBMITTED_NO_AGENTS'); - return result; - }, - [agentCount] + [updatePackagePolicy, setFormState] ); - const createAgentPolicy = useCallback(async (): Promise<AgentPolicy | undefined> => { - let createdAgentPolicy; - setFormState('LOADING'); - // do not create agent policy with system integration if package policy already is for system package - const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; - const resp = await sendCreateAgentPolicy(newAgentPolicy, { - withSysMonitoring: withSysMonitoring && !packagePolicyIsSystem, - }); - if (resp.error) { - setFormState('VALID'); - throw resp.error; - } - if (resp.data) { - createdAgentPolicy = resp.data.item; - setAgentPolicy(createdAgentPolicy); - updatePackagePolicy({ policy_id: createdAgentPolicy.id }); - } - return createdAgentPolicy; - }, [packagePolicy?.package?.name, newAgentPolicy, withSysMonitoring, updatePackagePolicy]); - - const onSubmit = useCallback( - async ({ - force, - overrideCreatedAgentPolicy, - }: { overrideCreatedAgentPolicy?: AgentPolicy; force?: boolean } = {}) => { - if (formState === 'VALID' && hasErrors) { - setFormState('INVALID'); - return; - } - if (agentCount !== 0 && formState !== 'CONFIRM') { - setFormState('CONFIRM'); - return; - } - let createdPolicy = overrideCreatedAgentPolicy; - if (selectedPolicyTab === SelectedPolicyTab.NEW && !overrideCreatedAgentPolicy) { - try { - createdPolicy = await createAgentPolicy(); - } catch (e) { - notifications.toasts.addError(e, { - title: i18n.translate('xpack.fleet.createAgentPolicy.errorNotificationTitle', { - defaultMessage: 'Unable to create agent policy', - }), - }); - return; - } - } - - setFormState('LOADING'); - // passing pkgPolicy with policy_id here as setPackagePolicy doesn't propagate immediately - const { error, data } = await savePackagePolicy({ - ...packagePolicy, - policy_id: createdPolicy?.id ?? packagePolicy.policy_id, - force, - }); - if (!error) { - setSavedPackagePolicy(data!.item); - - const hasAgentsAssigned = agentCount && agentPolicy; - if (!hasAgentsAssigned) { - setFormState('SUBMITTED_NO_AGENTS'); - return; - } - onSaveNavigate(data!.item); - - notifications.toasts.addSuccess({ - title: i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationTitle', { - defaultMessage: `'{packagePolicyName}' integration added.`, - values: { - packagePolicyName: packagePolicy.name, - }, - }), - text: hasAgentsAssigned - ? i18n.translate('xpack.fleet.createPackagePolicy.addedNotificationMessage', { - defaultMessage: `Fleet will deploy updates to all agents that use the '{agentPolicyName}' policy.`, - values: { - agentPolicyName: agentPolicy!.name, - }, - }) - : undefined, - 'data-test-subj': 'packagePolicyCreateSuccessToast', - }); - } else { - if (isVerificationError(error)) { - setFormState('VALID'); // don't show the add agent modal - const forceInstall = await confirmForceInstall(packagePolicy.package!); - - if (forceInstall) { - // skip creating the agent policy because it will have already been successfully created - onSubmit({ overrideCreatedAgentPolicy: createdPolicy, force: true }); - } - return; - } - notifications.toasts.addError(error, { - title: 'Error', - }); - setFormState('VALID'); - } - }, - [ - formState, - hasErrors, - agentCount, - selectedPolicyTab, - savePackagePolicy, - packagePolicy, - createAgentPolicy, - notifications.toasts, - agentPolicy, - onSaveNavigate, - confirmForceInstall, - ] - ); + const { devtoolRequest, devtoolRequestDescription, showDevtoolsRequest } = useDevToolsRequest({ + newAgentPolicy, + packagePolicy, + selectedPolicyTab, + withSysMonitoring, + packageInfo, + }); const integrationInfo = useMemo( () => @@ -488,6 +262,7 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ withSysMonitoring, updateSelectedPolicyTab, queryParamsPolicyId, + setHasAgentPolicyError, ] ); @@ -564,47 +339,10 @@ export const CreatePackagePolicySinglePage: CreatePackagePolicyParams = ({ }, ]; - const { showDevtoolsRequest: isShowDevtoolRequestExperimentEnabled } = - ExperimentalFeaturesService.get(); - - const showDevtoolsRequest = - !HIDDEN_API_REFERENCE_PACKAGES.includes(packageInfo?.name ?? '') && - isShowDevtoolRequestExperimentEnabled; - - const [devtoolRequest, devtoolRequestDescription] = useMemo(() => { - if (selectedPolicyTab === SelectedPolicyTab.NEW) { - const packagePolicyIsSystem = packagePolicy?.package?.name === FLEET_SYSTEM_PACKAGE; - return [ - `${generateCreateAgentPolicyDevToolsRequest( - newAgentPolicy, - withSysMonitoring && !packagePolicyIsSystem - )}\n\n${generateCreatePackagePolicyDevToolsRequest({ - ...packagePolicy, - })}`, - i18n.translate( - 'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription', - { - defaultMessage: - 'These Kibana requests creates a new agent policy and a new package policy.', - } - ), - ]; - } - - return [ - generateCreatePackagePolicyDevToolsRequest({ - ...packagePolicy, - }), - i18n.translate('xpack.fleet.createPackagePolicy.devtoolsRequestDescription', { - defaultMessage: 'This Kibana request creates a new package policy.', - }), - ]; - }, [packagePolicy, newAgentPolicy, withSysMonitoring, selectedPolicyTab]); - // Display package error if there is one if (packageInfoError) { return ( - <Error + <ErrorComponent title={ <FormattedMessage id="xpack.fleet.createPackagePolicy.StepSelectPolicy.errorLoadingPackageTitle" diff --git a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts index 57b2b93fabb25..3a0033435ed9d 100644 --- a/x-pack/plugins/fleet/public/hooks/use_request/epm.ts +++ b/x-pack/plugins/fleet/public/hooks/use_request/epm.ts @@ -143,6 +143,16 @@ export const sendInstallPackage = (pkgName: string, pkgVersion: string, force: b }); }; +export const sendBulkInstallPackages = (packages: string[]) => { + return sendRequest<InstallPackageResponse, FleetErrorResponse>({ + path: epmRouteService.getBulkInstallPath(), + method: 'post', + body: { + packages, + }, + }); +}; + export const sendRemovePackage = (pkgName: string, pkgVersion: string, force: boolean = false) => { return sendRequest<DeletePackageResponse>({ path: epmRouteService.getRemovePath(pkgName, pkgVersion), From 0e7070ad71a75b317314654510e3e5d5317e9db4 Mon Sep 17 00:00:00 2001 From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com> Date: Tue, 4 Oct 2022 11:32:54 -0400 Subject: [PATCH 048/174] [ResponseOps][Stack Connectors] Adding custom validators to sub actions framework (#142376) * Adding custom validators * Updating README and tests * Fixing translation error * Addressing feedback Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../actions/server/actions_config.test.ts | 9 +++ x-pack/plugins/actions/server/index.ts | 2 + .../server/sub_action_framework/README.md | 18 +++++- .../sub_action_framework/helpers/index.ts | 8 +++ .../helpers/validators.ts | 30 ++++++++++ .../server/sub_action_framework/types.ts | 26 +++++++- .../sub_action_framework/validators.test.ts | 59 ++++++++++++++++++- .../server/sub_action_framework/validators.ts | 40 ++++++++++++- x-pack/plugins/actions/server/types.ts | 2 +- 9 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts create mode 100644 x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index a6b68d907cb44..b1af4a843b496 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -43,6 +43,15 @@ const defaultActionsConfig: ActionsConfig = { }; describe('ensureUriAllowed', () => { + test('throws an error when the Uri is an empty string', () => { + const config: ActionsConfig = defaultActionsConfig; + expect(() => + getActionsConfigurationUtilities(config).ensureUriAllowed('') + ).toThrowErrorMatchingInlineSnapshot( + `"target url \\"\\" is not added to the Kibana config xpack.actions.allowedHosts"` + ); + }); + test('returns true when "any" hostnames are allowed', () => { const config: ActionsConfig = { ...defaultActionsConfig, diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 7f9b45c368e90..1c7a66978ffb3 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -139,3 +139,5 @@ export const config: PluginConfigDescriptor<ActionsConfig> = { }, ], }; + +export { urlAllowListValidator } from './sub_action_framework/helpers'; diff --git a/x-pack/plugins/actions/server/sub_action_framework/README.md b/x-pack/plugins/actions/server/sub_action_framework/README.md index 90951692f5457..7c2ab0755a0ad 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/README.md +++ b/x-pack/plugins/actions/server/sub_action_framework/README.md @@ -6,6 +6,7 @@ The Kibana actions plugin provides a framework to create executable actions that - Register a sub action and map it to a function of your choice. - Define a schema for the parameters of your sub action. +- Define custom validators (or use the provided helpers) for the parameters of your sub action. - Define a response schema for responses from external services. - Create connectors that are supported by the Cases management system. @@ -353,4 +354,19 @@ plugins.actions.registerSubActionConnectorType({ }); ``` -You can see a full example in [x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts](../../../../test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts) \ No newline at end of file +You can see a full example in [x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts](../../../../test/alerting_api_integration/common/fixtures/plugins/alerts/server/sub_action_connector.ts) + +### Example: Register sub action connector with custom validators + +The sub actions framework allows custom validators during registration of the connector type. Below is an example of including the URL validation for the `TestSubActionConnector` `url` configuration field. + +```typescript +plugins.actions.registerSubActionConnectorType({ + id: '.test-sub-action-connector', + name: 'Test: Sub action connector', + minimumLicenseRequired: 'platinum' as const, + schema: { config: TestConfigSchema, secrets: TestSecretsSchema }, + validators: [{type: ValidatorType.CONFIG, validate: urlAllowListValidator('url')}] + Service: TestSubActionConnector, +}); +``` diff --git a/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts b/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts new file mode 100644 index 0000000000000..c69caff6b0c71 --- /dev/null +++ b/x-pack/plugins/actions/server/sub_action_framework/helpers/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { urlAllowListValidator } from './validators'; diff --git a/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts b/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts new file mode 100644 index 0000000000000..7618fef0f3ea4 --- /dev/null +++ b/x-pack/plugins/actions/server/sub_action_framework/helpers/validators.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; +import { ValidatorServices } from '../../types'; + +export const urlAllowListValidator = <T>(urlKey: string) => { + return (obj: T, validatorServices: ValidatorServices) => { + const { configurationUtilities } = validatorServices; + try { + const url = get(obj, urlKey, ''); + + configurationUtilities.ensureUriAllowed(url); + } catch (allowListError) { + throw new Error( + i18n.translate('xpack.actions.subActionsFramework.urlValidationError', { + defaultMessage: 'error validating url: {message}', + values: { + message: allowListError.message, + }, + }) + ); + } + }; +}; diff --git a/x-pack/plugins/actions/server/sub_action_framework/types.ts b/x-pack/plugins/actions/server/sub_action_framework/types.ts index cdc05524cf842..f584d73d24443 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/types.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/types.ts @@ -10,7 +10,7 @@ import { Logger } from '@kbn/logging'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { ActionTypeParams, Services } from '../types'; +import { ActionTypeParams, Services, ValidatorType as ValidationSchema } from '../types'; import { SubActionConnector } from './sub_action_connector'; export interface ServiceParams<Config, Secrets> { @@ -34,6 +34,29 @@ export type IServiceAbstract<Config, Secrets> = abstract new ( params: ServiceParams<Config, Secrets> ) => SubActionConnector<Config, Secrets>; +export enum ValidatorType { + CONFIG, + SECRETS, +} + +interface Validate<T> { + validator: ValidateFn<T>; +} + +export type ValidateFn<T> = NonNullable<ValidationSchema<T>['customValidator']>; + +interface ConfigValidator<T> extends Validate<T> { + type: ValidatorType.CONFIG; +} + +interface SecretsValidator<T> extends Validate<T> { + type: ValidatorType.SECRETS; +} + +export type Validators<Config, Secrets> = Array< + ConfigValidator<Config> | SecretsValidator<Secrets> +>; + export interface SubActionConnectorType<Config, Secrets> { id: string; name: string; @@ -43,6 +66,7 @@ export interface SubActionConnectorType<Config, Secrets> { config: Type<Config>; secrets: Type<Secrets>; }; + validators?: Array<ConfigValidator<Config> | SecretsValidator<Secrets>>; Service: IService<Config, Secrets>; } diff --git a/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts b/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts index 6cae35141b498..b28adc0b545bf 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/validators.test.ts @@ -14,7 +14,7 @@ import { TestSecrets, TestSubActionConnector, } from './mocks'; -import { IService } from './types'; +import { IService, SubActionConnectorType, ValidatorType } from './types'; import { buildValidators } from './validators'; describe('Validators', () => { @@ -36,6 +36,39 @@ describe('Validators', () => { return buildValidators({ configurationUtilities: mockedActionsConfig, connector }); }; + const createValidatorWithCustomValidation = (Service: IService<TestConfig, TestSecrets>) => { + const configValidator = jest.fn(); + const secretsValidator = jest.fn(); + + const connector: SubActionConnectorType<TestConfig, TestSecrets> = { + id: '.test', + name: 'Test', + minimumLicenseRequired: 'basic' as const, + supportedFeatureIds: ['alerting'], + schema: { + config: TestConfigSchema, + secrets: TestSecretsSchema, + }, + validators: [ + { + type: ValidatorType.CONFIG, + validator: configValidator, + }, + { + type: ValidatorType.SECRETS, + validator: secretsValidator, + }, + ], + Service, + }; + + return { + validators: buildValidators({ configurationUtilities: mockedActionsConfig, connector }), + configValidator, + secretsValidator, + }; + }; + beforeEach(() => { jest.resetAllMocks(); jest.clearAllMocks(); @@ -96,4 +129,28 @@ describe('Validators', () => { const { params } = validator; expect(() => params.schema.validate({ subAction, subActionParams: {} })).toThrow(); }); + + it('calls the config and secrets custom validator functions', () => { + const validator = createValidatorWithCustomValidation(TestSubActionConnector); + + validator.validators.config.customValidator?.( + { url: 'http://www.example.com' }, + { configurationUtilities: mockedActionsConfig } + ); + + validator.validators.secrets.customValidator?.( + { password: '123', username: 'sam' }, + { configurationUtilities: mockedActionsConfig } + ); + + expect(validator.configValidator).toHaveBeenCalledWith( + { url: 'http://www.example.com' }, + expect.anything() + ); + + expect(validator.secretsValidator).toHaveBeenCalledWith( + { password: '123', username: 'sam' }, + expect.anything() + ); + }); }); diff --git a/x-pack/plugins/actions/server/sub_action_framework/validators.ts b/x-pack/plugins/actions/server/sub_action_framework/validators.ts index be6dafed28163..e9cbbb3ae8f80 100644 --- a/x-pack/plugins/actions/server/sub_action_framework/validators.ts +++ b/x-pack/plugins/actions/server/sub_action_framework/validators.ts @@ -7,8 +7,8 @@ import { schema } from '@kbn/config-schema'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { ActionTypeConfig, ActionTypeSecrets } from '../types'; -import { SubActionConnectorType } from './types'; +import { ActionTypeConfig, ActionTypeSecrets, ValidatorServices } from '../types'; +import { SubActionConnectorType, ValidateFn, Validators, ValidatorType } from './types'; export const buildValidators = < Config extends ActionTypeConfig, @@ -20,12 +20,16 @@ export const buildValidators = < configurationUtilities: ActionsConfigurationUtilities; connector: SubActionConnectorType<Config, Secrets>; }) => { + const { config, secrets } = buildCustomValidators(connector.validators); + return { config: { schema: connector.schema.config, + customValidator: config, }, secrets: { schema: connector.schema.secrets, + customValidator: secrets, }, params: { schema: schema.object({ @@ -42,3 +46,35 @@ export const buildValidators = < }, }; }; + +const buildCustomValidators = <Config, Secrets>(validators?: Validators<Config, Secrets>) => { + const partitionedValidators: { + config: Array<ValidateFn<Config>>; + secrets: Array<ValidateFn<Secrets>>; + } = { config: [], secrets: [] }; + + for (const validatorInfo of validators ?? []) { + if (validatorInfo.type === ValidatorType.CONFIG) { + partitionedValidators.config.push(validatorInfo.validator); + } else { + partitionedValidators.secrets.push(validatorInfo.validator); + } + } + + return { + config: createCustomValidatorFunction(partitionedValidators.config), + secrets: createCustomValidatorFunction(partitionedValidators.secrets), + }; +}; + +const createCustomValidatorFunction = <T>(validators: Array<ValidateFn<T>>) => { + if (validators.length <= 0) { + return; + } + + return (value: T, validatorServices: ValidatorServices) => { + for (const validate of validators) { + validate(value, validatorServices); + } + }; +}; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index ae344d4f62dbc..c92761ad0a288 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -91,7 +91,7 @@ export type ExecutorType<Config, Secrets, Params, ResultData> = ( options: ActionTypeExecutorOptions<Config, Secrets, Params> ) => Promise<ActionTypeExecutorResult<ResultData>>; -interface ValidatorType<Type> { +export interface ValidatorType<Type> { schema: { validate(value: unknown): Type; }; From 25b79a9cdbc1bf6809698d4cfad825850d6b7923 Mon Sep 17 00:00:00 2001 From: Gerard Soldevila <gerard.soldevila@elastic.co> Date: Tue, 4 Oct 2022 17:43:41 +0200 Subject: [PATCH 049/174] Collect metrics about the active/idle connections to ES nodes (#141434) * Collect metrics about the connections from esClient to ES nodes * Misc enhancements following PR remarks and comments * Fix UTs * Fix mock typings * Minimize API surface, fix mocks typings * Fix incomplete mocks * Fix renameed agentManager => agentStore in remaining UT * Cover edge cases for getAgentsSocketsStats() * Misc NIT enhancements * Revert incorrect import type statements --- .../src/status/lib/load_status.test.ts | 13 ++ .../index.ts | 2 +- .../src/agent_manager.test.ts | 21 ++- .../src/agent_manager.ts | 36 +++-- .../src/cluster_client.test.ts | 52 +++---- .../src/cluster_client.ts | 15 +- .../src/configure_client.test.ts | 60 +++---- .../src/configure_client.ts | 8 +- .../index.ts | 1 + .../src/agent_manager.mocks.ts | 13 ++ .../src/elasticsearch_service.test.mocks.ts | 8 +- .../src/elasticsearch_service.test.ts | 9 +- .../src/elasticsearch_service.ts | 3 +- .../src/types.ts | 2 + .../src/elasticsearch_service.mock.ts | 2 + .../src/client/cluster_client.ts | 4 +- .../BUILD.bazel | 3 + .../index.ts | 1 + .../src/elasticsearch_client.test.ts | 33 ++++ .../src/elasticsearch_client.ts | 26 ++++ .../get_agents_sockets_stats.test.mocks.ts | 29 ++++ .../src/get_agents_sockets_stats.test.ts | 147 ++++++++++++++++++ .../src/get_agents_sockets_stats.ts | 81 ++++++++++ .../core-metrics-server-internal/BUILD.bazel | 9 +- .../src/logging/get_ops_metrics_log.test.ts | 2 + .../src/metrics_service.test.ts | 28 ++-- .../src/metrics_service.ts | 9 +- .../src/ops_metrics_collector.test.mocks.ts | 2 + .../src/ops_metrics_collector.test.ts | 9 +- .../src/ops_metrics_collector.ts | 10 +- .../core-metrics-server-mocks/index.ts | 2 +- .../src/metrics_service.mock.ts | 21 ++- .../core/metrics/core-metrics-server/index.ts | 2 + .../core-metrics-server/src/metrics.ts | 42 +++++ .../src/routes/status.ts | 1 + src/cli_setup/utils.ts | 2 +- src/core/server/server.ts | 5 +- .../ops_stats_collector.test.ts.snap | 13 ++ 38 files changed, 617 insertions(+), 109 deletions(-) create mode 100644 packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts create mode 100644 packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts diff --git a/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts b/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts index ac40eedfccb7d..dd750a56fbf2d 100644 --- a/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts +++ b/packages/core/apps/core-apps-browser-internal/src/status/lib/load_status.test.ts @@ -61,6 +61,19 @@ const mockedResponse: StatusResponse = { '15m': 0.1, }, }, + elasticsearch_client: { + protocol: 'https', + connectedNodes: 3, + nodesWithActiveSockets: 3, + nodesWithIdleSockets: 1, + totalActiveSockets: 25, + totalIdleSockets: 2, + totalQueuedRequests: 0, + mostActiveNodeSockets: 15, + averageActiveSocketsPerNode: 8, + mostIdleNodeSockets: 2, + averageIdleSocketsPerNode: 0.5, + }, process: { pid: 1, memory: { diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts index aa1364c179e18..6f1f276c7d089 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/index.ts @@ -9,7 +9,7 @@ export { ScopedClusterClient } from './src/scoped_cluster_client'; export { ClusterClient } from './src/cluster_client'; export { configureClient } from './src/configure_client'; -export { AgentManager } from './src/agent_manager'; +export { type AgentStore, AgentManager } from './src/agent_manager'; export { getRequestDebugMeta, getErrorMessage } from './src/log_query_and_deprecation'; export { PRODUCT_RESPONSE_HEADER, diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts index 811d9d95831ef..dfa8a077d2e53 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.test.ts @@ -104,10 +104,10 @@ describe('AgentManager', () => { const agentFactory = agentManager.getAgentFactory(); const agent = agentFactory({ url: new URL('http://elastic-node-1:9200') }); // eslint-disable-next-line dot-notation - expect(agentManager['httpStore'].has(agent)).toEqual(true); + expect(agentManager['agents'].has(agent)).toEqual(true); agent.destroy(); // eslint-disable-next-line dot-notation - expect(agentManager['httpStore'].has(agent)).toEqual(false); + expect(agentManager['agents'].has(agent)).toEqual(false); }); }); @@ -122,4 +122,21 @@ describe('AgentManager', () => { }); }); }); + + describe('#getAgents()', () => { + it('returns the created HTTP and HTTPs Agent instances', () => { + const agentManager = new AgentManager(); + const agentFactory1 = agentManager.getAgentFactory(); + const agentFactory2 = agentManager.getAgentFactory(); + const agent1 = agentFactory1({ url: new URL('http://elastic-node-1:9200') }); + const agent2 = agentFactory2({ url: new URL('http://elastic-node-1:9200') }); + const agent3 = agentFactory1({ url: new URL('https://elastic-node-1:9200') }); + const agent4 = agentFactory2({ url: new URL('https://elastic-node-1:9200') }); + + const agents = agentManager.getAgents(); + + expect(agents.size).toEqual(4); + expect([...agents]).toEqual(expect.arrayContaining([agent1, agent2, agent3, agent4])); + }); + }); }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts index eb68014561d77..9a57cc44e04ad 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/agent_manager.ts @@ -8,7 +8,7 @@ import { Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent } from 'https'; -import { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; +import type { ConnectionOptions, HttpAgentOptions } from '@elastic/elasticsearch'; const HTTPS = 'https:'; const DEFAULT_CONFIG: HttpAgentOptions = { @@ -22,6 +22,14 @@ const DEFAULT_CONFIG: HttpAgentOptions = { export type NetworkAgent = HttpAgent | HttpsAgent; export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent; +export interface AgentFactoryProvider { + getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory; +} + +export interface AgentStore { + getAgents(): Set<NetworkAgent>; +} + /** * Allows obtaining Agent factories, which can then be fed into elasticsearch-js's Client class. * Ideally, we should obtain one Agent factory for each ES Client class. @@ -33,15 +41,11 @@ export type AgentFactory = (connectionOpts: ConnectionOptions) => NetworkAgent; * exposes methods that can modify the underlying pools, effectively impacting the connections of other Clients. * @internal **/ -export class AgentManager { - // Stores Https Agent instances - private httpsStore: Set<HttpsAgent>; - // Stores Http Agent instances - private httpStore: Set<HttpAgent>; +export class AgentManager implements AgentFactoryProvider, AgentStore { + private agents: Set<HttpAgent>; constructor(private agentOptions: HttpAgentOptions = DEFAULT_CONFIG) { - this.httpsStore = new Set(); - this.httpStore = new Set(); + this.agents = new Set(); } public getAgentFactory(agentOptions?: HttpAgentOptions): AgentFactory { @@ -61,8 +65,8 @@ export class AgentManager { connectionOpts.tls ); httpsAgent = new HttpsAgent(config); - this.httpsStore.add(httpsAgent); - dereferenceOnDestroy(this.httpsStore, httpsAgent); + this.agents.add(httpsAgent); + dereferenceOnDestroy(this.agents, httpsAgent); } return httpsAgent; @@ -71,19 +75,23 @@ export class AgentManager { if (!httpAgent) { const config = Object.assign({}, DEFAULT_CONFIG, this.agentOptions, agentOptions); httpAgent = new HttpAgent(config); - this.httpStore.add(httpAgent); - dereferenceOnDestroy(this.httpStore, httpAgent); + this.agents.add(httpAgent); + dereferenceOnDestroy(this.agents, httpAgent); } return httpAgent; }; } + + public getAgents(): Set<NetworkAgent> { + return this.agents; + } } -const dereferenceOnDestroy = (protocolStore: Set<NetworkAgent>, agent: NetworkAgent) => { +const dereferenceOnDestroy = (store: Set<NetworkAgent>, agent: NetworkAgent) => { const doDestroy = agent.destroy.bind(agent); agent.destroy = () => { - protocolStore.delete(agent); + store.delete(agent); doDestroy(); }; }; diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts index e5be9fc0ab718..f371e3425b0c7 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.test.ts @@ -46,7 +46,7 @@ describe('ClusterClient', () => { let authHeaders: ReturnType<typeof httpServiceMock.createAuthHeaderStorage>; let internalClient: jest.Mocked<Client>; let scopedClient: jest.Mocked<Client>; - let agentManager: AgentManager; + let agentFactoryProvider: AgentManager; const mockTransport = { mockTransport: true }; @@ -54,7 +54,7 @@ describe('ClusterClient', () => { logger = loggingSystemMock.createLogger(); internalClient = createClient(); scopedClient = createClient(); - agentManager = new AgentManager(); + agentFactoryProvider = new AgentManager(); authHeaders = httpServiceMock.createAuthHeaderStorage(); authHeaders.get.mockImplementation(() => ({ @@ -84,21 +84,21 @@ describe('ClusterClient', () => { authHeaders, type: 'custom-type', getExecutionContext: getExecutionContextMock, - agentManager, + agentFactoryProvider, kibanaVersion, }); expect(configureClientMock).toHaveBeenCalledTimes(2); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, - agentManager, + agentFactoryProvider, kibanaVersion, type: 'custom-type', getExecutionContext: getExecutionContextMock, }); expect(configureClientMock).toHaveBeenCalledWith(config, { logger, - agentManager, + agentFactoryProvider, kibanaVersion, type: 'custom-type', getExecutionContext: getExecutionContextMock, @@ -113,7 +113,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -128,7 +128,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -155,7 +155,7 @@ describe('ClusterClient', () => { authHeaders, getExecutionContext, getUnauthorizedErrorHandler, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -179,7 +179,7 @@ describe('ClusterClient', () => { authHeaders, getExecutionContext, getUnauthorizedErrorHandler, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -212,7 +212,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -237,7 +237,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -271,7 +271,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({}); @@ -305,7 +305,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -344,7 +344,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({}); @@ -373,7 +373,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -410,7 +410,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({}); @@ -445,7 +445,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -482,7 +482,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest(); @@ -513,7 +513,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -547,7 +547,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = httpServerMock.createKibanaRequest({ @@ -579,7 +579,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = { @@ -612,7 +612,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); const request = { @@ -640,7 +640,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -658,7 +658,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -703,7 +703,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -720,7 +720,7 @@ describe('ClusterClient', () => { logger, type: 'custom-type', authHeaders, - agentManager, + agentFactoryProvider, kibanaVersion, }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts index f243c98ecf798..2a2f6ef1334a2 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/cluster_client.ts @@ -24,9 +24,12 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { configureClient } from './configure_client'; import { ScopedClusterClient } from './scoped_cluster_client'; import { getDefaultHeaders } from './headers'; -import { createInternalErrorHandler, InternalUnauthorizedErrorHandler } from './retry_unauthorized'; +import { + createInternalErrorHandler, + type InternalUnauthorizedErrorHandler, +} from './retry_unauthorized'; import { createTransport } from './create_transport'; -import { AgentManager } from './agent_manager'; +import type { AgentFactoryProvider } from './agent_manager'; const noop = () => undefined; @@ -49,7 +52,7 @@ export class ClusterClient implements ICustomClusterClient { authHeaders, getExecutionContext = noop, getUnauthorizedErrorHandler = noop, - agentManager, + agentFactoryProvider, kibanaVersion, }: { config: ElasticsearchClientConfig; @@ -58,7 +61,7 @@ export class ClusterClient implements ICustomClusterClient { authHeaders?: IAuthHeadersStorage; getExecutionContext?: () => string | undefined; getUnauthorizedErrorHandler?: () => UnauthorizedErrorHandler | undefined; - agentManager: AgentManager; + agentFactoryProvider: AgentFactoryProvider; kibanaVersion: string; }) { this.config = config; @@ -71,7 +74,7 @@ export class ClusterClient implements ICustomClusterClient { logger, type, getExecutionContext, - agentManager, + agentFactoryProvider, kibanaVersion, }); this.rootScopedClient = configureClient(config, { @@ -79,7 +82,7 @@ export class ClusterClient implements ICustomClusterClient { type, getExecutionContext, scoped: true, - agentManager, + agentFactoryProvider, kibanaVersion, }); } diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts index 40824d306ac48..fe511f46278d9 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.test.ts @@ -10,7 +10,6 @@ jest.mock('./log_query_and_deprecation', () => ({ __esModule: true, instrumentEsQueryAndDeprecationLogger: jest.fn(), })); -jest.mock('./agent_manager'); import { Agent } from 'http'; import { @@ -24,9 +23,8 @@ import { ClusterConnectionPool } from '@elastic/elasticsearch'; import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { configureClient } from './configure_client'; import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation'; -import { AgentManager } from './agent_manager'; +import { type AgentFactoryProvider, AgentManager } from './agent_manager'; -const AgentManagerMock = AgentManager as jest.Mock<AgentManager>; const kibanaVersion = '1.0.0'; const createFakeConfig = ( @@ -46,31 +44,17 @@ const createFakeClient = () => { return client; }; -const createFakeAgentFactory = (logger: MockedLogger) => { - const agentFactory = () => new Agent(); - - AgentManagerMock.mockImplementationOnce(() => { - const agentManager = new AgentManager(); - agentManager.getAgentFactory = () => agentFactory; - return agentManager; - }); - - const agentManager = new AgentManager(); - - return { agentManager, agentFactory }; -}; - describe('configureClient', () => { let logger: MockedLogger; let config: ElasticsearchClientConfig; - let agentManager: AgentManager; + let agentFactoryProvider: AgentFactoryProvider; beforeEach(() => { logger = loggingSystemMock.createLogger(); config = createFakeConfig(); parseClientOptionsMock.mockReturnValue({}); ClientMock.mockImplementation(() => createFakeClient()); - agentManager = new AgentManager(); + agentFactoryProvider = new AgentManager(); }); afterEach(() => { @@ -80,14 +64,26 @@ describe('configureClient', () => { }); it('calls `parseClientOptions` with the correct parameters', () => { - configureClient(config, { logger, type: 'test', scoped: false, agentManager, kibanaVersion }); + configureClient(config, { + logger, + type: 'test', + scoped: false, + agentFactoryProvider, + kibanaVersion, + }); expect(parseClientOptionsMock).toHaveBeenCalledTimes(1); expect(parseClientOptionsMock).toHaveBeenCalledWith(config, false, kibanaVersion); parseClientOptionsMock.mockClear(); - configureClient(config, { logger, type: 'test', scoped: true, agentManager, kibanaVersion }); + configureClient(config, { + logger, + type: 'test', + scoped: true, + agentFactoryProvider, + kibanaVersion, + }); expect(parseClientOptionsMock).toHaveBeenCalledTimes(1); expect(parseClientOptionsMock).toHaveBeenCalledWith(config, true, kibanaVersion); @@ -103,7 +99,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -112,13 +108,17 @@ describe('configureClient', () => { expect(client).toBe(ClientMock.mock.results[0].value); }); - it('constructs a client using the provided `agentManager`', () => { - const { agentManager: customAgentManager, agentFactory } = createFakeAgentFactory(logger); + it('constructs a client using the provided `agentFactoryProvider`', () => { + const agentFactory = () => new Agent(); + const customAgentFactoryProvider = { + getAgentFactory: () => agentFactory, + }; + const client = configureClient(config, { logger, type: 'test', scoped: false, - agentManager: customAgentManager, + agentFactoryProvider: customAgentFactoryProvider, kibanaVersion, }); @@ -134,7 +134,7 @@ describe('configureClient', () => { type: 'test', scoped: false, getExecutionContext, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -148,7 +148,7 @@ describe('configureClient', () => { type: 'test', scoped: true, getExecutionContext, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -164,7 +164,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -185,7 +185,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); @@ -203,7 +203,7 @@ describe('configureClient', () => { logger, type: 'test', scoped: false, - agentManager, + agentFactoryProvider, kibanaVersion, }); diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts index e1c8048c6a89e..2fd7a4d4a74bb 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-internal/src/configure_client.ts @@ -12,7 +12,7 @@ import type { ElasticsearchClientConfig } from '@kbn/core-elasticsearch-server'; import { parseClientOptions } from './client_config'; import { instrumentEsQueryAndDeprecationLogger } from './log_query_and_deprecation'; import { createTransport } from './create_transport'; -import { AgentManager } from './agent_manager'; +import type { AgentFactoryProvider } from './agent_manager'; const noop = () => undefined; @@ -23,14 +23,14 @@ export const configureClient = ( type, scoped = false, getExecutionContext = noop, - agentManager, + agentFactoryProvider, kibanaVersion, }: { logger: Logger; type: string; scoped?: boolean; getExecutionContext?: () => string | undefined; - agentManager: AgentManager; + agentFactoryProvider: AgentFactoryProvider; kibanaVersion: string; } ): Client => { @@ -38,7 +38,7 @@ export const configureClient = ( const KibanaTransport = createTransport({ getExecutionContext }); const client = new Client({ ...clientOptions, - agent: agentManager.getAgentFactory(clientOptions.agent), + agent: agentFactoryProvider.getAgentFactory(clientOptions.agent), Transport: KibanaTransport, Connection: HttpConnection, // using ClusterConnectionPool until https://github.com/elastic/elasticsearch-js/issues/1714 is addressed diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts index c46381d57a7b6..0b66d449df013 100644 --- a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/index.ts @@ -15,3 +15,4 @@ export type { DeeplyMockedApi, ElasticsearchClientMock, } from './src/mocks'; +export { createAgentStoreMock } from './src/agent_manager.mocks'; diff --git a/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.ts new file mode 100644 index 0000000000000..2fd8812b3aae0 --- /dev/null +++ b/packages/core/elasticsearch/core-elasticsearch-client-server-mocks/src/agent_manager.mocks.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 type { AgentStore, NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal'; + +export const createAgentStoreMock = (agents: Set<NetworkAgent> = new Set()): AgentStore => ({ + getAgents: jest.fn(() => agents), +}); diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts index cd6d36f0cb111..68a56ff28bc8d 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.mocks.ts @@ -6,8 +6,14 @@ * Side Public License, v 1. */ +import type { AgentManager } from '@kbn/core-elasticsearch-client-server-internal'; + export const MockClusterClient = jest.fn(); -export const MockAgentManager = jest.fn(); +export const MockAgentManager: jest.MockedClass<typeof AgentManager> = jest.fn().mockReturnValue({ + getAgents: jest.fn(), + getAgentFactory: jest.fn(), +}); + jest.mock('@kbn/core-elasticsearch-client-server-internal', () => ({ ClusterClient: MockClusterClient, AgentManager: MockAgentManager, diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts index 5b54a2c35683e..ecd364b4283cf 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.test.ts @@ -135,7 +135,7 @@ describe('#preboot', () => { ); }); - it('creates a ClusterClient using the internal AgentManager', async () => { + it('creates a ClusterClient using the internal AgentManager as AgentFactoryProvider ', async () => { const prebootContract = await elasticsearchService.preboot(); const customConfig = { keepAlive: true }; const clusterClient = prebootContract.createClient('custom-type', customConfig); @@ -145,7 +145,7 @@ describe('#preboot', () => { expect(MockClusterClient).toHaveBeenCalledTimes(1); expect(MockClusterClient.mock.calls[0][0]).toEqual( // eslint-disable-next-line dot-notation - expect.objectContaining({ agentManager: elasticsearchService['agentManager'] }) + expect.objectContaining({ agentFactoryProvider: elasticsearchService['agentManager'] }) ); }); @@ -201,6 +201,11 @@ describe('#setup', () => { ); }); + it('returns an AgentStore as part of the contract', async () => { + const setupContract = await elasticsearchService.setup(setupDeps); + expect(typeof setupContract.agentStore.getAgents).toEqual('function'); + }); + it('esNodeVersionCompatibility$ only starts polling when subscribed to', async () => { const mockedClient = mockClusterClientInstance.asInternalUser; mockedClient.nodes.info.mockImplementation(() => diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts index f345732c7a7c4..fddff84293140 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/elasticsearch_service.ts @@ -120,6 +120,7 @@ export class ElasticsearchService } this.unauthorizedErrorHandler = handler; }, + agentStore: this.agentManager, }; } @@ -182,7 +183,7 @@ export class ElasticsearchService authHeaders: this.authHeaders, getExecutionContext: () => this.executionContextClient?.getAsHeader(), getUnauthorizedErrorHandler: () => this.unauthorizedErrorHandler, - agentManager: this.agentManager, + agentFactoryProvider: this.agentManager, kibanaVersion: this.kibanaVersion, }); } diff --git a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts index 8d05ad0c4cd0a..b03b86c7bdd1c 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-internal/src/types.ts @@ -12,6 +12,7 @@ import type { ElasticsearchServiceStart, ElasticsearchServiceSetup, } from '@kbn/core-elasticsearch-server'; +import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; import type { ServiceStatus } from '@kbn/core-status-common'; import type { NodesVersionCompatibility, NodeInfo } from './version_check/ensure_es_version'; import type { ClusterInfo } from './get_cluster_info'; @@ -21,6 +22,7 @@ export type InternalElasticsearchServicePreboot = ElasticsearchServicePreboot; /** @internal */ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup { + agentStore: AgentStore; clusterInfo$: Observable<ClusterInfo>; esNodesCompatibility$: Observable<NodesVersionCompatibility>; status$: Observable<ServiceStatus<ElasticsearchStatusMeta>>; diff --git a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts index a1323be0ea71b..26d81da24318c 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server-mocks/src/elasticsearch_service.mock.ts @@ -13,6 +13,7 @@ import { elasticsearchClientMock, type ClusterClientMock, type CustomClusterClientMock, + createAgentStoreMock, } from '@kbn/core-elasticsearch-client-server-mocks'; import type { ElasticsearchClientConfig, @@ -94,6 +95,7 @@ const createInternalSetupContractMock = () => { level: ServiceStatusLevels.available, summary: 'Elasticsearch is available', }), + agentStore: createAgentStoreMock(), }; return internalSetupContract; }; diff --git a/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts b/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts index 57eadf70ef68a..a8e065d357ee1 100644 --- a/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts +++ b/packages/core/elasticsearch/core-elasticsearch-server/src/client/cluster_client.ts @@ -7,8 +7,8 @@ */ import type { ElasticsearchClient } from './client'; -import { ScopeableRequest } from './scopeable_request'; -import { IScopedClusterClient } from './scoped_cluster_client'; +import type { ScopeableRequest } from './scopeable_request'; +import type { IScopedClusterClient } from './scoped_cluster_client'; /** * Represents an Elasticsearch cluster API client created by the platform. diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel b/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel index 2b789e97cbe69..9761bcbf1cefb 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel +++ b/packages/core/metrics/core-metrics-collectors-server-internal/BUILD.bazel @@ -39,6 +39,8 @@ RUNTIME_DEPS = [ "//packages/kbn-logging", "@npm//moment", "@npm//getos", + ### test dependencies + "//packages/core/elasticsearch/core-elasticsearch-client-server-mocks", ] TYPES_DEPS = [ @@ -50,6 +52,7 @@ TYPES_DEPS = [ "@npm//@types/hapi__hapi", "//packages/kbn-logging:npm_module_types", "//packages/core/metrics/core-metrics-server:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-client-server-internal:npm_module_types", ] jsts_transpiler( diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/index.ts b/packages/core/metrics/core-metrics-collectors-server-internal/index.ts index a4639202353e1..351129cdc8ba3 100644 --- a/packages/core/metrics/core-metrics-collectors-server-internal/index.ts +++ b/packages/core/metrics/core-metrics-collectors-server-internal/index.ts @@ -11,3 +11,4 @@ export type { OpsMetricsCollectorOptions } from './src/os'; export { ProcessMetricsCollector } from './src/process'; export { ServerMetricsCollector } from './src/server'; export { EventLoopDelaysMonitor } from './src/event_loop_delays_monitor'; +export { ElasticsearchClientsMetricsCollector } from './src/elasticsearch_client'; diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts new file mode 100644 index 0000000000000..363fca6430dbe --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { Agent as HttpAgent } from 'http'; +import { Agent as HttpsAgent } from 'https'; +import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; +import { createAgentStoreMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { getAgentsSocketsStatsMock } from './get_agents_sockets_stats.test.mocks'; +import { ElasticsearchClientsMetricsCollector } from './elasticsearch_client'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; + +jest.mock('@kbn/core-elasticsearch-client-server-internal'); + +describe('ElasticsearchClientsMetricsCollector', () => { + test('#collect calls getAgentsSocketsStats with the Agents managed by the provided AgentManager', async () => { + const agents = new Set<HttpAgent>([new HttpAgent(), new HttpsAgent()]); + const agentStore = createAgentStoreMock(agents); + getAgentsSocketsStatsMock.mockReturnValueOnce(sampleEsClientMetrics); + + const esClientsMetricsCollector = new ElasticsearchClientsMetricsCollector(agentStore); + const metrics = await esClientsMetricsCollector.collect(); + + expect(agentStore.getAgents).toHaveBeenCalledTimes(1); + expect(getAgentsSocketsStats).toHaveBeenCalledTimes(1); + expect(getAgentsSocketsStats).toHaveBeenNthCalledWith(1, agents); + expect(metrics).toEqual(sampleEsClientMetrics); + }); +}); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.ts new file mode 100644 index 0000000000000..278fd0218f8c0 --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/elasticsearch_client.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ElasticsearchClientsMetrics, MetricsCollector } from '@kbn/core-metrics-server'; +import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; + +export class ElasticsearchClientsMetricsCollector + implements MetricsCollector<ElasticsearchClientsMetrics> +{ + constructor(private readonly agentStore: AgentStore) {} + + public async collect(): Promise<ElasticsearchClientsMetrics> { + return await getAgentsSocketsStats(this.agentStore.getAgents()); + } + + public reset() { + // we do not have a state in this Collector, aka metrics are not accumulated over time. + // Thus, we don't need to perform any cleanup to reset the collected metrics + } +} diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts new file mode 100644 index 0000000000000..4e9688ccc91b9 --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.mocks.ts @@ -0,0 +1,29 @@ +/* + * 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 { Agent as HttpAgent } from 'http'; +import { Agent as HttpsAgent } from 'https'; + +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; + +export const getHttpAgentMock = (overrides: Partial<HttpAgent>) => { + return Object.assign(new HttpAgent(), overrides); +}; + +export const getHttpsAgentMock = (overrides: Partial<HttpsAgent>) => { + return Object.assign(new HttpsAgent(), overrides); +}; + +export const getAgentsSocketsStatsMock: jest.MockedFunction<typeof getAgentsSocketsStats> = + jest.fn(); + +jest.doMock('./get_agents_sockets_stats', () => { + return { + getAgentsSocketsStats: getAgentsSocketsStatsMock, + }; +}); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts new file mode 100644 index 0000000000000..513bf2caa8545 --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.test.ts @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { Socket } from 'net'; +import { Agent, IncomingMessage } from 'http'; +import { getAgentsSocketsStats } from './get_agents_sockets_stats'; +import { getHttpAgentMock, getHttpsAgentMock } from './get_agents_sockets_stats.test.mocks'; + +jest.mock('net'); + +const mockSocket = new Socket(); +const mockIncomingMessage = new IncomingMessage(mockSocket); + +describe('getAgentsSocketsStats()', () => { + it('extracts aggregated stats from the specified agents', () => { + const agent1 = getHttpAgentMock({ + sockets: { + node1: [mockSocket, mockSocket, mockSocket], + node2: [mockSocket], + }, + freeSockets: { + node1: [mockSocket], + node3: [mockSocket, mockSocket, mockSocket, mockSocket], + }, + requests: { + node1: [mockIncomingMessage, mockIncomingMessage], + }, + }); + + const agent2 = getHttpAgentMock({ + sockets: { + node1: [mockSocket, mockSocket, mockSocket], + node4: [mockSocket], + }, + freeSockets: { + node3: [mockSocket, mockSocket, mockSocket, mockSocket], + }, + requests: { + node4: [mockIncomingMessage, mockIncomingMessage, mockIncomingMessage, mockIncomingMessage], + }, + }); + + const stats = getAgentsSocketsStats(new Set<Agent>([agent1, agent2])); + expect(stats).toEqual({ + averageActiveSocketsPerNode: 2.6666666666666665, + averageIdleSocketsPerNode: 4.5, + connectedNodes: 4, + mostActiveNodeSockets: 6, + mostIdleNodeSockets: 8, + nodesWithActiveSockets: 3, + nodesWithIdleSockets: 2, + protocol: 'http', + totalActiveSockets: 8, + totalIdleSockets: 9, + totalQueuedRequests: 6, + }); + }); + + it('takes into account Agent types to determine the `protocol`', () => { + const httpAgent = getHttpAgentMock({ + sockets: { node1: [mockSocket] }, + freeSockets: {}, + requests: {}, + }); + + const httpsAgent = getHttpsAgentMock({ + sockets: { node1: [mockSocket] }, + freeSockets: {}, + requests: {}, + }); + + const noAgents = new Set<Agent>(); + const httpAgents = new Set<Agent>([httpAgent, httpAgent]); + const httpsAgents = new Set<Agent>([httpsAgent, httpsAgent]); + const mixedAgents = new Set<Agent>([httpAgent, httpsAgent]); + + expect(getAgentsSocketsStats(noAgents).protocol).toEqual('none'); + expect(getAgentsSocketsStats(httpAgents).protocol).toEqual('http'); + expect(getAgentsSocketsStats(httpsAgents).protocol).toEqual('https'); + expect(getAgentsSocketsStats(mixedAgents).protocol).toEqual('mixed'); + }); + + it('does not take into account those Agents that have not had any connection to any node', () => { + const pristineAgentProps = { + sockets: {}, + freeSockets: {}, + requests: {}, + }; + const agent1 = getHttpAgentMock(pristineAgentProps); + const agent2 = getHttpAgentMock(pristineAgentProps); + const agent3 = getHttpAgentMock(pristineAgentProps); + + const stats = getAgentsSocketsStats(new Set<Agent>([agent1, agent2, agent3])); + + expect(stats).toEqual({ + averageActiveSocketsPerNode: 0, + averageIdleSocketsPerNode: 0, + connectedNodes: 0, + mostActiveNodeSockets: 0, + mostIdleNodeSockets: 0, + nodesWithActiveSockets: 0, + nodesWithIdleSockets: 0, + protocol: 'none', + totalActiveSockets: 0, + totalIdleSockets: 0, + totalQueuedRequests: 0, + }); + }); + + it('takes into account those Agents that have hold mappings to one or more nodes, but that do not currently have any pending requests, active connections or idle connections', () => { + const emptyAgentProps = { + sockets: { + node1: [], + }, + freeSockets: { + node2: [], + }, + requests: { + node3: [], + }, + }; + + const agent1 = getHttpAgentMock(emptyAgentProps); + const agent2 = getHttpAgentMock(emptyAgentProps); + + const stats = getAgentsSocketsStats(new Set<Agent>([agent1, agent2])); + + expect(stats).toEqual({ + averageActiveSocketsPerNode: 0, + averageIdleSocketsPerNode: 0, + connectedNodes: 3, + mostActiveNodeSockets: 0, + mostIdleNodeSockets: 0, + nodesWithActiveSockets: 0, + nodesWithIdleSockets: 0, + protocol: 'http', + totalActiveSockets: 0, + totalIdleSockets: 0, + totalQueuedRequests: 0, + }); + }); +}); diff --git a/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.ts new file mode 100644 index 0000000000000..e28c92a56a8a4 --- /dev/null +++ b/packages/core/metrics/core-metrics-collectors-server-internal/src/get_agents_sockets_stats.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 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 { NetworkAgent } from '@kbn/core-elasticsearch-client-server-internal'; +import { Agent as HttpsAgent } from 'https'; +import { mean } from 'lodash'; +import type { + ElasticsearchClientProtocol, + ElasticsearchClientsMetrics, +} from '@kbn/core-metrics-server'; + +export const getAgentsSocketsStats = (agents: Set<NetworkAgent>): ElasticsearchClientsMetrics => { + const nodes = new Set<string>(); + let totalActiveSockets = 0; + let totalIdleSockets = 0; + let totalQueuedRequests = 0; + let http: boolean = false; + let https: boolean = false; + + const nodesWithActiveSockets: Record<string, number> = {}; + const nodesWithIdleSockets: Record<string, number> = {}; + + agents.forEach((agent) => { + const agentRequests = Object.entries(agent.requests) ?? []; + const agentSockets = Object.entries(agent.sockets) ?? []; + const agentFreeSockets = Object.entries(agent.freeSockets) ?? []; + + if (agentRequests.length || agentSockets.length || agentFreeSockets.length) { + if (agent instanceof HttpsAgent) https = true; + else http = true; + + agentRequests.forEach(([node, queue]) => { + nodes.add(node); + totalQueuedRequests += queue?.length ?? 0; + }); + + agentSockets.forEach(([node, sockets]) => { + nodes.add(node); + const activeSockets = sockets?.length ?? 0; + totalActiveSockets += activeSockets; + nodesWithActiveSockets[node] = (nodesWithActiveSockets[node] ?? 0) + activeSockets; + }); + + agentFreeSockets.forEach(([node, freeSockets]) => { + nodes.add(node); + const idleSockets = freeSockets?.length ?? 0; + totalIdleSockets += idleSockets; + nodesWithIdleSockets[node] = (nodesWithIdleSockets[node] ?? 0) + idleSockets; + }); + } + }); + + const activeSocketCounters = Object.values(nodesWithActiveSockets); + const idleSocketCounters = Object.values(nodesWithIdleSockets); + const protocol: ElasticsearchClientProtocol = http + ? https + ? 'mixed' + : 'http' + : https + ? 'https' + : 'none'; + + return { + protocol, + connectedNodes: nodes.size, + nodesWithActiveSockets: activeSocketCounters.filter(Boolean).length, + nodesWithIdleSockets: idleSocketCounters.filter(Boolean).length, + totalActiveSockets, + totalIdleSockets, + totalQueuedRequests, + mostActiveNodeSockets: activeSocketCounters.length ? Math.max(...activeSocketCounters) : 0, + averageActiveSocketsPerNode: activeSocketCounters.length ? mean(activeSocketCounters) : 0, + mostIdleNodeSockets: idleSocketCounters.length ? Math.max(...idleSocketCounters) : 0, + averageIdleSocketsPerNode: idleSocketCounters.length ? mean(idleSocketCounters) : 0, + }; +}; diff --git a/packages/core/metrics/core-metrics-server-internal/BUILD.bazel b/packages/core/metrics/core-metrics-server-internal/BUILD.bazel index da7883016afd2..0a7f393ec0b31 100644 --- a/packages/core/metrics/core-metrics-server-internal/BUILD.bazel +++ b/packages/core/metrics/core-metrics-server-internal/BUILD.bazel @@ -37,11 +37,15 @@ NPM_MODULE_EXTRA_FILES = [ RUNTIME_DEPS = [ "@npm//rxjs", "@npm//moment", - "//packages/kbn-logging-mocks", "//packages/kbn-config-schema", - "//packages/core/http/core-http-server-mocks", "//packages/core/metrics/core-metrics-collectors-server-internal", + "//packages/core/elasticsearch/core-elasticsearch-server-internal", + ### test dependencies + "//packages/kbn-logging-mocks", + "//packages/core/http/core-http-server-mocks", + "//packages/core/metrics/core-metrics-server-mocks", "//packages/core/metrics/core-metrics-collectors-server-mocks", + "//packages/core/elasticsearch/core-elasticsearch-server-mocks", ] @@ -57,6 +61,7 @@ TYPES_DEPS = [ "//packages/core/http/core-http-server-internal:npm_module_types", "//packages/core/metrics/core-metrics-server:npm_module_types", "//packages/core/metrics/core-metrics-collectors-server-internal:npm_module_types", + "//packages/core/elasticsearch/core-elasticsearch-server-internal:npm_module_types", ] diff --git a/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts b/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts index 8de7a5fa5dadf..d997433667e27 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/logging/get_ops_metrics_log.test.ts @@ -8,6 +8,7 @@ import type { OpsMetrics } from '@kbn/core-metrics-server'; import { getEcsOpsMetricsLog } from './get_ops_metrics_log'; +import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; import { collectorMock } from '@kbn/core-metrics-collectors-server-mocks'; function createBaseOpsMetrics(): OpsMetrics { @@ -24,6 +25,7 @@ function createBaseOpsMetrics(): OpsMetrics { memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, uptime_in_millis: 1, }, + elasticsearch_client: sampleEsClientMetrics, response_times: { avg_in_millis: 1, max_in_millis: 1 }, requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, concurrent_connections: 1, diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts index de78b534b2dc7..351e2aca43f56 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.test.ts @@ -8,13 +8,15 @@ import moment from 'moment'; +import { take } from 'rxjs/operators'; import { configServiceMock } from '@kbn/config-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { mockOpsCollector } from './metrics_service.test.mocks'; import { MetricsService } from './metrics_service'; -import { take } from 'rxjs/operators'; +import { OpsMetricsCollector } from './ops_metrics_collector'; const testInterval = 100; @@ -24,6 +26,7 @@ const logger = loggingSystemMock.create(); describe('MetricsService', () => { const httpMock = httpServiceMock.createInternalSetupContract(); + const esServiceMock = elasticsearchServiceMock.createInternalSetup(); let metricsService: MetricsService; beforeEach(() => { @@ -43,9 +46,16 @@ describe('MetricsService', () => { describe('#start', () => { it('invokes setInterval with the configured interval', async () => { - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); await metricsService.start(); + expect(OpsMetricsCollector).toHaveBeenCalledTimes(1); + expect(OpsMetricsCollector).toHaveBeenCalledWith( + httpMock.server, + esServiceMock.agentStore, + expect.objectContaining({ logger: logger.get('metrics') }) + ); + expect(setInterval).toHaveBeenCalledTimes(1); expect(setInterval).toHaveBeenCalledWith(expect.any(Function), testInterval); }); @@ -53,7 +63,7 @@ describe('MetricsService', () => { it('collects the metrics at every interval', async () => { mockOpsCollector.collect.mockResolvedValue(dummyMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); await metricsService.start(); expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); @@ -68,7 +78,7 @@ describe('MetricsService', () => { it('resets the collector after each collection', async () => { mockOpsCollector.collect.mockResolvedValue(dummyMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); // `advanceTimersByTime` only ensure the interval handler is executed @@ -108,7 +118,7 @@ describe('MetricsService', () => { .mockResolvedValueOnce(firstMetrics) .mockResolvedValueOnce(secondMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); const nextEmission = async () => { @@ -157,7 +167,7 @@ describe('MetricsService', () => { mockOpsCollector.collect .mockResolvedValueOnce(firstMetrics) .mockResolvedValueOnce(secondMetrics); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); const nextEmission = async () => { @@ -176,7 +186,7 @@ describe('MetricsService', () => { it('omits metrics from log message if they are missing or malformed', async () => { const opsLogger = logger.get('metrics', 'ops'); mockOpsCollector.collect.mockResolvedValueOnce({ secondMetrics: 'metrics' }); - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); await metricsService.start(); expect(loggingSystemMock.collect(opsLogger).debug[0]).toMatchInlineSnapshot(` Array [ @@ -219,7 +229,7 @@ describe('MetricsService', () => { describe('#stop', () => { it('stops the metrics interval', async () => { - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); expect(mockOpsCollector.collect).toHaveBeenCalledTimes(1); @@ -235,7 +245,7 @@ describe('MetricsService', () => { }); it('completes the metrics observable', async () => { - await metricsService.setup({ http: httpMock }); + await metricsService.setup({ http: httpMock, elasticsearchService: esServiceMock }); const { getOpsMetrics$ } = await metricsService.start(); let completed = false; diff --git a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts index 8a05b4b57843c..95a9dc09bba57 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/metrics_service.ts @@ -10,6 +10,7 @@ import { firstValueFrom, ReplaySubject } from 'rxjs'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { Logger } from '@kbn/logging'; import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import type { InternalElasticsearchServiceSetup } from '@kbn/core-elasticsearch-server-internal'; import type { OpsMetrics, MetricsServiceSetup, @@ -21,6 +22,7 @@ import { getEcsOpsMetricsLog } from './logging'; export interface MetricsServiceSetupDeps { http: InternalHttpServiceSetup; + elasticsearchService: InternalElasticsearchServiceSetup; } /** @internal */ @@ -45,12 +47,15 @@ export class MetricsService this.opsMetricsLogger = coreContext.logger.get('metrics', 'ops'); } - public async setup({ http }: MetricsServiceSetupDeps): Promise<InternalMetricsServiceSetup> { + public async setup({ + http, + elasticsearchService, + }: MetricsServiceSetupDeps): Promise<InternalMetricsServiceSetup> { const config = await firstValueFrom( this.coreContext.configService.atPath<OpsConfigType>(OPS_CONFIG_PATH) ); - this.metricsCollector = new OpsMetricsCollector(http.server, { + this.metricsCollector = new OpsMetricsCollector(http.server, elasticsearchService.agentStore, { logger: this.logger, ...config.cGroupOverrides, }); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts index b96449fdc2f64..d70753b9f4644 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.mocks.ts @@ -11,11 +11,13 @@ import { collectorMock } from '@kbn/core-metrics-collectors-server-mocks'; export const mockOsCollector = collectorMock.create(); export const mockProcessCollector = collectorMock.create(); export const mockServerCollector = collectorMock.create(); +export const mockEsClientCollector = collectorMock.create(); jest.doMock('@kbn/core-metrics-collectors-server-internal', () => { return { OsMetricsCollector: jest.fn().mockImplementation(() => mockOsCollector), ProcessMetricsCollector: jest.fn().mockImplementation(() => mockProcessCollector), ServerMetricsCollector: jest.fn().mockImplementation(() => mockServerCollector), + ElasticsearchClientsMetricsCollector: jest.fn().mockImplementation(() => mockEsClientCollector), }; }); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts index cd80c35b37f86..87011a663404f 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.test.ts @@ -8,7 +8,10 @@ import { loggerMock } from '@kbn/logging-mocks'; import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { sampleEsClientMetrics } from '@kbn/core-metrics-server-mocks'; +import { AgentManager } from '@kbn/core-elasticsearch-client-server-internal'; import { + mockEsClientCollector, mockOsCollector, mockProcessCollector, mockServerCollector, @@ -20,7 +23,8 @@ describe('OpsMetricsCollector', () => { beforeEach(() => { const hapiServer = httpServiceMock.createInternalSetupContract().server; - collector = new OpsMetricsCollector(hapiServer, { logger: loggerMock.create() }); + const agentManager = new AgentManager(); + collector = new OpsMetricsCollector(hapiServer, agentManager, { logger: loggerMock.create() }); mockOsCollector.collect.mockResolvedValue('osMetrics'); }); @@ -33,12 +37,14 @@ describe('OpsMetricsCollector', () => { requests: 'serverRequestsMetrics', response_times: 'serverTimingMetrics', }); + mockEsClientCollector.collect.mockResolvedValue(sampleEsClientMetrics); const metrics = await collector.collect(); expect(mockOsCollector.collect).toHaveBeenCalledTimes(1); expect(mockProcessCollector.collect).toHaveBeenCalledTimes(1); expect(mockServerCollector.collect).toHaveBeenCalledTimes(1); + expect(mockEsClientCollector.collect).toHaveBeenCalledTimes(1); expect(metrics).toEqual({ collected_at: expect.any(Date), @@ -47,6 +53,7 @@ describe('OpsMetricsCollector', () => { os: 'osMetrics', requests: 'serverRequestsMetrics', response_times: 'serverTimingMetrics', + elasticsearch_client: sampleEsClientMetrics, }); }); }); diff --git a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts index 10958d93c2562..8a10f4071b11b 100644 --- a/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts +++ b/packages/core/metrics/core-metrics-server-internal/src/ops_metrics_collector.ts @@ -8,28 +8,33 @@ import { Server as HapiServer } from '@hapi/hapi'; import type { OpsMetrics, MetricsCollector } from '@kbn/core-metrics-server'; +import type { AgentStore } from '@kbn/core-elasticsearch-client-server-internal'; import { ProcessMetricsCollector, OsMetricsCollector, type OpsMetricsCollectorOptions, ServerMetricsCollector, + ElasticsearchClientsMetricsCollector, } from '@kbn/core-metrics-collectors-server-internal'; export class OpsMetricsCollector implements MetricsCollector<OpsMetrics> { private readonly processCollector: ProcessMetricsCollector; private readonly osCollector: OsMetricsCollector; private readonly serverCollector: ServerMetricsCollector; + private readonly esClientCollector: ElasticsearchClientsMetricsCollector; - constructor(server: HapiServer, opsOptions: OpsMetricsCollectorOptions) { + constructor(server: HapiServer, agentStore: AgentStore, opsOptions: OpsMetricsCollectorOptions) { this.processCollector = new ProcessMetricsCollector(); this.osCollector = new OsMetricsCollector(opsOptions); this.serverCollector = new ServerMetricsCollector(server); + this.esClientCollector = new ElasticsearchClientsMetricsCollector(agentStore); } public async collect(): Promise<OpsMetrics> { - const [processes, os, server] = await Promise.all([ + const [processes, os, esClient, server] = await Promise.all([ this.processCollector.collect(), this.osCollector.collect(), + this.esClientCollector.collect(), this.serverCollector.collect(), ]); @@ -43,6 +48,7 @@ export class OpsMetricsCollector implements MetricsCollector<OpsMetrics> { process: processes[0], processes, os, + elasticsearch_client: esClient, ...server, }; } diff --git a/packages/core/metrics/core-metrics-server-mocks/index.ts b/packages/core/metrics/core-metrics-server-mocks/index.ts index d252b2253243e..02d13b8ed5ad8 100644 --- a/packages/core/metrics/core-metrics-server-mocks/index.ts +++ b/packages/core/metrics/core-metrics-server-mocks/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { metricsServiceMock } from './src/metrics_service.mock'; +export { metricsServiceMock, sampleEsClientMetrics } from './src/metrics_service.mock'; diff --git a/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts b/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts index 6bbe176ce37e8..44601caeaa85c 100644 --- a/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts +++ b/packages/core/metrics/core-metrics-server-mocks/src/metrics_service.mock.ts @@ -17,7 +17,25 @@ import { mocked as eventLoopDelaysMonitorMock, collectorMock, } from '@kbn/core-metrics-collectors-server-mocks'; -import type { MetricsServiceSetup, MetricsServiceStart } from '@kbn/core-metrics-server'; +import type { + ElasticsearchClientsMetrics, + MetricsServiceSetup, + MetricsServiceStart, +} from '@kbn/core-metrics-server'; + +export const sampleEsClientMetrics: ElasticsearchClientsMetrics = { + protocol: 'https', + connectedNodes: 3, + nodesWithActiveSockets: 3, + nodesWithIdleSockets: 1, + totalActiveSockets: 25, + totalIdleSockets: 2, + totalQueuedRequests: 0, + mostActiveNodeSockets: 15, + averageActiveSocketsPerNode: 8, + mostIdleNodeSockets: 2, + averageIdleSocketsPerNode: 0.5, +}; const createInternalSetupContractMock = () => { const setupContract: jest.Mocked<InternalMetricsServiceSetup> = { @@ -39,6 +57,7 @@ const createInternalSetupContractMock = () => { memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, uptime_in_millis: 1, }, + elasticsearch_client: sampleEsClientMetrics, response_times: { avg_in_millis: 1, max_in_millis: 1 }, requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, concurrent_connections: 1, diff --git a/packages/core/metrics/core-metrics-server/index.ts b/packages/core/metrics/core-metrics-server/index.ts index 51e0b7fe3d95d..49bd2a4251623 100644 --- a/packages/core/metrics/core-metrics-server/index.ts +++ b/packages/core/metrics/core-metrics-server/index.ts @@ -14,4 +14,6 @@ export type { OpsProcessMetrics, OpsOsMetrics, OpsServerMetrics, + ElasticsearchClientProtocol, + ElasticsearchClientsMetrics, } from './src/metrics'; diff --git a/packages/core/metrics/core-metrics-server/src/metrics.ts b/packages/core/metrics/core-metrics-server/src/metrics.ts index dbfa643c8eccc..958f6b75f55e4 100644 --- a/packages/core/metrics/core-metrics-server/src/metrics.ts +++ b/packages/core/metrics/core-metrics-server/src/metrics.ts @@ -40,6 +40,44 @@ export interface IntervalHistogram { }; } +/** + * Protocol(s) used by the Elasticsearch Client + * @public + */ + +export type ElasticsearchClientProtocol = 'none' | 'http' | 'https' | 'mixed'; + +/** + * Metrics related to the elasticsearch clients + * @public + */ +export interface ElasticsearchClientsMetrics { + /** The protocol (or protocols) that these Agents are using */ + protocol: ElasticsearchClientProtocol; + /** Number of ES nodes that ES-js client is connecting to */ + connectedNodes: number; + /** Number of nodes with active connections */ + nodesWithActiveSockets: number; + /** Number of nodes with available connections (alive but idle). + * Note that a node can have both active and idle connections at the same time + */ + nodesWithIdleSockets: number; + /** Total number of active sockets (all nodes, all connections) */ + totalActiveSockets: number; + /** Total number of available sockets (alive but idle, all nodes, all connections) */ + totalIdleSockets: number; + /** Total number of queued requests (all nodes, all connections) */ + totalQueuedRequests: number; + /** Number of active connections of the node with most active connections */ + mostActiveNodeSockets: number; + /** Average of active sockets per node (all connections) */ + averageActiveSocketsPerNode: number; + /** Number of idle connections of the node with most idle connections */ + mostIdleNodeSockets: number; + /** Average of available (idle) sockets per node (all connections) */ + averageIdleSocketsPerNode: number; +} + /** * Process related metrics * @public @@ -165,6 +203,10 @@ export interface OpsServerMetrics { export interface OpsMetrics { /** Time metrics were recorded at. */ collected_at: Date; + /** + * Metrics related to the elasticsearch client + */ + elasticsearch_client: ElasticsearchClientsMetrics; /** * Process related metrics. * @deprecated use the processes field instead. diff --git a/packages/core/status/core-status-server-internal/src/routes/status.ts b/packages/core/status/core-status-server-internal/src/routes/status.ts index 34a5a9b4dcd20..199f55159a7c6 100644 --- a/packages/core/status/core-status-server-internal/src/routes/status.ts +++ b/packages/core/status/core-status-server-internal/src/routes/status.ts @@ -135,6 +135,7 @@ export const registerStatusRoute = ({ ...lastMetrics.requests, status_codes: lastMetrics.requests.statusCodes, }, + elasticsearch_client: lastMetrics.elasticsearch_client, }, }; diff --git a/src/cli_setup/utils.ts b/src/cli_setup/utils.ts index 5c66fa84c0f30..47b8199f16ea0 100644 --- a/src/cli_setup/utils.ts +++ b/src/cli_setup/utils.ts @@ -48,7 +48,7 @@ export const elasticsearch = new ElasticsearchService(logger, kibanaPackageJson. logger, type, // we use an independent AgentManager for cli_setup, no need to track performance of this one - agentManager: new AgentManager(), + agentFactoryProvider: new AgentManager(), kibanaVersion: kibanaPackageJson.version, }); }, diff --git a/src/core/server/server.ts b/src/core/server/server.ts index b7f41dd31dd04..e0a2c2c44f254 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -280,7 +280,10 @@ export class Server { executionContext: executionContextSetup, }); - const metricsSetup = await this.metrics.setup({ http: httpSetup }); + const metricsSetup = await this.metrics.setup({ + http: httpSetup, + elasticsearchService: elasticsearchServiceSetup, + }); const coreUsageDataSetup = this.coreUsageData.setup({ http: httpSetup, diff --git a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap index f962eca858199..d77d43293480b 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap +++ b/src/plugins/kibana_usage_collection/server/collectors/ops_stats/__snapshots__/ops_stats_collector.test.ts.snap @@ -3,6 +3,19 @@ exports[`telemetry_ops_stats should return something when there is a metric 1`] = ` Object { "concurrent_connections": 1, + "elasticsearch_client": Object { + "averageActiveSocketsPerNode": 8, + "averageIdleSocketsPerNode": 0.5, + "connectedNodes": 3, + "mostActiveNodeSockets": 15, + "mostIdleNodeSockets": 2, + "nodesWithActiveSockets": 3, + "nodesWithIdleSockets": 1, + "protocol": "https", + "totalActiveSockets": 25, + "totalIdleSockets": 2, + "totalQueuedRequests": 0, + }, "os": Object { "load": Object { "15m": 1, From b81993d6d805c432e19472cf1117b2d5fbe297c0 Mon Sep 17 00:00:00 2001 From: Justin Kambic <jk@elastic.co> Date: Tue, 4 Oct 2022 11:44:22 -0400 Subject: [PATCH 050/174] [Synthetics] Serialize errors before sending to redux store (#142603) * [Synthetics UI] Serialize errors before sending to redux store to prevent warnings (#142259) * Serialize errors before sending to redux store to prevent warnings. * Serialize response errors in monitor list effect. * Revise reducer case to avoid type error. --- .../public/apps/synthetics/state/index_status/actions.ts | 5 +++-- .../public/apps/synthetics/state/index_status/index.ts | 4 ++-- .../apps/synthetics/state/monitor_details/index.ts | 7 +++---- .../public/apps/synthetics/state/monitor_list/actions.ts | 9 +++++---- .../public/apps/synthetics/state/monitor_list/effects.ts | 4 ++-- .../public/apps/synthetics/state/monitor_list/index.ts | 4 ++-- .../public/apps/synthetics/state/overview/index.ts | 8 ++++---- .../apps/synthetics/state/service_locations/actions.ts | 5 ++++- .../apps/synthetics/state/service_locations/index.ts | 3 ++- .../synthetics/state/synthetics_enablement/actions.ts | 7 ++++--- .../apps/synthetics/state/synthetics_enablement/index.ts | 3 ++- .../public/apps/synthetics/state/utils/actions.ts | 4 ++-- .../public/apps/synthetics/state/utils/fetch_effect.ts | 7 ++++--- .../legacy_uptime/state/private_locations/index.ts | 6 +++--- 14 files changed, 42 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts index 36e2e2514910e..e522af3bfed7c 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/actions.ts @@ -5,10 +5,11 @@ * 2.0. */ -import type { IHttpFetchError } from '@kbn/core-http-browser'; import { createAction } from '@reduxjs/toolkit'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export const getIndexStatus = createAction<void>('[INDEX STATUS] GET'); export const getIndexStatusSuccess = createAction<StatesIndexStatus>('[INDEX STATUS] GET SUCCESS'); -export const getIndexStatusFail = createAction<IHttpFetchError>('[INDEX STATUS] GET FAIL'); +export const getIndexStatusFail = + createAction<IHttpSerializedFetchError>('[INDEX STATUS] GET FAIL'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts index f5351c65d0d6b..19ef8f94938a3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/index_status/index.ts @@ -6,7 +6,7 @@ */ import { createReducer } from '@reduxjs/toolkit'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { StatesIndexStatus } from '../../../../../common/runtime_types'; import { getIndexStatus, getIndexStatusSuccess, getIndexStatusFail } from './actions'; @@ -33,7 +33,7 @@ export const indexStatusReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getIndexStatusFail, (state, action) => { - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; state.loading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts index a2d9379df778e..b1fb95d5d5ee4 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_details/index.ts @@ -5,9 +5,8 @@ * 2.0. */ -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { createReducer } from '@reduxjs/toolkit'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { getMonitorRecentPingsAction, setMonitorDetailsLocationAction, @@ -47,7 +46,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getMonitorRecentPingsAction.fail, (state, action) => { - state.error = serializeHttpFetchError(action.payload as IHttpFetchError<ResponseErrorBody>); + state.error = action.payload; state.loading = false; }) @@ -59,7 +58,7 @@ export const monitorDetailsReducer = createReducer(initialState, (builder) => { state.syntheticsMonitorLoading = false; }) .addCase(getMonitorAction.fail, (state, action) => { - state.error = serializeHttpFetchError(action.payload as IHttpFetchError<ResponseErrorBody>); + state.error = action.payload; state.syntheticsMonitorLoading = false; }); }); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts index fcfc3d4f22cf7..5a8c38284e034 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/actions.ts @@ -5,13 +5,13 @@ * 2.0. */ -import { IHttpFetchError } from '@kbn/core-http-browser'; import { createAction } from '@reduxjs/toolkit'; import { EncryptedSyntheticsMonitor, MonitorManagementListResult, } from '../../../../../common/runtime_types'; import { createAsyncAction } from '../utils/actions'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; @@ -29,7 +29,8 @@ export const fetchUpsertSuccessAction = createAction<{ id: string; attributes: { enabled: boolean }; }>('fetchUpsertMonitorSuccess'); -export const fetchUpsertFailureAction = createAction<{ id: string; error: IHttpFetchError }>( - 'fetchUpsertMonitorFailure' -); +export const fetchUpsertFailureAction = createAction<{ + id: string; + error: IHttpSerializedFetchError; +}>('fetchUpsertMonitorFailure'); export const clearMonitorUpsertStatus = createAction<string>('clearMonitorUpsertStatus'); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts index 0dee2edfd7903..67aaa4ec982ed 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/effects.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { IHttpFetchError } from '@kbn/core-http-browser'; import { PayloadAction } from '@reduxjs/toolkit'; import { call, put, takeEvery, takeLeading } from 'redux-saga/effects'; import { fetchEffectFactory } from '../utils/fetch_effect'; +import { serializeHttpFetchError } from '../utils/http_error'; import { fetchMonitorListAction, fetchUpsertFailureAction, @@ -40,7 +40,7 @@ export function* upsertMonitorEffect() { ); } catch (error) { yield put( - fetchUpsertFailureAction({ id: action.payload.id, error: error as IHttpFetchError }) + fetchUpsertFailureAction({ id: action.payload.id, error: serializeHttpFetchError(error) }) ); } } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts index e1f564c0d0a3f..997f853c9bfc5 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/monitor_list/index.ts @@ -10,7 +10,7 @@ import { FETCH_STATUS } from '@kbn/observability-plugin/public'; import { ConfigKey, MonitorManagementListResult } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorListPageState } from './models'; import { @@ -58,7 +58,7 @@ export const monitorListReducer = createReducer(initialState, (builder) => { }) .addCase(fetchMonitorListAction.fail, (state, action) => { state.loading = false; - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; }) .addCase(fetchUpsertMonitorAction, (state, action) => { state.monitorUpsertStatuses[action.payload.id] = { diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts index 49159b29ef461..82272638ffb11 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/overview/index.ts @@ -9,7 +9,7 @@ import { createReducer } from '@reduxjs/toolkit'; import { MonitorOverviewResult, OverviewStatus } from '../../../../../common/runtime_types'; -import { IHttpSerializedFetchError, serializeHttpFetchError } from '../utils/http_error'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { MonitorOverviewPageState } from './models'; import { @@ -60,13 +60,13 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => { }) .addCase(fetchMonitorOverviewAction.fail, (state, action) => { state.loading = false; - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; }) .addCase(quietFetchOverviewAction.success, (state, action) => { state.data = action.payload; }) .addCase(quietFetchOverviewAction.fail, (state, action) => { - state.error = serializeHttpFetchError(action.payload); + state.error = action.payload; }) .addCase(setOverviewPerPageAction, (state, action) => { state.pageState = { @@ -79,7 +79,7 @@ export const monitorOverviewReducer = createReducer(initialState, (builder) => { state.status = action.payload; }) .addCase(fetchOverviewStatusAction.fail, (state, action) => { - state.statusError = serializeHttpFetchError(action.payload); + state.statusError = action.payload; }) .addCase(clearOverviewStatusErrorAction, (state) => { state.statusError = null; diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts index 794e16d0292c5..dbdd53d4cbcb7 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/actions.ts @@ -7,10 +7,13 @@ import { createAction } from '@reduxjs/toolkit'; import { ServiceLocations, ThrottlingOptions } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export const getServiceLocations = createAction('[SERVICE LOCATIONS] GET'); export const getServiceLocationsSuccess = createAction<{ throttling: ThrottlingOptions | undefined; locations: ServiceLocations; }>('[SERVICE LOCATIONS] GET SUCCESS'); -export const getServiceLocationsFailure = createAction<Error>('[SERVICE LOCATIONS] GET FAILURE'); +export const getServiceLocationsFailure = createAction<IHttpSerializedFetchError>( + '[SERVICE LOCATIONS] GET FAILURE' +); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts index e13fe756ec7fd..9a338458e603f 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/service_locations/index.ts @@ -11,6 +11,7 @@ import { ServiceLocations, ThrottlingOptions, } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; import { getServiceLocations, @@ -22,7 +23,7 @@ export interface ServiceLocationsState { locations: ServiceLocations; throttling: ThrottlingOptions | null; loading: boolean; - error: Error | null; + error: IHttpSerializedFetchError | null; locationsLoaded?: boolean; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts index c38fadc0952a6..0c7abffd1b289 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/actions.ts @@ -7,23 +7,24 @@ import { createAction } from '@reduxjs/toolkit'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export const getSyntheticsEnablement = createAction('[SYNTHETICS_ENABLEMENT] GET'); export const getSyntheticsEnablementSuccess = createAction<MonitorManagementEnablementResult>( '[SYNTHETICS_ENABLEMENT] GET SUCCESS' ); -export const getSyntheticsEnablementFailure = createAction<Error>( +export const getSyntheticsEnablementFailure = createAction<IHttpSerializedFetchError>( '[SYNTHETICS_ENABLEMENT] GET FAILURE' ); export const disableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] DISABLE'); export const disableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] DISABLE SUCCESS'); -export const disableSyntheticsFailure = createAction<Error>( +export const disableSyntheticsFailure = createAction<IHttpSerializedFetchError>( '[SYNTHETICS_ENABLEMENT] DISABLE FAILURE' ); export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE'); export const enableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS'); -export const enableSyntheticsFailure = createAction<Error>( +export const enableSyntheticsFailure = createAction<IHttpSerializedFetchError>( '[SYNTHETICS_ENABLEMENT] ENABLE FAILURE' ); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts index 62ed85ad17e86..3bf9ff69bf005 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/synthetics_enablement/index.ts @@ -18,10 +18,11 @@ import { getSyntheticsEnablementFailure, } from './actions'; import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types'; +import { IHttpSerializedFetchError } from '../utils/http_error'; export interface SyntheticsEnablementState { loading: boolean; - error: Error | null; + error: IHttpSerializedFetchError | null; enablement: MonitorManagementEnablementResult | null; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts index 416c3134d6034..35e93fd91484e 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/actions.ts @@ -6,13 +6,13 @@ */ import { createAction } from '@reduxjs/toolkit'; -import type { IHttpFetchError } from '@kbn/core-http-browser'; +import type { IHttpSerializedFetchError } from './http_error'; export function createAsyncAction<Payload, SuccessPayload>(actionStr: string) { return { get: createAction<Payload>(actionStr), success: createAction<SuccessPayload>(`${actionStr}_SUCCESS`), - fail: createAction<IHttpFetchError>(`${actionStr}_FAIL`), + fail: createAction<IHttpSerializedFetchError>(`${actionStr}_FAIL`), }; } diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts index b07f1fa542633..294da718a6fd3 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts +++ b/x-pack/plugins/synthetics/public/apps/synthetics/state/utils/fetch_effect.ts @@ -8,6 +8,7 @@ import { call, put } from 'redux-saga/effects'; import { PayloadAction } from '@reduxjs/toolkit'; import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { IHttpSerializedFetchError, serializeHttpFetchError } from './http_error'; /** * Factory function for a fetch effect. It expects three action creators, @@ -23,7 +24,7 @@ import type { IHttpFetchError } from '@kbn/core-http-browser'; export function fetchEffectFactory<T, R, S, F>( fetch: (request: T) => Promise<R>, success: (response: R) => PayloadAction<S>, - fail: (error: IHttpFetchError) => PayloadAction<F> + fail: (error: IHttpSerializedFetchError) => PayloadAction<F> ) { return function* (action: PayloadAction<T>): Generator { try { @@ -32,14 +33,14 @@ export function fetchEffectFactory<T, R, S, F>( // eslint-disable-next-line no-console console.error(response); - yield put(fail(response as IHttpFetchError)); + yield put(fail(serializeHttpFetchError(response as IHttpFetchError))); } else { yield put(success(response as R)); } } catch (error) { // eslint-disable-next-line no-console console.error(error); - yield put(fail(error as IHttpFetchError)); + yield put(fail(serializeHttpFetchError(error))); } }; } diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts index 0ff45023143ec..831f8a9cbf6bb 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/private_locations/index.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { createReducer } from '@reduxjs/toolkit'; import { AgentPolicy } from '@kbn/fleet-plugin/common'; +import { IHttpSerializedFetchError } from '../../../apps/synthetics/state'; import { getAgentPoliciesAction, setAddingNewPrivateLocation, @@ -24,7 +24,7 @@ export interface AgentPoliciesList { export interface AgentPoliciesState { data: AgentPoliciesList | null; loading: boolean; - error: IHttpFetchError<ResponseErrorBody> | null; + error: IHttpSerializedFetchError | null; isManageFlyoutOpen?: boolean; isAddingNewPrivateLocation?: boolean; } @@ -47,7 +47,7 @@ export const agentPoliciesReducer = createReducer(initialState, (builder) => { state.loading = false; }) .addCase(getAgentPoliciesAction.fail, (state, action) => { - state.error = action.payload as IHttpFetchError<ResponseErrorBody>; + state.error = action.payload; state.loading = false; }) .addCase(setManageFlyoutOpen, (state, action) => { From 524363d9ff6774fb242d2a1923d91e43fd147501 Mon Sep 17 00:00:00 2001 From: Adam Demjen <demjened@gmail.com> Date: Tue, 4 Oct 2022 11:48:12 -0400 Subject: [PATCH 051/174] [8.6][ML Inference] Add ML inference failure handler (#142488) * Add ML Inference failure handler --- .../common/ml_inference_pipeline/index.ts | 14 ++ .../create_pipeline_definitions.test.ts | 121 +++++++++--------- 2 files changed, 71 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts index 3bc43fa14d7b2..00d893ba9abaa 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.ts @@ -52,6 +52,20 @@ export const generateMlInferencePipelineBody = ({ }, model_id: model.model_id, target_field: `ml.inference.${destinationField}`, + on_failure: [ + { + append: { + field: '_source._ingest.inference_errors', + value: [ + { + pipeline: pipelineName, + message: `Processor 'inference' in pipeline '${pipelineName}' failed with message '{{ _ingest.on_failure_message }}'`, + timestamp: '{{{ _ingest.timestamp }}}', + }, + ], + }, + }, + ], }, }, { diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 5abd7db73170b..183e27a765c2f 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { merge } from 'lodash'; import { ElasticsearchClient } from '@kbn/core/server'; @@ -41,7 +42,7 @@ describe('createIndexPipelineDefinitions util function', () => { describe('formatMlPipelineBody util function', () => { const pipelineName = 'ml-inference-my-ml-proc'; const modelId = 'my-model-id'; - let modelInputField = 'my-model-input-field'; + const modelInputField = 'my-model-input-field'; const modelType = 'pytorch'; const inferenceConfigKey = 'my-model-type'; const modelTypes = ['pytorch', 'my-model-type']; @@ -49,6 +50,55 @@ describe('formatMlPipelineBody util function', () => { const sourceField = 'my-source-field'; const destField = 'my-dest-field'; + const expectedResult = { + description: '', + processors: [ + { + remove: { + field: `ml.inference.${destField}`, + ignore_missing: true, + }, + }, + { + inference: { + field_map: { + [sourceField]: modelInputField, + }, + model_id: modelId, + target_field: `ml.inference.${destField}`, + on_failure: [ + { + append: { + field: '_source._ingest.inference_errors', + value: [ + { + pipeline: pipelineName, + message: `Processor 'inference' in pipeline '${pipelineName}' failed with message '{{ _ingest.on_failure_message }}'`, + timestamp: '{{{ _ingest.timestamp }}}', + }, + ], + }, + }, + ], + }, + }, + { + append: { + field: '_source._ingest.processors', + value: [ + { + model_version: modelVersion, + pipeline: pipelineName, + processed_timestamp: '{{{ _ingest.timestamp }}}', + types: modelTypes, + }, + ], + }, + }, + ], + version: 1, + }; + const mockClient = { ml: { getTrainedModels: jest.fn(), @@ -60,41 +110,6 @@ describe('formatMlPipelineBody util function', () => { }); it('should return the pipeline body', async () => { - const expectedResult = { - description: '', - processors: [ - { - remove: { - field: `ml.inference.${destField}`, - ignore_missing: true, - }, - }, - { - inference: { - field_map: { - [sourceField]: modelInputField, - }, - model_id: modelId, - target_field: `ml.inference.${destField}`, - }, - }, - { - append: { - field: '_source._ingest.processors', - value: [ - { - model_version: modelVersion, - pipeline: pipelineName, - processed_timestamp: '{{{ _ingest.timestamp }}}', - types: modelTypes, - }, - ], - }, - }, - ], - version: 1, - }; - const mockResponse = { count: 1, trained_model_configs: [ @@ -136,41 +151,19 @@ describe('formatMlPipelineBody util function', () => { }); it('should insert a placeholder if model has no input fields', async () => { - modelInputField = 'MODEL_INPUT_FIELD'; - const expectedResult = { - description: '', + const expectedResultWithNoInputField = merge({}, expectedResult, { processors: [ - { - remove: { - field: `ml.inference.${destField}`, - ignore_missing: true, - }, - }, + {}, // append - we'll leave it untouched { inference: { field_map: { - [sourceField]: modelInputField, + [sourceField]: 'MODEL_INPUT_FIELD', }, - model_id: modelId, - target_field: `ml.inference.${destField}`, - }, - }, - { - append: { - field: '_source._ingest.processors', - value: [ - { - model_version: modelVersion, - pipeline: pipelineName, - processed_timestamp: '{{{ _ingest.timestamp }}}', - types: modelTypes, - }, - ], }, }, ], - version: 1, - }; + }); + const mockResponse = { count: 1, trained_model_configs: [ @@ -193,7 +186,7 @@ describe('formatMlPipelineBody util function', () => { destField, mockClient as unknown as ElasticsearchClient ); - expect(actualResult).toEqual(expectedResult); + expect(actualResult).toEqual(expectedResultWithNoInputField); expect(mockClient.ml.getTrainedModels).toHaveBeenCalledTimes(1); }); }); From 19734bc35e1f8ffd53d89a51e569a8c0acfadf71 Mon Sep 17 00:00:00 2001 From: Byron Hulcher <byronhulcher@gmail.com> Date: Tue, 4 Oct 2022 12:01:32 -0400 Subject: [PATCH 052/174] Remove Minutes from connector scheduling frequency dropdown (#142619) --- .../components/search_index/connector/connector_scheduling.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx index ca9a415c4c958..5438cd1a25f11 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/connector/connector_scheduling.tsx @@ -177,6 +177,7 @@ export const ConnectorSchedulingComponent: React.FC = () => { setScheduling({ ...scheduling, interval: expression }); setHasChanges(true); }} + frequencyBlockList={['MINUTE']} /> </EuiFlexItem> <EuiFlexItem> From 1c25d93cd24c2404f6559ca0c276c546c0919d5b Mon Sep 17 00:00:00 2001 From: Byron Hulcher <byronhulcher@gmail.com> Date: Tue, 4 Oct 2022 12:03:44 -0400 Subject: [PATCH 053/174] [Enterprise Search] Update copy for landing page (#141979) --- .../components/product_selector/product_selector.tsx | 2 +- .../add_content_empty_prompt/add_content_empty_prompt.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx index 01997959ec413..7be1264f2a9da 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_overview/components/product_selector/product_selector.tsx @@ -71,7 +71,7 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({ <> <AddContentEmptyPrompt title={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptTitle', { - defaultMessage: 'A new start for search', + defaultMessage: 'Add data and start searching', })} buttonLabel={i18n.translate('xpack.enterpriseSearch.overview.emptyPromptButtonLabel', { defaultMessage: 'Create an Elasticsearch index', diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/add_content_empty_prompt/add_content_empty_prompt.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/add_content_empty_prompt/add_content_empty_prompt.tsx index 700e07b34b53d..f2eeb2a29adf6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/add_content_empty_prompt/add_content_empty_prompt.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/add_content_empty_prompt/add_content_empty_prompt.tsx @@ -60,7 +60,7 @@ export const AddContentEmptyPrompt: React.FC<EmptyPromptProps> = ({ title, butto <p> {i18n.translate('xpack.enterpriseSearch.emptyState.description', { defaultMessage: - 'An Elasticsearch index is where your content gets stored. Get started by creating an Elasticsearch index and selecting an ingestion method. Options include the Elastic web crawler, third party data integrations, or using Elasticsearch API endpoints.', + 'Your content is stored in an Elasticsearch index. Get started by creating an Elasticsearch index and selecting an ingestion method. Options include the Elastic web crawler, third party data integrations, or using Elasticsearch API endpoints.', })} </p> <p> From 4c18c0a25e0276883f9c78455e94a513633dac74 Mon Sep 17 00:00:00 2001 From: Byron Hulcher <byronhulcher@gmail.com> Date: Tue, 4 Oct 2022 12:05:21 -0400 Subject: [PATCH 054/174] Update copy for authentication header form (#142620) --- .../authentication_panel_edit_content.tsx | 13 +++++++++++-- .../authentication_panel/constants.ts | 2 +- .../public/applications/shared/constants/labels.ts | 4 ---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx index eeed5bb377fe7..8c7522dc868b4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/authentication_panel_edit_content.tsx @@ -21,7 +21,9 @@ import { EuiFlexItem, } from '@elastic/eui'; -import { USERNAME_LABEL, PASSWORD_LABEL, TOKEN_LABEL } from '../../../../shared/constants'; +import { i18n } from '@kbn/i18n'; + +import { USERNAME_LABEL, PASSWORD_LABEL } from '../../../../shared/constants'; import { AuthenticationPanelLogic } from './authentication_panel_logic'; import { AUTHENTICATION_LABELS } from './constants'; @@ -82,7 +84,14 @@ export const AuthenticationPanelEditContent: React.FC = () => { onChange={() => selectAuthOption('raw')} > <EuiForm> - <EuiFormRow label={TOKEN_LABEL}> + <EuiFormRow + label={i18n.translate( + 'xpack.enterpriseSearch.crawler.authenticationPanel.editForm.headerValueLabel', + { + defaultMessage: 'Header value', + } + )} + > <EuiFieldPassword type="dual" value={headerContent} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.ts index 2918f54892cc5..4f84b23aa8474 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/crawler_domain_detail/authentication_panel/constants.ts @@ -15,6 +15,6 @@ export const AUTHENTICATION_LABELS = { } ), raw: i18n.translate('xpack.enterpriseSearch.crawler.authenticationPanel.rawAuthenticationLabel', { - defaultMessage: 'Bearer authentication', + defaultMessage: 'Authentication header', }), }; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts index fa982547f7fc5..edfef1ba1f3fc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/constants/labels.ts @@ -15,10 +15,6 @@ export const PASSWORD_LABEL = i18n.translate('xpack.enterpriseSearch.passwordLab defaultMessage: 'Password', }); -export const TOKEN_LABEL = i18n.translate('xpack.enterpriseSearch.tokenLabel', { - defaultMessage: 'Token', -}); - export const TYPE_LABEL = i18n.translate('xpack.enterpriseSearch.typeLabel', { defaultMessage: 'Type', }); From 2a1df2dd7b8269a606d378f59bc9456e85668b5c Mon Sep 17 00:00:00 2001 From: Jan Monschke <jan.monschke@elastic.co> Date: Tue, 4 Oct 2022 18:11:10 +0200 Subject: [PATCH 055/174] fix: handle the `undefined` case correctly (#142580) --- ...elated_alerts_by_process_ancestry.test.tsx | 21 ++++++++++++++++++- .../related_alerts_by_process_ancestry.tsx | 9 ++++---- .../use_alert_prevalence_from_process_tree.ts | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx index f95bf9234cc16..bd91f55d704da 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.test.tsx @@ -116,7 +116,7 @@ describe('RelatedAlertsByProcessAncestry', () => { }); }); - it('renders a special message when there are no alerts to display', async () => { + it('renders a special message when there are no alerts to display (empty response)', async () => { mockUseAlertPrevalenceFromProcessTree.mockReturnValue({ loading: false, error: false, @@ -134,4 +134,23 @@ describe('RelatedAlertsByProcessAncestry', () => { expect(screen.getByText(PROCESS_ANCESTRY_EMPTY)).toBeInTheDocument(); }); }); + + it('renders a special message when there are no alerts to display (undefined case)', async () => { + mockUseAlertPrevalenceFromProcessTree.mockReturnValue({ + loading: false, + error: false, + alertIds: undefined, + }); + + render( + <TestProviders> + <RelatedAlertsByProcessAncestry {...props} /> + </TestProviders> + ); + + userEvent.click(screen.getByText(PROCESS_ANCESTRY)); + await waitFor(() => { + expect(screen.getByText(PROCESS_ANCESTRY_EMPTY)).toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx index 330cb7ae113b3..28737c60f4e07 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_process_ancestry.tsx @@ -70,15 +70,12 @@ export const RelatedAlertsByProcessAncestry = React.memo<Props>( const [cache, setCache] = useState<Partial<Cache>>({}); const onToggle = useCallback((isOpen: boolean) => setShowContent(isOpen), []); - const isEmpty = !!cache.alertIds && cache.alertIds.length === 0; // Makes sure the component is not fetching data before the accordion // has been openend. const renderContent = useCallback(() => { if (!showContent) { return null; - } else if (isEmpty) { - return PROCESS_ANCESTRY_EMPTY; } else if (cache.alertIds) { return ( <ActualRelatedAlertsByProcessAncestry @@ -98,7 +95,7 @@ export const RelatedAlertsByProcessAncestry = React.memo<Props>( onCacheLoad={setCache} /> ); - }, [showContent, cache, data, eventId, timelineId, index, originalDocumentId, isEmpty]); + }, [showContent, cache, data, eventId, timelineId, index, originalDocumentId]); return ( <InsightAccordion @@ -143,7 +140,7 @@ const FetchAndNotifyCachedAlertsByProcessAncestry: React.FC<{ }); useEffect(() => { - if (alertIds) { + if (alertIds && alertIds.length !== 0) { onCacheLoad({ alertIds }); } }, [alertIds, onCacheLoad]); @@ -152,6 +149,8 @@ const FetchAndNotifyCachedAlertsByProcessAncestry: React.FC<{ return <EuiLoadingSpinner />; } else if (error) { return <>{PROCESS_ANCESTRY_ERROR}</>; + } else if (!alertIds || alertIds.length === 0) { + return <>{PROCESS_ANCESTRY_EMPTY}</>; } return null; diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts index 1a59271614c57..e3bc22ec2decb 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts +++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts @@ -18,7 +18,7 @@ interface UserAlertPrevalenceFromProcessTreeResult { } interface ProcessTreeAlertPrevalenceResponse { - alertIds: string[]; + alertIds: string[] | undefined; } interface EntityResponse { From 43bbbc6b27e62fabec14cf67b7fdc70db6b16322 Mon Sep 17 00:00:00 2001 From: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:23:48 -0700 Subject: [PATCH 056/174] [RAM] Fix rule details page not displaying the rule snooze status properly (#142292) * Fix rule details not displaying rule snooze status * Unit and E2E tests * Fix test * Fix test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../alerting/server/routes/resolve_rule.ts | 2 +- .../server/rules_client/rules_client.ts | 6 ++- .../server/rules_client/tests/resolve.test.ts | 52 ++++++++++++++++++ .../apps/triggers_actions_ui/details.ts | 54 +++++++++++++++++-- 4 files changed, 108 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts index cde747f9272fe..b3576c0c5ed44 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -75,7 +75,7 @@ export const resolveRuleRoute = ( verifyAccessAndContext(licenseState, async function (context, req, res) { const rulesClient = (await context.alerting).getRulesClient(); const { id } = req.params; - const rule = await rulesClient.resolve({ id }); + const rule = await rulesClient.resolve({ id, includeSnoozeData: true }); return res.ok({ body: rewriteBodyRes(rule), }); diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 7374868a11dbb..d08f12f054a50 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -715,9 +715,11 @@ export class RulesClient { public async resolve<Params extends RuleTypeParams = never>({ id, includeLegacyId, + includeSnoozeData = false, }: { id: string; includeLegacyId?: boolean; + includeSnoozeData?: boolean; }): Promise<ResolvedSanitizedRule<Params>> { const { saved_object: result, ...resolveResponse } = await this.unsecuredSavedObjectsClient.resolve<RawRule>('alert', id); @@ -750,7 +752,9 @@ export class RulesClient { result.attributes.alertTypeId, result.attributes, result.references, - includeLegacyId + includeLegacyId, + false, + includeSnoozeData ); return { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 297c4b6d60fcc..b4a48be3a37fc 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -201,6 +201,58 @@ describe('resolve()', () => { `); }); + test('calls saved objects client with id and includeSnoozeData params', async () => { + const rulesClient = new RulesClient(rulesClientParams); + unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ + saved_object: { + id: '1', + type: 'alert', + attributes: { + legacyId: 'some-legacy-id', + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + snoozeSchedule: [ + { + duration: 10000, + rRule: { + dtstart: new Date().toISOString(), + tzid: 'UTC', + count: 1, + }, + }, + ], + muteAll: false, + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + notifyWhen: 'onActiveAlert', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }, + outcome: 'aliasMatch', + alias_target_id: '2', + }); + const result = await rulesClient.resolve({ id: '1', includeSnoozeData: true }); + expect(result.isSnoozedUntil).toBeTruthy(); + }); + test('should call useSavedObjectReferences.injectReferences if defined for rule type', async () => { const injectReferencesFn = jest.fn().mockReturnValue({ bar: true, diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index d32c5bd58a94c..45147118b93aa 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -238,7 +238,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('should snooze the rule', async () => { - const snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed'); + let snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed'); await snoozeBadge.click(); const snoozeIndefinite = await testSubjects.find('ruleSnoozeIndefiniteApply'); @@ -247,18 +247,64 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.try(async () => { await testSubjects.existOrFail('rulesListNotifyBadge-snoozedIndefinitely'); }); + + // Unsnooze the rule for the next test + snoozeBadge = await testSubjects.find('rulesListNotifyBadge-snoozedIndefinitely'); + await snoozeBadge.click(); + + const snoozeCancel = await testSubjects.find('ruleSnoozeCancel'); + await snoozeCancel.click(); + await pageObjects.header.waitUntilLoadingHasFinished(); }); - it('should unsnooze the rule', async () => { - const snoozeBadge = await testSubjects.find('rulesListNotifyBadge-snoozedIndefinitely'); + it('should snooze the rule for a set duration', async () => { + let snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed'); + await snoozeBadge.click(); + + const snooze8h = await testSubjects.find('linkSnooze8h'); + await snooze8h.click(); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + await retry.try(async () => { + await testSubjects.existOrFail('rulesListNotifyBadge-snoozed'); + }); + + // Unsnooze the rule for the next test + snoozeBadge = await testSubjects.find('rulesListNotifyBadge-snoozed'); await snoozeBadge.click(); const snoozeCancel = await testSubjects.find('ruleSnoozeCancel'); await snoozeCancel.click(); + await pageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('should add snooze schedule', async () => { + let snoozeBadge = await testSubjects.find('rulesListNotifyBadge-unsnoozed'); + await snoozeBadge.click(); + + const addScheduleButton = await testSubjects.find('ruleAddSchedule'); + await addScheduleButton.click(); + + const saveScheduleButton = await testSubjects.find('scheduler-saveSchedule'); + await saveScheduleButton.click(); + + await pageObjects.header.waitUntilLoadingHasFinished(); await retry.try(async () => { - await testSubjects.existOrFail('rulesListNotifyBadge-unsnoozed'); + await testSubjects.existOrFail('rulesListNotifyBadge-scheduled'); }); + + // Unsnooze the rule for the next test + snoozeBadge = await testSubjects.find('rulesListNotifyBadge-scheduled'); + await snoozeBadge.click(); + + const snoozeCancel = await testSubjects.find('ruleRemoveAllSchedules'); + await snoozeCancel.click(); + + const confirmButton = await testSubjects.find('confirmModalConfirmButton'); + await confirmButton.click(); + await pageObjects.header.waitUntilLoadingHasFinished(); }); }); From 36b2296fe8b5c994a320e20521e5826ec6c3804d Mon Sep 17 00:00:00 2001 From: Kurt <kc13greiner@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:29:11 -0400 Subject: [PATCH 057/174] Checking if security license isEnabled, and if not, throwing 404 that is expected downstream (#142410) --- .../share_saved_object_permissions.test.ts | 19 +++++++++++++++++++ .../spaces/share_saved_object_permissions.ts | 10 +++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts index e5e3135a68024..a8fa3888efeb9 100644 --- a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.test.ts @@ -36,6 +36,7 @@ describe('Share Saved Object Permissions', () => { describe('GET /internal/security/_share_saved_object_permissions', () => { let routeHandler: RequestHandler<any, any, any, SecurityRequestHandlerContext>; let routeConfig: RouteConfig<any, any, any, any>; + beforeEach(() => { const [shareRouteConfig, shareRouteHandler] = router.get.mock.calls.find( ([{ path }]) => path === '/internal/security/_share_saved_object_permissions' @@ -50,6 +51,24 @@ describe('Share Saved Object Permissions', () => { expect(routeConfig.validate).toHaveProperty('query'); }); + it('returns `not found` when security is diabled', async () => { + routeParamsMock.license.isEnabled = jest.fn().mockReturnValue(false); + + const request = httpServerMock.createKibanaRequest({ + query: { + type: 'foo-type', + }, + }); + + await expect( + routeHandler(mockContext, request, kibanaResponseFactory) + ).resolves.toMatchObject({ + status: 404, + }); + + expect(routeParamsMock.license.isEnabled).toHaveBeenCalled(); + }); + it('returns `true` when the user is authorized globally', async () => { const checkPrivilegesWithRequest = jest.fn().mockResolvedValue({ hasAllRequested: true }); diff --git a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.ts b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.ts index 574be3ce37a01..536220eff03da 100644 --- a/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.ts +++ b/x-pack/plugins/security/server/routes/authorization/spaces/share_saved_object_permissions.ts @@ -11,7 +11,11 @@ import type { RouteDefinitionParams } from '../..'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; -export function defineShareSavedObjectPermissionRoutes({ router, authz }: RouteDefinitionParams) { +export function defineShareSavedObjectPermissionRoutes({ + router, + authz, + license, +}: RouteDefinitionParams) { router.get( { path: '/internal/security/_share_saved_object_permissions', @@ -21,6 +25,10 @@ export function defineShareSavedObjectPermissionRoutes({ router, authz }: RouteD let shareToAllSpaces = true; const { type } = request.query; + if (!license.isEnabled()) { + return response.notFound(); + } + try { const checkPrivileges = authz.checkPrivilegesWithRequest(request); shareToAllSpaces = ( From 7e01603310a3a6192c52805bf4a20c95550de4ab Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:38:16 -0600 Subject: [PATCH 058/174] skip failing test suite (#142564) --- .../spaces_only/tests/alerting/run_soon.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts index 050c220ab1b0f..f32665a5a1fac 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/run_soon.ts @@ -18,7 +18,8 @@ export default function createRunSoonTests({ getService }: FtrProviderContext) { const es = getService('es'); const esArchiver = getService('esArchiver'); - describe('runSoon', () => { + // Failing: See https://github.com/elastic/kibana/issues/142564 + describe.skip('runSoon', () => { const objectRemover = new ObjectRemover(supertest); before(async () => { From 5ff2232c26b7eafee2e632b2a6c7320d90341038 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Oct 2022 10:40:43 -0600 Subject: [PATCH 059/174] skip failing test suite (#141864) --- .../spaces_only/tests/alerting/disable.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts index feec6431ee3cf..495e423e43194 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/disable.ts @@ -26,7 +26,8 @@ export default function createDisableRuleTests({ getService }: FtrProviderContex const retry = getService('retry'); const supertest = getService('supertest'); - describe('disable', () => { + // Failing: See https://github.com/elastic/kibana/issues/141864 + describe.skip('disable', () => { const objectRemover = new ObjectRemover(supertestWithoutAuth); const ruleUtils = new RuleUtils({ space: Spaces.space1, supertestWithoutAuth }); From 1c8e0ed785146f064b14b936f2498c5a15a4b1a7 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" <christiane.heiligers@elastic.co> Date: Tue, 4 Oct 2022 09:51:06 -0700 Subject: [PATCH 060/174] Migrate server-side http_resources service to packages (#142537) Co-authored-by: pgayvallet <pierre.gayvallet@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 3 + package.json | 6 + packages/BUILD.bazel | 6 + .../BUILD.bazel | 115 ++++++++++++++++++ .../README.md | 3 + .../index.ts | 11 ++ .../jest.config.js | 13 ++ .../kibana.jsonc | 7 ++ .../package.json | 7 ++ .../src}/get_apm_config.test.mocks.ts | 0 .../src}/get_apm_config.test.ts | 0 .../src}/get_apm_config.ts | 0 .../src}/http_resources_service.test.mocks.ts | 0 .../src}/http_resources_service.test.ts | 26 ++-- .../src}/http_resources_service.ts | 14 ++- .../src/index.ts | 11 ++ .../http_resources_service_test_mocks.ts | 35 ++++++ .../src/types.ts | 24 ++++ .../tsconfig.json | 17 +++ .../BUILD.bazel | 110 +++++++++++++++++ .../README.md | 3 + .../core-http-resources-server-mocks/index.ts | 10 ++ .../jest.config.js | 13 ++ .../kibana.jsonc | 7 ++ .../package.json | 8 ++ .../src/http_resources_server.mock.ts | 21 +++- .../tsconfig.json | 17 +++ .../core-http-resources-server/BUILD.bazel | 107 ++++++++++++++++ .../http/core-http-resources-server/README.md | 3 + .../http/core-http-resources-server/index.ts | 15 +++ .../core-http-resources-server/jest.config.js | 13 ++ .../core-http-resources-server/kibana.jsonc | 7 ++ .../core-http-resources-server/package.json | 7 ++ .../core-http-resources-server/src}/index.ts | 4 - .../core-http-resources-server/src}/types.ts | 15 --- .../core-http-resources-server/tsconfig.json | 17 +++ src/core/server/core_app/core_app.test.ts | 2 +- src/core/server/core_app/core_app.ts | 2 +- src/core/server/index.ts | 4 +- src/core/server/internal_types.ts | 5 +- src/core/server/mocks.ts | 4 +- src/core/server/server.ts | 2 +- yarn.lock | 24 ++++ 43 files changed, 675 insertions(+), 43 deletions(-) create mode 100644 packages/core/http/core-http-resources-server-internal/BUILD.bazel create mode 100644 packages/core/http/core-http-resources-server-internal/README.md create mode 100644 packages/core/http/core-http-resources-server-internal/index.ts create mode 100644 packages/core/http/core-http-resources-server-internal/jest.config.js create mode 100644 packages/core/http/core-http-resources-server-internal/kibana.jsonc create mode 100644 packages/core/http/core-http-resources-server-internal/package.json rename {src/core/server/http_resources => packages/core/http/core-http-resources-server-internal/src}/get_apm_config.test.mocks.ts (100%) rename {src/core/server/http_resources => packages/core/http/core-http-resources-server-internal/src}/get_apm_config.test.ts (100%) rename {src/core/server/http_resources => packages/core/http/core-http-resources-server-internal/src}/get_apm_config.ts (100%) rename {src/core/server/http_resources => packages/core/http/core-http-resources-server-internal/src}/http_resources_service.test.mocks.ts (100%) rename {src/core/server/http_resources => packages/core/http/core-http-resources-server-internal/src}/http_resources_service.test.ts (91%) rename {src/core/server/http_resources => packages/core/http/core-http-resources-server-internal/src}/http_resources_service.ts (96%) create mode 100644 packages/core/http/core-http-resources-server-internal/src/index.ts create mode 100644 packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts create mode 100644 packages/core/http/core-http-resources-server-internal/src/types.ts create mode 100644 packages/core/http/core-http-resources-server-internal/tsconfig.json create mode 100644 packages/core/http/core-http-resources-server-mocks/BUILD.bazel create mode 100644 packages/core/http/core-http-resources-server-mocks/README.md create mode 100644 packages/core/http/core-http-resources-server-mocks/index.ts create mode 100644 packages/core/http/core-http-resources-server-mocks/jest.config.js create mode 100644 packages/core/http/core-http-resources-server-mocks/kibana.jsonc create mode 100644 packages/core/http/core-http-resources-server-mocks/package.json rename src/core/server/http_resources/http_resources_service.mock.ts => packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts (65%) create mode 100644 packages/core/http/core-http-resources-server-mocks/tsconfig.json create mode 100644 packages/core/http/core-http-resources-server/BUILD.bazel create mode 100644 packages/core/http/core-http-resources-server/README.md create mode 100644 packages/core/http/core-http-resources-server/index.ts create mode 100644 packages/core/http/core-http-resources-server/jest.config.js create mode 100644 packages/core/http/core-http-resources-server/kibana.jsonc create mode 100644 packages/core/http/core-http-resources-server/package.json rename {src/core/server/http_resources => packages/core/http/core-http-resources-server/src}/index.ts (80%) rename {src/core/server/http_resources => packages/core/http/core-http-resources-server/src}/types.ts (91%) create mode 100644 packages/core/http/core-http-resources-server/tsconfig.json diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b7d588bc89269..759a966ab3fe1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -745,6 +745,9 @@ packages/core/http/core-http-context-server-internal @elastic/kibana-core packages/core/http/core-http-context-server-mocks @elastic/kibana-core packages/core/http/core-http-request-handler-context-server @elastic/kibana-core packages/core/http/core-http-request-handler-context-server-internal @elastic/kibana-core +packages/core/http/core-http-resources-server @elastic/kibana-core +packages/core/http/core-http-resources-server-internal @elastic/kibana-core +packages/core/http/core-http-resources-server-mocks @elastic/kibana-core packages/core/http/core-http-router-server-internal @elastic/kibana-core packages/core/http/core-http-router-server-mocks @elastic/kibana-core packages/core/http/core-http-server @elastic/kibana-core diff --git a/package.json b/package.json index f122f532d5274..9825802a80292 100644 --- a/package.json +++ b/package.json @@ -215,6 +215,9 @@ "@kbn/core-http-context-server-mocks": "link:bazel-bin/packages/core/http/core-http-context-server-mocks", "@kbn/core-http-request-handler-context-server": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server", "@kbn/core-http-request-handler-context-server-internal": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal", + "@kbn/core-http-resources-server": "link:bazel-bin/packages/core/http/core-http-resources-server", + "@kbn/core-http-resources-server-internal": "link:bazel-bin/packages/core/http/core-http-resources-server-internal", + "@kbn/core-http-resources-server-mocks": "link:bazel-bin/packages/core/http/core-http-resources-server-mocks", "@kbn/core-http-router-server-internal": "link:bazel-bin/packages/core/http/core-http-router-server-internal", "@kbn/core-http-router-server-mocks": "link:bazel-bin/packages/core/http/core-http-router-server-mocks", "@kbn/core-http-server": "link:bazel-bin/packages/core/http/core-http-server", @@ -942,6 +945,9 @@ "@types/kbn__core-http-context-server-mocks": "link:bazel-bin/packages/core/http/core-http-context-server-mocks/npm_module_types", "@types/kbn__core-http-request-handler-context-server": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server/npm_module_types", "@types/kbn__core-http-request-handler-context-server-internal": "link:bazel-bin/packages/core/http/core-http-request-handler-context-server-internal/npm_module_types", + "@types/kbn__core-http-resources-server": "link:bazel-bin/packages/core/http/core-http-resources-server/npm_module_types", + "@types/kbn__core-http-resources-server-internal": "link:bazel-bin/packages/core/http/core-http-resources-server-internal/npm_module_types", + "@types/kbn__core-http-resources-server-mocks": "link:bazel-bin/packages/core/http/core-http-resources-server-mocks/npm_module_types", "@types/kbn__core-http-router-server-internal": "link:bazel-bin/packages/core/http/core-http-router-server-internal/npm_module_types", "@types/kbn__core-http-router-server-mocks": "link:bazel-bin/packages/core/http/core-http-router-server-mocks/npm_module_types", "@types/kbn__core-http-server": "link:bazel-bin/packages/core/http/core-http-server/npm_module_types", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 0d5ecd4bc4cfc..97b7064f4cd7f 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -81,6 +81,9 @@ filegroup( "//packages/core/http/core-http-context-server-mocks:build", "//packages/core/http/core-http-request-handler-context-server:build", "//packages/core/http/core-http-request-handler-context-server-internal:build", + "//packages/core/http/core-http-resources-server:build", + "//packages/core/http/core-http-resources-server-internal:build", + "//packages/core/http/core-http-resources-server-mocks:build", "//packages/core/http/core-http-router-server-internal:build", "//packages/core/http/core-http-router-server-mocks:build", "//packages/core/http/core-http-server:build", @@ -418,6 +421,9 @@ filegroup( "//packages/core/http/core-http-context-server-mocks:build_types", "//packages/core/http/core-http-request-handler-context-server:build_types", "//packages/core/http/core-http-request-handler-context-server-internal:build_types", + "//packages/core/http/core-http-resources-server:build_types", + "//packages/core/http/core-http-resources-server-internal:build_types", + "//packages/core/http/core-http-resources-server-mocks:build_types", "//packages/core/http/core-http-router-server-internal:build_types", "//packages/core/http/core-http-router-server-mocks:build_types", "//packages/core/http/core-http-server:build_types", diff --git a/packages/core/http/core-http-resources-server-internal/BUILD.bazel b/packages/core/http/core-http-resources-server-internal/BUILD.bazel new file mode 100644 index 0000000000000..8c286485efafb --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/BUILD.bazel @@ -0,0 +1,115 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-http-resources-server-internal" +PKG_REQUIRE_NAME = "@kbn/core-http-resources-server-internal" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "@npm//elastic-apm-node", + "//packages/kbn-logging", + "//packages/kbn-apm-config-loader", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "@npm//elastic-apm-node", + "//packages/kbn-apm-config-loader:npm_module_types", + "//packages/kbn-logging:npm_module_types", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-resources-server:npm_module_types", + "//packages/core/rendering/core-rendering-server-internal:npm_module_types", + "//packages/core/http/core-http-request-handler-context-server:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/http/core-http-resources-server-internal/README.md b/packages/core/http/core-http-resources-server-internal/README.md new file mode 100644 index 0000000000000..e1fa72ba6a294 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-resources-server-internal + +This package contains the internal types and implementation for Core's internal `http` resources service. diff --git a/packages/core/http/core-http-resources-server-internal/index.ts b/packages/core/http/core-http-resources-server-internal/index.ts new file mode 100644 index 0000000000000..270425ba81f27 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { HttpResourcesService } from './src'; + +export type { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './src'; diff --git a/packages/core/http/core-http-resources-server-internal/jest.config.js b/packages/core/http/core-http-resources-server-internal/jest.config.js new file mode 100644 index 0000000000000..2b328b864899a --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['<rootDir>/packages/core/http/core-http-resources-server-internal'], +}; diff --git a/packages/core/http/core-http-resources-server-internal/kibana.jsonc b/packages/core/http/core-http-resources-server-internal/kibana.jsonc new file mode 100644 index 0000000000000..bea96a4a60844 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-resources-server-internal", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-resources-server-internal/package.json b/packages/core/http/core-http-resources-server-internal/package.json new file mode 100644 index 0000000000000..71e4a44a35504 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/core-http-resources-server-internal", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/http_resources/get_apm_config.test.mocks.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.mocks.ts similarity index 100% rename from src/core/server/http_resources/get_apm_config.test.mocks.ts rename to packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.mocks.ts diff --git a/src/core/server/http_resources/get_apm_config.test.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.ts similarity index 100% rename from src/core/server/http_resources/get_apm_config.test.ts rename to packages/core/http/core-http-resources-server-internal/src/get_apm_config.test.ts diff --git a/src/core/server/http_resources/get_apm_config.ts b/packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts similarity index 100% rename from src/core/server/http_resources/get_apm_config.ts rename to packages/core/http/core-http-resources-server-internal/src/get_apm_config.ts diff --git a/src/core/server/http_resources/http_resources_service.test.mocks.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts similarity index 100% rename from src/core/server/http_resources/http_resources_service.test.mocks.ts rename to packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.mocks.ts diff --git a/src/core/server/http_resources/http_resources_service.test.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts similarity index 91% rename from src/core/server/http_resources/http_resources_service.test.ts rename to packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts index ea04f30847508..9bf0de0eca829 100644 --- a/src/core/server/http_resources/http_resources_service.test.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.test.ts @@ -12,11 +12,13 @@ import type { RouteConfig } from '@kbn/core-http-server'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; -import { coreMock } from '../mocks'; import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; import { HttpResourcesService, PrebootDeps, SetupDeps } from './http_resources_service'; -import { httpResourcesMock } from './http_resources_service.mock'; -import { HttpResources } from '..'; +import type { HttpResources } from '@kbn/core-http-resources-server'; +import { + createCoreRequestHandlerContextMock, + createHttpResourcesResponseFactory, +} from './test_helpers/http_resources_service_test_mocks'; const coreContext = mockCoreContext.create(); @@ -26,7 +28,7 @@ describe('HttpResources service', () => { let setupDeps: SetupDeps; let router: ReturnType<typeof httpServiceMock.createRouter>; const kibanaRequest = httpServerMock.createKibanaRequest(); - const context = coreMock.createCustomRequestHandlerContext({}); + const context = createCoreRequestHandlerContextMock(); const apmConfig = { mockApmConfig: true }; beforeEach(() => { @@ -66,7 +68,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(getDeps().rendering.render).toHaveBeenCalledWith( kibanaRequest, @@ -92,7 +94,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ @@ -112,7 +114,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(getDeps().rendering.render).toHaveBeenCalledWith( kibanaRequest, @@ -138,7 +140,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ @@ -159,7 +161,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ body: htmlBody, @@ -186,7 +188,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ @@ -208,7 +210,7 @@ describe('HttpResources service', () => { }); const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ body: jsBody, @@ -235,7 +237,7 @@ describe('HttpResources service', () => { const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); + const responseFactory = createHttpResourcesResponseFactory(); await routeHandler(context, kibanaRequest, responseFactory); expect(responseFactory.ok).toHaveBeenCalledWith({ diff --git a/src/core/server/http_resources/http_resources_service.ts b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts similarity index 96% rename from src/core/server/http_resources/http_resources_service.ts rename to packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts index 7cc88699ea7ba..1032611748d36 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/packages/core/http/core-http-resources-server-internal/src/http_resources_service.ts @@ -23,21 +23,29 @@ import type { InternalRenderingServiceSetup, } from '@kbn/core-rendering-server-internal'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; -import { - InternalHttpResourcesSetup, +import type { HttpResources, HttpResourcesResponseOptions, HttpResourcesRenderOptions, HttpResourcesRequestHandler, HttpResourcesServiceToolkit, -} from './types'; +} from '@kbn/core-http-resources-server'; + +import type { InternalHttpResourcesSetup } from './types'; + import { getApmConfig } from './get_apm_config'; +/** + * @internal + */ export interface PrebootDeps { http: InternalHttpServicePreboot; rendering: InternalRenderingServicePreboot; } +/** + * @internal + */ export interface SetupDeps { http: InternalHttpServiceSetup; rendering: InternalRenderingServiceSetup; diff --git a/packages/core/http/core-http-resources-server-internal/src/index.ts b/packages/core/http/core-http-resources-server-internal/src/index.ts new file mode 100644 index 0000000000000..b4c1502a92cab --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/src/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { HttpResourcesService } from './http_resources_service'; + +export type { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './types'; diff --git a/packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts b/packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts new file mode 100644 index 0000000000000..c9c0251355012 --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/src/test_helpers/http_resources_service_test_mocks.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; + +// partial duplicate of coreMock +export function createCoreRequestHandlerContextMock() { + return { + core: { + uiSettings: { client: uiSettingsServiceMock.createClient() }, + }, + }; +} + +// duplicate of public mock for internal testing only +export function createHttpResourcesResponseFactory() { + const mocked: jest.Mocked<HttpResourcesServiceToolkit> = { + renderCoreApp: jest.fn(), + renderAnonymousCoreApp: jest.fn(), + renderHtml: jest.fn(), + renderJs: jest.fn(), + }; + + return { + ...httpServerMock.createResponseFactory(), + ...mocked, + }; +} diff --git a/packages/core/http/core-http-resources-server-internal/src/types.ts b/packages/core/http/core-http-resources-server-internal/src/types.ts new file mode 100644 index 0000000000000..f68520a6387bb --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/src/types.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IRouter } from '@kbn/core-http-server'; +import type { HttpResources } from '@kbn/core-http-resources-server'; + +/** + * Allows to configure HTTP response parameters + * @internal + */ +export interface InternalHttpResourcesPreboot { + createRegistrar(router: IRouter): HttpResources; +} + +/** + * Allows to configure HTTP response parameters + * @internal + */ +export type InternalHttpResourcesSetup = InternalHttpResourcesPreboot; diff --git a/packages/core/http/core-http-resources-server-internal/tsconfig.json b/packages/core/http/core-http-resources-server-internal/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/http/core-http-resources-server-internal/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/http/core-http-resources-server-mocks/BUILD.bazel b/packages/core/http/core-http-resources-server-mocks/BUILD.bazel new file mode 100644 index 0000000000000..81eefd0db2ee2 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/BUILD.bazel @@ -0,0 +1,110 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-http-resources-server-mocks" +PKG_REQUIRE_NAME = "@kbn/core-http-resources-server-mocks" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ + "//packages/core/http/core-http-server-mocks", + "//packages/core/http/core-http-resources-server-internal", +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/kbn-utility-types:npm_module_types", + "//packages/core/http/core-http-server-mocks:npm_module_types", + "//packages/core/http/core-http-resources-server:npm_module_types", + "//packages/core/http/core-http-resources-server-internal:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/http/core-http-resources-server-mocks/README.md b/packages/core/http/core-http-resources-server-mocks/README.md new file mode 100644 index 0000000000000..6d9bd3de0dad2 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-resources-server-mocks + +This package contains the mocks for Core's internal `http` resources service. diff --git a/packages/core/http/core-http-resources-server-mocks/index.ts b/packages/core/http/core-http-resources-server-mocks/index.ts new file mode 100644 index 0000000000000..9b848c2bd32a1 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/index.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 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 { httpResourcesMock } from './src/http_resources_server.mock'; +export type { HttpResourcesMock } from './src/http_resources_server.mock'; diff --git a/packages/core/http/core-http-resources-server-mocks/jest.config.js b/packages/core/http/core-http-resources-server-mocks/jest.config.js new file mode 100644 index 0000000000000..7241454e43f5a --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['<rootDir>/packages/core/http/core-http-resources-server-mocks'], +}; diff --git a/packages/core/http/core-http-resources-server-mocks/kibana.jsonc b/packages/core/http/core-http-resources-server-mocks/kibana.jsonc new file mode 100644 index 0000000000000..e8703bdd42aa3 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-resources-server-mocks", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-resources-server-mocks/package.json b/packages/core/http/core-http-resources-server-mocks/package.json new file mode 100644 index 0000000000000..47247cd2abaf5 --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@kbn/core-http-resources-server-mocks", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "author": "Kibana Core", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/http_resources/http_resources_service.mock.ts b/packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts similarity index 65% rename from src/core/server/http_resources/http_resources_service.mock.ts rename to packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts index 6c50d720ceab2..aec435e650893 100644 --- a/src/core/server/http_resources/http_resources_service.mock.ts +++ b/packages/core/http/core-http-resources-server-mocks/src/http_resources_server.mock.ts @@ -7,7 +7,25 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import { HttpResources, HttpResourcesServiceToolkit } from './types'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import { HttpResourcesService } from '@kbn/core-http-resources-server-internal'; +import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; + +export type HttpResourcesMock = jest.Mocked<PublicMethodsOf<HttpResourcesService>>; + +function createHttpResourcesService() { + const mock: HttpResourcesMock = { + preboot: jest.fn(), + setup: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + }; + + mock.preboot.mockReturnValue(createInternalHttpResourcesPreboot()); + mock.setup.mockReturnValue(createInternalHttpResourcesSetup()); + + return mock; +} const createHttpResourcesMock = (): jest.Mocked<HttpResources> => ({ register: jest.fn(), @@ -38,6 +56,7 @@ function createHttpResourcesResponseFactory() { } export const httpResourcesMock = { + create: createHttpResourcesService, createRegistrar: createHttpResourcesMock, createPrebootContract: createInternalHttpResourcesPreboot, createSetupContract: createInternalHttpResourcesSetup, diff --git a/packages/core/http/core-http-resources-server-mocks/tsconfig.json b/packages/core/http/core-http-resources-server-mocks/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/http/core-http-resources-server-mocks/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/packages/core/http/core-http-resources-server/BUILD.bazel b/packages/core/http/core-http-resources-server/BUILD.bazel new file mode 100644 index 0000000000000..16583b6801b4a --- /dev/null +++ b/packages/core/http/core-http-resources-server/BUILD.bazel @@ -0,0 +1,107 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library") +load("//src/dev/bazel:index.bzl", "jsts_transpiler", "pkg_npm", "pkg_npm_types", "ts_project") + +PKG_DIRNAME = "core-http-resources-server" +PKG_REQUIRE_NAME = "@kbn/core-http-resources-server" + +SOURCE_FILES = glob( + [ + "**/*.ts", + ], + exclude = [ + "**/*.config.js", + "**/*.mock.*", + "**/*.test.*", + "**/*.stories.*", + "**/__snapshots__/**", + "**/integration_tests/**", + "**/mocks/**", + "**/scripts/**", + "**/storybook/**", + "**/test_fixtures/**", + "**/test_helpers/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", +] + +RUNTIME_DEPS = [ +] + +TYPES_DEPS = [ + "@npm//@types/node", + "@npm//@types/jest", + "//packages/core/http/core-http-server:npm_module_types", + "//packages/core/http/core-http-request-handler-context-server:npm_module_types", +] + +jsts_transpiler( + name = "target_node", + srcs = SRCS, + build_pkg_name = package_name(), +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.bazel.json", + ], +) + +ts_project( + name = "tsc_types", + args = ['--pretty'], + srcs = SRCS, + deps = TYPES_DEPS, + declaration = True, + declaration_map = True, + emit_declaration_only = True, + out_dir = "target_types", + tsconfig = ":tsconfig", +) + +js_library( + name = PKG_DIRNAME, + srcs = NPM_MODULE_EXTRA_FILES, + deps = RUNTIME_DEPS + [":target_node"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [":" + PKG_DIRNAME], +) + +filegroup( + name = "build", + srcs = [":npm_module"], + visibility = ["//visibility:public"], +) + +pkg_npm_types( + name = "npm_module_types", + srcs = SRCS, + deps = [":tsc_types"], + package_name = PKG_REQUIRE_NAME, + tsconfig = ":tsconfig", + visibility = ["//visibility:public"], +) + +filegroup( + name = "build_types", + srcs = [":npm_module_types"], + visibility = ["//visibility:public"], +) diff --git a/packages/core/http/core-http-resources-server/README.md b/packages/core/http/core-http-resources-server/README.md new file mode 100644 index 0000000000000..a2f4321f19214 --- /dev/null +++ b/packages/core/http/core-http-resources-server/README.md @@ -0,0 +1,3 @@ +# @kbn/core-http-resources-server + +This package contains the public types for Core's HTTP resources service. diff --git a/packages/core/http/core-http-resources-server/index.ts b/packages/core/http/core-http-resources-server/index.ts new file mode 100644 index 0000000000000..c4dbd3842b2ee --- /dev/null +++ b/packages/core/http/core-http-resources-server/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type { + HttpResourcesRenderOptions, + HttpResourcesResponseOptions, + HttpResourcesServiceToolkit, + HttpResourcesRequestHandler, + HttpResources, +} from './src'; diff --git a/packages/core/http/core-http-resources-server/jest.config.js b/packages/core/http/core-http-resources-server/jest.config.js new file mode 100644 index 0000000000000..bfa504fb873d5 --- /dev/null +++ b/packages/core/http/core-http-resources-server/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../../../..', + roots: ['<rootDir>/packages/core/http/core-http-resources-server'], +}; diff --git a/packages/core/http/core-http-resources-server/kibana.jsonc b/packages/core/http/core-http-resources-server/kibana.jsonc new file mode 100644 index 0000000000000..a05c1223a7817 --- /dev/null +++ b/packages/core/http/core-http-resources-server/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-common", + "id": "@kbn/core-http-resources-server", + "owner": "@elastic/kibana-core", + "runtimeDeps": [], + "typeDeps": [], +} diff --git a/packages/core/http/core-http-resources-server/package.json b/packages/core/http/core-http-resources-server/package.json new file mode 100644 index 0000000000000..156bc4c8948b4 --- /dev/null +++ b/packages/core/http/core-http-resources-server/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/core-http-resources-server", + "private": true, + "version": "1.0.0", + "main": "./target_node/index.js", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/src/core/server/http_resources/index.ts b/packages/core/http/core-http-resources-server/src/index.ts similarity index 80% rename from src/core/server/http_resources/index.ts rename to packages/core/http/core-http-resources-server/src/index.ts index 9148c08a922e9..8fbe0e3c1a5e2 100644 --- a/src/core/server/http_resources/index.ts +++ b/packages/core/http/core-http-resources-server/src/index.ts @@ -6,14 +6,10 @@ * Side Public License, v 1. */ -export { HttpResourcesService } from './http_resources_service'; - export type { HttpResourcesRenderOptions, HttpResourcesResponseOptions, HttpResourcesServiceToolkit, HttpResourcesRequestHandler, HttpResources, - InternalHttpResourcesPreboot, - InternalHttpResourcesSetup, } from './types'; diff --git a/src/core/server/http_resources/types.ts b/packages/core/http/core-http-resources-server/src/types.ts similarity index 91% rename from src/core/server/http_resources/types.ts rename to packages/core/http/core-http-resources-server/src/types.ts index 246a7394c3ade..6d2e6728e28f6 100644 --- a/src/core/server/http_resources/types.ts +++ b/packages/core/http/core-http-resources-server/src/types.ts @@ -7,7 +7,6 @@ */ import type { - IRouter, RouteConfig, IKibanaResponse, ResponseHeaders, @@ -85,20 +84,6 @@ export type HttpResourcesRequestHandler< Context extends RequestHandlerContext = RequestHandlerContext > = RequestHandler<P, Q, B, Context, 'get', KibanaResponseFactory & HttpResourcesServiceToolkit>; -/** - * Allows to configure HTTP response parameters - * @internal - */ -export interface InternalHttpResourcesPreboot { - createRegistrar(router: IRouter): HttpResources; -} - -/** - * Allows to configure HTTP response parameters - * @internal - */ -export type InternalHttpResourcesSetup = InternalHttpResourcesPreboot; - /** * HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. * Provides API allowing plug-ins to respond with: diff --git a/packages/core/http/core-http-resources-server/tsconfig.json b/packages/core/http/core-http-resources-server/tsconfig.json new file mode 100644 index 0000000000000..71bb40fe57f3f --- /dev/null +++ b/packages/core/http/core-http-resources-server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../../tsconfig.bazel.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "target_types", + "stripInternal": false, + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ] +} diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index 76663eeed2fd1..1ea3eeef29a09 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -12,7 +12,7 @@ import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { mockRouter } from '@kbn/core-http-router-server-mocks'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; import { coreMock, httpServerMock } from '../mocks'; -import { httpResourcesMock } from '../http_resources/http_resources_service.mock'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; import { PluginType } from '../plugins'; import { CoreApp } from './core_app'; import { RequestHandlerContext } from '..'; diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index b8701d7646b7e..f0940f6abad50 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -21,7 +21,7 @@ import type { IBasePath, } from '@kbn/core-http-server'; import type { UiPlugins } from '@kbn/core-plugins-base-server-internal'; -import { HttpResources, HttpResourcesServiceToolkit } from '../http_resources'; +import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server'; import { InternalCorePreboot, InternalCoreSetup } from '../internal_types'; import { registerBundleRoutes } from './bundle_routes'; import type { InternalCoreAppRequestHandlerContext } from './internal_types'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index dafd53e374fe8..6232a17eb6111 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -69,7 +69,7 @@ import type { I18nServiceSetup } from '@kbn/core-i18n-server'; import type { StatusServiceSetup } from '@kbn/core-status-server'; import type { UiSettingsServiceSetup, UiSettingsServiceStart } from '@kbn/core-ui-settings-server'; import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server'; -import { HttpResources } from './http_resources'; +import type { HttpResources } from '@kbn/core-http-resources-server'; import { PluginsServiceSetup, PluginsServiceStart } from './plugins'; export type { PluginOpaqueId } from '@kbn/core-base-common'; @@ -228,7 +228,7 @@ export type { HttpResourcesResponseOptions, HttpResourcesServiceToolkit, HttpResourcesRequestHandler, -} from './http_resources'; +} from '@kbn/core-http-resources-server'; export type { LoggingServiceSetup, diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 683d08fe4f849..c66fdf9a968d2 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -59,7 +59,10 @@ import type { InternalUiSettingsServiceStart, } from '@kbn/core-ui-settings-server-internal'; import type { InternalRenderingServiceSetup } from '@kbn/core-rendering-server-internal'; -import { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './http_resources'; +import type { + InternalHttpResourcesPreboot, + InternalHttpResourcesSetup, +} from '@kbn/core-http-resources-server-internal'; /** @internal */ export interface InternalCorePreboot { diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index cd0429415e7cb..356fd4deb44d6 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -32,6 +32,7 @@ import { i18nServiceMock } from '@kbn/core-i18n-server-mocks'; import { statusServiceMock } from '@kbn/core-status-server-mocks'; import { uiSettingsServiceMock } from '@kbn/core-ui-settings-server-mocks'; import { renderingServiceMock } from '@kbn/core-rendering-server-mocks'; +import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; import type { PluginInitializerContext, CoreSetup, @@ -40,7 +41,6 @@ import type { CorePreboot, RequestHandlerContext, } from '.'; -import { httpResourcesMock } from './http_resources/http_resources_service.mock'; import { SharedGlobalConfig } from './plugins'; export { configServiceMock, configDeprecationsMock } from '@kbn/config-mocks'; @@ -48,7 +48,7 @@ export { loggingSystemMock } from '@kbn/core-logging-server-mocks'; export { httpServerMock, sessionStorageMock, httpServiceMock } from '@kbn/core-http-server-mocks'; export { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; export { typeRegistryMock as savedObjectsTypeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -export { httpResourcesMock } from './http_resources/http_resources_service.mock'; +export { httpResourcesMock } from '@kbn/core-http-resources-server-mocks'; export { savedObjectsServiceMock } from '@kbn/core-saved-objects-server-mocks'; export { savedObjectsClientMock, diff --git a/src/core/server/server.ts b/src/core/server/server.ts index e0a2c2c44f254..07e10cc72845a 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -68,8 +68,8 @@ import type { } from '@kbn/core-http-request-handler-context-server'; import { RenderingService } from '@kbn/core-rendering-server-internal'; +import { HttpResourcesService } from '@kbn/core-http-resources-server-internal'; import { CoreApp } from './core_app'; -import { HttpResourcesService } from './http_resources'; import { PluginsService, config as pluginsConfig } from './plugins'; import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from './internal_types'; import { DiscoveredPlugins } from './plugins'; diff --git a/yarn.lock b/yarn.lock index 62d61b9fd93f3..89dc648f58db5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2962,6 +2962,18 @@ version "0.0.0" uid "" +"@kbn/core-http-resources-server-internal@link:bazel-bin/packages/core/http/core-http-resources-server-internal": + version "0.0.0" + uid "" + +"@kbn/core-http-resources-server-mocks@link:bazel-bin/packages/core/http/core-http-resources-server-mocks": + version "0.0.0" + uid "" + +"@kbn/core-http-resources-server@link:bazel-bin/packages/core/http/core-http-resources-server": + version "0.0.0" + uid "" + "@kbn/core-http-router-server-internal@link:bazel-bin/packages/core/http/core-http-router-server-internal": version "0.0.0" uid "" @@ -7107,6 +7119,18 @@ version "0.0.0" uid "" +"@types/kbn__core-http-resources-server-internal@link:bazel-bin/packages/core/http/core-http-resources-server-internal/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-http-resources-server-mocks@link:bazel-bin/packages/core/http/core-http-resources-server-mocks/npm_module_types": + version "0.0.0" + uid "" + +"@types/kbn__core-http-resources-server@link:bazel-bin/packages/core/http/core-http-resources-server/npm_module_types": + version "0.0.0" + uid "" + "@types/kbn__core-http-router-server-internal@link:bazel-bin/packages/core/http/core-http-router-server-internal/npm_module_types": version "0.0.0" uid "" From df9d1e866d4b35b4ebe39c02b32fedef448b8497 Mon Sep 17 00:00:00 2001 From: Steph Milovic <stephanie.milovic@elastic.co> Date: Tue, 4 Oct 2022 10:54:03 -0600 Subject: [PATCH 061/174] [Security Solution] [Cases] Bugfix, properly encode `externalId` json (#142624) --- .../cases/cases_webhook/service.test.ts | 71 +++++++++++++++++++ .../cases/cases_webhook/service.ts | 16 ++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts index 8e5c34e1e9ca5..73d25df29e8c6 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.test.ts @@ -474,6 +474,77 @@ describe('Cases webhook service', () => { expect(requestMock).not.toHaveBeenCalled(); expect(res).toBeUndefined(); }); + + test('properly encodes external system id as string in request body', async () => { + requestMock.mockImplementation(() => + createAxiosResponse({ + data: { + id: '1', + key: 'CK-1', + }, + }) + ); + service = createExternalService( + actionId, + { + config: { + ...config, + createCommentJson: '{"body":{{{case.comment}}},"id":{{{external.system.id}}}}', + }, + secrets, + }, + logger, + configurationUtilities + ); + await service.createComment(commentReq); + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + method: CasesWebhookMethods.POST, + configurationUtilities, + url: 'https://coolsite.net/issue/1/comment', + data: `{"body":"comment","id":"1"}`, + }); + }); + + test('properly encodes external system id as number in request body', async () => { + const commentReq2 = { + incidentId: 1 as unknown as string, + comment: { + comment: 'comment', + commentId: 'comment-1', + }, + }; + requestMock.mockImplementation(() => + createAxiosResponse({ + data: { + id: '1', + key: 'CK-1', + }, + }) + ); + service = createExternalService( + actionId, + { + config: { + ...config, + createCommentJson: '{"body":{{{case.comment}}},"id":{{{external.system.id}}}}', + }, + secrets, + }, + logger, + configurationUtilities + ); + await service.createComment(commentReq2); + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + method: CasesWebhookMethods.POST, + configurationUtilities, + url: 'https://coolsite.net/issue/1/comment', + data: `{"body":"comment","id":1}`, + }); + }); }); describe('bad urls', () => { diff --git a/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts index 518debe43a002..790bdffc84982 100644 --- a/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts +++ b/x-pack/plugins/stack_connectors/server/connector_types/cases/cases_webhook/service.ts @@ -190,6 +190,7 @@ export const createExternalService = ( }, }, }); + const normalizedUrl = validateAndNormalizeUrl( `${updateUrl}`, configurationUtilities, @@ -197,6 +198,7 @@ export const createExternalService = ( ); const { tags, title, description } = incident; + const json = renderMustacheStringNoEscape(updateIncidentJson, { ...stringifyObjValues({ title, @@ -205,12 +207,13 @@ export const createExternalService = ( }), external: { system: { - id: incidentId, + id: JSON.stringify(incidentId), }, }, }); validateJson(json, 'Update case JSON body'); + const res = await request({ axios: axiosInstance, method: updateIncidentMethod, @@ -223,7 +226,9 @@ export const createExternalService = ( throwDescriptiveErrorIfResponseIsNotValid({ res, }); + const updatedIncident = await getIncident(incidentId as string); + const viewUrl = renderMustacheStringNoEscape(viewIncidentUrl, { external: { system: { @@ -232,11 +237,13 @@ export const createExternalService = ( }, }, }); + const normalizedViewUrl = validateAndNormalizeUrl( `${viewUrl}`, configurationUtilities, 'View case URL' ); + return { id: incidentId, title: updatedIncident.title, @@ -253,6 +260,7 @@ export const createExternalService = ( if (!createCommentUrl || !createCommentJson || !createCommentMethod) { return; } + const commentUrl = renderMustacheStringNoEscape(createCommentUrl, { external: { system: { @@ -260,20 +268,24 @@ export const createExternalService = ( }, }, }); + const normalizedUrl = validateAndNormalizeUrl( `${commentUrl}`, configurationUtilities, 'Create comment URL' ); + const json = renderMustacheStringNoEscape(createCommentJson, { ...stringifyObjValues({ comment: comment.comment }), external: { system: { - id: incidentId, + id: JSON.stringify(incidentId), }, }, }); + validateJson(json, 'Create comment JSON body'); + const res = await request({ axios: axiosInstance, method: createCommentMethod, From 04189db4c5d0a1fbbdb65dacdd2b8d36d673137c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= <contact@patrykkopycinski.com> Date: Tue, 4 Oct 2022 18:54:48 +0200 Subject: [PATCH 062/174] [Osquery] Fix ResponseActions form logic (#142612) --- .../osquery_response_action_type/index.tsx | 138 +++++++----------- 1 file changed, 54 insertions(+), 84 deletions(-) diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx b/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx index 0fec230a35008..cd7a7b182772b 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx +++ b/x-pack/plugins/osquery/public/shared_components/osquery_response_action_type/index.tsx @@ -9,12 +9,11 @@ import React, { forwardRef, useCallback, useEffect, useMemo, useState } from 're import { EuiSpacer } from '@elastic/eui'; import uuid from 'uuid'; import { useForm as useHookForm, FormProvider } from 'react-hook-form'; -import { get, isEmpty, map } from 'lodash'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { get, isEmpty, map, omit } from 'lodash'; import type { ECSMapping } from '@kbn/osquery-io-ts-types'; import { QueryPackSelectable } from '../../live_queries/form/query_pack_selectable'; -import { useFormContext } from '../../shared_imports'; +import { useFormContext, useFormData } from '../../shared_imports'; import type { ArrayItem } from '../../shared_imports'; import { useKibana } from '../../common/lib/kibana'; import { LiveQueryQueryField } from '../../live_queries/form/live_query_query_field'; @@ -50,19 +49,25 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< ResponseActionValidatorRef, OsqueryResponseActionsParamsFormProps >(({ item }, ref) => { + const { updateFieldValues } = useFormContext(); + const [data] = useFormData({ watch: [item.path] }); + const { params: defaultParams } = get(data, item.path); const uniqueId = useMemo(() => uuid.v4(), []); const hooksForm = useHookForm<OsqueryResponseActionsParamsFormFields>({ - defaultValues: { - ecs_mapping: {}, - id: uniqueId, - }, + defaultValues: defaultParams + ? { + ...omit(defaultParams, ['ecsMapping', 'packId']), + ecs_mapping: defaultParams.ecsMapping ?? {}, + packId: [defaultParams.packId] ?? [], + } + : { + ecs_mapping: {}, + id: uniqueId, + }, }); - const { watch, setValue, register, clearErrors, formState, handleSubmit } = hooksForm; + const { watch, register, formState, handleSubmit, reset } = hooksForm; const { errors, isValid } = formState; - const context = useFormContext(); - const data = context.getFormData(); - const { params: defaultParams } = get(data, item.path); const watchedValues = watch(); const { data: packData } = usePack({ @@ -72,51 +77,37 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< const [queryType, setQueryType] = useState<string>( !isEmpty(defaultParams?.queries) ? 'pack' : 'query' ); - const onSubmit = useCallback(async () => { - try { - if (queryType === 'pack') { - context.updateFieldValues({ - [item.path]: { - actionTypeId: OSQUERY_TYPE, - params: { - id: watchedValues.id, - packId: watchedValues?.packId?.length ? watchedValues?.packId[0] : undefined, - queries: packData - ? map(packData.queries, (query, queryId: string) => ({ - ...query, - id: queryId, - })) - : watchedValues.queries, - }, - }, - }); - } else { - context.updateFieldValues({ - [item.path]: { - actionTypeId: OSQUERY_TYPE, - params: { - id: watchedValues.id, - savedQueryId: watchedValues.savedQueryId, - query: watchedValues.query, - ecsMapping: watchedValues.ecs_mapping, - }, - }, - }); - } - // eslint-disable-next-line no-empty - } catch (e) {} - }, [ - context, - item.path, - packData, - queryType, - watchedValues.ecs_mapping, - watchedValues.id, - watchedValues?.packId, - watchedValues.queries, - watchedValues.query, - watchedValues.savedQueryId, - ]); + const onSubmit = useCallback( + async (formData) => { + updateFieldValues({ + [item.path]: + queryType === 'pack' + ? { + actionTypeId: OSQUERY_TYPE, + params: { + id: formData.id, + packId: formData?.packId?.length ? formData?.packId[0] : undefined, + queries: packData + ? map(packData.queries, (query, queryId: string) => ({ + ...query, + id: queryId, + })) + : formData.queries, + }, + } + : { + actionTypeId: OSQUERY_TYPE, + params: { + id: formData.id, + savedQueryId: formData.savedQueryId, + query: formData.query, + ecsMapping: formData.ecs_mapping, + }, + }, + }); + }, + [updateFieldValues, item.path, packData, queryType] + ); useEffect(() => { // @ts-expect-error update types @@ -135,41 +126,20 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< register('id'); }, [register]); - const permissions = useKibana().services.application.capabilities.osquery; - - useEffectOnce(() => { - if (defaultParams && defaultParams.id) { - const { packId, ecsMapping, ...restParams } = defaultParams; - // TODO change map into forEach, and type defaultParams - map(restParams, (value, key: keyof OsqueryResponseActionsParamsFormFields) => { - if (!isEmpty(value)) { - setValue(key, value); - } - }); - - if (!isEmpty(ecsMapping)) { - setValue('ecs_mapping', ecsMapping); - } + useEffect(() => { + const subscription = watch(() => handleSubmit(onSubmit)()); - if (!isEmpty(packId)) { - setValue('packId', [packId]); - } - } - }); + return () => subscription.unsubscribe(); + }, [handleSubmit, onSubmit, watch]); - const resetFormFields = useCallback(() => { - setValue('packId', []); - setValue('savedQueryId', ''); - setValue('query', ''); - setValue('ecs_mapping', {}); - clearErrors(); - }, [clearErrors, setValue]); + const permissions = useKibana().services.application.capabilities.osquery; const canRunPacks = useMemo( () => !!((permissions.runSavedQueries || permissions.writeLiveQueries) && permissions.readPacks), [permissions] ); + const canRunSingleQuery = useMemo( () => !!( @@ -196,7 +166,7 @@ const OsqueryResponseActionParamsFormComponent = forwardRef< setQueryType={setQueryType} canRunPacks={canRunPacks} canRunSingleQuery={canRunSingleQuery} - resetFormFields={resetFormFields} + resetFormFields={reset} /> <EuiSpacer size="m" /> {queryType === 'query' && <LiveQueryQueryField />} From ae07eb260433acf487e7b7cd90d9f0702fdad7be Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@elastic.co> Date: Tue, 4 Oct 2022 11:59:30 -0500 Subject: [PATCH 063/174] skip suite failing es promotion. #142642 --- .../api_integration/apis/monitoring/logstash/node_detail_mb.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/monitoring/logstash/node_detail_mb.js b/x-pack/test/api_integration/apis/monitoring/logstash/node_detail_mb.js index 5d58316a83d95..0aed0c7b1c552 100644 --- a/x-pack/test/api_integration/apis/monitoring/logstash/node_detail_mb.js +++ b/x-pack/test/api_integration/apis/monitoring/logstash/node_detail_mb.js @@ -13,7 +13,8 @@ export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - describe('node detail mb', () => { + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/142642 + describe.skip('node detail mb', () => { const archive = 'x-pack/test/api_integration/apis/monitoring/es_archives/logstash_8'; const timeRange = { min: '2022-06-17T13:19:00.000Z', From 87fa95d49e5cdd80e8ebffbb078009b0f4636480 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet <nicolas.chaulet@elastic.co> Date: Tue, 4 Oct 2022 13:18:39 -0400 Subject: [PATCH 064/174] [Fleet] Remove Fleet server upgrade modal (#142622) --- .../openapi/components/schemas/settings.yaml | 2 - .../fleet/common/types/models/settings.ts | 1 - .../components/fleet_server_upgrade_modal.tsx | 223 ------------------ .../fleet/sections/agents/index.test.tsx | 4 +- .../fleet/sections/agents/index.tsx | 29 +-- .../fleet/server/saved_objects/index.ts | 4 +- .../saved_objects/migrations/to_v8_6_0.ts | 20 ++ .../fleet/server/types/rest_spec/settings.ts | 1 - .../translations/translations/fr-FR.json | 12 - .../translations/translations/ja-JP.json | 12 - .../translations/translations/zh-CN.json | 12 - 11 files changed, 26 insertions(+), 294 deletions(-) delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx create mode 100644 x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts diff --git a/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml b/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml index 952683400b230..280460771989e 100644 --- a/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml +++ b/x-pack/plugins/fleet/common/openapi/components/schemas/settings.yaml @@ -5,8 +5,6 @@ properties: type: string has_seen_add_data_notice: type: boolean - has_seen_fleet_migration_notice: - type: boolean fleet_server_hosts: type: array items: diff --git a/x-pack/plugins/fleet/common/types/models/settings.ts b/x-pack/plugins/fleet/common/types/models/settings.ts index 269fad8391b99..17bbe7a73c1da 100644 --- a/x-pack/plugins/fleet/common/types/models/settings.ts +++ b/x-pack/plugins/fleet/common/types/models/settings.ts @@ -9,7 +9,6 @@ import type { SavedObjectAttributes } from '@kbn/core/public'; export interface BaseSettings { has_seen_add_data_notice?: boolean; - has_seen_fleet_migration_notice?: boolean; fleet_server_hosts: string[]; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx deleted file mode 100644 index 0f9a8a3bbdd50..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/components/fleet_server_upgrade_modal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useCallback, useEffect, useState } from 'react'; -import { - EuiButton, - EuiCheckbox, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiLink, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; -import { i18n } from '@kbn/i18n'; - -import { - sendGetAgents, - sendGetOneAgentPolicy, - sendPutSettings, - useLink, - useStartServices, -} from '../../../hooks'; -import type { PackagePolicy } from '../../../types'; -import { FLEET_SERVER_PACKAGE } from '../../../constants'; - -interface Props { - onClose: () => void; -} - -export const FleetServerUpgradeModal: React.FunctionComponent<Props> = ({ onClose }) => { - const { getAssetsPath } = useLink(); - const { notifications, cloud, docLinks } = useStartServices(); - - const isCloud = !!cloud?.cloudId; - - const [checked, setChecked] = useState(false); - const [isAgentsAndPoliciesLoaded, setAgentsAndPoliciesLoaded] = useState(false); - - // Check if an other agent than the fleet server is already enrolled - useEffect(() => { - async function check() { - try { - const agentPoliciesAlreadyChecked: { [k: string]: boolean } = {}; - - const res = await sendGetAgents({ - page: 1, - perPage: 10, - showInactive: false, - }); - - if (res.error) { - throw res.error; - } - - for (const agent of res.data?.items ?? []) { - if (!agent.policy_id || agentPoliciesAlreadyChecked[agent.policy_id]) { - continue; - } - - agentPoliciesAlreadyChecked[agent.policy_id] = true; - const policyRes = await sendGetOneAgentPolicy(agent.policy_id); - const hasFleetServer = - (policyRes.data?.item.package_policies as PackagePolicy[]).some((p: PackagePolicy) => { - return p.package?.name === FLEET_SERVER_PACKAGE; - }) ?? false; - if (!hasFleetServer) { - await sendPutSettings({ - has_seen_fleet_migration_notice: true, - }); - onClose(); - return; - } - } - setAgentsAndPoliciesLoaded(true); - } catch (err) { - notifications.toasts.addError(err, { - title: i18n.translate('xpack.fleet.fleetServerUpgradeModal.errorLoadingAgents', { - defaultMessage: `Error loading agents`, - }), - }); - } - } - - check(); - }, [notifications.toasts, onClose]); - - const onChange = useCallback(async () => { - try { - setChecked(!checked); - await sendPutSettings({ - has_seen_fleet_migration_notice: !checked, - }); - } catch (error) { - notifications.toasts.addError(error, { - title: i18n.translate('xpack.fleet.fleetServerUpgradeModal.failedUpdateTitle', { - defaultMessage: `Error saving settings`, - }), - }); - } - }, [checked, setChecked, notifications]); - - if (!isAgentsAndPoliciesLoaded) { - return null; - } - - return ( - <EuiModal onClose={onClose}> - <EuiModalHeader> - <EuiModalHeaderTitle> - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.modalTitle" - defaultMessage="Enroll your agents into Fleet Server" - /> - </EuiModalHeaderTitle> - </EuiModalHeader> - <EuiModalBody> - <EuiImage - size="fullWidth" - src={getAssetsPath('./announcement.jpg')} - alt={i18n.translate('xpack.fleet.fleetServerUpgradeModal.announcementImageAlt', { - defaultMessage: 'Fleet Server upgrade announcement', - })} - /> - <EuiSpacer size="m" /> - <EuiText> - {isCloud ? ( - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.cloudDescriptionMessage" - defaultMessage="Fleet Server is now available and it provides improved scalability and security. If you already had an APM instance on Elastic Cloud, we've upgraded it to APM & Fleet. If not, you can add one to your deployment for free. {existingAgentsMessage} To continue using Fleet, you must use Fleet Server and install the new version of Elastic Agent on each host." - values={{ - existingAgentsMessage: ( - <strong> - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.existingAgentText" - defaultMessage="Your existing Elastic Agents have been automatically unenrolled and have stopped sending data." - /> - </strong> - ), - }} - /> - ) : ( - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.onPremDescriptionMessage" - defaultMessage="Fleet Server is now available and it provides improved scalability and security. {existingAgentsMessage} To continue using Fleet, you must install a Fleet Server and the new version of Elastic Agent on each host. Learn more in our {link}." - values={{ - existingAgentsMessage: ( - <strong> - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.existingAgentText" - defaultMessage="Your existing Elastic Agents have been automatically unenrolled and have stopped sending data." - /> - </strong> - ), - link: ( - <EuiLink - href={docLinks.links.fleet.upgradeElasticAgent} - external={true} - target="_blank" - > - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.fleetServerMigrationGuide" - defaultMessage="Fleet Server migration guide" - /> - </EuiLink> - ), - }} - /> - )} - </EuiText> - <EuiSpacer size="l" /> - <EuiText> - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.breakingChangeMessage" - defaultMessage="This is a breaking change, which is why we are making it in a beta release. We are sorry for the inconvenience. Please share {link} if you have questions or need help." - values={{ - link: ( - <EuiLink href="https://ela.st/fleet-feedback" target="_blank"> - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.fleetFeedbackLink" - defaultMessage="feedback" - /> - </EuiLink> - ), - }} - /> - </EuiText> - </EuiModalBody> - <EuiModalFooter> - <EuiFlexGroup justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiCheckbox - id="fleetServerModalCheckbox" - label={i18n.translate('xpack.fleet.fleetServerUpgradeModal.checkboxLabel', { - defaultMessage: 'Do not show this message again', - })} - checked={checked} - onChange={onChange} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton fill onClick={onClose}> - <FormattedMessage - id="xpack.fleet.fleetServerUpgradeModal.closeButton" - defaultMessage="Close and get started" - /> - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </EuiModalFooter> - </EuiModal> - ); -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx index 7139cabed4f6f..e30e4512e1de7 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.test.tsx @@ -51,9 +51,7 @@ describe('AgentApp', () => { mockedUseGetSettings.mockReturnValue({ isLoading: false, data: { - item: { - has_seen_fleet_migration_notice: true, - }, + item: {}, }, } as any); mockedUseAuthz.mockReturnValue({ diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx index ed770311ad9e9..0cf9e7d1ba6bf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/index.tsx @@ -5,28 +5,20 @@ * 2.0. */ -import React, { useCallback, useEffect, useState } from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { Router, Route, Switch, useHistory } from 'react-router-dom'; import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FLEET_ROUTING_PATHS } from '../../constants'; import { Loading, Error } from '../../components'; -import { - useConfig, - useFleetStatus, - useBreadcrumbs, - useAuthz, - useGetSettings, - useFlyoutContext, -} from '../../hooks'; +import { useConfig, useFleetStatus, useBreadcrumbs, useAuthz, useFlyoutContext } from '../../hooks'; import { DefaultLayout, WithoutHeaderLayout } from '../../layouts'; import { AgentListPage } from './agent_list_page'; import { FleetServerRequirementPage, MissingESRequirementsPage } from './agent_requirements_page'; import { AgentDetailsPage } from './agent_details_page'; import { NoAccessPage } from './error_pages/no_access'; -import { FleetServerUpgradeModal } from './components/fleet_server_upgrade_modal'; export const AgentsApp: React.FunctionComponent = () => { useBreadcrumbs('agent_list'); @@ -36,20 +28,6 @@ export const AgentsApp: React.FunctionComponent = () => { const fleetStatus = useFleetStatus(); const flyoutContext = useFlyoutContext(); - const settings = useGetSettings(); - - const [fleetServerModalVisible, setFleetServerModalVisible] = useState(false); - const onCloseFleetServerModal = useCallback(() => { - setFleetServerModalVisible(false); - }, [setFleetServerModalVisible]); - - useEffect(() => { - // if it's undefined do not show the modal - if (settings.data && settings.data?.item.has_seen_fleet_migration_notice === false) { - setFleetServerModalVisible(true); - } - }, [settings.data]); - if (!agents.enabled) return null; if (!fleetStatus.missingRequirements && fleetStatus.isLoading) { return <Loading />; @@ -114,9 +92,6 @@ export const AgentsApp: React.FunctionComponent = () => { </Route> <Route path={FLEET_ROUTING_PATHS.agents}> <DefaultLayout section="agents" rightColumn={rightColumn}> - {fleetServerModalVisible && ( - <FleetServerUpgradeModal onClose={onCloseFleetServerModal} /> - )} {displayInstructions ? ( <FleetServerRequirementPage showEnrollmentRecommendation={false} /> ) : ( diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 55d38b00dec3d..ed03e26b64537 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -46,6 +46,7 @@ import { migratePackagePolicyToV840, } from './migrations/to_v8_4_0'; import { migratePackagePolicyToV850, migrateAgentPolicyToV850 } from './migrations/to_v8_5_0'; +import { migrateSettingsToV860 } from './migrations/to_v8_6_0'; /* * Saved object types and mappings @@ -56,6 +57,7 @@ import { migratePackagePolicyToV850, migrateAgentPolicyToV850 } from './migratio const getSavedObjectTypes = ( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ): { [key: string]: SavedObjectsType } => ({ + // Deprecated [GLOBAL_SETTINGS_SAVED_OBJECT_TYPE]: { name: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE, hidden: false, @@ -67,12 +69,12 @@ const getSavedObjectTypes = ( properties: { fleet_server_hosts: { type: 'keyword' }, has_seen_add_data_notice: { type: 'boolean', index: false }, - has_seen_fleet_migration_notice: { type: 'boolean', index: false }, }, }, migrations: { '7.10.0': migrateSettingsToV7100, '7.13.0': migrateSettingsToV7130, + '8.6.0': migrateSettingsToV860, }, }, [AGENT_POLICY_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts new file mode 100644 index 0000000000000..5134249ddd1ef --- /dev/null +++ b/x-pack/plugins/fleet/server/saved_objects/migrations/to_v8_6_0.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectMigrationFn } from '@kbn/core/server'; + +import type { Settings } from '../../../common/types'; + +export const migrateSettingsToV860: SavedObjectMigrationFn<Settings, Settings> = ( + settingsDoc, + migrationContext +) => { + // @ts-expect-error has_seen_fleet_migration_notice property does not exists anymore + delete settingsDoc.attributes.has_seen_fleet_migration_notice; + + return settingsDoc; +}; diff --git a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts index 678a71c3214c9..4544b677cba8c 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/settings.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/settings.ts @@ -23,7 +23,6 @@ export const PutSettingsRequestSchema = { }) ), has_seen_add_data_notice: schema.maybe(schema.boolean()), - has_seen_fleet_migration_notice: schema.maybe(schema.boolean()), additional_yaml_config: schema.maybe(schema.string()), // Deprecated not used kibana_urls: schema.maybe( diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 006c2744d4eb0..a3e0dff96d953 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -12779,9 +12779,6 @@ "xpack.fleet.fleetServerSetup.deploymentModeProductionOption": "{production} : fournissez vos propres certificats. Cette option demande aux agents de prĂ©ciser une clĂ© de certificat lors de leur enregistrement avec Fleet", "xpack.fleet.fleetServerSetup.deploymentModeQuickStartOption": "{quickStart} : le serveur Fleet gĂ©nĂšre un certificat autosignĂ©. Les agents suivants doivent ĂȘtre enregistrĂ©s avec l'indicateur --insecure. Non recommandĂ© pour les cas d'utilisation en production.", "xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage": "Le serveur Fleet doit ĂȘtre configurĂ©. Pour cela, le privilĂšge de cluster {roleName} est requis. Contactez votre administrateur.", - "xpack.fleet.fleetServerUpgradeModal.breakingChangeMessage": "Il s'agit d'un changement majeur, c'est pourquoi nous l'appliquons dans une version bĂȘta. Nous vous prions de nous excuser pour la gĂȘne occasionnĂ©e. Partagez {link} si vous avez des questions ou si vous avez besoin d'aide.", - "xpack.fleet.fleetServerUpgradeModal.cloudDescriptionMessage": "Le serveur Fleet est dĂ©sormais disponible et renforce la scalabilitĂ© et la sĂ©curitĂ©. Si vous aviez dĂ©jĂ  une instance APM sur Elastic Cloud, nous l'avons mise Ă  niveau vers APM et Fleet. Dans le cas contraire, vous pouvez en ajouter une gratuitement Ă  votre dĂ©ploiement. {existingAgentsMessage} Pour continuer Ă  utiliser Fleet, vous devez utiliser le serveur Fleet et installer la nouvelle version d'Elastic Agent sur chaque hĂŽte.", - "xpack.fleet.fleetServerUpgradeModal.onPremDescriptionMessage": "Le serveur Fleet est dĂ©sormais disponible et renforce la scalabilitĂ© et la sĂ©curitĂ©. {existingAgentsMessage} Pour continuer Ă  utiliser Fleet, vous devez installer un serveur Fleet et la nouvelle version d'Elastic Agent sur chaque hĂŽte. Apprenez-en plus avec notre {link}.", "xpack.fleet.homeIntegration.tutorialModule.noticeText": "{notePrefix} Une version plus rĂ©cente de ce module est {availableAsIntegrationLink}. Pour en savoir plus sur les intĂ©grations et le nouvel agent Elastic Agent, lisez notre {blogPostLink}.", "xpack.fleet.integration.settings.versionInfo.updatesAvailableBody": "Passez Ă  la version {latestVersion} pour bĂ©nĂ©ficier des fonctionnalitĂ©s les plus rĂ©centes.", "xpack.fleet.integrations.confirmUpdateModal.body.agentCount": "{agentCount, plural, one {# agent} other {# agents}}", @@ -13508,15 +13505,6 @@ "xpack.fleet.fleetServerSetup.waitingText": "En attente de connexion d'un serveur Fleet
", "xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle": "Autorisation refusĂ©e", "xpack.fleet.fleetServerUnhealthy.requestError": "Une erreur s’est produite lors de la rĂ©cupĂ©ration du statut du serveur Fleet.", - "xpack.fleet.fleetServerUpgradeModal.announcementImageAlt": "Annonce de mise Ă  niveau du serveur Fleet", - "xpack.fleet.fleetServerUpgradeModal.checkboxLabel": "Ne plus afficher ce message", - "xpack.fleet.fleetServerUpgradeModal.closeButton": "Fermer et dĂ©marrer", - "xpack.fleet.fleetServerUpgradeModal.errorLoadingAgents": "Erreur lors du chargement des agents", - "xpack.fleet.fleetServerUpgradeModal.existingAgentText": "Vos agents Elastic existants ont Ă©tĂ© dĂ©senregistrĂ©s automatiquement et ont cessĂ© d'envoyer des donnĂ©es.", - "xpack.fleet.fleetServerUpgradeModal.failedUpdateTitle": "Erreur lors de l'enregistrement des paramĂštres", - "xpack.fleet.fleetServerUpgradeModal.fleetFeedbackLink": "commentaires", - "xpack.fleet.fleetServerUpgradeModal.fleetServerMigrationGuide": "Guide sur la migration du serveur Fleet", - "xpack.fleet.fleetServerUpgradeModal.modalTitle": "Enregistrer vos agents sur le serveur Fleet", "xpack.fleet.genericActionsMenuText": "Ouvrir", "xpack.fleet.homeIntegration.tutorialDirectory.fleetAppButtonText": "Tester les intĂ©grations", "xpack.fleet.homeIntegration.tutorialModule.noticeText.blogPostLink": "article de blog d'annonce", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4601cc9bb3919..97a0c9fc821ef 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12764,9 +12764,6 @@ "xpack.fleet.fleetServerSetup.deploymentModeProductionOption": "{production} – 独è‡ȘăźèšŒæ˜Žæ›žă‚’æŒ‡ćźšă—ăŸă™ă€‚ă“ăźă‚Șăƒ—ă‚·ăƒ§ăƒłă§ăŻă€Fleetに登éŒČă™ă‚‹ăšăă«ă€ă‚šăƒŒă‚žă‚§ăƒłăƒˆă§èšŒæ˜Žæ›žé”ă‚’æŒ‡ćźšă™ă‚‹ćż…èŠăŒă‚ă‚ŠăŸă™ă€‚", "xpack.fleet.fleetServerSetup.deploymentModeQuickStartOption": "{quickStart} – Fleetă‚”ăƒŒăƒăƒŒăŻè‡Șć·±çœČćèšŒæ˜Žæ›žă‚’ç”Ÿæˆă—ăŸă™ă€‚ćŸŒç¶šăźă‚šăƒŒă‚žă‚§ăƒłăƒˆăŻ--insecureăƒ•ăƒ©ă‚°ă‚’äœżç”šă—ăŠç™»éŒČă™ă‚‹ćż…èŠăŒă‚ă‚ŠăŸă™ă€‚æœŹç•ȘăƒŠăƒŒă‚čă‚±ăƒŒă‚čă«ăŻæŽšć„šă•ă‚ŒăŸă›ă‚“ă€‚", "xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage": "Fleetă‚”ăƒŒăƒăƒŒă‚’èš­ćźšă™ă‚‹ćż…èŠăŒă‚ă‚ŠăŸă™ă€‚ă“ă‚Œă«ăŻ{roleName}ă‚Żăƒ©ă‚čă‚żăƒŒæš©é™ăŒćż…èŠă§ă™ă€‚çźĄç†è€…ă«ăŠć•ă„ćˆă‚ă›ăă ă•ă„ă€‚", - "xpack.fleet.fleetServerUpgradeModal.breakingChangeMessage": "ă“ă‚ŒăŻć€§ăă„ć€‰æ›Žă§ă‚ă‚‹ăŸă‚ă€ăƒ™ăƒŒă‚żăƒȘăƒȘăƒŒă‚čă«ă—ăŠă„ăŸă™ă€‚ă”äžäŸżă‚’ăŠă‹ă‘ă—ăŠă„ă‚‹ă“ăšă‚’ăŠè©«ăłç”łă—äžŠă’ăŸă™ă€‚ă”èłȘć•ăŒă‚ă‚‹ć Žćˆă‚„ă€ă‚”ăƒăƒŒăƒˆăŒćż…èŠăȘ栮搈は、{link}ă‚’ć…±æœ‰ă—ăŠăă ă•ă„ă€‚", - "xpack.fleet.fleetServerUpgradeModal.cloudDescriptionMessage": "Fleetă‚”ăƒŒăƒăƒŒă‚’äœżç”šă§ăăŸă™ă€‚ă‚čă‚±ăƒŒăƒ©ăƒ“ăƒȘăƒ†ă‚Łăšă‚»ă‚­ăƒ„ăƒȘăƒ†ă‚ŁăŒćŒ·ćŒ–ă•ă‚ŒăŸă—ăŸă€‚ă™ă§ă«Elastic Cloudă‚Żăƒ©ă‚Šăƒ‰ă«APMă‚€ăƒłă‚čタンă‚čがあった栮搈は、APM & Fleetă«ă‚ąăƒƒăƒ—ă‚°ăƒŹăƒŒăƒ‰ă•ă‚ŒăŸă—ăŸă€‚ăă†ă§ăȘă„ć ŽćˆăŻă€ç„Ąæ–™ă§ăƒ‡ăƒ—ăƒ­ă‚€ă«èżœćŠ ă§ăăŸă™ă€‚{existingAgentsMessage}ćŒ•ăç¶šăFleetă‚’äœżç”šă™ă‚‹ă«ăŻă€Fleetă‚”ăƒŒăƒăƒŒă‚’äœżç”šă—ăŠă€ć„ăƒ›ă‚čăƒˆă«æ–°ă—ă„ăƒăƒŒă‚žăƒ§ăƒłăźElastică‚šăƒŒă‚žă‚§ăƒłăƒˆă‚’ă‚€ăƒłă‚čăƒˆăƒŒăƒ«ă™ă‚‹ćż…èŠăŒă‚ă‚ŠăŸă™ă€‚", - "xpack.fleet.fleetServerUpgradeModal.onPremDescriptionMessage": "Fleetă‚”ăƒŒăƒăƒŒăŒäœżç”šă§ăăŸă™ă€‚ă‚čă‚±ăƒŒăƒ©ăƒ“ăƒȘăƒ†ă‚Łăšă‚»ă‚­ăƒ„ăƒȘティがæ”čć–„ă•ă‚ŒăŠă„ăŸă™ă€‚{existingAgentsMessage} Fleetă‚’äœżç”šă—ç¶šă‘ă‚‹ă«ăŻă€Fleetă‚”ăƒŒăƒăƒŒăšæ–°ă—ă„ăƒăƒŒă‚žăƒ§ăƒłăźElastică‚šăƒŒă‚žă‚§ăƒłăƒˆă‚’ć„ăƒ›ă‚čăƒˆă«ă‚€ăƒłă‚čăƒˆăƒŒăƒ«ă™ă‚‹ćż…èŠăŒă‚ă‚ŠăŸă™ă€‚è©łçŽ°ă«ă€ă„ăŠăŻă€{link}ă‚’ă”èŠ§ăă ă•ă„ă€‚", "xpack.fleet.homeIntegration.tutorialModule.noticeText": "{notePrefix}ă“ăźăƒąă‚žăƒ„ăƒŒăƒ«ăźæ–°ă—ă„ăƒăƒŒă‚žăƒ§ăƒłăŻ{availableAsIntegrationLink}ă§ă™ă€‚ç”±ćˆăšæ–°ă—ă„Elastică‚šăƒŒă‚žă‚§ăƒłăƒˆăźè©łçŽ°ă«ă€ă„ăŠăŻă€{blogPostLink}をおèȘ­ăżăă ă•ă„。", "xpack.fleet.integration.settings.versionInfo.updatesAvailableBody": "ăƒăƒŒă‚žăƒ§ăƒł{latestVersion}ă«ă‚ąăƒƒăƒ—ă‚°ăƒŹăƒŒăƒ‰ă—ăŠæœ€æ–°ăźæ©Ÿèƒœă‚’ć…„æ‰‹", "xpack.fleet.integrations.confirmUpdateModal.body.agentCount": "{agentCount, plural, other {# ć€‹ăźă‚šăƒŒă‚žă‚§ăƒłăƒˆ}}", @@ -13494,15 +13491,6 @@ "xpack.fleet.fleetServerSetup.waitingText": "Fleetă‚”ăƒŒăƒăƒŒăźæŽ„ç¶šă‚’ćŸ…æ©Ÿă—ăŠă„ăŸă™...", "xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle": "ăƒ‘ăƒŒăƒŸăƒƒă‚·ăƒ§ăƒłăŒæ‹’ćŠă•ă‚ŒăŸă—ăŸ", "xpack.fleet.fleetServerUnhealthy.requestError": "Fleetă‚”ăƒŒăƒăƒŒă‚čăƒ†ăƒŒă‚żă‚čăźć–ćŸ—äž­ă«ă‚šăƒ©ăƒŒăŒç™șç”Ÿă—ăŸă—ăŸ", - "xpack.fleet.fleetServerUpgradeModal.announcementImageAlt": "Fleetă‚”ăƒŒăƒăƒŒă‚ąăƒƒăƒ—ă‚°ăƒŹăƒŒăƒ‰é€šçŸ„", - "xpack.fleet.fleetServerUpgradeModal.checkboxLabel": "æŹĄć›žä»„é™ă“ăźăƒĄăƒƒă‚»ăƒŒă‚žă‚’èĄšç€șしăȘい", - "xpack.fleet.fleetServerUpgradeModal.closeButton": "閉じど開構する", - "xpack.fleet.fleetServerUpgradeModal.errorLoadingAgents": "ă‚šăƒŒă‚žă‚§ăƒłăƒˆăźèȘ­ăżèŸŒăżă‚šăƒ©ăƒŒ", - "xpack.fleet.fleetServerUpgradeModal.existingAgentText": "æ—ąć­˜ăźElastică‚šăƒŒă‚žă‚§ăƒłăƒˆăŻè‡Ș拕的に登éŒČè§Łé™€ă•ă‚Œă€ăƒ‡ăƒŒă‚żăźé€äżĄă‚’ćœæ­ąă—ăŸă—ăŸă€‚", - "xpack.fleet.fleetServerUpgradeModal.failedUpdateTitle": "èš­ćźšăźäżć­˜ă‚šăƒ©ăƒŒ", - "xpack.fleet.fleetServerUpgradeModal.fleetFeedbackLink": "ăƒ•ă‚ŁăƒŒăƒ‰ăƒăƒƒă‚Ż", - "xpack.fleet.fleetServerUpgradeModal.fleetServerMigrationGuide": "Fleetă‚”ăƒŒăƒăƒŒç§»èĄŒă‚Źă‚€ăƒ‰", - "xpack.fleet.fleetServerUpgradeModal.modalTitle": "ă‚šăƒŒă‚žă‚§ăƒłăƒˆă‚’Fleetă‚”ăƒŒăƒăƒŒă«ç™»éŒČ", "xpack.fleet.genericActionsMenuText": "開く", "xpack.fleet.homeIntegration.tutorialDirectory.fleetAppButtonText": "ç”±ćˆă‚’è©Šă™", "xpack.fleet.homeIntegration.tutorialModule.noticeText.blogPostLink": "ç™șèĄšăƒ–ăƒ­ă‚°æŠ•çšż", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 3181f9602038b..19bf1d26365fb 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12784,9 +12784,6 @@ "xpack.fleet.fleetServerSetup.deploymentModeProductionOption": "{production} – 提䟛悚è‡Șć·±çš„èŻäčŠă€‚æłšć†Œćˆ° Fleet 时歀选éĄčć°†éœ€èŠä»Łç†æŒ‡ćźšèŻäčŠćŻ†é’„", "xpack.fleet.fleetServerSetup.deploymentModeQuickStartOption": "{quickStart} – Fleet æœćŠĄć™šć°†ç”Ÿæˆè‡Șç­ŸćèŻäčŠă€‚ćż…éĄ»äœżç”š --insecure æ ‡ćż—æłšć†ŒćŽç»­ä»Łç†ă€‚äžæŽšèç”šäșŽç”Ÿäș§ç”šäŸ‹ă€‚", "xpack.fleet.fleetServerSetupPermissionDeniedErrorMessage": "éœ€èŠèźŸçœź Fleet æœćŠĄć™šă€‚èż™éœ€èŠ {roleName} é›†çŸ€æƒé™ă€‚èŻ·è”çł»æ‚šçš„çźĄç†ć‘˜ă€‚", - "xpack.fleet.fleetServerUpgradeModal.breakingChangeMessage": "èż™æ˜Żäž€éĄčé‡ć€§æ›Žæ”čïŒŒæ‰€ä»„æˆ‘ä»Źćœšć…Źæ”‹ç‰ˆäž­èż›èĄŒèŻ„æ›Žæ”čă€‚éžćžžæŠ±æ­‰ćžŠæ„äžäŸżă€‚ćŠ‚æžœæ‚šæœ‰ç–‘é—źæˆ–éœ€èŠćžźćŠ©ïŒŒèŻ·ć…±äș« {link}。", - "xpack.fleet.fleetServerUpgradeModal.cloudDescriptionMessage": "Fleet æœćŠĄć™šçŽ°ćœšćŻç”šćč¶æäŸ›æ”čć–„çš„ćŻæ‰©ć±•æ€§ć’Œćź‰ć…šæ€§ă€‚ćŠ‚æžœæ‚šćœš Elastic Cloud 侊ć·Č有 APM ćźžäŸ‹ïŒŒćˆ™æˆ‘ä»Źć·Čć°†ć…¶ć‡çș§ćˆ° APM 撌 Fleetă€‚ćŠ‚æžœæČĄæœ‰ïŒŒćŻä»„ć…èŽč氆侀äžȘæ·»ćŠ ćˆ°æ‚šçš„éƒšçœČ。{existingAgentsMessage}èŠç»§ç»­äœżç”š FleetïŒŒćż…éĄ»äœżç”š Fleet æœćŠĄć™šćč¶ćœšæŻäžȘäž»æœșäžŠćź‰èŁ…æ–°ç‰ˆ Elastic 代理。", - "xpack.fleet.fleetServerUpgradeModal.onPremDescriptionMessage": "Fleet æœćŠĄć™šçŽ°ćœšćŻç”šäž”æäŸ›æ”čć–„çš„ćŻæ‰©ć±•æ€§ć’Œćź‰ć…šæ€§ă€‚{existingAgentsMessage}èŠç»§ç»­äœżç”š FleetïŒŒćż…éĄ»ćœšć„äžȘäž»æœșäžŠćź‰èŁ… Fleet æœćŠĄć™šć’Œæ–°ç‰ˆ Elastic ä»Łç†ă€‚èŻŠç»†äș†è§Łæˆ‘仏的 {link}。", "xpack.fleet.homeIntegration.tutorialModule.noticeText": "{notePrefix} æ­€æšĄć—çš„èŸƒæ–°ç‰ˆæœŹäžș {availableAsIntegrationLink}ă€‚èŠèŻŠç»†äș†è§Łé›†æˆć’Œæ–° Elastic ä»Łç†ïŒŒèŻ·é˜…èŻ»æˆ‘ä»Źçš„{blogPostLink}。", "xpack.fleet.integration.settings.versionInfo.updatesAvailableBody": "捇çș§ćˆ°ç‰ˆæœŹ {latestVersion} ćŻèŽ·ć–æœ€æ–°ćŠŸèƒœ", "xpack.fleet.integrations.confirmUpdateModal.body.agentCount": "{agentCount, plural, other {# äžȘ代理}}", @@ -13514,15 +13511,6 @@ "xpack.fleet.fleetServerSetup.waitingText": "等怙 Fleet æœćŠĄć™šèżžæŽ„......", "xpack.fleet.fleetServerSetupPermissionDeniedErrorTitle": "æƒé™èą«æ‹’ç»", "xpack.fleet.fleetServerUnhealthy.requestError": "æć– Fleet æœćŠĄć™šçŠ¶æ€æ—¶ć‡ș错", - "xpack.fleet.fleetServerUpgradeModal.announcementImageAlt": "Fleet æœćŠĄć™šć‡çș§ć…Źć‘Š", - "xpack.fleet.fleetServerUpgradeModal.checkboxLabel": "äžć†æ˜Ÿç€șæ­€æ¶ˆæŻ", - "xpack.fleet.fleetServerUpgradeModal.closeButton": "慳闭ćč¶ćŒ€ć§‹äœżç”š", - "xpack.fleet.fleetServerUpgradeModal.errorLoadingAgents": "ćŠ èœœä»Łç†æ—¶ć‡ș错", - "xpack.fleet.fleetServerUpgradeModal.existingAgentText": "悚现有的 Elastic 代理ć·Čèą«è‡ȘćŠšé”€æłšäž”ć·Čćœæ­ąć‘é€æ•°æźă€‚", - "xpack.fleet.fleetServerUpgradeModal.failedUpdateTitle": "äżć­˜èźŸçœźæ—¶ć‡ș错", - "xpack.fleet.fleetServerUpgradeModal.fleetFeedbackLink": "揍驈", - "xpack.fleet.fleetServerUpgradeModal.fleetServerMigrationGuide": "Fleet æœćŠĄć™šèżç§»æŒ‡ć—", - "xpack.fleet.fleetServerUpgradeModal.modalTitle": "ć°†ä»Łç†æłšć†Œćˆ° Fleet æœćŠĄć™š", "xpack.fleet.genericActionsMenuText": "æ‰“ćŒ€", "xpack.fleet.homeIntegration.tutorialDirectory.fleetAppButtonText": "èŻ•ç”šé›†æˆ", "xpack.fleet.homeIntegration.tutorialModule.noticeText.blogPostLink": "慬摊捚漱", From 61342b30738c499ac5b078d912d18cd8b10c10cf Mon Sep 17 00:00:00 2001 From: Dominique Clarke <dominique.clarke@elastic.co> Date: Tue, 4 Oct 2022 13:39:42 -0400 Subject: [PATCH 065/174] [Synthetics] preserve id field on monitor attributes (#142478) * synthetics - preserve id field on monitor attributes * adjust tests * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * adjust jest tests * adjust tests * adjust types * adjust tests * adjust types * update tests * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../synthetics/common/constants/monitor_defaults.ts | 12 +++++++++--- .../common/constants/monitor_management.ts | 3 +++ .../common/formatters/common/formatters.ts | 3 +++ .../synthetics/common/formatters/tcp/formatters.ts | 1 + .../monitor_management/monitor_types.ts | 4 ++++ .../monitor_add_edit/form/formatter.test.tsx | 3 +++ .../components/fleet_package/common/normalizers.ts | 3 +++ .../components/fleet_package/tcp/normalizers.ts | 1 + .../server/synthetics_service/formatters/common.ts | 3 +++ .../server/synthetics_service/formatters/tcp.ts | 1 + .../normalizers/browser_monitor.test.ts | 3 +++ .../project_monitor/normalizers/http_monitor.test.ts | 2 ++ .../project_monitor/normalizers/icmp_monitor.test.ts | 3 +++ .../project_monitor/normalizers/tcp_monitor.test.ts | 6 ++++++ .../project_monitor_formatter.test.ts | 2 ++ .../apis/uptime/rest/add_monitor_project.ts | 5 +++++ .../apis/uptime/rest/fixtures/browser_monitor.json | 3 ++- .../apis/uptime/rest/fixtures/http_monitor.json | 3 ++- .../apis/uptime/rest/fixtures/icmp_monitor.json | 3 ++- .../apis/uptime/rest/fixtures/tcp_monitor.json | 4 +++- 20 files changed, 61 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts index a926cba109e62..ff2aefa12d94d 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_defaults.ts @@ -47,6 +47,9 @@ export const DEFAULT_COMMON_FIELDS: CommonFields = { [ConfigKey.NAMESPACE]: DEFAULT_NAMESPACE_STRING, [ConfigKey.MONITOR_SOURCE_TYPE]: SourceType.UI, [ConfigKey.JOURNEY_ID]: '', + + // Deprecated, slated to be removed in a future version + [ConfigKey.ID]: '', }; export const DEFAULT_BROWSER_ADVANCED_FIELDS: BrowserAdvancedFields = { @@ -88,15 +91,17 @@ export const DEFAULT_BROWSER_SIMPLE_FIELDS: BrowserSimpleFields = { [ConfigKey.SOURCE_ZIP_FOLDER]: '', [ConfigKey.SOURCE_ZIP_PROXY_URL]: '', [ConfigKey.TEXT_ASSERTION]: '', + [ConfigKey.URLS]: '', + [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, + [ConfigKey.TIMEOUT]: null, + + // Deprecated, slated to be removed in a future version [ConfigKey.ZIP_URL_TLS_CERTIFICATE_AUTHORITIES]: undefined, [ConfigKey.ZIP_URL_TLS_CERTIFICATE]: undefined, [ConfigKey.ZIP_URL_TLS_KEY]: undefined, [ConfigKey.ZIP_URL_TLS_KEY_PASSPHRASE]: undefined, [ConfigKey.ZIP_URL_TLS_VERIFICATION_MODE]: undefined, [ConfigKey.ZIP_URL_TLS_VERSION]: undefined, - [ConfigKey.URLS]: '', - [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.MULTISTEP, - [ConfigKey.TIMEOUT]: null, }; export const DEFAULT_HTTP_SIMPLE_FIELDS: HTTPSimpleFields = { @@ -143,6 +148,7 @@ export const DEFAULT_TCP_SIMPLE_FIELDS: TCPSimpleFields = { is_tls_enabled: false, }, [ConfigKey.HOSTS]: '', + [ConfigKey.URLS]: '', [ConfigKey.MONITOR_TYPE]: DataStream.TCP, [ConfigKey.FORM_MONITOR_TYPE]: FormMonitorType.TCP, [ConfigKey.PORT]: null, diff --git a/x-pack/plugins/synthetics/common/constants/monitor_management.ts b/x-pack/plugins/synthetics/common/constants/monitor_management.ts index d613d234fc3c1..da3f138091723 100644 --- a/x-pack/plugins/synthetics/common/constants/monitor_management.ts +++ b/x-pack/plugins/synthetics/common/constants/monitor_management.ts @@ -77,6 +77,9 @@ export enum ConfigKey { ZIP_URL_TLS_KEY_PASSPHRASE = 'source.zip_url.ssl.key_passphrase', ZIP_URL_TLS_VERIFICATION_MODE = 'source.zip_url.ssl.verification_mode', ZIP_URL_TLS_VERSION = 'source.zip_url.ssl.supported_protocols', + + // deprecated, slated to be removed in a future version + ID = 'id', } export const secretKeys = [ diff --git a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts index 739c7184e7221..882182b2fe07f 100644 --- a/x-pack/plugins/synthetics/common/formatters/common/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/common/formatters.ts @@ -32,6 +32,9 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.PROJECT_ID]: null, [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, [ConfigKey.ORIGINAL_SPACE]: null, + + // Deprecated, slated to be removed in a later release + [ConfigKey.ID]: null, }; export const arrayToJsonFormatter = (value: string[] = []) => diff --git a/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts b/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts index bec7ceb444845..98c9a50bcd2fb 100644 --- a/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts +++ b/x-pack/plugins/synthetics/common/formatters/tcp/formatters.ts @@ -20,6 +20,7 @@ export const tcpFormatters: TCPFormatMap = { [ConfigKey.RESPONSE_RECEIVE_CHECK]: null, [ConfigKey.REQUEST_SEND_CHECK]: null, [ConfigKey.PORT]: null, + [ConfigKey.URLS]: null, ...tlsFormatters, ...commonFormatters, }; diff --git a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts index bbb6eb1bb30d6..163041986f1a3 100644 --- a/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts +++ b/x-pack/plugins/synthetics/common/runtime_types/monitor_management/monitor_types.ts @@ -86,6 +86,7 @@ export const CommonFieldsCodec = t.intersection([ [ConfigKey.PROJECT_ID]: t.string, [ConfigKey.ORIGINAL_SPACE]: t.string, [ConfigKey.CUSTOM_HEARTBEAT_ID]: t.string, + [ConfigKey.ID]: t.string, }), ]); @@ -98,6 +99,9 @@ export const TCPSimpleFieldsCodec = t.intersection([ [ConfigKey.HOSTS]: t.string, [ConfigKey.PORT]: t.union([t.number, t.null]), }), + t.partial({ + [ConfigKey.URLS]: t.string, + }), CommonFieldsCodec, ]); diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx index 6ff7cd651b334..2903a0375d038 100644 --- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx +++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_add_edit/form/formatter.test.tsx @@ -136,6 +136,7 @@ describe('format', () => { urls: 'sample url', 'url.port': null, username: '', + id: '', }); }); @@ -284,6 +285,7 @@ describe('format', () => { type: 'browser', 'url.port': null, urls: '', + id: '', }); } ); @@ -350,6 +352,7 @@ describe('format', () => { urls: 'sample url', 'url.port': null, username: '', + id: '', }); }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts index d05730c5dbe17..31ba9784e22a4 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/common/normalizers.ts @@ -96,4 +96,7 @@ export const commonNormalizers: CommonNormalizerMap = { [ConfigKey.PROJECT_ID]: getCommonNormalizer(ConfigKey.PROJECT_ID), [ConfigKey.CUSTOM_HEARTBEAT_ID]: getCommonNormalizer(ConfigKey.CUSTOM_HEARTBEAT_ID), [ConfigKey.ORIGINAL_SPACE]: getCommonNormalizer(ConfigKey.ORIGINAL_SPACE), + + // Deprecated, slated to be removed in a future release + [ConfigKey.ID]: getCommonNormalizer(ConfigKey.ID), }; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/normalizers.ts b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/normalizers.ts index 86efeeae69206..4fd671732260b 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/normalizers.ts +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/tcp/normalizers.ts @@ -38,6 +38,7 @@ export const tcpNormalizers: TCPNormalizerMap = { [ConfigKey.PROXY_USE_LOCAL_RESOLVER]: getTCPNormalizer(ConfigKey.PROXY_USE_LOCAL_RESOLVER), [ConfigKey.RESPONSE_RECEIVE_CHECK]: getTCPNormalizer(ConfigKey.RESPONSE_RECEIVE_CHECK), [ConfigKey.REQUEST_SEND_CHECK]: getTCPNormalizer(ConfigKey.REQUEST_SEND_CHECK), + [ConfigKey.URLS]: getTCPNormalizer(ConfigKey.URLS), ...tlsNormalizers, ...commonNormalizers, }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts index a2427357e3682..5e9f35f030bd8 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/common.ts @@ -34,6 +34,9 @@ export const commonFormatters: CommonFormatMap = { [ConfigKey.PROJECT_ID]: null, [ConfigKey.CUSTOM_HEARTBEAT_ID]: null, [ConfigKey.ORIGINAL_SPACE]: null, + + // Deprecated, slated to be removed in a later releae + [ConfigKey.ID]: null, }; export const arrayFormatter = (value: string[] = []) => (value.length ? value : null); diff --git a/x-pack/plugins/synthetics/server/synthetics_service/formatters/tcp.ts b/x-pack/plugins/synthetics/server/synthetics_service/formatters/tcp.ts index 25ba5c08e9b3c..7b89a464039fc 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/formatters/tcp.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/formatters/tcp.ts @@ -19,6 +19,7 @@ export const tcpFormatters: TCPFormatMap = { [ConfigKey.PROXY_USE_LOCAL_RESOLVER]: null, [ConfigKey.RESPONSE_RECEIVE_CHECK]: null, [ConfigKey.REQUEST_SEND_CHECK]: null, + [ConfigKey.URLS]: null, ...tlsFormatters, ...commonFormatters, }; diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts index 9b32b61a59b35..3133e98b57935 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/browser_monitor.test.ts @@ -157,6 +157,7 @@ describe('browser normalizers', () => { original_space: 'test-space', custom_heartbeat_id: 'test-id-1-test-project-id-test-space', timeout: null, + id: '', }, unsupportedKeys: [], errors: [], @@ -213,6 +214,7 @@ describe('browser normalizers', () => { original_space: 'test-space', custom_heartbeat_id: 'test-id-2-test-project-id-test-space', timeout: null, + id: '', }, unsupportedKeys: [], errors: [], @@ -276,6 +278,7 @@ describe('browser normalizers', () => { original_space: 'test-space', custom_heartbeat_id: 'test-id-3-test-project-id-test-space', timeout: null, + id: '', }, unsupportedKeys: [], errors: [], diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts index cea5aa8b50de8..055c375bfb3b3 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/http_monitor.test.ts @@ -181,6 +181,7 @@ describe('http normalizers', () => { urls: 'http://localhost:9200', 'url.port': null, username: '', + id: '', }, unsupportedKeys: ['check.response.body', 'unsupportedKey.nestedUnsupportedKey'], }, @@ -235,6 +236,7 @@ describe('http normalizers', () => { urls: 'http://localhost:9200', 'url.port': null, username: '', + id: '', }, unsupportedKeys: [], }, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts index 74ac2cb2bfaf3..17bdd9e8ca24e 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/icmp_monitor.test.ts @@ -127,6 +127,7 @@ describe('icmp normalizers', () => { timeout: '60', type: 'icmp', wait: '30', + id: '', }, unsupportedKeys: [], }, @@ -166,6 +167,7 @@ describe('icmp normalizers', () => { timeout: '16', type: 'icmp', wait: '60', + id: '', }, unsupportedKeys: [], }, @@ -218,6 +220,7 @@ describe('icmp normalizers', () => { timeout: '16', type: 'icmp', wait: '1', + id: '', }, unsupportedKeys: ['unsupportedKey.nestedUnsupportedKey'], }, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts index a479bbc09d47b..9fbcb0c3b4ddf 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/normalizers/tcp_monitor.test.ts @@ -142,6 +142,8 @@ describe('tcp normalizers', () => { tags: ['service:smtp', 'org:google'], timeout: '16', type: 'tcp', + id: '', + urls: '', }, unsupportedKeys: [], }, @@ -194,6 +196,8 @@ describe('tcp normalizers', () => { tags: ['tag1', 'tag2'], timeout: '16', type: 'tcp', + id: '', + urls: '', }, unsupportedKeys: [], }, @@ -259,6 +263,8 @@ describe('tcp normalizers', () => { tags: ['tag1', 'tag2'], timeout: '16', type: 'tcp', + id: '', + urls: '', }, unsupportedKeys: ['ports', 'unsupportedKey.nestedUnsupportedKey'], }, diff --git a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts index a5fb9b774cf2c..6cefad03b83fc 100644 --- a/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts +++ b/x-pack/plugins/synthetics/server/synthetics_service/project_monitor/project_monitor_formatter.test.ts @@ -549,6 +549,7 @@ const payloadData = [ type: 'browser', 'url.port': null, urls: '', + id: '', }, { __ui: { @@ -607,6 +608,7 @@ const payloadData = [ type: 'browser', 'url.port': null, urls: '', + id: '', }, ]; diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts index a8eec4c568dc9..75361d7ae213e 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts @@ -194,6 +194,7 @@ export default function ({ getService }: FtrProviderContext) { type: 'browser', 'url.port': null, urls: '', + id: '', }); } } finally { @@ -307,6 +308,7 @@ export default function ({ getService }: FtrProviderContext) { type: 'http', urls: Array.isArray(monitor.urls) ? monitor.urls?.[0] : monitor.urls, 'url.port': null, + id: '', }); } } finally { @@ -406,6 +408,8 @@ export default function ({ getService }: FtrProviderContext) { type: 'tcp', hosts: Array.isArray(monitor.hosts) ? monitor.hosts?.[0] : monitor.hosts, 'url.port': null, + urls: '', + id: '', }); } } finally { @@ -507,6 +511,7 @@ export default function ({ getService }: FtrProviderContext) { monitor.wait?.slice(-1) === 's' ? monitor.wait?.slice(0, -1) : `${parseInt(monitor.wait?.slice(0, -1) || '1', 10) * 60}`, + id: '', }); } } finally { diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json index cfd4fc1b7d122..0d1508bf780cc 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/browser_monitor.json @@ -47,5 +47,6 @@ "origin": "ui", "form_monitor_type": "multistep", "urls": "", - "url.port": null + "url.port": null, + "id": "" } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json index 45a2e11e9f306..2f68e70f0e00d 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/http_monitor.json @@ -79,5 +79,6 @@ "revision": 1, "origin": "ui", "form_monitor_type": "http", - "journey_id": "" + "journey_id": "", + "id": "" } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json index 052c811461ae7..fb6efa3a604d2 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/icmp_monitor.json @@ -19,5 +19,6 @@ "name": "Test HTTP Monitor 04", "namespace": "testnamespace", "origin": "ui", - "form_monitor_type": "icmp" + "form_monitor_type": "icmp", + "id": "" } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json index e0b4ca03b1d8d..bfa0b3a1a7242 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/tcp_monitor.json @@ -15,6 +15,7 @@ "is_zip_url_tls_enabled": false }, "hosts": "example-host:40", + "urls": "example-host:40", "url.port": null, "proxy_url": "", "proxy_use_local_resolver": false, @@ -32,5 +33,6 @@ "name": "Test HTTP Monitor 04", "namespace": "testnamespace", "origin": "ui", - "form_monitor_type": "tcp" + "form_monitor_type": "tcp", + "id": "" } From 035720b49f5f54e55b94344b3fa4ca916502cbf3 Mon Sep 17 00:00:00 2001 From: Philippe Oberti <philippe.oberti@elastic.co> Date: Tue, 4 Oct 2022 12:43:49 -0500 Subject: [PATCH 066/174] [TIP] Reorganize table folder within indicators module (#142510) --- .../components}/actions_row_cell.tsx | 8 ++++---- .../components}/cell_actions.tsx | 12 ++++++------ .../components}/cell_renderer.tsx | 15 +++++++-------- .../field_browser/field_browser.test.tsx} | 4 ++-- .../components/field_browser/field_browser.tsx} | 2 +- .../components/field_browser/index.ts} | 2 +- .../components/table/components/index.ts | 12 ++++++++++++ .../components/open_flyout_button/index.ts} | 2 +- .../open_flyout_button.stories.tsx} | 6 +++--- .../open_flyout_button.test.tsx} | 6 +++--- .../open_flyout_button/open_flyout_button.tsx} | 2 +- .../contexts}/context.ts | 2 +- .../indicators/components/table/contexts/index.ts | 8 ++++++++ .../use_toolbar_options.test.tsx.snap | 0 .../indicators/components/table/hooks/index.ts | 9 +++++++++ .../hooks/use_column_settings.test.ts | 2 +- .../hooks/use_column_settings.ts | 0 .../hooks/use_toolbar_options.test.tsx | 2 +- .../hooks/use_toolbar_options.tsx | 5 ++--- .../{indicators_table => table}/index.tsx | 2 +- .../table.stories.tsx} | 4 ++-- .../table.test.tsx} | 8 ++------ .../indicators_table.tsx => table/table.tsx} | 10 ++++------ .../modules/indicators/indicators_page.test.tsx | 2 +- .../public/modules/indicators/indicators_page.tsx | 4 ++-- 25 files changed, 75 insertions(+), 54 deletions(-) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table/components}/actions_row_cell.tsx (76%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table/components}/cell_actions.tsx (84%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table/components}/cell_renderer.tsx (78%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_field_browser/indicators_field_browser.test.tsx => table/components/field_browser/field_browser.test.tsx} (90%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_field_browser/indicators_field_browser.tsx => table/components/field_browser/field_browser.tsx} (93%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_field_browser/index.tsx => table/components/field_browser/index.ts} (85%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/index.ts rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{open_indicator_flyout_button/index.tsx => table/components/open_flyout_button/index.ts} (84%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{open_indicator_flyout_button/open_indicator_flyout_button.stories.tsx => table/components/open_flyout_button/open_flyout_button.stories.tsx} (85%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{open_indicator_flyout_button/open_indicator_flyout_button.test.tsx => table/components/open_flyout_button/open_flyout_button.test.tsx} (82%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{open_indicator_flyout_button/open_indicator_flyout_button.tsx => table/components/open_flyout_button/open_flyout_button.tsx} (95%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table/contexts}/context.ts (89%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/index.ts rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table}/hooks/__snapshots__/use_toolbar_options.test.tsx.snap (100%) create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/index.ts rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table}/hooks/use_column_settings.test.ts (99%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table}/hooks/use_column_settings.ts (100%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table}/hooks/use_toolbar_options.test.tsx (96%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table}/hooks/use_toolbar_options.tsx (94%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table => table}/index.tsx (87%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table/indicators_table.stories.tsx => table/table.stories.tsx} (97%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table/indicators_table.test.tsx => table/table.test.tsx} (94%) rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicators_table/indicators_table.tsx => table/table.tsx} (95%) diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx similarity index 76% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx index 2f84b14db0c30..34a4d3cfee753 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/actions_row_cell.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/actions_row_cell.tsx @@ -7,10 +7,10 @@ import React, { useContext, VFC } from 'react'; import { EuiFlexGroup } from '@elastic/eui'; -import { InvestigateInTimelineButtonIcon } from '../../../timeline/components/investigate_in_timeline'; -import { Indicator } from '../../../../../common/types/indicator'; -import { OpenIndicatorFlyoutButton } from '../open_indicator_flyout_button/open_indicator_flyout_button'; -import { IndicatorsTableContext } from './context'; +import { InvestigateInTimelineButtonIcon } from '../../../../timeline/components/investigate_in_timeline'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { OpenIndicatorFlyoutButton } from './open_flyout_button'; +import { IndicatorsTableContext } from '../contexts'; const INVESTIGATE_TEST_ID = 'tiIndicatorTableInvestigateInTimelineButtonIcon'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx similarity index 84% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx index 9cca631ac3b5f..baed5eda31478 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_actions.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx @@ -7,12 +7,12 @@ import React, { VFC } from 'react'; import { EuiDataGridColumnCellActionProps } from '@elastic/eui/src/components/datagrid/data_grid_types'; -import { Indicator } from '../../../../../common/types/indicator'; -import { AddToTimelineCellAction } from '../../../timeline/components/add_to_timeline'; -import { FilterInCellAction } from '../../../query_bar/components/filter_in'; -import { FilterOutCellAction } from '../../../query_bar/components/filter_out'; -import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../utils/field_value'; -import type { Pagination } from '../../services/fetch_indicators'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { AddToTimelineCellAction } from '../../../../timeline/components/add_to_timeline'; +import { FilterInCellAction } from '../../../../query_bar/components/filter_in'; +import { FilterOutCellAction } from '../../../../query_bar/components/filter_out'; +import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../utils/field_value'; +import type { Pagination } from '../../../services/fetch_indicators'; export const CELL_TIMELINE_BUTTON_TEST_ID = 'tiIndicatorsTableCellTimelineButton'; export const CELL_FILTER_IN_BUTTON_TEST_ID = 'tiIndicatorsTableCellFilterInButton'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx similarity index 78% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx index 394d996d0ce9a..97c37c1598ccd 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/cell_renderer.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx @@ -6,14 +6,13 @@ */ import { EuiDataGridCellValueElementProps } from '@elastic/eui'; -import { useContext, useEffect } from 'react'; -import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme'; -import React from 'react'; -import { useKibana } from '../../../../hooks/use_kibana'; -import { Indicator } from '../../../../../common/types/indicator'; -import { IndicatorFieldValue } from '../indicator_field_value'; -import { IndicatorsTableContext } from './context'; -import { ActionsRowCell } from './actions_row_cell'; +import React, { useContext, useEffect } from 'react'; +import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme'; +import { useKibana } from '../../../../../hooks/use_kibana'; +import { Indicator } from '../../../../../../common/types/indicator'; +import { IndicatorFieldValue } from '../../indicator_field_value'; +import { IndicatorsTableContext } from '../contexts'; +import { ActionsRowCell } from '.'; export const cellRendererFactory = (from: number) => { return ({ rowIndex, columnId, setCellProps }: EuiDataGridCellValueElementProps) => { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/field_browser.test.tsx similarity index 90% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/field_browser.test.tsx index 29e97062cf22d..f8a0e9b9f99c6 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/field_browser.test.tsx @@ -8,10 +8,10 @@ import { mockedTriggersActionsUiService, TestProvidersComponent, -} from '../../../../common/mocks/test_providers'; +} from '../../../../../../common/mocks/test_providers'; import { render } from '@testing-library/react'; import React from 'react'; -import { IndicatorsFieldBrowser } from './indicators_field_browser'; +import { IndicatorsFieldBrowser } from '.'; const stub = jest.fn(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/field_browser.tsx similarity index 93% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/field_browser.tsx index 22cada18b84d8..fae02b89cefc3 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/indicators_field_browser.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/field_browser.tsx @@ -7,7 +7,7 @@ import { BrowserField } from '@kbn/rule-registry-plugin/common'; import { VFC } from 'react'; -import { useKibana } from '../../../../hooks/use_kibana'; +import { useKibana } from '../../../../../../hooks/use_kibana'; export interface IndicatorsFieldBrowserProps { browserFields: Readonly<Record<string, Partial<BrowserField>>>; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/index.ts similarity index 85% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/index.ts index 8fefebf88e799..8a6f5ffcfe963 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_field_browser/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/field_browser/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicators_field_browser'; +export * from './field_browser'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/index.ts new file mode 100644 index 0000000000000..d4cb33c72afb6 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './actions_row_cell'; +export * from './cell_actions'; +export * from './cell_renderer'; +export * from './field_browser'; +export * from './open_flyout_button'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/index.ts similarity index 84% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/index.ts index bc3de03078a92..6e3b46b779a25 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export * from './open_indicator_flyout_button'; +export * from './open_flyout_button'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.stories.tsx similarity index 85% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.stories.tsx index c64e2c5ebc067..d0bcec7068c21 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.stories.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { ComponentStory } from '@storybook/react'; import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public'; -import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_settings_service'; -import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { OpenIndicatorFlyoutButton } from './open_indicator_flyout_button'; +import { mockUiSettingsService } from '../../../../../../common/mocks/mock_kibana_ui_settings_service'; +import { generateMockIndicator, Indicator } from '../../../../../../../common/types/indicator'; +import { OpenIndicatorFlyoutButton } from '.'; export default { component: OpenIndicatorFlyoutButton, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.test.tsx similarity index 82% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.test.tsx index ca46976a379b6..f68be6d7ea553 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.test.tsx @@ -7,9 +7,9 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { BUTTON_TEST_ID, OpenIndicatorFlyoutButton } from './open_indicator_flyout_button'; -import { generateMockIndicator } from '../../../../../common/types/indicator'; -import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; +import { BUTTON_TEST_ID, OpenIndicatorFlyoutButton } from '.'; +import { generateMockIndicator } from '../../../../../../../common/types/indicator'; +import { TestProvidersComponent } from '../../../../../../common/mocks/test_providers'; const mockIndicator = generateMockIndicator(); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.tsx similarity index 95% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.tsx index 08a27381ce9a7..7ae0584447a5b 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/open_indicator_flyout_button/open_indicator_flyout_button.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/open_flyout_button/open_flyout_button.tsx @@ -8,7 +8,7 @@ import React, { VFC } from 'react'; import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Indicator } from '../../../../../common/types/indicator'; +import { Indicator } from '../../../../../../../common/types/indicator'; export const BUTTON_TEST_ID = 'tiToggleIndicatorFlyoutButton'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/context.ts similarity index 89% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/context.ts index 41596a257dc9e..e0125544a6453 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/context.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/context.ts @@ -6,7 +6,7 @@ */ import { createContext, Dispatch, SetStateAction } from 'react'; -import { Indicator } from '../../../../../common/types/indicator'; +import { Indicator } from '../../../../../../common/types/indicator'; export interface IndicatorsTableContextValue { expanded: Indicator | undefined; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/index.ts new file mode 100644 index 0000000000000..e9a2f3ab7be99 --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/contexts/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './context'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/__snapshots__/use_toolbar_options.test.tsx.snap diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/index.ts new file mode 100644 index 0000000000000..e92a0e6cf055b --- /dev/null +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export * from './use_column_settings'; +export * from './use_toolbar_options'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.test.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.test.ts similarity index 99% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.test.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.test.ts index 32b1e6998e954..e5df4a3931bd3 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.test.ts +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.test.ts @@ -7,7 +7,7 @@ import { mockedServices, TestProvidersComponent } from '../../../../../common/mocks/test_providers'; import { act, renderHook } from '@testing-library/react-hooks'; -import { useColumnSettings } from './use_column_settings'; +import { useColumnSettings } from '.'; const renderUseColumnSettings = () => renderHook(() => useColumnSettings(), { wrapper: TestProvidersComponent }); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.ts similarity index 100% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_column_settings.ts rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.ts diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_toolbar_options.test.tsx similarity index 96% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_toolbar_options.test.tsx index ecf1cbf0a477a..8e7d8576e313f 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_toolbar_options.test.tsx @@ -7,7 +7,7 @@ import { TestProvidersComponent } from '../../../../../common/mocks/test_providers'; import { renderHook } from '@testing-library/react-hooks'; -import { useToolbarOptions } from './use_toolbar_options'; +import { useToolbarOptions } from '.'; describe('useToolbarOptions()', () => { it('should return correct value for 0 indicators total', () => { diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_toolbar_options.tsx similarity index 94% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_toolbar_options.tsx index 12bd94951e33c..d1907160e0d31 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/hooks/use_toolbar_options.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_toolbar_options.tsx @@ -5,13 +5,12 @@ * 2.0. */ -import React from 'react'; -import { useMemo } from 'react'; +import React, { useMemo } from 'react'; import { EuiButtonIcon, EuiDataGridColumn, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { BrowserField } from '@kbn/rule-registry-plugin/common'; import { useInspector } from '../../../../../hooks/use_inspector'; -import { IndicatorsFieldBrowser } from '../../indicators_field_browser'; +import { IndicatorsFieldBrowser } from '../components'; const INSPECT_BUTTON_TEST_ID = 'tiIndicatorsGridInspect'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/index.tsx similarity index 87% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/index.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/index.tsx index b337d57fe4e1f..0572205ad74e6 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/index.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/index.tsx @@ -5,4 +5,4 @@ * 2.0. */ -export * from './indicators_table'; +export * from './table'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx similarity index 97% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx index 95217171cb9e5..f11977145d7a4 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.stories.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx @@ -10,9 +10,9 @@ import { DataView } from '@kbn/data-views-plugin/common'; import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context'; import { StoryProvidersComponent } from '../../../../common/mocks/story_providers'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { IndicatorsTable } from './indicators_table'; +import { IndicatorsTable } from '.'; import { IndicatorsFiltersContext } from '../../containers/indicators_filters/context'; -import { DEFAULT_COLUMNS } from './hooks/use_column_settings'; +import { DEFAULT_COLUMNS } from './hooks'; export default { component: IndicatorsTable, diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.test.tsx similarity index 94% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.test.tsx index eb1d4c3411c1f..2d51a75cd2c83 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.test.tsx @@ -7,14 +7,10 @@ import { act, render, screen } from '@testing-library/react'; import React from 'react'; -import { - IndicatorsTable, - IndicatorsTableProps, - TABLE_UPDATE_PROGRESS_TEST_ID, -} from './indicators_table'; +import { IndicatorsTable, IndicatorsTableProps, TABLE_UPDATE_PROGRESS_TEST_ID } from '.'; import { TestProvidersComponent } from '../../../../common/mocks/test_providers'; import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator'; -import { BUTTON_TEST_ID } from '../open_indicator_flyout_button'; +import { BUTTON_TEST_ID } from './components/open_flyout_button'; import { TITLE_TEST_ID } from '../flyout'; import { SecuritySolutionDataViewBase } from '../../../../types'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx similarity index 95% rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx index 7391003471870..aa1afd0d5b11c 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicators_table/indicators_table.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { VFC, useState, useMemo } from 'react'; +import React, { useMemo, useState, VFC } from 'react'; import { EuiDataGrid, EuiDataGridColumnCellActionProps, @@ -19,15 +19,13 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { EuiDataGridColumn } from '@elastic/eui/src/components/datagrid/data_grid_types'; -import { CellActions } from './cell_actions'; +import { CellActions, cellRendererFactory } from './components'; import { BrowserFields, SecuritySolutionDataViewBase } from '../../../../types'; import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator'; -import { cellRendererFactory } from './cell_renderer'; import { EmptyState } from '../../../../components/empty_state'; -import { IndicatorsTableContext, IndicatorsTableContextValue } from './context'; +import { IndicatorsTableContext, IndicatorsTableContextValue } from './contexts'; import { IndicatorsFlyout } from '../flyout'; -import { useToolbarOptions } from './hooks/use_toolbar_options'; -import { ColumnSettingsValue } from './hooks/use_column_settings'; +import { ColumnSettingsValue, useToolbarOptions } from './hooks'; import { useFieldTypes } from '../../../../hooks/use_field_types'; import { getFieldSchema } from '../../utils/get_field_schema'; import { Pagination } from '../../services/fetch_indicators'; diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx index 7f4db9fa75262..527507584f0e1 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx @@ -13,7 +13,7 @@ import { useAggregatedIndicators } from './hooks/use_aggregated_indicators'; import { useFilters } from '../query_bar/hooks/use_filters'; import moment from 'moment'; import { TestProvidersComponent } from '../../common/mocks/test_providers'; -import { TABLE_TEST_ID } from './components/indicators_table'; +import { TABLE_TEST_ID } from './components/table'; import { mockTimeRange } from '../../common/mocks/mock_indicators_filters_context'; jest.mock('../query_bar/hooks/use_filters'); diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx index fcf690631d740..195bd74238241 100644 --- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx +++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx @@ -8,7 +8,7 @@ import React, { FC, VFC } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { IndicatorsBarChartWrapper } from './components/barchart'; -import { IndicatorsTable } from './components/indicators_table/indicators_table'; +import { IndicatorsTable } from './components/table'; import { useIndicators } from './hooks/use_indicators'; import { DefaultPageLayout } from '../../components/layout'; import { useFilters } from '../query_bar/hooks/use_filters'; @@ -16,7 +16,7 @@ import { FiltersGlobal } from '../../containers/filters_global'; import { useSourcererDataView } from './hooks/use_sourcerer_data_view'; import { FieldTypesProvider } from '../../containers/field_types_provider'; import { InspectorProvider } from '../../containers/inspector'; -import { useColumnSettings } from './components/indicators_table/hooks/use_column_settings'; +import { useColumnSettings } from './components/table/hooks'; import { useAggregatedIndicators } from './hooks/use_aggregated_indicators'; import { IndicatorsFilters } from './containers/indicators_filters'; import { useSecurityContext } from '../../hooks/use_security_context'; From a8694c7e1a75ce4dd942e73a2ea449e0d68a1dc6 Mon Sep 17 00:00:00 2001 From: Kevin Delemme <kevin.delemme@elastic.co> Date: Tue, 4 Oct 2022 13:59:42 -0400 Subject: [PATCH 067/174] feat(slo): Handle updating SLO (#142273) --- .../observability/server/assets/constants.ts | 3 +- .../observability/server/routes/slo/route.ts | 34 ++++++- .../server/services/slo/create_slo.ts | 12 ++- .../server/services/slo/delete_slo.test.ts | 9 +- .../server/services/slo/delete_slo.ts | 9 +- .../server/services/slo/fixtures/slo.ts | 38 ++++--- .../server/services/slo/get_slo.test.ts | 3 + .../server/services/slo/get_slo.ts | 3 + .../server/services/slo/index.ts | 1 + .../services/slo/slo_repository.test.ts | 8 +- .../server/services/slo/slo_repository.ts | 30 ++---- .../apm_transaction_duration.test.ts.snap | 11 +++ .../apm_transaction_error_rate.test.ts.snap | 11 +++ .../apm_transaction_duration.test.ts | 5 +- .../apm_transaction_duration.ts | 51 +++++----- .../apm_transaction_error_rate.test.ts | 5 +- .../apm_transaction_error_rate.ts | 53 +++++----- .../services/slo/transform_manager.test.ts | 18 ++-- .../server/services/slo/transform_manager.ts | 4 +- .../server/services/slo/update_slo.test.ts | 99 +++++++++++++++++++ .../server/services/slo/update_slo.ts | 88 +++++++++++++++++ .../observability/server/types/models/slo.ts | 55 +++++------ .../server/types/rest_specs/slo.ts | 63 +++++++++++- .../server/types/schema/common.ts | 14 ++- .../server/types/schema/indicators.ts | 6 +- .../observability/server/types/schema/slo.ts | 15 +-- 26 files changed, 482 insertions(+), 166 deletions(-) create mode 100644 x-pack/plugins/observability/server/services/slo/update_slo.test.ts create mode 100644 x-pack/plugins/observability/server/services/slo/update_slo.ts diff --git a/x-pack/plugins/observability/server/assets/constants.ts b/x-pack/plugins/observability/server/assets/constants.ts index 182ca89712dcc..3b862a7eb3018 100644 --- a/x-pack/plugins/observability/server/assets/constants.ts +++ b/x-pack/plugins/observability/server/assets/constants.ts @@ -12,4 +12,5 @@ export const SLO_RESOURCES_VERSION = 1; export const SLO_INGEST_PIPELINE_NAME = `${SLO_INDEX_TEMPLATE_NAME}.monthly`; export const SLO_DESTINATION_INDEX_NAME = `${SLO_INDEX_TEMPLATE_NAME}-v${SLO_RESOURCES_VERSION}`; -export const getSLOTransformId = (sloId: string) => `slo-${sloId}`; +export const getSLOTransformId = (sloId: string, sloRevision: number) => + `slo-${sloId}-${sloRevision}`; diff --git a/x-pack/plugins/observability/server/routes/slo/route.ts b/x-pack/plugins/observability/server/routes/slo/route.ts index 63ef4e1e07e64..c80abf6b60b5f 100644 --- a/x-pack/plugins/observability/server/routes/slo/route.ts +++ b/x-pack/plugins/observability/server/routes/slo/route.ts @@ -12,22 +12,23 @@ import { DefaultTransformManager, KibanaSavedObjectsSLORepository, GetSLO, + UpdateSLO, } from '../../services/slo'; - import { ApmTransactionDurationTransformGenerator, ApmTransactionErrorRateTransformGenerator, TransformGenerator, } from '../../services/slo/transform_generators'; -import { SLITypes } from '../../types/models'; +import { IndicatorTypes } from '../../types/models'; import { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema, + updateSLOParamsSchema, } from '../../types/rest_specs'; import { createObservabilityServerRoute } from '../create_observability_server_route'; -const transformGenerators: Record<SLITypes, TransformGenerator> = { +const transformGenerators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_duration': new ApmTransactionDurationTransformGenerator(), 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; @@ -53,6 +54,26 @@ const createSLORoute = createObservabilityServerRoute({ }, }); +const updateSLORoute = createObservabilityServerRoute({ + endpoint: 'PUT /api/observability/slos/{id}', + options: { + tags: [], + }, + params: updateSLOParamsSchema, + handler: async ({ context, params, logger }) => { + const esClient = (await context.core).elasticsearch.client.asCurrentUser; + const soClient = (await context.core).savedObjects.client; + + const repository = new KibanaSavedObjectsSLORepository(soClient); + const transformManager = new DefaultTransformManager(transformGenerators, esClient, logger); + const updateSLO = new UpdateSLO(repository, transformManager, esClient); + + const response = await updateSLO.execute(params.path.id, params.body); + + return response; + }, +}); + const deleteSLORoute = createObservabilityServerRoute({ endpoint: 'DELETE /api/observability/slos/{id}', options: { @@ -89,4 +110,9 @@ const getSLORoute = createObservabilityServerRoute({ }, }); -export const slosRouteRepository = { ...createSLORoute, ...getSLORoute, ...deleteSLORoute }; +export const slosRouteRepository = { + ...createSLORoute, + ...updateSLORoute, + ...getSLORoute, + ...deleteSLORoute, +}; diff --git a/x-pack/plugins/observability/server/services/slo/create_slo.ts b/x-pack/plugins/observability/server/services/slo/create_slo.ts index 8a81228db96f9..35a908a48bd2f 100644 --- a/x-pack/plugins/observability/server/services/slo/create_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/create_slo.ts @@ -20,8 +20,8 @@ export class CreateSLO { private transformManager: TransformManager ) {} - public async execute(sloParams: CreateSLOParams): Promise<CreateSLOResponse> { - const slo = this.toSLO(sloParams); + public async execute(params: CreateSLOParams): Promise<CreateSLOResponse> { + const slo = this.toSLO(params); await this.resourceInstaller.ensureCommonResourcesInstalled(); await this.repository.save(slo); @@ -48,10 +48,14 @@ export class CreateSLO { return this.toResponse(slo); } - private toSLO(sloParams: CreateSLOParams): SLO { + private toSLO(params: CreateSLOParams): SLO { + const now = new Date(); return { - ...sloParams, + ...params, id: uuid.v1(), + revision: 1, + created_at: now, + updated_at: now, }; } diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts index 2e43c81f6d382..8353e0fa16f21 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.test.ts @@ -30,12 +30,17 @@ describe('DeleteSLO', () => { describe('happy path', () => { it('removes the transform, the roll up data and the SLO from the repository', async () => { const slo = createSLO(createAPMTransactionErrorRateIndicator()); + mockRepository.findById.mockResolvedValueOnce(slo); await deleteSLO.execute(slo.id); expect(mockRepository.findById).toHaveBeenCalledWith(slo.id); - expect(mockTransformManager.stop).toHaveBeenCalledWith(getSLOTransformId(slo.id)); - expect(mockTransformManager.uninstall).toHaveBeenCalledWith(getSLOTransformId(slo.id)); + expect(mockTransformManager.stop).toHaveBeenCalledWith( + getSLOTransformId(slo.id, slo.revision) + ); + expect(mockTransformManager.uninstall).toHaveBeenCalledWith( + getSLOTransformId(slo.id, slo.revision) + ); expect(mockEsClient.deleteByQuery).toHaveBeenCalledWith( expect.objectContaining({ index: `${SLO_INDEX_TEMPLATE_NAME}*`, diff --git a/x-pack/plugins/observability/server/services/slo/delete_slo.ts b/x-pack/plugins/observability/server/services/slo/delete_slo.ts index a7d931174a59a..8ec8d2060f730 100644 --- a/x-pack/plugins/observability/server/services/slo/delete_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/delete_slo.ts @@ -19,15 +19,14 @@ export class DeleteSLO { ) {} public async execute(sloId: string): Promise<void> { - // ensure the slo exists on the request's space. - await this.repository.findById(sloId); + const slo = await this.repository.findById(sloId); - const sloTransformId = getSLOTransformId(sloId); + const sloTransformId = getSLOTransformId(slo.id, slo.revision); await this.transformManager.stop(sloTransformId); await this.transformManager.uninstall(sloTransformId); - await this.deleteRollupData(sloId); - await this.repository.deleteById(sloId); + await this.deleteRollupData(slo.id); + await this.repository.deleteById(slo.id); } private async deleteRollupData(sloId: string): Promise<void> { diff --git a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts index d9d4aa5756663..9a193d9fb1cfa 100644 --- a/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts +++ b/x-pack/plugins/observability/server/services/slo/fixtures/slo.ts @@ -6,10 +6,15 @@ */ import uuid from 'uuid'; -import { SLI, SLO } from '../../../types/models'; +import { + APMTransactionDurationIndicator, + APMTransactionErrorRateIndicator, + Indicator, + SLO, +} from '../../../types/models'; import { CreateSLOParams } from '../../../types/rest_specs'; -const commonSLO: Omit<CreateSLOParams, 'indicator'> = { +const defaultSLO: Omit<SLO, 'indicator' | 'id' | 'created_at' | 'updated_at'> = { name: 'irrelevant', description: 'irrelevant', time_window: { @@ -20,20 +25,29 @@ const commonSLO: Omit<CreateSLOParams, 'indicator'> = { objective: { target: 0.999, }, + revision: 1, }; -export const createSLOParams = (indicator: SLI): CreateSLOParams => ({ - ...commonSLO, +export const createSLOParams = (indicator: Indicator): CreateSLOParams => ({ + ...defaultSLO, indicator, }); -export const createSLO = (indicator: SLI): SLO => ({ - ...commonSLO, - id: uuid.v1(), - indicator, -}); +export const createSLO = (indicator: Indicator): SLO => { + const now = new Date(); + return { + ...defaultSLO, + id: uuid.v1(), + indicator, + revision: 1, + created_at: now, + updated_at: now, + }; +}; -export const createAPMTransactionErrorRateIndicator = (params = {}): SLI => ({ +export const createAPMTransactionErrorRateIndicator = ( + params: Partial<APMTransactionErrorRateIndicator['params']> = {} +): Indicator => ({ type: 'slo.apm.transaction_error_rate', params: { environment: 'irrelevant', @@ -45,7 +59,9 @@ export const createAPMTransactionErrorRateIndicator = (params = {}): SLI => ({ }, }); -export const createAPMTransactionDurationIndicator = (params = {}): SLI => ({ +export const createAPMTransactionDurationIndicator = ( + params: Partial<APMTransactionDurationIndicator['params']> = {} +): Indicator => ({ type: 'slo.apm.transaction_duration', params: { environment: 'irrelevant', diff --git a/x-pack/plugins/observability/server/services/slo/get_slo.test.ts b/x-pack/plugins/observability/server/services/slo/get_slo.test.ts index 768424d6b9eec..dac1b7aa8ca28 100644 --- a/x-pack/plugins/observability/server/services/slo/get_slo.test.ts +++ b/x-pack/plugins/observability/server/services/slo/get_slo.test.ts @@ -49,6 +49,9 @@ describe('GetSLO', () => { duration: '7d', is_rolling: true, }, + created_at: slo.created_at, + updated_at: slo.updated_at, + revision: slo.revision, }); }); }); diff --git a/x-pack/plugins/observability/server/services/slo/get_slo.ts b/x-pack/plugins/observability/server/services/slo/get_slo.ts index 1730dec464964..26ae0ee511842 100644 --- a/x-pack/plugins/observability/server/services/slo/get_slo.ts +++ b/x-pack/plugins/observability/server/services/slo/get_slo.ts @@ -26,6 +26,9 @@ export class GetSLO { time_window: slo.time_window, budgeting_method: slo.budgeting_method, objective: slo.objective, + revision: slo.revision, + created_at: slo.created_at, + updated_at: slo.updated_at, }; } } diff --git a/x-pack/plugins/observability/server/services/slo/index.ts b/x-pack/plugins/observability/server/services/slo/index.ts index 7515262628f08..58b31e424842b 100644 --- a/x-pack/plugins/observability/server/services/slo/index.ts +++ b/x-pack/plugins/observability/server/services/slo/index.ts @@ -11,3 +11,4 @@ export * from './transform_manager'; export * from './create_slo'; export * from './delete_slo'; export * from './get_slo'; +export * from './update_slo'; diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts index 8c8e38285f1f2..fe0ebe406ef35 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.test.ts @@ -20,11 +20,7 @@ const SOME_SLO = createSLO(createAPMTransactionDurationIndicator()); function aStoredSLO(slo: SLO): SavedObject<StoredSLO> { return { id: slo.id, - attributes: { - ...slo, - updated_at: new Date().toISOString(), - created_at: new Date().toISOString(), - }, + attributes: slo, type: SO_SLO_TYPE, references: [], }; @@ -73,7 +69,7 @@ describe('KibanaSavedObjectsSLORepository', () => { updated_at: expect.anything(), created_at: expect.anything(), }), - { id: SOME_SLO.id } + { id: SOME_SLO.id, overwrite: true } ); }); diff --git a/x-pack/plugins/observability/server/services/slo/slo_repository.ts b/x-pack/plugins/observability/server/services/slo/slo_repository.ts index 29e16346fcdb1..8a8abcd834652 100644 --- a/x-pack/plugins/observability/server/services/slo/slo_repository.ts +++ b/x-pack/plugins/observability/server/services/slo/slo_repository.ts @@ -22,24 +22,18 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { constructor(private soClient: SavedObjectsClientContract) {} async save(slo: SLO): Promise<SLO> { - const now = new Date().toISOString(); - const savedSLO = await this.soClient.create<StoredSLO>( - SO_SLO_TYPE, - { - ...slo, - created_at: now, - updated_at: now, - }, - { id: slo.id } - ); + const savedSLO = await this.soClient.create<StoredSLO>(SO_SLO_TYPE, slo, { + id: slo.id, + overwrite: true, + }); - return toSLOModel(savedSLO.attributes); + return savedSLO.attributes; } async findById(id: string): Promise<SLO> { try { const slo = await this.soClient.get<StoredSLO>(SO_SLO_TYPE, id); - return toSLOModel(slo.attributes); + return slo.attributes; } catch (err) { if (SavedObjectsErrorHelpers.isNotFoundError(err)) { throw new SLONotFound(`SLO [${id}] not found`); @@ -59,15 +53,3 @@ export class KibanaSavedObjectsSLORepository implements SLORepository { } } } - -function toSLOModel(slo: StoredSLO): SLO { - return { - id: slo.id, - name: slo.name, - description: slo.description, - indicator: slo.indicator, - time_window: slo.time_window, - budgeting_method: slo.budgeting_method, - objective: slo.objective, - }; -} diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap index 7b7f49061fd57..d9a9c5852192b 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_duration.test.ts.snap @@ -82,6 +82,11 @@ Object { "field": "slo.id", }, }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, }, }, "settings": Object { @@ -127,6 +132,12 @@ Object { }, "type": "keyword", }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, }, }, "sync": Object { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap index 8b73f76a8082d..d59faee05b930 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/__snapshots__/apm_transaction_error_rate.test.ts.snap @@ -87,6 +87,11 @@ Object { "field": "slo.id", }, }, + "slo.revision": Object { + "terms": Object { + "field": "slo.revision", + }, + }, }, }, "settings": Object { @@ -132,6 +137,12 @@ Object { }, "type": "keyword", }, + "slo.revision": Object { + "script": Object { + "source": "emit(1)", + }, + "type": "long", + }, }, }, "sync": Object { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts index ba984e542619b..08d70bbe831a8 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.test.ts @@ -19,10 +19,13 @@ describe('APM Transaction Duration Transform Generator', () => { transform_id: expect.any(String), source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, }); - expect(transform.transform_id).toEqual(`slo-${anSLO.id}`); + expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`); expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({ script: { source: `emit('${anSLO.id}')` }, }); + expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({ + script: { source: `emit(${anSLO.revision})` }, + }); }); it("does not include the query filter when params are '*'", async () => { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts index bc45e12abbb30..061f020bab2f9 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_duration.ts @@ -10,71 +10,67 @@ import { MappingRuntimeFieldType, TransformPutTransformRequest, } from '@elastic/elasticsearch/lib/api/types'; -import { ALL_VALUE } from '../../../types/schema'; +import { ALL_VALUE, apmTransactionDurationIndicatorSchema } from '../../../types/schema'; import { SLO_DESTINATION_INDEX_NAME, SLO_INGEST_PIPELINE_NAME, getSLOTransformId, } from '../../../assets/constants'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; -import { - SLO, - apmTransactionDurationSLOSchema, - APMTransactionDurationSLO, -} from '../../../types/models'; +import { SLO, APMTransactionDurationIndicator } from '../../../types/models'; import { TransformGenerator } from '.'; const APM_SOURCE_INDEX = 'metrics-apm*'; export class ApmTransactionDurationTransformGenerator implements TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { - if (!apmTransactionDurationSLOSchema.is(slo)) { + if (!apmTransactionDurationIndicatorSchema.is(slo.indicator)) { throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } return getSLOTransformTemplate( this.buildTransformId(slo), - this.buildSource(slo), + this.buildSource(slo, slo.indicator), this.buildDestination(), this.buildGroupBy(), - this.buildAggregations(slo) + this.buildAggregations(slo.indicator) ); } - private buildTransformId(slo: APMTransactionDurationSLO): string { - return getSLOTransformId(slo.id); + private buildTransformId(slo: SLO): string { + return getSLOTransformId(slo.id, slo.revision); } - private buildSource(slo: APMTransactionDurationSLO) { + private buildSource(slo: SLO, indicator: APMTransactionDurationIndicator) { const queryFilter = []; - if (slo.indicator.params.service !== ALL_VALUE) { + if (indicator.params.service !== ALL_VALUE) { queryFilter.push({ match: { - 'service.name': slo.indicator.params.service, + 'service.name': indicator.params.service, }, }); } - if (slo.indicator.params.environment !== ALL_VALUE) { + if (indicator.params.environment !== ALL_VALUE) { queryFilter.push({ match: { - 'service.environment': slo.indicator.params.environment, + 'service.environment': indicator.params.environment, }, }); } - if (slo.indicator.params.transaction_name !== ALL_VALUE) { + if (indicator.params.transaction_name !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.name': slo.indicator.params.transaction_name, + 'transaction.name': indicator.params.transaction_name, }, }); } - if (slo.indicator.params.transaction_type !== ALL_VALUE) { + if (indicator.params.transaction_type !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.type': slo.indicator.params.transaction_type, + 'transaction.type': indicator.params.transaction_type, }, }); } @@ -88,6 +84,12 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera source: `emit('${slo.id}')`, }, }, + 'slo.revision': { + type: 'long' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.revision})`, + }, + }, }, query: { bool: { @@ -118,6 +120,11 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera field: 'slo.id', }, }, + 'slo.revision': { + terms: { + field: 'slo.revision', + }, + }, '@timestamp': { date_histogram: { field: '@timestamp', @@ -147,8 +154,8 @@ export class ApmTransactionDurationTransformGenerator implements TransformGenera }; } - private buildAggregations(slo: APMTransactionDurationSLO) { - const truncatedThreshold = Math.trunc(slo.indicator.params['threshold.us']); + private buildAggregations(indicator: APMTransactionDurationIndicator) { + const truncatedThreshold = Math.trunc(indicator.params['threshold.us']); return { _numerator: { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts index 2bc88c576f8c4..e5f705b985d4d 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.test.ts @@ -19,10 +19,13 @@ describe('APM Transaction Error Rate Transform Generator', () => { transform_id: expect.any(String), source: { runtime_mappings: { 'slo.id': { script: { source: expect.any(String) } } } }, }); - expect(transform.transform_id).toEqual(`slo-${anSLO.id}`); + expect(transform.transform_id).toEqual(`slo-${anSLO.id}-${anSLO.revision}`); expect(transform.source.runtime_mappings!['slo.id']).toMatchObject({ script: { source: `emit('${anSLO.id}')` }, }); + expect(transform.source.runtime_mappings!['slo.revision']).toMatchObject({ + script: { source: `emit(${anSLO.revision})` }, + }); }); it("uses default values when 'good_status_codes' is not specified", async () => { diff --git a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts index 23a9a03f6e14c..e9a796d67e36f 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_generators/apm_transaction_error_rate.ts @@ -10,7 +10,7 @@ import { MappingRuntimeFieldType, TransformPutTransformRequest, } from '@elastic/elasticsearch/lib/api/types'; -import { ALL_VALUE } from '../../../types/schema'; +import { ALL_VALUE, apmTransactionErrorRateIndicatorSchema } from '../../../types/schema'; import { getSLOTransformTemplate } from '../../../assets/transform_templates/slo_transform_template'; import { TransformGenerator } from '.'; import { @@ -18,11 +18,7 @@ import { SLO_INGEST_PIPELINE_NAME, getSLOTransformId, } from '../../../assets/constants'; -import { - apmTransactionErrorRateSLOSchema, - APMTransactionErrorRateSLO, - SLO, -} from '../../../types/models'; +import { APMTransactionErrorRateIndicator, SLO } from '../../../types/models'; const APM_SOURCE_INDEX = 'metrics-apm*'; const ALLOWED_STATUS_CODES = ['2xx', '3xx', '4xx', '5xx']; @@ -30,53 +26,53 @@ const DEFAULT_GOOD_STATUS_CODES = ['2xx', '3xx', '4xx']; export class ApmTransactionErrorRateTransformGenerator implements TransformGenerator { public getTransformParams(slo: SLO): TransformPutTransformRequest { - if (!apmTransactionErrorRateSLOSchema.is(slo)) { + if (!apmTransactionErrorRateIndicatorSchema.is(slo.indicator)) { throw new Error(`Cannot handle SLO of indicator type: ${slo.indicator.type}`); } return getSLOTransformTemplate( this.buildTransformId(slo), - this.buildSource(slo), + this.buildSource(slo, slo.indicator), this.buildDestination(), this.buildGroupBy(), - this.buildAggregations(slo) + this.buildAggregations(slo, slo.indicator) ); } - private buildTransformId(slo: APMTransactionErrorRateSLO): string { - return getSLOTransformId(slo.id); + private buildTransformId(slo: SLO): string { + return getSLOTransformId(slo.id, slo.revision); } - private buildSource(slo: APMTransactionErrorRateSLO) { + private buildSource(slo: SLO, indicator: APMTransactionErrorRateIndicator) { const queryFilter = []; - if (slo.indicator.params.service !== ALL_VALUE) { + if (indicator.params.service !== ALL_VALUE) { queryFilter.push({ match: { - 'service.name': slo.indicator.params.service, + 'service.name': indicator.params.service, }, }); } - if (slo.indicator.params.environment !== ALL_VALUE) { + if (indicator.params.environment !== ALL_VALUE) { queryFilter.push({ match: { - 'service.environment': slo.indicator.params.environment, + 'service.environment': indicator.params.environment, }, }); } - if (slo.indicator.params.transaction_name !== ALL_VALUE) { + if (indicator.params.transaction_name !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.name': slo.indicator.params.transaction_name, + 'transaction.name': indicator.params.transaction_name, }, }); } - if (slo.indicator.params.transaction_type !== ALL_VALUE) { + if (indicator.params.transaction_type !== ALL_VALUE) { queryFilter.push({ match: { - 'transaction.type': slo.indicator.params.transaction_type, + 'transaction.type': indicator.params.transaction_type, }, }); } @@ -90,6 +86,12 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener source: `emit('${slo.id}')`, }, }, + 'slo.revision': { + type: 'long' as MappingRuntimeFieldType, + script: { + source: `emit(${slo.revision})`, + }, + }, }, query: { bool: { @@ -120,6 +122,11 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener field: 'slo.id', }, }, + 'slo.revision': { + terms: { + field: 'slo.revision', + }, + }, '@timestamp': { date_histogram: { field: '@timestamp', @@ -149,10 +156,8 @@ export class ApmTransactionErrorRateTransformGenerator implements TransformGener }; } - private buildAggregations(slo: APMTransactionErrorRateSLO) { - const goodStatusCodesFilter = this.getGoodStatusCodesFilter( - slo.indicator.params.good_status_codes - ); + private buildAggregations(slo: SLO, indicator: APMTransactionErrorRateIndicator) { + const goodStatusCodesFilter = this.getGoodStatusCodesFilter(indicator.params.good_status_codes); return { 'slo.numerator': { diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts index 434e6841ff0e9..dc67c6e1485c9 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.test.ts @@ -20,7 +20,7 @@ import { ApmTransactionErrorRateTransformGenerator, TransformGenerator, } from './transform_generators'; -import { SLO, SLITypes } from '../../types/models'; +import { SLO, IndicatorTypes } from '../../types/models'; import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; describe('TransformManager', () => { @@ -36,7 +36,7 @@ describe('TransformManager', () => { describe('Unhappy path', () => { it('throws when no generator exists for the slo indicator type', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_duration': new DummyTransformGenerator(), }; const service = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -58,7 +58,7 @@ describe('TransformManager', () => { it('throws when transform generator fails', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_duration': new FailTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -82,7 +82,7 @@ describe('TransformManager', () => { it('installs the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -91,14 +91,14 @@ describe('TransformManager', () => { const transformId = await transformManager.install(slo); expect(esClientMock.transform.putTransform).toHaveBeenCalledTimes(1); - expect(transformId).toBe(`slo-${slo.id}`); + expect(transformId).toBe(`slo-${slo.id}-${slo.revision}`); }); }); describe('Start', () => { it('starts the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -112,7 +112,7 @@ describe('TransformManager', () => { describe('Stop', () => { it('stops the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -126,7 +126,7 @@ describe('TransformManager', () => { describe('Uninstall', () => { it('uninstalls the transform', async () => { // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); @@ -141,7 +141,7 @@ describe('TransformManager', () => { new EsErrors.ConnectionError('irrelevant') ); // @ts-ignore defining only a subset of the possible SLI - const generators: Record<SLITypes, TransformGenerator> = { + const generators: Record<IndicatorTypes, TransformGenerator> = { 'slo.apm.transaction_error_rate': new ApmTransactionErrorRateTransformGenerator(), }; const transformManager = new DefaultTransformManager(generators, esClientMock, loggerMock); diff --git a/x-pack/plugins/observability/server/services/slo/transform_manager.ts b/x-pack/plugins/observability/server/services/slo/transform_manager.ts index 154660fccaf9f..66c4f972ad48c 100644 --- a/x-pack/plugins/observability/server/services/slo/transform_manager.ts +++ b/x-pack/plugins/observability/server/services/slo/transform_manager.ts @@ -7,7 +7,7 @@ import { ElasticsearchClient, Logger } from '@kbn/core/server'; -import { SLO, SLITypes } from '../../types/models'; +import { SLO, IndicatorTypes } from '../../types/models'; import { retryTransientEsErrors } from '../../utils/retry'; import { TransformGenerator } from './transform_generators'; @@ -22,7 +22,7 @@ export interface TransformManager { export class DefaultTransformManager implements TransformManager { constructor( - private generators: Record<SLITypes, TransformGenerator>, + private generators: Record<IndicatorTypes, TransformGenerator>, private esClient: ElasticsearchClient, private logger: Logger ) {} diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.test.ts b/x-pack/plugins/observability/server/services/slo/update_slo.test.ts new file mode 100644 index 0000000000000..6964887fe914e --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/update_slo.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; +import { getSLOTransformId } from '../../assets/constants'; +import { SLO } from '../../types/models'; +import { createAPMTransactionErrorRateIndicator, createSLO } from './fixtures/slo'; +import { createSLORepositoryMock, createTransformManagerMock } from './mocks'; +import { SLORepository } from './slo_repository'; +import { TransformManager } from './transform_manager'; +import { UpdateSLO } from './update_slo'; + +describe('UpdateSLO', () => { + let mockRepository: jest.Mocked<SLORepository>; + let mockTransformManager: jest.Mocked<TransformManager>; + let mockEsClient: jest.Mocked<ElasticsearchClient>; + let updateSLO: UpdateSLO; + + beforeEach(() => { + mockRepository = createSLORepositoryMock(); + mockTransformManager = createTransformManagerMock(); + mockEsClient = elasticsearchServiceMock.createElasticsearchClient(); + updateSLO = new UpdateSLO(mockRepository, mockTransformManager, mockEsClient); + }); + + describe('without breaking changes', () => { + it('updates the SLO saved object without revision bump', async () => { + const slo = createSLO(createAPMTransactionErrorRateIndicator()); + mockRepository.findById.mockResolvedValueOnce(slo); + + const newName = 'new slo name'; + const response = await updateSLO.execute(slo.id, { name: newName }); + + expectTransformManagerNeverCalled(); + expect(mockEsClient.deleteByQuery).not.toBeCalled(); + expect(mockRepository.save).toBeCalledWith( + expect.objectContaining({ ...slo, name: newName, updated_at: expect.anything() }) + ); + expect(response.name).toBe(newName); + expect(response.updated_at).not.toBe(slo.updated_at); + }); + }); + + describe('with breaking changes', () => { + it('removes the obsolete data from the SLO previous revision', async () => { + const slo = createSLO(createAPMTransactionErrorRateIndicator({ environment: 'development' })); + mockRepository.findById.mockResolvedValueOnce(slo); + + const newIndicator = createAPMTransactionErrorRateIndicator({ environment: 'production' }); + await updateSLO.execute(slo.id, { indicator: newIndicator }); + + expectDeletionOfObsoleteSLOData(slo); + expect(mockRepository.save).toBeCalledWith( + expect.objectContaining({ + ...slo, + indicator: newIndicator, + revision: 2, + updated_at: expect.anything(), + }) + ); + expectInstallationOfNewSLOTransform(); + }); + }); + + function expectTransformManagerNeverCalled() { + expect(mockTransformManager.stop).not.toBeCalled(); + expect(mockTransformManager.uninstall).not.toBeCalled(); + expect(mockTransformManager.start).not.toBeCalled(); + expect(mockTransformManager.install).not.toBeCalled(); + } + + function expectInstallationOfNewSLOTransform() { + expect(mockTransformManager.start).toBeCalled(); + expect(mockTransformManager.install).toBeCalled(); + } + + function expectDeletionOfObsoleteSLOData(originalSlo: SLO) { + const transformId = getSLOTransformId(originalSlo.id, originalSlo.revision); + expect(mockTransformManager.stop).toBeCalledWith(transformId); + expect(mockTransformManager.uninstall).toBeCalledWith(transformId); + expect(mockEsClient.deleteByQuery).toBeCalledWith( + expect.objectContaining({ + query: { + bool: { + filter: [ + { term: { 'slo.id': originalSlo.id } }, + { term: { 'slo.revision': originalSlo.revision } }, + ], + }, + }, + }) + ); + } +}); diff --git a/x-pack/plugins/observability/server/services/slo/update_slo.ts b/x-pack/plugins/observability/server/services/slo/update_slo.ts new file mode 100644 index 0000000000000..cee487c09a39f --- /dev/null +++ b/x-pack/plugins/observability/server/services/slo/update_slo.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import deepEqual from 'fast-deep-equal'; +import { ElasticsearchClient } from '@kbn/core/server'; + +import { getSLOTransformId, SLO_INDEX_TEMPLATE_NAME } from '../../assets/constants'; +import { UpdateSLOParams, UpdateSLOResponse } from '../../types/rest_specs'; +import { SLORepository } from './slo_repository'; +import { TransformManager } from './transform_manager'; +import { SLO } from '../../types/models'; + +export class UpdateSLO { + constructor( + private repository: SLORepository, + private transformManager: TransformManager, + private esClient: ElasticsearchClient + ) {} + + public async execute(sloId: string, params: UpdateSLOParams): Promise<UpdateSLOResponse> { + const originalSlo = await this.repository.findById(sloId); + const { hasBreakingChange, updatedSlo } = this.updateSLO(originalSlo, params); + + if (hasBreakingChange) { + await this.deleteObsoleteSLORevisionData(originalSlo); + + await this.repository.save(updatedSlo); + await this.transformManager.install(updatedSlo); + await this.transformManager.start(getSLOTransformId(updatedSlo.id, updatedSlo.revision)); + } else { + await this.repository.save(updatedSlo); + } + + return this.toResponse(updatedSlo); + } + + private updateSLO(originalSlo: SLO, params: UpdateSLOParams) { + let hasBreakingChange = false; + const updatedSlo: SLO = Object.assign({}, originalSlo, params, { updated_at: new Date() }); + + if (!deepEqual(originalSlo.indicator, updatedSlo.indicator)) { + hasBreakingChange = true; + } + + if (hasBreakingChange) { + updatedSlo.revision++; + } + + return { hasBreakingChange, updatedSlo }; + } + + private async deleteObsoleteSLORevisionData(originalSlo: SLO) { + const originalSloTransformId = getSLOTransformId(originalSlo.id, originalSlo.revision); + await this.transformManager.stop(originalSloTransformId); + await this.transformManager.uninstall(originalSloTransformId); + await this.deleteRollupData(originalSlo.id, originalSlo.revision); + } + + private async deleteRollupData(sloId: string, sloRevision: number): Promise<void> { + await this.esClient.deleteByQuery({ + index: `${SLO_INDEX_TEMPLATE_NAME}*`, + wait_for_completion: false, + query: { + bool: { + filter: [{ term: { 'slo.id': sloId } }, { term: { 'slo.revision': sloRevision } }], + }, + }, + }); + } + + private toResponse(slo: SLO): UpdateSLOResponse { + return { + id: slo.id, + name: slo.name, + description: slo.description, + indicator: slo.indicator, + budgeting_method: slo.budgeting_method, + time_window: slo.time_window, + objective: slo.objective, + created_at: slo.created_at, + updated_at: slo.updated_at, + }; + } +} diff --git a/x-pack/plugins/observability/server/types/models/slo.ts b/x-pack/plugins/observability/server/types/models/slo.ts index 1e30c4972f997..2c4e1ad64f60b 100644 --- a/x-pack/plugins/observability/server/types/models/slo.ts +++ b/x-pack/plugins/observability/server/types/models/slo.ts @@ -9,48 +9,45 @@ import * as t from 'io-ts'; import { apmTransactionDurationIndicatorSchema, apmTransactionErrorRateIndicatorSchema, + budgetingMethodSchema, + dateType, indicatorSchema, indicatorTypesSchema, - commonSLOSchema, + objectiveSchema, + rollingTimeWindowSchema, } from '../schema'; -const sloSchema = t.intersection([ - commonSLOSchema, - t.type({ - id: t.string, - }), -]); +const sloSchema = t.type({ + id: t.string, + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + revision: t.number, + created_at: dateType, + updated_at: dateType, +}); -const apmTransactionErrorRateSLOSchema = t.intersection([ - sloSchema, - t.type({ indicator: apmTransactionErrorRateIndicatorSchema }), -]); +const storedSLOSchema = sloSchema; -const apmTransactionDurationSLOSchema = t.intersection([ - sloSchema, - t.type({ indicator: apmTransactionDurationIndicatorSchema }), -]); - -const storedSLOSchema = t.intersection([ - sloSchema, - t.type({ created_at: t.string, updated_at: t.string }), -]); +export { sloSchema, storedSLOSchema }; type SLO = t.TypeOf<typeof sloSchema>; -type APMTransactionErrorRateSLO = t.TypeOf<typeof apmTransactionErrorRateSLOSchema>; -type APMTransactionDurationSLO = t.TypeOf<typeof apmTransactionDurationSLOSchema>; -type SLI = t.TypeOf<typeof indicatorSchema>; -type SLITypes = t.TypeOf<typeof indicatorTypesSchema>; +type APMTransactionErrorRateIndicator = t.TypeOf<typeof apmTransactionErrorRateIndicatorSchema>; +type APMTransactionDurationIndicator = t.TypeOf<typeof apmTransactionDurationIndicatorSchema>; +type Indicator = t.TypeOf<typeof indicatorSchema>; +type IndicatorTypes = t.TypeOf<typeof indicatorTypesSchema>; type StoredSLO = t.TypeOf<typeof storedSLOSchema>; -export { apmTransactionDurationSLOSchema, apmTransactionErrorRateSLOSchema }; export type { SLO, - APMTransactionErrorRateSLO, - APMTransactionDurationSLO, - SLI, - SLITypes, + Indicator, + IndicatorTypes, + APMTransactionErrorRateIndicator, + APMTransactionDurationIndicator, StoredSLO, }; diff --git a/x-pack/plugins/observability/server/types/rest_specs/slo.ts b/x-pack/plugins/observability/server/types/rest_specs/slo.ts index 7ee912cfc3303..fe8905f41e9ac 100644 --- a/x-pack/plugins/observability/server/types/rest_specs/slo.ts +++ b/x-pack/plugins/observability/server/types/rest_specs/slo.ts @@ -6,10 +6,18 @@ */ import * as t from 'io-ts'; -import { commonSLOSchema } from '../schema'; +import { dateType, indicatorSchema } from '../schema'; +import { budgetingMethodSchema, objectiveSchema, rollingTimeWindowSchema } from '../schema/slo'; const createSLOParamsSchema = t.type({ - body: commonSLOSchema, + body: t.type({ + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + }), }); const createSLOResponseSchema = t.type({ @@ -28,11 +36,56 @@ const getSLOParamsSchema = t.type({ }), }); -const getSLOResponseSchema = t.intersection([t.type({ id: t.string }), commonSLOSchema]); +const getSLOResponseSchema = t.type({ + id: t.string, + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + revision: t.number, + created_at: dateType, + updated_at: dateType, +}); + +const updateSLOParamsSchema = t.type({ + path: t.type({ + id: t.string, + }), + body: t.partial({ + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + }), +}); + +const updateSLOResponseSchema = t.type({ + id: t.string, + name: t.string, + description: t.string, + indicator: indicatorSchema, + time_window: rollingTimeWindowSchema, + budgeting_method: budgetingMethodSchema, + objective: objectiveSchema, + created_at: dateType, + updated_at: dateType, +}); type CreateSLOParams = t.TypeOf<typeof createSLOParamsSchema.props.body>; type CreateSLOResponse = t.TypeOf<typeof createSLOResponseSchema>; type GetSLOResponse = t.TypeOf<typeof getSLOResponseSchema>; +type UpdateSLOParams = t.TypeOf<typeof updateSLOParamsSchema.props.body>; +type UpdateSLOResponse = t.TypeOf<typeof updateSLOResponseSchema>; -export { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema }; -export type { CreateSLOParams, CreateSLOResponse, GetSLOResponse }; +export { createSLOParamsSchema, deleteSLOParamsSchema, getSLOParamsSchema, updateSLOParamsSchema }; +export type { + CreateSLOParams, + CreateSLOResponse, + GetSLOResponse, + UpdateSLOParams, + UpdateSLOResponse, +}; diff --git a/x-pack/plugins/observability/server/types/schema/common.ts b/x-pack/plugins/observability/server/types/schema/common.ts index 02c8167b21fe6..521b729f6678d 100644 --- a/x-pack/plugins/observability/server/types/schema/common.ts +++ b/x-pack/plugins/observability/server/types/schema/common.ts @@ -5,10 +5,22 @@ * 2.0. */ +import { either } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; const ALL_VALUE = '*'; const allOrAnyString = t.union([t.literal(ALL_VALUE), t.string]); -export { allOrAnyString, ALL_VALUE }; +const dateType = new t.Type<Date, string, unknown>( + 'DateTime', + (input: unknown): input is Date => input instanceof Date, + (input: unknown, context: t.Context) => + either.chain(t.string.validate(input, context), (value: string) => { + const decoded = new Date(value); + return isNaN(decoded.getTime()) ? t.failure(input, context) : t.success(decoded); + }), + (date: Date): string => date.toISOString() +); + +export { allOrAnyString, ALL_VALUE, dateType }; diff --git a/x-pack/plugins/observability/server/types/schema/indicators.ts b/x-pack/plugins/observability/server/types/schema/indicators.ts index 4717c5fc915a0..2b9c06590b1a9 100644 --- a/x-pack/plugins/observability/server/types/schema/indicators.ts +++ b/x-pack/plugins/observability/server/types/schema/indicators.ts @@ -8,7 +8,7 @@ import * as t from 'io-ts'; import { allOrAnyString } from './common'; -const apmTransactionDurationIndicatorTypeSchema = t.literal('slo.apm.transaction_duration'); +const apmTransactionDurationIndicatorTypeSchema = t.literal<string>('slo.apm.transaction_duration'); const apmTransactionDurationIndicatorSchema = t.type({ type: apmTransactionDurationIndicatorTypeSchema, @@ -21,7 +21,9 @@ const apmTransactionDurationIndicatorSchema = t.type({ }), }); -const apmTransactionErrorRateIndicatorTypeSchema = t.literal('slo.apm.transaction_error_rate'); +const apmTransactionErrorRateIndicatorTypeSchema = t.literal<string>( + 'slo.apm.transaction_error_rate' +); const apmTransactionErrorRateIndicatorSchema = t.type({ type: apmTransactionErrorRateIndicatorTypeSchema, diff --git a/x-pack/plugins/observability/server/types/schema/slo.ts b/x-pack/plugins/observability/server/types/schema/slo.ts index 6f92bbe3d29d8..78389f742ea92 100644 --- a/x-pack/plugins/observability/server/types/schema/slo.ts +++ b/x-pack/plugins/observability/server/types/schema/slo.ts @@ -7,11 +7,9 @@ import * as t from 'io-ts'; -import { indicatorSchema } from './indicators'; - const rollingTimeWindowSchema = t.type({ duration: t.string, - is_rolling: t.literal(true), + is_rolling: t.literal<boolean>(true), }); const budgetingMethodSchema = t.literal('occurrences'); @@ -20,13 +18,4 @@ const objectiveSchema = t.type({ target: t.number, }); -const commonSLOSchema = t.type({ - name: t.string, - description: t.string, - indicator: indicatorSchema, - time_window: rollingTimeWindowSchema, - budgeting_method: budgetingMethodSchema, - objective: objectiveSchema, -}); - -export { commonSLOSchema, rollingTimeWindowSchema, budgetingMethodSchema, objectiveSchema }; +export { rollingTimeWindowSchema, budgetingMethodSchema, objectiveSchema }; From c8439872c4db191540d50e6b6ce00382e6850859 Mon Sep 17 00:00:00 2001 From: Rodney Norris <rodney.norris@elastic.co> Date: Tue, 4 Oct 2022 13:56:02 -0500 Subject: [PATCH 068/174] [Enterprise Search] Index Pipelines JSON Configurations (#142609) * [Enterprise Search] pipelines json configs tab Add the right panel with tabs for the Search Index Pipelines page, along with the JSON configurations tab. The index pipeliens get request was already returning the index specific ingest pipelines so I added the default pipeline to this result so we can get all the pipelines related to an index with a single call. * removed unnecessary listener The modal is closed from the pipelines logic and this createApiSuccess listener ended up being completely unnecessary. --- .../ml_inference/ml_inference_logic.ts | 13 -- .../pipelines/pipeline_json_badges.tsx | 147 ++++++++++++++++++ .../search_index/pipelines/pipelines.tsx | 36 ++++- .../pipelines_json_configurations.tsx | 142 +++++++++++++++++ .../pipelines_json_configurations_logic.ts | 102 ++++++++++++ .../search_index/pipelines/pipelines_logic.ts | 6 + .../shared/pipelines/is_managed.ts | 28 ++++ .../server/lib/pipelines/get_pipeline.ts | 25 +++ .../routes/enterprise_search/indices.ts | 12 +- 9 files changed, 493 insertions(+), 18 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts create mode 100644 x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts index e1251f03d044b..2c79e3b3ee8ff 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.ts @@ -18,16 +18,11 @@ import { import { HttpError, Status } from '../../../../../../../common/types/api'; import { MlInferencePipeline } from '../../../../../../../common/types/pipelines'; -import { generateEncodedPath } from '../../../../../shared/encode_path_params'; import { getErrorsFromHttpResponse } from '../../../../../shared/flash_messages/handle_api_errors'; -import { KibanaLogic } from '../../../../../shared/kibana'; import { MappingsApiLogic } from '../../../../api/mappings/mappings_logic'; import { CreateMlInferencePipelineApiLogic } from '../../../../api/ml_models/create_ml_inference_pipeline'; import { MLModelsApiLogic } from '../../../../api/ml_models/ml_models_logic'; -import { SEARCH_INDEX_TAB_PATH } from '../../../../routes'; -import { SearchIndexTabId } from '../../search_index'; - import { AddInferencePipelineFormErrors, InferencePipelineConfiguration } from './types'; import { isSupportedMLModel, @@ -126,14 +121,6 @@ export const MLInferenceLogic = kea< }, events: {}, listeners: ({ values, actions }) => ({ - createApiSuccess: () => { - KibanaLogic.values.navigateToUrl( - generateEncodedPath(SEARCH_INDEX_TAB_PATH, { - indexName: values.addInferencePipelineModal.indexName, - tabId: SearchIndexTabId.PIPELINES, - }) - ); - }, createPipeline: () => { const { addInferencePipelineModal: { configuration, indexName }, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx new file mode 100644 index 0000000000000..9a690ab437dab --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipeline_json_badges.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useValues } from 'kea'; + +import { EuiBadgeGroup, EuiBadge, EuiToolTip } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { DEFAULT_PIPELINE_NAME } from '../../../../../../common/constants'; + +import { isManagedPipeline } from '../../../../shared/pipelines/is_managed'; + +import { IndexPipelinesConfigurationsLogic } from './pipelines_json_configurations_logic'; + +const ManagedPipelineBadge: React.FC = () => ( + <EuiToolTip + position="top" + content={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.managed.description', + { defaultMessage: 'This pipeline is managed and cannot be edited' } + )} + > + <EuiBadge iconType="lock" color="warning"> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.managed', + { defaultMessage: 'Managed' } + )} + </EuiBadge> + </EuiToolTip> +); + +const UnmanagedPipelineBadge: React.FC = () => ( + <EuiToolTip + position="top" + content={ + <FormattedMessage + id="xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.unmanaged.description" + defaultMessage="Edit this pipeline from {ingestPipelines} in Stack Management" + values={{ + ingestPipelines: ( + <strong> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.ingestPipelines', + { defaultMessage: 'Ingest Pipelines' } + )} + </strong> + ), + }} + /> + } + > + <EuiBadge iconType="lockOpen"> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.unmanaged', + { defaultMessage: 'Unmanaged' } + )} + </EuiBadge> + </EuiToolTip> +); + +const SharedPipelineBadge: React.FC = () => ( + <EuiToolTip + position="top" + content={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.shared.description', + { defaultMessage: 'This pipeline is shared across all Enterprise Search ingestion methods' } + )} + > + <EuiBadge iconType="logstashIf" color="hollow"> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.shared', + { defaultMessage: 'Shared' } + )} + </EuiBadge> + </EuiToolTip> +); + +const IndexPipelineBadge: React.FC = () => ( + <EuiToolTip + position="top" + content={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.indexSpecific.description', + { defaultMessage: 'This pipeline contains configurations specific to this index only' } + )} + > + <EuiBadge iconType="document" color="hollow"> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.indexSpecific', + { defaultMessage: 'Index specific' } + )} + </EuiBadge> + </EuiToolTip> +); + +const MlInferenceBadge: React.FC = () => ( + <EuiToolTip + position="top" + content={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.mlInference.description', + { + defaultMessage: + 'This pipeline references one or more ML Inference Pipelines for this index', + } + )} + > + <EuiBadge iconType="compute" color="hollow"> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.mlInference', + { defaultMessage: 'ML Inference' } + )} + </EuiBadge> + </EuiToolTip> +); + +export const PipelineJSONBadges: React.FC = () => { + const { + indexName, + selectedPipeline: pipeline, + selectedPipelineId: pipelineName, + } = useValues(IndexPipelinesConfigurationsLogic); + if (!pipeline) { + return <></>; + } + const badges: JSX.Element[] = []; + if (isManagedPipeline(pipeline)) { + badges.push(<ManagedPipelineBadge />); + } else { + badges.push(<UnmanagedPipelineBadge />); + } + if (pipelineName === DEFAULT_PIPELINE_NAME) { + badges.push(<SharedPipelineBadge />); + } + if (pipelineName?.endsWith('@ml-inference')) { + badges.push(<MlInferenceBadge />); + } else if (pipelineName?.includes(indexName)) { + badges.push(<IndexPipelineBadge />); + } + return <EuiBadgeGroup gutterSize="s">{badges}</EuiBadgeGroup>; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx index 9cab24190a2de..f695b7c541c5a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines.tsx @@ -9,7 +9,15 @@ import React from 'react'; import { useActions, useValues } from 'kea'; -import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiPanel, + EuiSpacer, + EuiTabbedContent, + EuiTabbedContentTab, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -21,6 +29,7 @@ import { IngestPipelinesCard } from './ingest_pipelines_card'; import { AddMLInferencePipelineButton } from './ml_inference/add_ml_inference_button'; import { AddMLInferencePipelineModal } from './ml_inference/add_ml_inference_pipeline_modal'; import { MlInferencePipelineProcessorsCard } from './ml_inference_pipeline_processors_card'; +import { PipelinesJSONConfigurations } from './pipelines_json_configurations'; import { PipelinesLogic } from './pipelines_logic'; export const SearchIndexPipelines: React.FC = () => { @@ -34,6 +43,19 @@ export const SearchIndexPipelines: React.FC = () => { useActions(PipelinesLogic); const apiIndex = isApiIndex(index); + const pipelinesTabs: EuiTabbedContentTab[] = [ + { + content: <PipelinesJSONConfigurations />, + id: 'json-configurations', + name: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations', + { + defaultMessage: 'JSON configurations', + } + ), + }, + ]; + return ( <> <EuiSpacer /> @@ -82,8 +104,7 @@ export const SearchIndexPipelines: React.FC = () => { > <IngestPipelinesCard /> </DataPanel> - </EuiFlexItem> - <EuiFlexItem grow={5}> + <EuiSpacer /> <DataPanel hasBorder footerDocLink={ @@ -134,6 +155,15 @@ export const SearchIndexPipelines: React.FC = () => { <MlInferencePipelineProcessorsCard /> </DataPanel> </EuiFlexItem> + <EuiFlexItem grow={5}> + <EuiPanel color="subdued"> + <EuiTabbedContent + tabs={pipelinesTabs} + initialSelectedTab={pipelinesTabs[0]} + autoFocus="selected" + /> + </EuiPanel> + </EuiFlexItem> </EuiFlexGroup> {showAddMlInferencePipelineModal && ( <AddMLInferencePipelineModal onClose={closeAddMlInferencePipelineModal} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx new file mode 100644 index 0000000000000..3cba142347fc3 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations.tsx @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { useActions, useValues } from 'kea'; + +import { + EuiButtonEmpty, + EuiCodeBlock, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiLink, + EuiNotificationBadge, + EuiSelect, + EuiSpacer, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { DataPanel } from '../../../../shared/data_panel/data_panel'; +import { docLinks } from '../../../../shared/doc_links'; +import { HttpLogic } from '../../../../shared/http'; +import { isManagedPipeline } from '../../../../shared/pipelines/is_managed'; + +import { PipelineJSONBadges } from './pipeline_json_badges'; +import { IndexPipelinesConfigurationsLogic } from './pipelines_json_configurations_logic'; + +export const PipelinesJSONConfigurations: React.FC = () => { + const { http } = useValues(HttpLogic); + const { pipelineNames, selectedPipeline, selectedPipelineId, selectedPipelineJSON } = useValues( + IndexPipelinesConfigurationsLogic + ); + const { selectPipeline } = useActions(IndexPipelinesConfigurationsLogic); + return ( + <> + <EuiSpacer /> + <DataPanel + hasBorder + title={ + <h2> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.title', + { defaultMessage: 'Pipeline configurations' } + )} + </h2> + } + subtitle={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.subtitle', + { defaultMessage: 'View the JSON for your pipeline configurations on this index.' } + )} + footerDocLink={ + <EuiLink href={docLinks.ingestPipelines} target="_blank" color="subdued"> + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.ingestionPipelines.docLink', + { + defaultMessage: 'Learn more about how Enterprise Search uses ingest pipelines', + } + )} + </EuiLink> + } + action={ + pipelineNames.length > 0 && ( + <EuiNotificationBadge size="m">{pipelineNames.length}</EuiNotificationBadge> + ) + } + iconType="visVega" + > + <EuiFormRow + fullWidth + label={i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.selectLabel', + { defaultMessage: 'Select an ingest pipeline to view' } + )} + > + <EuiSelect + fullWidth + options={pipelineNames.map((name) => ({ text: name, value: name }))} + value={selectedPipelineId} + onChange={(e) => selectPipeline(e.target.value)} + /> + </EuiFormRow> + <EuiSpacer size="m" /> + {selectedPipeline && ( + <> + <EuiFlexGroup alignItems="center"> + <EuiFlexItem> + <PipelineJSONBadges /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + {isManagedPipeline(selectedPipeline) ? ( + <EuiButtonEmpty + size="s" + flush="both" + iconType="eye" + color="primary" + href={http.basePath.prepend( + `/app/management/ingest/ingest_pipelines/?pipeline=${selectedPipelineId}` + )} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.action.view', + { + defaultMessage: 'View in Stack Management', + } + )} + </EuiButtonEmpty> + ) : ( + <EuiButtonEmpty + size="s" + flush="both" + iconType="pencil" + color="primary" + href={http.basePath.prepend( + `/app/management/ingest/ingest_pipelines/edit/${selectedPipelineId}` + )} + > + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.tabs.jsonConfigurations.action.edit', + { + defaultMessage: 'Edit in Stack Management', + } + )} + </EuiButtonEmpty> + )} + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="m" /> + <EuiCodeBlock language="json" overflowHeight={300} isCopyable> + {selectedPipelineJSON} + </EuiCodeBlock> + </> + )} + </DataPanel> + </> + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.ts new file mode 100644 index 0000000000000..165cdfc99eb8b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_json_configurations_logic.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 { kea, MakeLogicType } from 'kea'; + +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; + +import { Actions } from '../../../../shared/api_logic/create_api_logic'; +import { + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse, + FetchCustomPipelineApiLogic, +} from '../../../api/index/fetch_custom_pipeline_api_logic'; +import { IndexNameLogic } from '../index_name_logic'; + +interface IndexPipelinesConfigurationsActions { + fetchIndexPipelinesDataSuccess: Actions< + FetchCustomPipelineApiLogicArgs, + FetchCustomPipelineApiLogicResponse + >['apiSuccess']; + selectPipeline: (pipeline: string) => { pipeline: string }; +} + +interface IndexPipelinesConfigurationsValues { + indexName: string; + indexPipelinesData: FetchCustomPipelineApiLogicResponse; + pipelineNames: string[]; + pipelines: Record<string, IngestPipeline>; + selectedPipeline: IngestPipeline | undefined; + selectedPipelineId: string; + selectedPipelineJSON: string; +} + +export const IndexPipelinesConfigurationsLogic = kea< + MakeLogicType<IndexPipelinesConfigurationsValues, IndexPipelinesConfigurationsActions> +>({ + actions: { + selectPipeline: (pipeline: string) => ({ pipeline }), + }, + connect: { + actions: [FetchCustomPipelineApiLogic, ['apiSuccess as fetchIndexPipelinesDataSuccess']], + values: [ + IndexNameLogic, + ['indexName'], + FetchCustomPipelineApiLogic, + ['data as indexPipelinesData'], + ], + }, + listeners: ({ actions, values }) => ({ + fetchIndexPipelinesDataSuccess: (pipelines) => { + const names = Object.keys(pipelines ?? {}).sort(); + if (names.length > 0 && values.selectedPipelineId.length === 0) { + const defaultPipeline = names.includes(values.indexName) ? values.indexName : names[0]; + actions.selectPipeline(defaultPipeline); + } + }, + }), + reducers: () => ({ + selectedPipelineId: [ + '', + { + selectPipeline: (_, { pipeline }) => pipeline, + }, + ], + }), + selectors: ({ selectors }) => ({ + pipelines: [ + () => [selectors.indexPipelinesData], + (indexPipelines: FetchCustomPipelineApiLogicResponse) => { + return indexPipelines ?? {}; + }, + ], + pipelineNames: [ + () => [selectors.pipelines], + (pipelines: Record<string, IngestPipeline>) => { + return Object.keys(pipelines).sort(); + }, + ], + selectedPipeline: [ + () => [selectors.selectedPipelineId, selectors.pipelines], + (selectedPipelineId: string, pipelines: Record<string, IngestPipeline>) => { + if (pipelines.hasOwnProperty(selectedPipelineId)) { + return pipelines[selectedPipelineId]; + } + return undefined; + }, + ], + selectedPipelineJSON: [ + () => [selectors.selectedPipeline], + (selectedPipeline: IngestPipeline | undefined) => { + if (selectedPipeline) { + return JSON.stringify(selectedPipeline, null, 2); + } + return ''; + }, + ], + }), +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts index 99d241507dd2a..bfcf297309d69 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -212,7 +212,10 @@ export const PipelinesLogic = kea<MakeLogicType<PipelinesValues, PipelinesAction actions.fetchCustomPipeline({ indexName: values.index.name }); }, createMlInferencePipelineSuccess: () => { + // Re-fetch processors to ensure we display newly added ml processor actions.fetchMlInferenceProcessors({ indexName: values.index.name }); + // Needed to ensure correct JSON is available in the JSON configurations tab + actions.fetchCustomPipeline({ indexName: values.index.name }); }, deleteMlPipelineError: (error) => flashAPIErrors(error), deleteMlPipelineSuccess: (value) => { @@ -229,7 +232,10 @@ export const PipelinesLogic = kea<MakeLogicType<PipelinesValues, PipelinesAction ) ); } + // Re-fetch processors to ensure we display newly removed ml processor actions.fetchMlInferenceProcessors({ indexName: values.index.name }); + // Needed to ensure correct JSON is available in the JSON configurations tab + actions.fetchCustomPipeline({ indexName: values.index.name }); }, fetchIndexApiSuccess: (index) => { if (!values.showModal) { diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts b/x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts new file mode 100644 index 0000000000000..30cf5ac145c87 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/pipelines/is_managed.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; + +interface IngestPipelineWithMetadata extends IngestPipeline { + _meta: { + managed?: boolean; + managed_by?: string; + }; +} + +const isIngestPipelineWithMetadata = ( + pipeline: IngestPipeline +): pipeline is IngestPipelineWithMetadata => { + return pipeline.hasOwnProperty('_meta'); +}; + +export const isManagedPipeline = (pipeline: IngestPipeline): boolean => { + if (isIngestPipelineWithMetadata(pipeline)) { + return Boolean(pipeline._meta.managed); + } + return false; +}; diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.ts new file mode 100644 index 0000000000000..a02b4cdd8b19b --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/get_pipeline.ts @@ -0,0 +1,25 @@ +/* + * 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 { IngestGetPipelineResponse } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IScopedClusterClient } from '@kbn/core/server'; + +export const getPipeline = async ( + pipelineName: string, + client: IScopedClusterClient +): Promise<IngestGetPipelineResponse> => { + try { + const pipelinesResponse = await client.asCurrentUser.ingest.getPipeline({ + id: pipelineName, + }); + + return pipelinesResponse; + } catch (error) { + // If we can't find anything, we return an empty object + return {}; + } +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index db46da11f5f57..ef6f8131ee2c1 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -13,6 +13,7 @@ import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; +import { DEFAULT_PIPELINE_NAME } from '../../../common/constants'; import { ErrorCode } from '../../../common/types/error_codes'; import { deleteConnectorById } from '../../lib/connectors/delete_connector'; @@ -28,6 +29,7 @@ import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_i import { generateApiKey } from '../../lib/indices/generate_api_key'; import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; +import { getPipeline } from '../../lib/pipelines/get_pipeline'; import { RouteDependencies } from '../../plugin'; import { createError } from '../../utils/create_error'; import { @@ -293,9 +295,15 @@ export function registerIndexRoutes({ elasticsearchErrorHandler(log, async (context, request, response) => { const indexName = decodeURIComponent(request.params.indexName); const { client } = (await context.core).elasticsearch; - const pipelines = await getCustomPipelines(indexName, client); + const [defaultPipeline, customPipelines] = await Promise.all([ + getPipeline(DEFAULT_PIPELINE_NAME, client), + getCustomPipelines(indexName, client), + ]); return response.ok({ - body: pipelines, + body: { + ...defaultPipeline, + ...customPipelines, + }, headers: { 'content-type': 'application/json' }, }); }) From 4a74dd383c7aa4a391d5a2753566ff6efd869c91 Mon Sep 17 00:00:00 2001 From: Giorgos Bamparopoulos <georgios.bamparopoulos@elastic.co> Date: Tue, 4 Oct 2022 20:01:39 +0100 Subject: [PATCH 069/174] [APM] Record e2e tests to Cypress dashboard and enable screenshots, videos and test retries (#142398) * Record e2e tests to Cypress dashboard and enable screenshots and videos * Delete videos that have no failures or retries * Set browser witdh and height for tests * Fix flaky test for storage explorer * Remove cypress plugin file * Fix typo in spec name * Enable test retries Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../scripts/steps/functional/apm_cypress.sh | 6 ++- x-pack/plugins/apm/ftr_e2e/cypress.config.ts | 19 +++---- .../storage_explorer/storage_explorer.cy.ts | 21 ++++++-- .../{aws_lamba.cy.ts => aws_lambda.cy.ts} | 0 .../index.ts => setup_cypress_node_events.ts} | 49 ++++++++++--------- 5 files changed, 59 insertions(+), 36 deletions(-) rename x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/{aws_lamba.cy.ts => aws_lambda.cy.ts} (100%) rename x-pack/plugins/apm/ftr_e2e/{cypress/plugins/index.ts => setup_cypress_node_events.ts} (62%) diff --git a/.buildkite/scripts/steps/functional/apm_cypress.sh b/.buildkite/scripts/steps/functional/apm_cypress.sh index 77b26fafee920..04f9aee280429 100755 --- a/.buildkite/scripts/steps/functional/apm_cypress.sh +++ b/.buildkite/scripts/steps/functional/apm_cypress.sh @@ -4,6 +4,8 @@ set -euo pipefail source .buildkite/scripts/common/util.sh +APM_CYPRESS_RECORD_KEY="$(retry 5 5 vault read -field=CYPRESS_RECORD_KEY secret/kibana-issues/dev/apm-cypress-dashboard-record-key)" + .buildkite/scripts/bootstrap.sh .buildkite/scripts/download_build_artifacts.sh @@ -15,4 +17,6 @@ cd "$XPACK_DIR" checks-reporter-with-killswitch "APM Cypress Tests" \ node plugins/apm/scripts/test/e2e.js \ - --kibana-install-dir "$KIBANA_BUILD_LOCATION" + --kibana-install-dir "$KIBANA_BUILD_LOCATION" \ + --record \ + --key "$APM_CYPRESS_RECORD_KEY" diff --git a/x-pack/plugins/apm/ftr_e2e/cypress.config.ts b/x-pack/plugins/apm/ftr_e2e/cypress.config.ts index 7a92b84ac36bd..bcccae43adc7e 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress.config.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress.config.ts @@ -6,9 +6,10 @@ */ import { defineConfig } from 'cypress'; -import { plugin } from './cypress/plugins'; +import { setupNodeEvents } from './setup_cypress_node_events'; module.exports = defineConfig({ + projectId: 'omwh6f', fileServerFolder: './cypress', fixturesFolder: './cypress/fixtures', screenshotsFolder: './cypress/screenshots', @@ -18,16 +19,16 @@ module.exports = defineConfig({ defaultCommandTimeout: 30000, execTimeout: 120000, pageLoadTimeout: 120000, - viewportHeight: 900, + viewportHeight: 1800, viewportWidth: 1440, - video: false, - screenshotOnRunFailure: false, + video: true, + videoUploadOnPasses: false, + screenshotOnRunFailure: true, + retries: { + runMode: 1, + }, e2e: { - // We've imported your old cypress plugins here. - // You may want to clean this up later by importing these. - setupNodeEvents(on, config) { - plugin(on, config); - }, + setupNodeEvents, baseUrl: 'http://localhost:5601', supportFile: './cypress/support/e2e.ts', specPattern: './cypress/e2e/**/*.cy.{js,jsx,ts,tsx}', diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts index 20577f8bf5793..2efebecf25756 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/power_user/storage_explorer/storage_explorer.cy.ts @@ -89,14 +89,27 @@ describe('Storage Explorer', () => { }); it('has a list of services and environments', () => { - cy.contains('opbeans-node'); - cy.contains('opbeans-java'); - cy.contains('opbeans-rum'); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-node' + ); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-java' + ); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-rum' + ); cy.get('td:contains(production)').should('have.length', 3); }); it('when clicking on a service it loads the service overview for that service', () => { - cy.contains('opbeans-node').click({ force: true }); + cy.contains( + '[data-test-subj="apmStorageExplorerServiceLink"]', + 'opbeans-node' + ).click(); + cy.url().should('include', '/apm/services/opbeans-node/overview'); cy.contains('h1', 'opbeans-node'); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts b/x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lambda.cy.ts similarity index 100% rename from x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lamba.cy.ts rename to x-pack/plugins/apm/ftr_e2e/cypress/e2e/read_only_user/service_overview/aws_lambda/aws_lambda.cy.ts diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts b/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts similarity index 62% rename from x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts rename to x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts index 8adaad0b71c63..0e3cd47966960 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/plugins/index.ts +++ b/x-pack/plugins/apm/ftr_e2e/setup_cypress_node_events.ts @@ -11,28 +11,13 @@ import { LogLevel, } from '@kbn/apm-synthtrace'; import { createEsClientForTesting } from '@kbn/test'; +import { some } from 'lodash'; +import del from 'del'; -// *********************************************************** -// This example plugins/index.ts can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ - -export const plugin: Cypress.PluginConfig = (on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - +export function setupNodeEvents( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +) { const client = createEsClientForTesting({ esUrl: config.env.ES_NODE, requestTimeout: config.env.ES_REQUEST_TIMEOUT, @@ -65,4 +50,24 @@ export const plugin: Cypress.PluginConfig = (on, config) => { return null; }, }); -}; + + on('after:spec', (spec, results) => { + // Delete videos that have no failures or retries + if (results && results.video) { + const failures = some(results.tests, (test) => { + return some(test.attempts, { state: 'failed' }); + }); + if (!failures) { + del(results.video); + } + } + }); + + on('before:browser:launch', (browser, launchOptions) => { + if (browser.name === 'electron' && browser.isHeadless) { + launchOptions.preferences.width = 1440; + launchOptions.preferences.height = 1600; + } + return launchOptions; + }); +} From d0def9eef1c796ec22f70987031e9831bdf29dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:02:36 -0400 Subject: [PATCH 070/174] [APM] Adding tech preview on the logs tab for AWS lambda service (#142605) * [APM] Adding tech preview on the logs tab for AWS lambda service * Addressing pr comments --- .../routing/templates/apm_service_template/index.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx index 0a0b17a4e273b..c1bda0d2acfe6 100644 --- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx @@ -267,9 +267,9 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { label: i18n.translate('xpack.apm.serviceDetails.metricsTabLabel', { defaultMessage: 'Metrics', }), - append: isServerlessAgent(runtimeName) ? ( + append: isServerlessAgent(runtimeName) && ( <TechnicalPreviewBadge icon="beaker" /> - ) : undefined, + ), hidden: isMetricsTabHidden({ agentName, runtimeName, @@ -318,6 +318,9 @@ function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) { label: i18n.translate('xpack.apm.home.serviceLogsTabLabel', { defaultMessage: 'Logs', }), + append: isServerlessAgent(runtimeName) && ( + <TechnicalPreviewBadge icon="beaker" /> + ), hidden: !agentName || isRumAgentName(agentName) || isMobileAgentName(agentName), }, From 1eb059de111082dc0ff35f260340a2114fb2d92f Mon Sep 17 00:00:00 2001 From: Abdul Wahab Zahid <awahab07@yahoo.com> Date: Tue, 4 Oct 2022 21:03:51 +0200 Subject: [PATCH 071/174] Fix: Consider all summary pings to determine if monitor is only fleet managed. (#142004) * Consider all ping to determine if monitor is only fleet managed. * Close popover on outside click as the built-in functionality is buggy. * Handle the case where only private locations are selected among a mix of locations. --- .../action_bar/action_bar.tsx | 109 ++++++++++-------- .../monitor_list/columns/test_now_col.tsx | 12 +- .../overview/monitor_list/monitor_list.tsx | 2 +- 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx index 0cae34a05e35b..6823206dc4b8c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/monitor_management/action_bar/action_bar.tsx @@ -14,6 +14,7 @@ import { EuiButtonEmpty, EuiText, EuiPopover, + EuiOutsideClickDetector, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -71,8 +72,10 @@ export const ActionBar = ({ const mouseMoveTimeoutIds = useRef<[number, number]>([0, 0]); const isReadOnly = monitor[ConfigKey.MONITOR_SOURCE_TYPE] === SourceType.PROJECT; - const hasServiceManagedLocation = monitor.locations?.some((loc) => loc.isServiceManaged); - const isOnlyPrivateLocations = !locations.some((loc) => loc.isServiceManaged); + const isAnyPublicLocationSelected = monitor.locations?.some((loc) => loc.isServiceManaged); + const isOnlyPrivateLocations = + !locations.some((loc) => loc.isServiceManaged) || + ((monitor.locations?.length ?? 0) > 0 && !isAnyPublicLocationSelected); const { data, status } = useFetcher(() => { if (!isSaving || !isValid) { @@ -150,55 +153,61 @@ export const ActionBar = ({ {onTestNow && ( <EuiFlexItem grow={false}> {/* Popover is used instead of EuiTooltip until the resolution of https://github.com/elastic/eui/issues/5604 */} - <EuiPopover - repositionOnScroll={true} - ownFocus={false} - initialFocus={''} - button={ - <EuiButton - css={{ width: '100%' }} - fill - size="s" - color="success" - iconType="play" - disabled={!isValid || isTestRunInProgress || !hasServiceManagedLocation} - data-test-subj={'monitorTestNowRunBtn'} - onClick={() => onTestNow()} - onMouseOver={() => { - // We need this custom logic to display a popover even when button is disabled. - clearTimeout(mouseMoveTimeoutIds.current[1]); - if (mouseMoveTimeoutIds.current[0] === 0) { - mouseMoveTimeoutIds.current[0] = setTimeout(() => { - clearTimeout(mouseMoveTimeoutIds.current[1]); - setIsPopoverOpen(true); - }, 250) as unknown as number; - } - }} - onMouseOut={() => { - // We need this custom logic to display a popover even when button is disabled. - clearTimeout(mouseMoveTimeoutIds.current[1]); - mouseMoveTimeoutIds.current[1] = setTimeout(() => { - clearTimeout(mouseMoveTimeoutIds.current[0]); - setIsPopoverOpen(false); - mouseMoveTimeoutIds.current = [0, 0]; - }, 100) as unknown as number; - }} - > - {testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL} - </EuiButton> - } - isOpen={isPopoverOpen} + <EuiOutsideClickDetector + onOutsideClick={() => { + setIsPopoverOpen(false); + }} > - <EuiText style={{ width: 260, outline: 'none' }}> - <p> - {isTestRunInProgress - ? TEST_SCHEDULED_LABEL - : isOnlyPrivateLocations || (isValid && !hasServiceManagedLocation) - ? PRIVATE_AVAILABLE_LABEL - : TEST_NOW_DESCRIPTION} - </p> - </EuiText> - </EuiPopover> + <EuiPopover + repositionOnScroll={true} + ownFocus={false} + initialFocus={''} + button={ + <EuiButton + css={{ width: '100%' }} + fill + size="s" + color="success" + iconType="play" + disabled={!isValid || isTestRunInProgress || !isAnyPublicLocationSelected} + data-test-subj={'monitorTestNowRunBtn'} + onClick={() => onTestNow()} + onMouseOver={() => { + // We need this custom logic to display a popover even when button is disabled. + clearTimeout(mouseMoveTimeoutIds.current[1]); + if (mouseMoveTimeoutIds.current[0] === 0) { + mouseMoveTimeoutIds.current[0] = setTimeout(() => { + clearTimeout(mouseMoveTimeoutIds.current[1]); + setIsPopoverOpen(true); + }, 250) as unknown as number; + } + }} + onMouseOut={() => { + // We need this custom logic to display a popover even when button is disabled. + clearTimeout(mouseMoveTimeoutIds.current[1]); + mouseMoveTimeoutIds.current[1] = setTimeout(() => { + clearTimeout(mouseMoveTimeoutIds.current[0]); + setIsPopoverOpen(false); + mouseMoveTimeoutIds.current = [0, 0]; + }, 100) as unknown as number; + }} + > + {testRun ? RE_RUN_TEST_LABEL : RUN_TEST_LABEL} + </EuiButton> + } + isOpen={isPopoverOpen} + > + <EuiText style={{ width: 260, outline: 'none' }}> + <p> + {isTestRunInProgress + ? TEST_SCHEDULED_LABEL + : isOnlyPrivateLocations || (isValid && !isAnyPublicLocationSelected) + ? PRIVATE_AVAILABLE_LABEL + : TEST_NOW_DESCRIPTION} + </p> + </EuiText> + </EuiPopover> + </EuiOutsideClickDetector> </EuiFlexItem> )} diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx index 17e8047ac64c7..390994646d08a 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/columns/test_now_col.tsx @@ -5,8 +5,8 @@ * 2.0. */ +import React, { useMemo } from 'react'; import { EuiButtonIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Ping } from '../../../../../../common/runtime_types'; import { testNowMonitorAction } from '../../../../state/actions'; @@ -16,17 +16,21 @@ import * as labels from '../translations'; export const TestNowColumn = ({ monitorId, configId, - selectedMonitor, + summaryPings, }: { monitorId: string; configId?: string; - selectedMonitor: Ping; + summaryPings: Ping[]; }) => { const dispatch = useDispatch(); const testNowRun = useSelector(testNowRunSelector(configId)); - if (selectedMonitor.monitor.fleet_managed) { + const isOnFleetManaged = useMemo(() => { + return summaryPings.every((ping) => !!ping.monitor.fleet_managed); + }, [summaryPings]); + + if (isOnFleetManaged) { return ( <EuiToolTip content={labels.PRIVATE_AVAILABLE_LABEL}> <>--</> diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx index 4ca114264feca..e0cc20d963a85 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/monitor_list/monitor_list.tsx @@ -212,7 +212,7 @@ export const MonitorListComponent: ({ <TestNowColumn monitorId={item.monitor_id} configId={item.configId} - selectedMonitor={item.state.summaryPings[0]} + summaryPings={item.state.summaryPings} /> ), }, From 7684d92efae2f90d409b371a4952a4f40b6aefde Mon Sep 17 00:00:00 2001 From: Sander Philipse <94373878+sphilipse@users.noreply.github.com> Date: Tue, 4 Oct 2022 21:10:16 +0200 Subject: [PATCH 072/174] [Enterprise Search] Custom pipelines update optimistically on creation (#142639) * [Enterprise Search] Custom pipelines update optimistically on creation * Fix tests --- .../pipelines/ingest_pipelines_card.tsx | 7 ++-- .../pipelines/pipelines_logic.test.ts | 16 ++++++--- .../search_index/pipelines/pipelines_logic.ts | 33 ++++++++++++++----- .../create_pipeline_definitions.test.ts | 6 ++-- .../pipelines/create_pipeline_definitions.ts | 10 +++--- 5 files changed, 48 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx index 7bf1ef06e1e75..c7a3872ce6e3e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ingest_pipelines_card.tsx @@ -37,7 +37,8 @@ import { PipelinesLogic } from './pipelines_logic'; export const IngestPipelinesCard: React.FC = () => { const { indexName } = useValues(IndexViewLogic); - const { canSetPipeline, index, pipelineState, showModal } = useValues(PipelinesLogic); + const { canSetPipeline, index, pipelineName, pipelineState, showModal } = + useValues(PipelinesLogic); const { closeModal, openModal, setPipelineState, savePipeline } = useActions(PipelinesLogic); const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic); const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic); @@ -61,7 +62,7 @@ export const IngestPipelinesCard: React.FC = () => { indexName={indexName} isGated={isGated} isLoading={false} - pipeline={pipelineState} + pipeline={{ ...pipelineState, name: pipelineName }} savePipeline={savePipeline} setPipeline={setPipelineState} showModal={showModal} @@ -111,7 +112,7 @@ export const IngestPipelinesCard: React.FC = () => { <CurlRequest document={{ body: 'body', title: 'Title' }} indexName={indexName} - pipeline={pipelineState} + pipeline={{ ...pipelineState, name: pipelineName }} /> </EuiAccordion> </EuiFlexItem> diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts index 7dc3a221cc57a..b847b2fdc6b8c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.test.ts @@ -23,14 +23,17 @@ const DEFAULT_PIPELINE_VALUES = { const DEFAULT_VALUES = { canSetPipeline: true, canUseMlInferencePipeline: false, + customPipelineData: undefined, defaultPipelineValues: DEFAULT_PIPELINE_VALUES, defaultPipelineValuesData: undefined, + hasIndexIngestionPipeline: false, index: undefined, + indexName: '', mlInferencePipelineProcessors: undefined, + pipelineName: DEFAULT_PIPELINE_VALUES.name, pipelineState: DEFAULT_PIPELINE_VALUES, - showModal: false, showAddMlInferencePipelineModal: false, - hasIndexIngestionPipeline: false, + showModal: false, }; describe('PipelinesLogic', () => { @@ -69,6 +72,7 @@ describe('PipelinesLogic', () => { ...connectorIndex, connector: { ...connectorIndex.connector }, }, + indexName: 'connector', }); expect(flashSuccessToast).toHaveBeenCalled(); expect(PipelinesLogic.actions.fetchIndexApiSuccess).toHaveBeenCalledWith({ @@ -86,8 +90,9 @@ describe('PipelinesLogic', () => { }); expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, - pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' }, hasIndexIngestionPipeline: true, + pipelineName: 'new_pipeline_name', + pipelineState: { ...DEFAULT_PIPELINE_VALUES, name: 'new_pipeline_name' }, }); }); describe('makeRequest', () => { @@ -152,12 +157,14 @@ describe('PipelinesLogic', () => { expect(PipelinesLogic.values).toEqual({ ...DEFAULT_VALUES, canUseMlInferencePipeline: true, + hasIndexIngestionPipeline: true, index: { ...connectorIndex, connector: { ...connectorIndex.connector, pipeline: newPipeline }, }, + indexName: 'connector', + pipelineName: 'new_pipeline_name', pipelineState: newPipeline, - hasIndexIngestionPipeline: true, }); }); it('should not set configState if modal is open', () => { @@ -172,6 +179,7 @@ describe('PipelinesLogic', () => { ...connectorIndex, connector: { ...connectorIndex.connector, pipeline: newPipeline }, }, + indexName: 'connector', showModal: true, }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts index bfcf297309d69..952c5baf77553 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/pipelines_logic.ts @@ -7,6 +7,7 @@ import { kea, MakeLogicType } from 'kea'; +import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types'; import { i18n } from '@kbn/i18n'; import { DEFAULT_PIPELINE_VALUES } from '../../../../../../common/constants'; @@ -105,11 +106,14 @@ type PipelinesActions = Pick< interface PipelinesValues { canSetPipeline: boolean; canUseMlInferencePipeline: boolean; + customPipelineData: Record<string, IngestPipeline | undefined>; defaultPipelineValues: IngestPipelineParams; defaultPipelineValuesData: IngestPipelineParams | null; hasIndexIngestionPipeline: boolean; index: FetchIndexApiResponse; + indexName: string; mlInferencePipelineProcessors: InferencePipeline[]; + pipelineName: string; pipelineState: IngestPipelineParams; showAddMlInferencePipelineModal: boolean; showModal: boolean; @@ -155,6 +159,8 @@ export const PipelinesLogic = kea<MakeLogicType<PipelinesValues, PipelinesAction ], ], values: [ + FetchCustomPipelineApiLogic, + ['data as customPipelineData'], FetchDefaultPipelineApiLogic, ['data as defaultPipelineValuesData'], FetchIndexApiLogic, @@ -296,15 +302,6 @@ export const PipelinesLogic = kea<MakeLogicType<PipelinesValues, PipelinesAction () => [selectors.index], (index: ElasticsearchIndexWithIngestion) => !isApiIndex(index), ], - defaultPipelineValues: [ - () => [selectors.defaultPipelineValuesData], - (pipeline: IngestPipelineParams | null) => pipeline ?? DEFAULT_PIPELINE_VALUES, - ], - hasIndexIngestionPipeline: [ - () => [selectors.pipelineState, selectors.defaultPipelineValues], - (pipelineState: IngestPipelineParams, defaultPipelineValues: IngestPipelineParams) => - pipelineState.name !== defaultPipelineValues.name, - ], canUseMlInferencePipeline: [ () => [ selectors.canSetPipeline, @@ -317,5 +314,23 @@ export const PipelinesLogic = kea<MakeLogicType<PipelinesValues, PipelinesAction pipelineState: IngestPipelineParams ) => canSetPipeline && hasIndexIngestionPipeline && pipelineState.run_ml_inference, ], + defaultPipelineValues: [ + () => [selectors.defaultPipelineValuesData], + (pipeline: IngestPipelineParams | null) => pipeline ?? DEFAULT_PIPELINE_VALUES, + ], + hasIndexIngestionPipeline: [ + () => [selectors.pipelineName, selectors.defaultPipelineValues], + (pipelineName: string, defaultPipelineValues: IngestPipelineParams) => + pipelineName !== defaultPipelineValues.name, + ], + indexName: [ + () => [selectors.index], + (index?: ElasticsearchIndexWithIngestion) => index?.name ?? '', + ], + pipelineName: [ + () => [selectors.pipelineState, selectors.customPipelineData, selectors.indexName], + (pipelineState, customPipelineData, indexName) => + customPipelineData && customPipelineData[indexName] ? indexName : pipelineState.name, + ], }), }); diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts index 183e27a765c2f..3d54396a7d742 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.test.ts @@ -30,11 +30,11 @@ describe('createIndexPipelineDefinitions util function', () => { jest.clearAllMocks(); }); - it('should create the pipelines', () => { + it('should create the pipelines', async () => { mockClient.ingest.putPipeline.mockImplementation(() => Promise.resolve({ acknowledged: true })); - expect( + await expect( createIndexPipelineDefinitions(indexName, mockClient as unknown as ElasticsearchClient) - ).toEqual(expectedResult); + ).resolves.toEqual(expectedResult); expect(mockClient.ingest.putPipeline).toHaveBeenCalledTimes(3); }); }); diff --git a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts index f32590fb516c5..4eba6dc5b0c8c 100644 --- a/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts +++ b/x-pack/plugins/enterprise_search/server/lib/pipelines/create_pipeline_definitions.ts @@ -27,24 +27,24 @@ export interface CreatedPipelines { * @param indexName the index for which the pipelines should be created. * @param esClient the Elasticsearch Client with which to create the pipelines. */ -export const createIndexPipelineDefinitions = ( +export const createIndexPipelineDefinitions = async ( indexName: string, esClient: ElasticsearchClient -): CreatedPipelines => { +): Promise<CreatedPipelines> => { // TODO: add back descriptions (see: https://github.com/elastic/elasticsearch-specification/issues/1827) - esClient.ingest.putPipeline({ + await esClient.ingest.putPipeline({ description: `Enterprise Search Machine Learning Inference pipeline for the '${indexName}' index`, id: getInferencePipelineNameFromIndexName(indexName), processors: [], version: 1, }); - esClient.ingest.putPipeline({ + await esClient.ingest.putPipeline({ description: `Enterprise Search customizable ingest pipeline for the '${indexName}' index`, id: `${indexName}@custom`, processors: [], version: 1, }); - esClient.ingest.putPipeline({ + await esClient.ingest.putPipeline({ _meta: { managed: true, managed_by: 'Enterprise Search', From 23524d99bb935427b71bebdf011200552a0a0566 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@elastic.co> Date: Tue, 4 Oct 2022 14:40:56 -0500 Subject: [PATCH 073/174] skip flaky suite. #142584 --- .../hooks/use_console_action_submitter.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx index 7ef506ea30f32..4402878081b9d 100644 --- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/hooks/use_console_action_submitter.test.tsx @@ -22,7 +22,8 @@ import type { ActionDetails } from '../../../../../common/endpoint/types'; import { act, waitFor } from '@testing-library/react'; import { responseActionsHttpMocks } from '../../../mocks/response_actions_http_mocks'; -describe('When using `useConsoleActionSubmitter()` hook', () => { +// FLAKY: https://github.com/elastic/kibana/issues/142584 +describe.skip('When using `useConsoleActionSubmitter()` hook', () => { let render: () => ReturnType<AppContextTestRender['render']>; let renderResult: ReturnType<AppContextTestRender['render']>; let renderArgs: UseConsoleActionSubmitterOptions; From bd6893302dd08fac7e401e33b91082ccc6673566 Mon Sep 17 00:00:00 2001 From: Alison Goryachev <alison.goryachev@elastic.co> Date: Tue, 4 Oct 2022 15:42:41 -0400 Subject: [PATCH 074/174] [Guided onboarding] Fix steps status (#142526) --- .../components/guide_panel_step.styles.ts | 13 ++- .../public/components/guide_panel_step.tsx | 84 ++++++++++--------- 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts index 498059564e6ea..8d34d45b7a53c 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.styles.ts @@ -8,18 +8,25 @@ import { EuiThemeComputed } from '@elastic/eui'; import { css } from '@emotion/react'; +import { StepStatus } from '../../common/types'; -export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed) => ({ +export const getGuidePanelStepStyles = (euiTheme: EuiThemeComputed, stepStatus: StepStatus) => ({ stepNumber: css` width: 24px; height: 24px; border-radius: 50%; - border: 2px solid ${euiTheme.colors.success}; + border: 2px solid + ${stepStatus === 'inactive' ? euiTheme.colors.lightShade : euiTheme.colors.success}; font-weight: ${euiTheme.font.weight.medium}; text-align: center; line-height: 1.4; + color: ${stepStatus === 'inactive' ? euiTheme.colors.subduedText : euiTheme.colors.text}; `, stepTitle: css` - font-weight: ${euiTheme.font.weight.bold}; + font-weight: ${euiTheme.font.weight.semiBold}; + color: ${stepStatus === 'inactive' ? euiTheme.colors.subduedText : euiTheme.colors.text}; + .euiAccordion-isOpen & { + color: ${euiTheme.colors.title}; + } `, }); diff --git a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx index 8a98d87debf1a..c05ad6ec310c7 100644 --- a/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx +++ b/src/plugins/guided_onboarding/public/components/guide_panel_step.tsx @@ -40,10 +40,10 @@ export const GuideStep = ({ navigateToStep, }: GuideStepProps) => { const { euiTheme } = useEuiTheme(); - const styles = getGuidePanelStepStyles(euiTheme); + const styles = getGuidePanelStepStyles(euiTheme, stepStatus); - const buttonContent = ( - <EuiFlexGroup gutterSize="s" responsive={false} justifyContent="center" alignItems="center"> + const stepTitleContent = ( + <EuiFlexGroup gutterSize="s" responsive={false}> <EuiFlexItem grow={false}> {stepStatus === 'complete' ? ( <EuiIcon type="checkInCircleFilled" size="l" color={euiTheme.colors.success} /> @@ -61,45 +61,49 @@ export const GuideStep = ({ return ( <div data-test-subj="guidePanelStep"> - <EuiAccordion - id={accordionId} - buttonContent={buttonContent} - arrowDisplay="right" - forceState={stepStatus === 'in_progress' || stepStatus === 'active' ? 'open' : 'closed'} - > - <> - <EuiSpacer size="s" /> + {stepStatus === 'complete' ? ( + <>{stepTitleContent}</> + ) : ( + <EuiAccordion + id={accordionId} + buttonContent={stepTitleContent} + arrowDisplay="right" + initialIsOpen={stepStatus === 'in_progress' || stepStatus === 'active'} + > + <> + <EuiSpacer size="s" /> - <EuiText size="s"> - <ul> - {stepConfig.descriptionList.map((description, index) => { - return <li key={`description-${index}`}>{description}</li>; - })} - </ul> - </EuiText> + <EuiText size="s"> + <ul> + {stepConfig.descriptionList.map((description, index) => { + return <li key={`description-${index}`}>{description}</li>; + })} + </ul> + </EuiText> - <EuiSpacer /> - {(stepStatus === 'in_progress' || stepStatus === 'active') && ( - <EuiFlexGroup justifyContent="flexEnd"> - <EuiFlexItem grow={false}> - <EuiButton - onClick={() => navigateToStep(stepConfig.id, stepConfig.location)} - fill - data-test-subj="activeStepButtonLabel" - > - {stepStatus === 'active' - ? i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { - defaultMessage: 'Start', - }) - : i18n.translate('guidedOnboarding.dropdownPanel.continueStepButtonLabel', { - defaultMessage: 'Continue', - })} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - )} - </> - </EuiAccordion> + <EuiSpacer /> + {(stepStatus === 'in_progress' || stepStatus === 'active') && ( + <EuiFlexGroup justifyContent="flexEnd"> + <EuiFlexItem grow={false}> + <EuiButton + onClick={() => navigateToStep(stepConfig.id, stepConfig.location)} + fill + data-test-subj="activeStepButtonLabel" + > + {stepStatus === 'active' + ? i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', { + defaultMessage: 'Start', + }) + : i18n.translate('guidedOnboarding.dropdownPanel.continueStepButtonLabel', { + defaultMessage: 'Continue', + })} + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + )} + </> + </EuiAccordion> + )} <EuiHorizontalRule margin="l" /> </div> From 76c234ad54709843cd92d7625cc806d75a1e3d53 Mon Sep 17 00:00:00 2001 From: Dominique Clarke <dominique.clarke@elastic.co> Date: Tue, 4 Oct 2022 15:45:57 -0400 Subject: [PATCH 075/174] [Synthetics] add zip url deprecation message to package and Uptime overview (#141679) * synthetics - add zip url deprecation message to package * add overview deprecation notice * adjust tests * add api integration tests * adjust functional tests * add dismiss logic and tests * adjust test * adjust content * Update x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx Co-authored-by: florent-leborgne <florent.leborgne@elastic.co> * Update x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx * Update x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx Co-authored-by: florent-leborgne <florent.leborgne@elastic.co> * Update x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx Co-authored-by: florent-leborgne <florent.leborgne@elastic.co> * Update x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/index.tsx * Update x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/index.tsx * Update x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx * add docs links * adjust types * adjust kibana services helper Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: florent-leborgne <florent.leborgne@elastic.co> --- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + .../synthetics/common/constants/rest_api.ts | 1 + .../plugins/synthetics/common/types/index.ts | 1 + .../common/types/zip_url_deprecation.ts | 10 + .../synthetics/public/kibana_services.ts | 2 +- .../browser/source_field.test.tsx | 25 +- .../fleet_package/browser/source_field.tsx | 235 ++++++++++-------- .../fleet_package/custom_fields.test.tsx | 9 +- ...s_policy_create_extension_wrapper.test.tsx | 6 +- ...ics_policy_edit_extension_wrapper.test.tsx | 8 +- .../overview/zip_url_deprecation/index.tsx | 85 +++++++ .../zip_url_deprecation.test.tsx | 70 ++++++ .../public/legacy_uptime/pages/overview.tsx | 18 +- .../state/api/has_zip_url_monitors.ts | 14 ++ .../routes/fleet/get_has_zip_url_monitors.ts | 31 +++ .../plugins/synthetics/server/routes/index.ts | 2 + .../api_integration/apis/uptime/rest/index.ts | 1 + .../uptime/rest/uptime_zip_url_deprecation.ts | 159 ++++++++++++ .../synthetics_integration_page.ts | 2 + 20 files changed, 567 insertions(+), 114 deletions(-) create mode 100644 x-pack/plugins/synthetics/common/types/zip_url_deprecation.ts create mode 100644 x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/index.tsx create mode 100644 x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/zip_url_deprecation.test.tsx create mode 100644 x-pack/plugins/synthetics/public/legacy_uptime/state/api/has_zip_url_monitors.ts create mode 100644 x-pack/plugins/synthetics/server/routes/fleet/get_has_zip_url_monitors.ts create mode 100644 x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 445bf9458d457..c4139f6423fae 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -458,6 +458,7 @@ export const getDocLinks = ({ kibanaBranch }: GetDocLinkOptions): DocLinks => { userExperience: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/user-experience.html`, createAlerts: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/create-alerts.html`, syntheticsCommandReference: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetics-configuration.html#synthetics-configuration-playwright-options`, + syntheticsProjectMonitors: `${ELASTIC_WEBSITE_URL}guide/en/observability/${DOC_LINK_VERSION}/synthetic-run-tests.html#synthetic-monitor-choose-project`, }, alerting: { guide: `${KIBANA_DOCS}create-and-manage-rules.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index d9902a7b11de3..38bf25b0aba7e 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -340,6 +340,7 @@ export interface DocLinks { userExperience: string; createAlerts: string; syntheticsCommandReference: string; + syntheticsProjectMonitors: string; }>; readonly alerting: Record<string, string>; readonly maps: Readonly<{ diff --git a/x-pack/plugins/synthetics/common/constants/rest_api.ts b/x-pack/plugins/synthetics/common/constants/rest_api.ts index d0d783e424f3a..dad434194f21d 100644 --- a/x-pack/plugins/synthetics/common/constants/rest_api.ts +++ b/x-pack/plugins/synthetics/common/constants/rest_api.ts @@ -45,6 +45,7 @@ export enum API_URLS { TRIGGER_MONITOR = '/internal/uptime/service/monitors/trigger', SERVICE_ALLOWED = '/internal/uptime/service/allowed', SYNTHETICS_APIKEY = '/internal/uptime/service/api_key', + SYNTHETICS_HAS_ZIP_URL_MONITORS = '/internal/uptime/fleet/has_zip_url_monitors', // Project monitor public endpoint SYNTHETICS_MONITORS_PROJECT = '/api/synthetics/service/project/monitors', diff --git a/x-pack/plugins/synthetics/common/types/index.ts b/x-pack/plugins/synthetics/common/types/index.ts index c8bd99a2a4802..b26fcaccba881 100644 --- a/x-pack/plugins/synthetics/common/types/index.ts +++ b/x-pack/plugins/synthetics/common/types/index.ts @@ -8,3 +8,4 @@ export * from './monitor_duration'; export * from './synthetics_monitor'; export * from './monitor_validation'; +export * from './zip_url_deprecation'; diff --git a/x-pack/plugins/synthetics/common/types/zip_url_deprecation.ts b/x-pack/plugins/synthetics/common/types/zip_url_deprecation.ts new file mode 100644 index 0000000000000..793d1d35ddb97 --- /dev/null +++ b/x-pack/plugins/synthetics/common/types/zip_url_deprecation.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. + */ + +export interface SyntheticsHasZipUrlMonitorsResponse { + hasZipUrlMonitors: boolean; +} diff --git a/x-pack/plugins/synthetics/public/kibana_services.ts b/x-pack/plugins/synthetics/public/kibana_services.ts index eb413b0260fb1..eb125eb87c744 100644 --- a/x-pack/plugins/synthetics/public/kibana_services.ts +++ b/x-pack/plugins/synthetics/public/kibana_services.ts @@ -12,4 +12,4 @@ export function setStartServices(core: CoreStart) { coreStart = core; } -export const getDocLinks = () => coreStart.docLinks; +export const getDocLinks = () => coreStart?.docLinks; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx index d334ad20f42e6..34706d4d17cbc 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.test.tsx @@ -68,6 +68,9 @@ describe('<SourceField />', () => { render(<WrappedComponent />); const zipUrl = 'test.zip'; + const zip = screen.getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); + const zipUrlField = screen.getByTestId('syntheticsBrowserZipUrl'); fireEvent.change(zipUrlField, { target: { value: zipUrl } }); @@ -79,6 +82,9 @@ describe('<SourceField />', () => { it('calls onBlur', () => { render(<WrappedComponent />); + const zip = screen.getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); + const zipUrlField = screen.getByTestId('syntheticsBrowserZipUrl'); fireEvent.click(zipUrlField); fireEvent.blur(zipUrlField); @@ -86,7 +92,15 @@ describe('<SourceField />', () => { expect(onBlur).toBeCalledWith(ConfigKey.SOURCE_ZIP_URL); }); - it('shows ZipUrl source type by default', async () => { + it('selects inline script by default', () => { + render(<WrappedComponent />); + + expect( + screen.getByText('Runs Synthetic test scripts that are defined inline.') + ).toBeInTheDocument(); + }); + + it('shows zip source type by default', async () => { render(<WrappedComponent />); expect(screen.getByTestId('syntheticsSourceTab__zipUrl')).toBeInTheDocument(); @@ -116,4 +130,13 @@ describe('<SourceField />', () => { expect(getByText('Parameters')).toBeInTheDocument(); }); + + it('shows deprecated for zip url', () => { + const { getByText, getByTestId } = render(<WrappedComponent />); + + const zip = getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); + + expect(getByText('Zip URL is deprecated')).toBeInTheDocument(); + }); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx index a15b0f8a7561d..397c4be744e61 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/browser/source_field.tsx @@ -10,6 +10,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiCode, + EuiCallOut, + EuiLink, EuiTabbedContent, EuiTabbedContentTab, EuiFormRow, @@ -26,6 +28,7 @@ import { CodeEditor } from '../code_editor'; import { ScriptRecorderFields } from './script_recorder_fields'; import { ZipUrlTLSFields } from './zip_url_tls_fields'; import { ConfigKey, MonacoEditorLangId, Validation } from '../types'; +import { getDocLinks } from '../../../../kibana_services'; enum SourceType { INLINE = 'syntheticsBrowserInlineConfig', @@ -64,14 +67,12 @@ export const defaultValues = { fileName: '', }; -const getDefaultTab = (defaultConfig: SourceConfig, isZipUrlSourceEnabled = true) => { +const getDefaultTab = (defaultConfig: SourceConfig) => { if (defaultConfig.inlineScript && defaultConfig.isGeneratedScript) { return SourceType.SCRIPT_RECORDER; - } else if (defaultConfig.inlineScript) { + } else { return SourceType.INLINE; } - - return isZipUrlSourceEnabled ? SourceType.ZIP : SourceType.INLINE; }; export const SourceField = ({ @@ -81,9 +82,7 @@ export const SourceField = ({ validate, }: Props) => { const { isZipUrlSourceEnabled } = usePolicyConfigContext(); - const [sourceType, setSourceType] = useState<SourceType>( - getDefaultTab(defaultConfig, isZipUrlSourceEnabled) - ); + const [sourceType, setSourceType] = useState<SourceType>(getDefaultTab(defaultConfig)); const [config, setConfig] = useState<SourceConfig>(defaultConfig); useEffect(() => { @@ -145,12 +144,138 @@ export const SourceField = ({ const zipUrlSourceTabId = 'syntheticsBrowserZipURLConfig'; const allTabs = [ + { + id: 'syntheticsBrowserInlineConfig', + name: ( + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.label" + defaultMessage="Inline script" + /> + ), + 'data-test-subj': `syntheticsSourceTab__inline`, + content: ( + <> + <EuiFormRow + isInvalid={isSourceInlineInvalid} + error={ + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.error" + defaultMessage="Script is required" + /> + } + helpText={ + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.helpText" + defaultMessage="Runs Synthetic test scripts that are defined inline." + /> + } + > + <CodeEditor + ariaLabel={i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.javascript.ariaLabel', + { + defaultMessage: 'JavaScript code editor', + } + )} + id="javascript" + languageId={MonacoEditorLangId.JAVASCRIPT} + onChange={(code) => { + setConfig((prevConfig) => ({ ...prevConfig, inlineScript: code })); + onFieldBlur(ConfigKey.SOURCE_INLINE); + }} + value={config.inlineScript} + /> + </EuiFormRow> + {params} + </> + ), + }, + { + id: 'syntheticsBrowserScriptRecorderConfig', + name: ( + <EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs"> + <EuiFlexItem grow={false}> + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.label" + defaultMessage="Script recorder" + /> + </EuiFlexItem> + <StyledBetaBadgeWrapper grow={false}> + <EuiBetaBadge + label={i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalLabel', + { + defaultMessage: 'Tech preview', + } + )} + iconType="beaker" + tooltipContent={i18n.translate( + 'xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalTooltip', + { + defaultMessage: + 'Preview the quickest way to create Elastic Synthetics monitoring scripts with our Elastic Synthetics Recorder', + } + )} + /> + </StyledBetaBadgeWrapper> + </EuiFlexGroup> + ), + 'data-test-subj': 'syntheticsSourceTab__scriptRecorder', + content: ( + <> + <ScriptRecorderFields + onChange={({ scriptText, fileName }) => + setConfig((prevConfig) => ({ + ...prevConfig, + inlineScript: scriptText, + isGeneratedScript: true, + fileName, + })) + } + script={config.inlineScript} + fileName={config.fileName} + /> + <EuiSpacer size="s" /> + {params} + </> + ), + }, { id: zipUrlSourceTabId, name: zipUrlLabel, 'data-test-subj': `syntheticsSourceTab__zipUrl`, content: ( <> + <EuiSpacer size="m" /> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.zipUrl.deprecation.title" + defaultMessage="Zip URL is deprecated" + /> + } + size="s" + color="warning" + > + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.zipUrl.deprecation.content" + defaultMessage="Zip URL is deprecated and will be removed in a future version. Use project monitors instead to create monitors from a remote repository and to migrate existing Zip URL monitors. {link}" + values={{ + link: ( + <EuiLink + target="_blank" + href={getDocLinks()?.links?.observability?.syntheticsProjectMonitors} + external + > + <FormattedMessage + id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.monitorType.browser.zipUrl.deprecation.link" + defaultMessage="Learn more" + /> + </EuiLink> + ), + }} + /> + </EuiCallOut> <EuiSpacer size="m" /> <EuiFormRow label={zipUrlLabel} @@ -278,102 +403,6 @@ export const SourceField = ({ </> ), }, - { - id: 'syntheticsBrowserInlineConfig', - name: ( - <FormattedMessage - id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.label" - defaultMessage="Inline script" - /> - ), - 'data-test-subj': `syntheticsSourceTab__inline`, - content: ( - <> - <EuiFormRow - isInvalid={isSourceInlineInvalid} - error={ - <FormattedMessage - id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.error" - defaultMessage="Script is required" - /> - } - helpText={ - <FormattedMessage - id="xpack.synthetics.createPackagePolicy.stepConfigure.monitorIntegrationSettingsSection.browser.inlineScript.helpText" - defaultMessage="Runs Synthetic test scripts that are defined inline." - /> - } - > - <CodeEditor - ariaLabel={i18n.translate( - 'xpack.synthetics.createPackagePolicy.stepConfigure.requestBody.codeEditor.javascript.ariaLabel', - { - defaultMessage: 'JavaScript code editor', - } - )} - id="javascript" - languageId={MonacoEditorLangId.JAVASCRIPT} - onChange={(code) => { - setConfig((prevConfig) => ({ ...prevConfig, inlineScript: code })); - onFieldBlur(ConfigKey.SOURCE_INLINE); - }} - value={config.inlineScript} - /> - </EuiFormRow> - {params} - </> - ), - }, - { - id: 'syntheticsBrowserScriptRecorderConfig', - name: ( - <EuiFlexGroup responsive={false} alignItems="center" gutterSize="xs"> - <EuiFlexItem grow={false}> - <FormattedMessage - id="xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.label" - defaultMessage="Script recorder" - /> - </EuiFlexItem> - <StyledBetaBadgeWrapper grow={false}> - <EuiBetaBadge - label={i18n.translate( - 'xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalLabel', - { - defaultMessage: 'Tech preview', - } - )} - iconType="beaker" - tooltipContent={i18n.translate( - 'xpack.synthetics.createPackagePolicy.stepConfigure.browser.scriptRecorder.experimentalTooltip', - { - defaultMessage: - 'Preview the quickest way to create Elastic Synthetics monitoring scripts with our Elastic Synthetics Recorder', - } - )} - /> - </StyledBetaBadgeWrapper> - </EuiFlexGroup> - ), - 'data-test-subj': 'syntheticsSourceTab__scriptRecorder', - content: ( - <> - <ScriptRecorderFields - onChange={({ scriptText, fileName }) => - setConfig((prevConfig) => ({ - ...prevConfig, - inlineScript: scriptText, - isGeneratedScript: true, - fileName, - })) - } - script={config.inlineScript} - fileName={config.fileName} - /> - <EuiSpacer size="s" /> - {params} - </> - ), - }, ]; const tabs = isZipUrlSourceEnabled diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx index f9a02507f5ec3..1de6946982a9e 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/custom_fields.test.tsx @@ -7,7 +7,7 @@ import 'jest-canvas-mock'; import React from 'react'; -import { fireEvent, waitFor } from '@testing-library/react'; +import { screen, fireEvent, waitFor } from '@testing-library/react'; import { render } from '../../lib/helper/rtl_helpers'; import { TCPContextProvider, @@ -247,6 +247,13 @@ describe('<CustomFields />', () => { fireEvent.change(monitorType, { target: { value: DataStream.BROWSER } }); // expect browser fields to be in the DOM + expect( + screen.getByText('Runs Synthetic test scripts that are defined inline.') + ).toBeInTheDocument(); + + const zip = screen.getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); + getAllByLabelText('Zip URL').forEach((node) => { expect(node).toBeInTheDocument(); }); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx index 1749a29ffa4fd..9c1ff4fc16126 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_create_extension_wrapper.test.tsx @@ -702,11 +702,15 @@ describe('<SyntheticsPolicyCreateExtension />', () => { }); it('handles browser validation', async () => { - const { getByText, getByLabelText, queryByText, getByRole } = render(<WrappedComponent />); + const { getByText, getByLabelText, queryByText, getByRole, getByTestId } = render( + <WrappedComponent /> + ); const monitorType = getByLabelText('Monitor Type') as HTMLInputElement; fireEvent.change(monitorType, { target: { value: DataStream.BROWSER } }); + const zip = getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); const zipUrl = getByRole('textbox', { name: 'Zip URL' }) as HTMLInputElement; const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx index 28eeafc00adcc..74d890053aa4c 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/fleet_package/synthetics_policy_edit_extension_wrapper.test.tsx @@ -636,10 +636,12 @@ describe('<SyntheticsPolicyEditExtension />', () => { }, ], }; - const { getByText, getByLabelText, queryByText, getByRole } = render( + const { getByText, getByLabelText, queryByText, getByRole, getByTestId } = render( <WrappedComponent policy={currentPolicy} /> ); + const zip = getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); const zipUrl = getByRole('textbox', { name: 'Zip URL' }) as HTMLInputElement; const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement; @@ -1074,9 +1076,11 @@ describe('<SyntheticsPolicyEditExtension />', () => { }, ], }; - const { getByLabelText, queryByLabelText, getByRole } = render( + const { getByLabelText, queryByLabelText, getByRole, getByTestId } = render( <WrappedComponent policy={currentPolicy} /> ); + const zip = getByTestId('syntheticsSourceTab__zipUrl'); + fireEvent.click(zip); const zipUrl = getByRole('textbox', { name: 'Zip URL' }) as HTMLInputElement; const monitorIntervalNumber = getByLabelText('Number') as HTMLInputElement; const monitorIntervalUnit = getByLabelText('Unit') as HTMLInputElement; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/index.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/index.tsx new file mode 100644 index 0000000000000..f5d841fc73637 --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/index.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useState, useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { EuiCallOut, EuiLink, EuiButton, EuiFlexItem, EuiFlexGroup, EuiSpacer } from '@elastic/eui'; +import { useFetcher } from '@kbn/observability-plugin/public'; +import { getHasZipUrlMonitors } from '../../../state/api/has_zip_url_monitors'; +import { getDocLinks } from '../../../../kibana_services'; + +export const ZIP_URL_DEPRECATION_SESSION_STORAGE_KEY = + 'SYNTHETICS_ZIP_URL_DEPRECATION_HAS_BEEN_DISMISSED'; + +export const ZipUrlDeprecation = () => { + const noticeHasBeenDismissed = + window.sessionStorage.getItem(ZIP_URL_DEPRECATION_SESSION_STORAGE_KEY) === 'true'; + const { data, loading } = useFetcher(() => { + return getHasZipUrlMonitors(); + }, []); + const hasZipUrlMonitors = !loading && data && data.hasZipUrlMonitors; + const [shouldShowNotice, setShouldShowNotice] = useState( + Boolean(hasZipUrlMonitors && !noticeHasBeenDismissed) + ); + + const handleDismissDeprecationNotice = () => { + window.sessionStorage.setItem(ZIP_URL_DEPRECATION_SESSION_STORAGE_KEY, 'true'); + setShouldShowNotice(false); + }; + + useEffect(() => { + setShouldShowNotice(Boolean(hasZipUrlMonitors && !noticeHasBeenDismissed)); + }, [hasZipUrlMonitors, noticeHasBeenDismissed]); + + return shouldShowNotice ? ( + <> + <EuiCallOut + title={ + <FormattedMessage + id="xpack.synthetics.browser.zipUrl.deprecation.title" + defaultMessage="Deprecation notice" + /> + } + color="warning" + > + <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiFlexItem> + <span> + <FormattedMessage + id="xpack.synthetics.browser.zipUrl.deprecation.content" + defaultMessage="Zip URL is deprecated and will be removed in a future version. Use project monitors instead to create monitors from a remote repository and to migrate existing Zip URL monitors. {link}" + values={{ + link: ( + <EuiLink + target="_blank" + href={getDocLinks()?.links?.observability?.syntheticsProjectMonitors} + external + > + <FormattedMessage + id="xpack.synthetics.browser.zipUrl.deprecation.link" + defaultMessage="Learn more" + /> + </EuiLink> + ), + }} + /> + </span> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiButton onClick={handleDismissDeprecationNotice} color="warning"> + <FormattedMessage + id="xpack.synthetics.browser.zipUrl.deprecation.dismiss" + defaultMessage="Dismiss" + /> + </EuiButton> + </EuiFlexItem> + </EuiFlexGroup> + </EuiCallOut> + <EuiSpacer size="s" /> + </> + ) : null; +}; diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/zip_url_deprecation.test.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/zip_url_deprecation.test.tsx new file mode 100644 index 0000000000000..2d49f529e0379 --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/components/overview/zip_url_deprecation/zip_url_deprecation.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { StubBrowserStorage } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; +import { render } from '../../../lib/helper/rtl_helpers'; +import { ZipUrlDeprecation, ZIP_URL_DEPRECATION_SESSION_STORAGE_KEY } from '.'; +import * as observabilityPublic from '@kbn/observability-plugin/public'; + +export const mockStorage = new StubBrowserStorage(); +jest.mock('@kbn/observability-plugin/public'); + +describe('ZipUrlDeprecation', () => { + const { FETCH_STATUS } = observabilityPublic; + it('shows deprecation notice when hasZipUrlMonitors is true', () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: FETCH_STATUS.SUCCESS, + data: { hasZipUrlMonitors: true }, + refetch: () => null, + loading: false, + }); + + render(<ZipUrlDeprecation />); + expect(screen.getByText('Deprecation notice')).toBeInTheDocument(); + }); + + it('does not show deprecation notice when hasZipUrlMonitors is false', () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: FETCH_STATUS.SUCCESS, + data: { hasZipUrlMonitors: false }, + refetch: () => null, + loading: false, + }); + + render(<ZipUrlDeprecation />); + expect(screen.queryByText('Deprecation notice')).not.toBeInTheDocument(); + }); + + it('dismisses notification', () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: FETCH_STATUS.SUCCESS, + data: { hasZipUrlMonitors: true }, + refetch: () => null, + loading: false, + }); + + render(<ZipUrlDeprecation />); + expect(screen.getByText('Deprecation notice')).toBeInTheDocument(); + userEvent.click(screen.getByText('Dismiss')); + expect(screen.queryByText('Deprecation notice')).not.toBeInTheDocument(); + }); + + it('does not show notification when session storage key is true', () => { + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: FETCH_STATUS.SUCCESS, + data: { hasZipUrlMonitors: true }, + refetch: () => null, + loading: false, + }); + mockStorage.setItem(ZIP_URL_DEPRECATION_SESSION_STORAGE_KEY, 'true'); + + render(<ZipUrlDeprecation />); + expect(screen.queryByText('Deprecation notice')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.tsx b/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.tsx index b7f32c2b42448..d03008b7ca556 100644 --- a/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.tsx +++ b/x-pack/plugins/synthetics/public/legacy_uptime/pages/overview.tsx @@ -12,6 +12,7 @@ import styled from 'styled-components'; import { useTrackPageview } from '@kbn/observability-plugin/public'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { MonitorList } from '../components/overview/monitor_list/monitor_list_container'; +import { ZipUrlDeprecation } from '../components/overview/zip_url_deprecation'; import { StatusPanel } from '../components/overview/status_panel'; import { QueryBar } from '../components/overview/query_bar/query_bar'; import { MONITORING_OVERVIEW_LABEL } from '../routes'; @@ -37,11 +38,18 @@ export const OverviewPageComponent = () => { return ( <> - <EuiFlexGroup gutterSize="xs" wrap responsive={false}> - <QueryBar /> - <EuiFlexItemStyled grow={true}> - <FilterGroup /> - </EuiFlexItemStyled> + <EuiFlexGroup direction="column" gutterSize="none"> + <EuiFlexItem grow={true}> + <ZipUrlDeprecation /> + </EuiFlexItem> + <EuiFlexItem> + <EuiFlexGroup gutterSize="xs" wrap responsive={false}> + <QueryBar /> + <EuiFlexItemStyled grow={true}> + <FilterGroup /> + </EuiFlexItemStyled> + </EuiFlexGroup> + </EuiFlexItem> </EuiFlexGroup> <EuiSpacer size="xs" /> <StatusPanel /> diff --git a/x-pack/plugins/synthetics/public/legacy_uptime/state/api/has_zip_url_monitors.ts b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/has_zip_url_monitors.ts new file mode 100644 index 0000000000000..78f45620aad84 --- /dev/null +++ b/x-pack/plugins/synthetics/public/legacy_uptime/state/api/has_zip_url_monitors.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { API_URLS } from '../../../../common/constants'; +import { SyntheticsHasZipUrlMonitorsResponse } from '../../../../common/types/zip_url_deprecation'; +import { apiService } from './utils'; + +export const getHasZipUrlMonitors = async (): Promise<SyntheticsHasZipUrlMonitorsResponse> => { + return await apiService.get(API_URLS.SYNTHETICS_HAS_ZIP_URL_MONITORS); +}; diff --git a/x-pack/plugins/synthetics/server/routes/fleet/get_has_zip_url_monitors.ts b/x-pack/plugins/synthetics/server/routes/fleet/get_has_zip_url_monitors.ts new file mode 100644 index 0000000000000..0527169b80ff4 --- /dev/null +++ b/x-pack/plugins/synthetics/server/routes/fleet/get_has_zip_url_monitors.ts @@ -0,0 +1,31 @@ +/* + * 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 { API_URLS } from '../../../common/constants'; +import { ConfigKey } from '../../../common/runtime_types'; +import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types'; + +export const getHasZipUrlMonitorRoute: SyntheticsRestApiRouteFactory = () => ({ + method: 'GET', + path: API_URLS.SYNTHETICS_HAS_ZIP_URL_MONITORS, + validate: {}, + handler: async ({ savedObjectsClient, server }): Promise<any> => { + const monitors = await server.fleet.packagePolicyService.list(savedObjectsClient, { + kuery: 'ingest-package-policies.package.name:synthetics', + }); + const hasZipUrlMonitors = monitors.items.some((item) => { + const browserInput = item.inputs.find((input) => input.type === 'synthetics/browser'); + const streams = browserInput?.streams || []; + return streams.find((stream) => stream.data_stream.dataset === 'browser')?.compiled_stream?.[ + ConfigKey.SOURCE_ZIP_URL + ]; + }); + return { + hasZipUrlMonitors, + monitors: [], + }; + }, +}); diff --git a/x-pack/plugins/synthetics/server/routes/index.ts b/x-pack/plugins/synthetics/server/routes/index.ts index 53819b5b380e5..63a2a0b889fe5 100644 --- a/x-pack/plugins/synthetics/server/routes/index.ts +++ b/x-pack/plugins/synthetics/server/routes/index.ts @@ -31,6 +31,7 @@ import { SyntheticsRestApiRouteFactory, SyntheticsStreamingRouteFactory, } from '../legacy_uptime/routes'; +import { getHasZipUrlMonitorRoute } from './fleet/get_has_zip_url_monitors'; export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ addSyntheticsMonitorRoute, @@ -49,6 +50,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [ getServiceAllowedRoute, getAPIKeySyntheticsRoute, syntheticsGetPingsRoute, + getHasZipUrlMonitorRoute, createGetCurrentStatusRoute, ]; diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index 255aececfd151..dfd1b3baffc5e 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -70,6 +70,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./monitor_duration')); loadTestFile(require.resolve('./index_status')); loadTestFile(require.resolve('./monitor_states_real_data')); + loadTestFile(require.resolve('./uptime_zip_url_deprecation.ts')); }); describe('uptime CRUD routes', () => { diff --git a/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts b/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts new file mode 100644 index 0000000000000..29f13966f9578 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/uptime_zip_url_deprecation.ts @@ -0,0 +1,159 @@ +/* + * 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 uuid from 'uuid/v4'; + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + + const supertest = getService('supertest'); + + const getBrowserZipInput = (zipUrl?: string) => ({ + type: 'synthetics/browser', + policy_template: 'synthetics', + enabled: false, + streams: [ + { + enabled: true, + release: 'beta', + data_stream: { type: 'synthetics', dataset: 'browser' }, + vars: { + __ui: { type: 'yaml' }, + enabled: { value: true, type: 'bool' }, + type: { value: 'browser', type: 'text' }, + name: { type: 'text' }, + schedule: { value: '"@every 3m"', type: 'text' }, + 'service.name': { type: 'text' }, + timeout: { type: 'text' }, + tags: { type: 'yaml' }, + 'source.zip_url.url': { type: 'text', value: zipUrl }, + 'source.zip_url.username': { type: 'text' }, + 'source.zip_url.folder': { type: 'text' }, + 'source.zip_url.password': { type: 'password' }, + 'source.inline.script': { type: 'yaml' }, + 'source.project.content': { type: 'text' }, + params: { type: 'yaml' }, + playwright_options: { type: 'yaml' }, + screenshots: { type: 'text' }, + synthetics_args: { type: 'text' }, + ignore_https_errors: { type: 'bool' }, + 'throttling.config': { type: 'text' }, + 'filter_journeys.tags': { type: 'yaml' }, + 'filter_journeys.match': { type: 'text' }, + 'source.zip_url.ssl.certificate_authorities': { type: 'yaml' }, + 'source.zip_url.ssl.certificate': { type: 'yaml' }, + 'source.zip_url.ssl.key': { type: 'yaml' }, + 'source.zip_url.ssl.key_passphrase': { type: 'text' }, + 'source.zip_url.ssl.verification_mode': { type: 'text' }, + 'source.zip_url.ssl.supported_protocols': { type: 'yaml' }, + 'source.zip_url.proxy_url': { type: 'text' }, + location_name: { value: 'Fleet managed', type: 'text' }, + id: { type: 'text' }, + config_id: { type: 'text' }, + run_once: { value: false, type: 'bool' }, + origin: { type: 'text' }, + 'monitor.project.id': { type: 'text' }, + 'monitor.project.name': { type: 'text' }, + }, + id: 'synthetics/browser-browser-2bfd7da0-22ed-11ed-8c6b-09a2d21dfbc3-27337270-22ed-11ed-8c6b-09a2d21dfbc3-default', + }, + ], + }); + + describe('UptimeZipUrlDeprecation', () => { + let agentPolicyId: string; + + before(async function () { + const { body: agentPolicyResponse } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `Test policy ${uuid()}`, + namespace: 'default', + }) + .expect(200); + agentPolicyId = agentPolicyResponse.item.id; + + // create a policy without a zip url + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `synthetics-test ${uuid()}`, + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [getBrowserZipInput()], + package: { + name: 'synthetics', + title: 'For Synthetics Tests', + version: '0.10.2', + }, + }) + .expect(200); + }); + + after(async function () { + await supertest + .post(`/api/fleet/agent_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ agentPolicyId }); + }); + + it('should return hasZipUrlMonitors false when there are not any zip url policies', async function () { + const { body } = await supertest + .get(`/internal/uptime/fleet/has_zip_url_monitors`) + .set('kbn-xsrf', 'xxxx') + .send() + .expect(200); + + expect(body.hasZipUrlMonitors).to.eql(false); + }); + + it('should return hasZipUrlMonitors true when there are zip url policies', async function () { + const { body } = await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `synthetics-test ${uuid()}`, + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + inputs: [getBrowserZipInput('testZipUrl')], + package: { + name: 'synthetics', + title: 'For Synthetics Tests', + version: '0.10.2', + }, + }) + .expect(200); + + const policyId = body.item.id; + + expect(body.item.inputs[0].streams[0].vars['source.zip_url.url'].value).to.eql('testZipUrl'); + + const { body: response } = await supertest + .get(`/internal/uptime/fleet/has_zip_url_monitors`) + .set('kbn-xsrf', 'xxxx') + .send() + .expect(200); + + expect(response.hasZipUrlMonitors).to.eql(true); + + // delete policy we just made + await supertest + .post(`/api/fleet/package_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true, packagePolicyIds: [policyId] }) + .expect(200); + }); + }); +} diff --git a/x-pack/test/functional_synthetics/page_objects/synthetics_integration_page.ts b/x-pack/test/functional_synthetics/page_objects/synthetics_integration_page.ts index fb0a4b7cc325e..8650a2b352e78 100644 --- a/x-pack/test/functional_synthetics/page_objects/synthetics_integration_page.ts +++ b/x-pack/test/functional_synthetics/page_objects/synthetics_integration_page.ts @@ -329,6 +329,8 @@ export function SyntheticsIntegrationPageProvider({ await testSubjects.click('syntheticsSourceTab__inline'); await this.fillCodeEditor(inlineScript); return; + } else { + await testSubjects.click('syntheticsSourceTab__zipUrl'); } await this.fillTextInputByTestSubj('syntheticsBrowserZipUrl', zipUrl); await this.fillTextInputByTestSubj('syntheticsBrowserZipUrlFolder', folder); From 77eb8029c81c087b1e524b96d42a5f716e716c59 Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski <jon@elastic.co> Date: Tue, 4 Oct 2022 14:48:45 -0500 Subject: [PATCH 076/174] [artifacts] Reuse Cloud image for tests (#141828) * [artifacts] Reuse Cloud image for tests In https://github.com/elastic/kibana/pull/141657 we turned on Cloud image builds in Kibana CI. This updates the Cloud deployment test suite to reuse the same image build. * update comment --- .buildkite/scripts/steps/artifacts/cloud.sh | 23 ++++++++------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.buildkite/scripts/steps/artifacts/cloud.sh b/.buildkite/scripts/steps/artifacts/cloud.sh index 4d2317ce0b6c7..16f280f68c539 100644 --- a/.buildkite/scripts/steps/artifacts/cloud.sh +++ b/.buildkite/scripts/steps/artifacts/cloud.sh @@ -7,31 +7,26 @@ set -euo pipefail source "$(dirname "$0")/../../common/util.sh" source .buildkite/scripts/steps/artifacts/env.sh -echo "--- Build and publish Cloud image" +echo "--- Push docker image" mkdir -p target -download_artifact "kibana-$FULL_VERSION-linux-x86_64.tar.gz" ./target --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" +download_artifact "kibana-cloud-$FULL_VERSION-docker-image.tar.gz" ./target --build "${KIBANA_BUILD_ID:-$BUILDKITE_BUILD_ID}" +docker load < "target/kibana-cloud-$FULL_VERSION-docker-image.tar.gz" TAG="$FULL_VERSION-$GIT_COMMIT" +KIBANA_BASE_IMAGE="docker.elastic.co/kibana-ci/kibana-cloud:$FULL_VERSION" KIBANA_TEST_IMAGE="docker.elastic.co/kibana-ci/kibana-cloud:$TAG" +# docker.elastic.co/kibana-ci/kibana-cloud:$FULL_VERSION -> :$FULL_VERSION-$GIT_COMMIT +docker tag "$KIBANA_BASE_IMAGE" "$KIBANA_TEST_IMAGE" + echo "$KIBANA_DOCKER_PASSWORD" | docker login -u "$KIBANA_DOCKER_USERNAME" --password-stdin docker.elastic.co trap 'docker logout docker.elastic.co' EXIT if docker manifest inspect $KIBANA_TEST_IMAGE &> /dev/null; then - echo "Distribution already exists, skipping build" + echo "Cloud image already exists, skipping docker push" else - node scripts/build \ - --skip-initialize \ - --skip-generic-folders \ - --skip-platform-folders \ - --skip-archives \ - --docker-images \ - --docker-tag-qualifier="$GIT_COMMIT" \ - --docker-push \ - --skip-docker-ubi \ - --skip-docker-ubuntu \ - --skip-docker-contexts + docker image push "$KIBANA_TEST_IMAGE" fi docker logout docker.elastic.co From 71aebb70240ba1acbf9ac452bdf09b777579386d Mon Sep 17 00:00:00 2001 From: Chris Cowan <chris@elastic.co> Date: Tue, 4 Oct 2022 13:53:59 -0600 Subject: [PATCH 077/174] [ResponseOps] Add kibana.alert.time_range field to Alert-As-Data mappings and populate it (#141309) * [ResponseOps] Add kibana.alert.time_range field to Alert-As-Data mappings and populate it * Fixing snapshots to match new reality * Removing the lte (end of range) for active alerts. * Fixing expected resutls for mapping test * fixing tests * updating readme * Fixing field name in README Co-authored-by: Faisal Kanout <faisal.kanout@elastic.co> --- .../kbn-rule-data-utils/src/technical_field_names.ts | 3 +++ x-pack/plugins/rule_registry/README.md | 1 + .../field_maps/technical_rule_field_map.test.ts | 4 ++++ .../assets/field_maps/technical_rule_field_map.ts | 4 ++++ .../server/utils/create_lifecycle_executor.ts | 8 +++++++- .../server/utils/create_lifecycle_rule_type.test.ts | 11 +++++++++++ 6 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/kbn-rule-data-utils/src/technical_field_names.ts b/packages/kbn-rule-data-utils/src/technical_field_names.ts index e6b6494e68a56..672c25d4e8fab 100644 --- a/packages/kbn-rule-data-utils/src/technical_field_names.ts +++ b/packages/kbn-rule-data-utils/src/technical_field_names.ts @@ -34,6 +34,7 @@ const ALERT_REASON = `${ALERT_NAMESPACE}.reason` as const; const ALERT_RISK_SCORE = `${ALERT_NAMESPACE}.risk_score` as const; const ALERT_SEVERITY = `${ALERT_NAMESPACE}.severity` as const; const ALERT_START = `${ALERT_NAMESPACE}.start` as const; +const ALERT_TIME_RANGE = `${ALERT_NAMESPACE}.time_range` as const; const ALERT_STATUS = `${ALERT_NAMESPACE}.status` as const; const ALERT_SYSTEM_STATUS = `${ALERT_NAMESPACE}.system_status` as const; const ALERT_UUID = `${ALERT_NAMESPACE}.uuid` as const; @@ -126,6 +127,7 @@ const fields = { ALERT_RULE_UPDATED_BY, ALERT_RULE_VERSION, ALERT_START, + ALERT_TIME_RANGE, ALERT_SEVERITY, ALERT_STATUS, ALERT_SYSTEM_STATUS, @@ -183,6 +185,7 @@ export { ALERT_RULE_VERSION, ALERT_SEVERITY, ALERT_START, + ALERT_TIME_RANGE, ALERT_SYSTEM_STATUS, ALERT_UUID, ECS_VERSION, diff --git a/x-pack/plugins/rule_registry/README.md b/x-pack/plugins/rule_registry/README.md index 6ca34fc9ece18..e0d79482e29f7 100644 --- a/x-pack/plugins/rule_registry/README.md +++ b/x-pack/plugins/rule_registry/README.md @@ -143,6 +143,7 @@ The following fields are defined in the technical field component template and s - `kibana.alert.ancestors`: the array of ancestors (if any) for the alert. - `kibana.alert.depth`: the depth of the alert in the ancestral tree (default 0). - `kibana.alert.building_block_type`: the building block type of the alert (default undefined). +- `kibana.alert.time_range`: the time range of an alert. (default undefined). # Alerts as data diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts index 06f00d9b6e6f8..32406f7a87fca 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.test.ts @@ -198,6 +198,10 @@ it('matches snapshot', () => { "required": false, "type": "keyword", }, + "kibana.alert.time_range": Object { + "format": "epoch_millis||strict_date_optional_time", + "type": "date_range", + }, "kibana.alert.uuid": Object { "required": true, "type": "keyword", diff --git a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts index ba1703b8be5da..2233f2d977010 100644 --- a/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts +++ b/x-pack/plugins/rule_registry/common/assets/field_maps/technical_rule_field_map.ts @@ -25,6 +25,10 @@ export const technicalRuleFieldMap = { [Fields.ALERT_UUID]: { type: 'keyword', required: true }, [Fields.ALERT_INSTANCE_ID]: { type: 'keyword', required: true }, [Fields.ALERT_START]: { type: 'date' }, + [Fields.ALERT_TIME_RANGE]: { + type: 'date_range', + format: 'epoch_millis||strict_date_optional_time', + }, [Fields.ALERT_END]: { type: 'date' }, [Fields.ALERT_DURATION]: { type: 'long' }, [Fields.ALERT_SEVERITY]: { type: 'keyword' }, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index da68ef3c4c7b6..160e06d03e92a 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -22,6 +22,7 @@ import { import { ParsedExperimentalFields } from '../../common/parse_experimental_fields'; import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; import { + ALERT_TIME_RANGE, ALERT_DURATION, ALERT_END, ALERT_INSTANCE_ID, @@ -235,7 +236,12 @@ export const createLifecycleExecutor = ...commonRuleFields, ...currentAlertData, [ALERT_DURATION]: (options.startedAt.getTime() - new Date(started).getTime()) * 1000, - + [ALERT_TIME_RANGE]: isRecovered + ? { + gte: started, + lte: commonRuleFields[TIMESTAMP], + } + : { gte: started }, [ALERT_INSTANCE_ID]: alertId, [ALERT_START]: started, [ALERT_UUID]: alertUuid, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts index 6a3660494d181..acb12645cbaed 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts @@ -12,6 +12,7 @@ import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_UUID, + ALERT_TIME_RANGE, } from '@kbn/rule-data-utils'; import { loggerMock } from '@kbn/logging-mocks'; import { castArray, omit } from 'lodash'; @@ -245,6 +246,9 @@ describe('createLifecycleRuleTypeFactory', () => { "kibana.alert.rule.uuid": "alertId", "kibana.alert.start": "2021-06-16T09:01:00.000Z", "kibana.alert.status": "active", + "kibana.alert.time_range": Object { + "gte": "2021-06-16T09:01:00.000Z", + }, "kibana.alert.workflow_status": "open", "kibana.space_ids": Array [ "spaceId", @@ -273,6 +277,9 @@ describe('createLifecycleRuleTypeFactory', () => { "kibana.alert.rule.uuid": "alertId", "kibana.alert.start": "2021-06-16T09:01:00.000Z", "kibana.alert.status": "active", + "kibana.alert.time_range": Object { + "gte": "2021-06-16T09:01:00.000Z", + }, "kibana.alert.workflow_status": "open", "kibana.space_ids": Array [ "spaceId", @@ -443,6 +450,10 @@ describe('createLifecycleRuleTypeFactory', () => { expect(opbeansNodeAlertDoc['event.action']).toBe('close'); expect(opbeansNodeAlertDoc[ALERT_STATUS]).toBe(ALERT_STATUS_RECOVERED); + expect(opbeansNodeAlertDoc[ALERT_TIME_RANGE]).toEqual({ + gte: '2021-06-16T09:01:00.000Z', + lte: '2021-06-16T09:02:00.000Z', + }); }); }); }); From 7f3541c8baa5250e45ccc6aae2da5adad5a7500e Mon Sep 17 00:00:00 2001 From: Juan Pablo Djeredjian <jpdjeredjian@gmail.com> Date: Tue, 4 Oct 2022 22:14:24 +0200 Subject: [PATCH 078/174] [Security Solution] Remove Optional label from Rule Schedule lookback time field (#142375) Relates to https://github.com/elastic/kibana/issues/141378 ## Summary Removes the "Optional" label from Rule Schedule lookback time field in the Rule Creation and Rule Editing pages. ## Screenshots ### Create Rule Page **Before** ![image](https://user-images.githubusercontent.com/5354282/193306696-97b4ba91-4c93-406f-a69c-e0e3cb4553dd.png) **After** ![image](https://user-images.githubusercontent.com/5354282/193306104-bffd2b4e-1131-4257-ac88-1f8cb3b1f5a5.png) ### Edit Rule Page **Before** ![image](https://user-images.githubusercontent.com/5354282/193306747-7cc192a1-a33f-471d-8830-f85f4cce45e9.png) **After** ![image](https://user-images.githubusercontent.com/5354282/193305897-8cfe029d-dc70-4d6d-9726-d00f5fde8e48.png) ### Checklist Delete any items that are not applicable to this PR. - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios (https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Dmitrii Shevchenko <dmitrii.shevchenko@elastic.co> --- .../detections/components/rules/step_schedule_rule/schema.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/schema.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/schema.tsx index c31da01b46e7f..23bed245e63a8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/schema.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_schedule_rule/schema.tsx @@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n'; -import { OptionalFieldLabel } from '../optional_field_label'; import type { ScheduleStepRule } from '../../../pages/detection_engine/rules/types'; import type { FormSchema } from '../../../../shared_imports'; @@ -35,7 +34,6 @@ export const schema: FormSchema<ScheduleStepRule> = { defaultMessage: 'Additional look-back time', } ), - labelAppend: OptionalFieldLabel, helpText: i18n.translate( 'xpack.securitySolution.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { From fc8407d222306577c7432cab3236162b52d106f3 Mon Sep 17 00:00:00 2001 From: Nick Peihl <nick.peihl@elastic.co> Date: Tue, 4 Oct 2022 17:18:58 -0400 Subject: [PATCH 079/174] Fix custom raster developer example (#142664) --- .../public/classes/custom_raster_source.tsx | 27 +++++++++++++++++-- .../kibana_tilemap_source.js | 3 +++ .../classes/sources/wms_source/wms_source.js | 3 +++ x-pack/plugins/maps/public/index.ts | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx b/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx index 8521e4333be7d..d36ed2485b5ba 100644 --- a/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx +++ b/x-pack/examples/third_party_maps_source_example/public/classes/custom_raster_source.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import _ from 'lodash'; import { ReactElement } from 'react'; import { calculateBounds } from '@kbn/data-plugin/common'; import { FieldFormatter, MIN_ZOOM, MAX_ZOOM } from '@kbn/maps-plugin/common'; @@ -16,15 +17,18 @@ import type { Timeslice, } from '@kbn/maps-plugin/common/descriptor_types'; import type { + DataRequest, IField, ImmutableSourceProperty, - ITMSSource, + IRasterSource, SourceEditorArgs, } from '@kbn/maps-plugin/public'; +import { RasterTileSourceData } from '@kbn/maps-plugin/public/classes/sources/raster_source'; +import { RasterTileSource } from 'maplibre-gl'; type CustomRasterSourceDescriptor = AbstractSourceDescriptor; -export class CustomRasterSource implements ITMSSource { +export class CustomRasterSource implements IRasterSource { static type = 'CUSTOM_RASTER'; readonly _descriptor: CustomRasterSourceDescriptor; @@ -39,6 +43,25 @@ export class CustomRasterSource implements ITMSSource { this._descriptor = sourceDescriptor; } + async canSkipSourceUpdate( + dataRequest: DataRequest, + nextRequestMeta: DataRequestMeta + ): Promise<boolean> { + const prevMeta = dataRequest.getMeta(); + if (!prevMeta) { + return Promise.resolve(false); + } + + return Promise.resolve(_.isEqual(prevMeta.timeslice, nextRequestMeta.timeslice)); + } + + isSourceStale(mbSource: RasterTileSource, sourceData: RasterTileSourceData): boolean { + if (!sourceData.url) { + return false; + } + return mbSource.tiles?.[0] !== sourceData.url; + } + cloneDescriptor(): CustomRasterSourceDescriptor { return { type: this._descriptor.type, diff --git a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js index 19a7ec2941102..9abe2997b4756 100644 --- a/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js +++ b/x-pack/plugins/maps/public/classes/sources/kibana_tilemap_source/kibana_tilemap_source.js @@ -41,15 +41,18 @@ export class KibanaTilemapSource extends AbstractSource { }, ]; } + isSourceStale(mbSource, sourceData) { if (!sourceData.url) { return false; } return mbSource.tiles?.[0] !== sourceData.url; } + async canSkipSourceUpdate() { return false; } + async getUrlTemplate() { const tilemap = getKibanaTileMap(); if (!tilemap.url) { diff --git a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js index a1c1c60d75561..3d682a504c2d3 100644 --- a/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js +++ b/x-pack/plugins/maps/public/classes/sources/wms_source/wms_source.js @@ -27,15 +27,18 @@ export class WMSSource extends AbstractSource { styles, }; } + isSourceStale(mbSource, sourceData) { if (!sourceData.url) { return false; } return mbSource.tiles?.[0] !== sourceData.url; } + async canSkipSourceUpdate() { return false; } + async getImmutableProperties() { return [ { label: getDataSourceLabel(), value: sourceTitle }, diff --git a/x-pack/plugins/maps/public/index.ts b/x-pack/plugins/maps/public/index.ts index e894d0f049b27..beb0d5153d89e 100644 --- a/x-pack/plugins/maps/public/index.ts +++ b/x-pack/plugins/maps/public/index.ts @@ -35,6 +35,7 @@ export type { MapEmbeddable, MapEmbeddableInput, MapEmbeddableOutput } from './e export type { EMSTermJoinConfig, SampleValuesConfig } from './ems_autosuggest'; export type { ITMSSource } from './classes/sources/tms_source'; +export type { IRasterSource } from './classes/sources/raster_source'; export type { GetFeatureActionsArgs, From f82f7560eb0f201f12ed6832e1ae0baf806da942 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:26:19 -0600 Subject: [PATCH 080/174] skip failing test suite (#142248) --- .../apps/ml/short_tests/notifications/notification_list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts b/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts index c9ad8d2423ef8..30cad369259c3 100644 --- a/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts +++ b/x-pack/test/functional/apps/ml/short_tests/notifications/notification_list.ts @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const ml = getService('ml'); const browser = getService('browser'); - describe('Notifications list', function () { + // Failing: See https://github.com/elastic/kibana/issues/142248 + describe.skip('Notifications list', function () { before(async () => { await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/ml/farequote'); await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); From 45ad233c818dfe2a8c4b63a01e8933131cafe249 Mon Sep 17 00:00:00 2001 From: christineweng <18648970+christineweng@users.noreply.github.com> Date: Tue, 4 Oct 2022 17:01:43 -0500 Subject: [PATCH 081/174] Added margin at the bottom to fully show event description list (#142529) --- .../public/timelines/components/graph_overlay/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx index 833320b78a5fc..d12bd77bcb21c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/graph_overlay/index.tsx @@ -46,7 +46,7 @@ const OverlayContainer = styled.div` const FullScreenOverlayStyles = css` position: fixed; top: 0; - bottom: 0; + bottom: 2em; left: 0; right: 0; z-index: ${euiThemeVars.euiZLevel3}; From 7a6ff848ab03797979b381d2be19a79b630fc626 Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Tue, 4 Oct 2022 18:01:52 -0400 Subject: [PATCH 082/174] [Security Solution] Ignore timerange in session view, to mirror session view component (#141137) * Ignore timerange in session view, to mirror session view component * Remove timerange from process ancestry insight --- .../insights/related_alerts_by_session.tsx | 1 + .../containers/alerts/use_alert_prevalence.ts | 58 +++++++++++++------ .../use_alert_prevalence_from_process_tree.ts | 3 +- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_session.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_session.tsx index 8b0b308829c3d..bf1d182997f0b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_session.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/insights/related_alerts_by_session.tsx @@ -43,6 +43,7 @@ export const RelatedAlertsBySession = React.memo<Props>( timelineId: timelineId ?? '', signalIndexName: null, includeAlertIds: true, + ignoreTimerange: true, }); const { fieldFromBrowserField } = getEnrichedFieldInfo({ diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts index 1f16a59c0f815..473d2bdc84f10 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts +++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence.ts @@ -24,6 +24,7 @@ interface UseAlertPrevalenceOptions { timelineId: string; signalIndexName: string | null; includeAlertIds?: boolean; + ignoreTimerange?: boolean; } interface UserAlertPrevalenceResult { @@ -39,13 +40,17 @@ export const useAlertPrevalence = ({ timelineId, signalIndexName, includeAlertIds = false, + ignoreTimerange = false, }: UseAlertPrevalenceOptions): UserAlertPrevalenceResult => { const timelineTime = useDeepEqualSelector((state) => inputsSelectors.timelineTimeRangeSelector(state) ); const globalTime = useGlobalTime(false); - - const { to, from } = timelineId === TimelineId.active ? timelineTime : globalTime; + let to: string | undefined; + let from: string | undefined; + if (ignoreTimerange === false) { + ({ to, from } = timelineId === TimelineId.active ? timelineTime : globalTime); + } const [initialQuery] = useState(() => generateAlertPrevalenceQuery(field, value, from, to, includeAlertIds) ); @@ -88,8 +93,8 @@ export const useAlertPrevalence = ({ const generateAlertPrevalenceQuery = ( field: string, value: string | string[] | undefined | null, - from: string, - to: string, + from: string | undefined, + to: string | undefined, includeAlertIds: boolean ) => { // if we don't want the alert ids included, we set size to 0 to reduce the response payload @@ -106,25 +111,15 @@ const generateAlertPrevalenceQuery = ( [field]: actualValue, }, }, - filter: [ - { - range: { - '@timestamp': { - gte: from, - lte: to, - }, - }, - }, - ], }, }; - if (Array.isArray(value) && value.length > 1) { - const shouldValues = value.map((val) => ({ match: { [field]: val } })); + if (from !== undefined && to !== undefined) { query = { + ...query, bool: { - minimum_should_match: 1, - must: [ + ...query.bool, + filter: [ { range: { '@timestamp': { @@ -134,9 +129,36 @@ const generateAlertPrevalenceQuery = ( }, }, ], + }, + }; + } + + if (Array.isArray(value) && value.length > 1) { + const shouldValues = value.map((val) => ({ match: { [field]: val } })); + query = { + bool: { + minimum_should_match: 1, should: shouldValues, }, }; + if (from !== undefined && to !== undefined) { + query = { + ...query, + bool: { + ...query.bool, + must: [ + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, + }, + ], + }, + }; + } } return { diff --git a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts index e3bc22ec2decb..9c179bd61e61d 100644 --- a/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts +++ b/x-pack/plugins/security_solution/public/common/containers/alerts/use_alert_prevalence_from_process_tree.ts @@ -99,7 +99,7 @@ export function useAlertPrevalenceFromProcessTree({ }: UseAlertPrevalenceFromProcessTree): UserAlertPrevalenceFromProcessTreeResult { const http = useHttp(); - const { selectedPatterns, to, from } = useTimelineDataFilters(timelineId); + const { selectedPatterns } = useTimelineDataFilters(timelineId); const alertAndOriginalIndices = [...new Set(selectedPatterns.concat(indices))]; const { loading, id, schema } = useAlertDocumentAnalyzerSchema({ documentId, @@ -115,7 +115,6 @@ export function useAlertPrevalenceFromProcessTree({ descendants: 500, indexPatterns: alertAndOriginalIndices, nodes: [id], - timeRange: { from, to }, includeHits: true, }), }); From 899081a45190200b8b8718e3a8ffd91641fcba3f Mon Sep 17 00:00:00 2001 From: Constance <constancecchen@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:15:16 -0700 Subject: [PATCH 083/174] Use `@emotion/server` for server-side security prompts (#142662) * Update all `@emotion` dependencies to latest * Install `@emotion/server` * Use Emotion server-side rendering for security prompt pages * snapshots --- package.json | 13 +- .../__snapshots__/prompt_page.test.tsx.snap | 4 +- .../unauthenticated_page.test.tsx.snap | 2 +- .../reset_session_page.test.tsx.snap | 2 +- .../plugins/security/server/prompt_page.tsx | 57 ++-- yarn.lock | 248 ++++++++++++++---- 6 files changed, 244 insertions(+), 82 deletions(-) diff --git a/package.json b/package.json index 9825802a80292..97e1fe7c24bce 100644 --- a/package.json +++ b/package.json @@ -116,10 +116,11 @@ "@elastic/react-search-ui": "^1.14.0", "@elastic/request-crypto": "2.0.1", "@elastic/search-ui-app-search-connector": "^1.14.0", - "@emotion/cache": "^11.9.3", - "@emotion/css": "^11.9.0", - "@emotion/react": "^11.9.3", - "@emotion/serialize": "^1.0.4", + "@emotion/cache": "^11.10.3", + "@emotion/css": "^11.10.0", + "@emotion/react": "^11.10.4", + "@emotion/serialize": "^1.1.0", + "@emotion/server": "^11.10.0", "@grpc/grpc-js": "^1.6.7", "@hapi/accept": "^5.0.2", "@hapi/boom": "^9.1.4", @@ -690,8 +691,8 @@ "@elastic/github-checks-reporter": "0.0.20b3", "@elastic/makelogs": "^6.0.0", "@elastic/synthetics": "^1.0.0-beta.22", - "@emotion/babel-preset-css-prop": "^11.2.0", - "@emotion/jest": "^11.9.4", + "@emotion/babel-preset-css-prop": "^11.10.0", + "@emotion/jest": "^11.10.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@istanbuljs/schema": "^0.1.2", "@jest/console": "^26.6.2", diff --git a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap index 97197720ce590..da3adab8b5f0e 100644 --- a/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap +++ b/x-pack/plugins/security/server/__snapshots__/prompt_page.test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PromptPage renders as expected with additional scripts 1`] = `"<html lang=\\"en\\"><head><title>ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected with additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; -exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; +exports[`PromptPage renders as expected without additional scripts 1`] = `"ElasticMockedFonts

Some Title

Some Body
Action#1
Action#2
"`; diff --git a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap index 9157859003d53..e6adef02b2417 100644 --- a/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authentication/__snapshots__/unauthenticated_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We couldn't log you in

We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

"`; +exports[`UnauthenticatedPage renders as expected 1`] = `"ElasticMockedFonts

We couldn't log you in

We hit an authentication error. Please check your credentials and try again. If you still can't log in, contact your system administrator.

"`; diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap index 3b553c7131df4..f987be427a5c0 100644 --- a/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap +++ b/x-pack/plugins/security/server/authorization/__snapshots__/reset_session_page.test.tsx.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; +exports[`ResetSessionPage renders as expected 1`] = `"ElasticMockedFonts

You do not have permission to access the requested page

Either go back to the previous page or log in as a different user.

"`; diff --git a/x-pack/plugins/security/server/prompt_page.tsx b/x-pack/plugins/security/server/prompt_page.tsx index 14f59df15db39..38bd77b444e83 100644 --- a/x-pack/plugins/security/server/prompt_page.tsx +++ b/x-pack/plugins/security/server/prompt_page.tsx @@ -17,8 +17,10 @@ import { icon as EuiIconAlert } from '@elastic/eui/lib/components/icon/assets/al // @ts-expect-error no definitions in component folder import { appendIconComponentCache } from '@elastic/eui/lib/components/icon/icon'; import createCache from '@emotion/cache'; +import createEmotionServer from '@emotion/server/create-instance'; import type { ReactNode } from 'react'; import React from 'react'; +import { renderToString } from 'react-dom/server'; import { Fonts } from '@kbn/core-rendering-server-internal'; import type { IBasePath } from '@kbn/core/server'; @@ -34,6 +36,8 @@ appendIconComponentCache({ alert: EuiIconAlert, }); +const emotionCache = createCache({ key: 'eui' }); + interface Props { buildNumber: number; basePath: IBasePath; @@ -51,6 +55,31 @@ export function PromptPage({ body, actions, }: Props) { + const content = ( + + + + + + {title}} + body={body} + actions={actions} + /> + + + + + + ); + + const { extractCriticalToChunks, constructStyleTagsFromChunks } = + createEmotionServer(emotionCache); + const chunks = extractCriticalToChunks(renderToString(content)); + const emotionStyles = constructStyleTagsFromChunks(chunks); + const uiPublicURL = `${basePath.serverBasePath}/ui`; const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`; const styleSheetPaths = [ @@ -60,16 +89,12 @@ export function PromptPage({ `${basePath.serverBasePath}/ui/legacy_light_theme.css`, ]; - // Emotion SSR styles will be prepended to the and emit a console log warning about :first-child selectors - const emotionCache = createCache({ - key: 'css', - prepend: true, - }); - return ( Elastic + {/* eslint-disable-next-line react/no-danger */} + + + + ); + }, +]; + export const BrokenSrc = Template.bind({}); +BrokenSrc.storyName = 'Broken src'; BrokenSrc.args = { - src: 'broken', + src: 'foo', }; +export const WithBlurhashAndBrokenSrc = Template.bind({}); +WithBlurhashAndBrokenSrc.storyName = 'With blurhash and broken src'; +WithBlurhashAndBrokenSrc.args = { + src: 'foo', +}; +WithBlurhashAndBrokenSrc.loaders = [ + async () => ({ + blurhash: await getImageMetadata(getBlob()), + }), +]; + export const OffScreen = Template.bind({}); -OffScreen.args = { ...defaultArgs, onFirstVisible: action('visible') }; +OffScreen.storyName = 'Offscreen'; +OffScreen.args = { onFirstVisible: action('visible') }; OffScreen.decorators = [ (Story) => ( <> diff --git a/x-pack/plugins/files/public/components/image/image.tsx b/x-pack/plugins/files/public/components/image/image.tsx index 96ac1a2eee78c..915f45c828f66 100644 --- a/x-pack/plugins/files/public/components/image/image.tsx +++ b/x-pack/plugins/files/public/components/image/image.tsx @@ -4,13 +4,30 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { MutableRefObject } from 'react'; -import type { ImgHTMLAttributes } from 'react'; +import React, { HTMLAttributes } from 'react'; +import { type ImgHTMLAttributes, useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import type { FileImageMetadata } from '../../../common'; import { useViewportObserver } from './use_viewport_observer'; +import { Img, type ImgProps, Blurhash } from './components'; +import { sizes } from './styles'; export interface Props extends ImgHTMLAttributes { src: string; alt: string; + /** + * Image metadata + */ + meta?: FileImageMetadata; + + /** + * @default original + */ + size?: ImgProps['size']; + /** + * Props to pass to the wrapper element + */ + wrapperProps?: HTMLAttributes; /** * Emits when the image first becomes visible */ @@ -28,22 +45,65 @@ export interface Props extends ImgHTMLAttributes { * ``` */ export const Image = React.forwardRef( - ({ src, alt, onFirstVisible, ...rest }, ref) => { + ( + { src, alt, onFirstVisible, onLoad, onError, meta, wrapperProps, size = 'original', ...rest }, + ref + ) => { + const [isLoaded, setIsLoaded] = useState(false); + const [blurDelayExpired, setBlurDelayExpired] = useState(false); const { isVisible, ref: observerRef } = useViewportObserver({ onFirstVisible }); + + useEffect(() => { + let unmounted = false; + const id = window.setTimeout(() => { + if (!unmounted) setBlurDelayExpired(true); + }, 200); + return () => { + unmounted = true; + window.clearTimeout(id); + }; + }, []); + + const knownSize = size ? sizes[size] : undefined; + return ( - { - observerRef(element); - if (ref) { - if (typeof ref === 'function') ref(element); - else (ref as MutableRefObject).current = element; - } - }} - // TODO: We should have a lower resolution alternative to display - src={isVisible ? src : undefined} - alt={alt} - /> +
+ {blurDelayExpired && meta?.blurhash && ( + + )} + { + setIsLoaded(true); + onLoad?.(ev); + }} + onError={(ev) => { + setIsLoaded(true); + onError?.(ev); + }} + {...rest} + /> +
); } ); diff --git a/x-pack/plugins/files/public/components/image/styles.ts b/x-pack/plugins/files/public/components/image/styles.ts new file mode 100644 index 0000000000000..b14121c667a50 --- /dev/null +++ b/x-pack/plugins/files/public/components/image/styles.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { css } from '@emotion/react'; + +// Values taken from @elastic/eui/src/components/image +export const sizes = { + s: css` + width: 100px; + `, + m: css` + width: 200px; + `, + l: css` + width: 360px; + `, + xl: css` + width: 600px; + `, + original: css` + width: auto; + `, + fullWidth: css` + width: 100%; + `, +}; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx index 8e0d8ed59392b..e85460ca7c1e3 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_file.tsx +++ b/x-pack/plugins/files/public/components/upload_file/upload_file.tsx @@ -40,7 +40,7 @@ export interface Props { /** * A files client that will be used process uploads. */ - client: FilesClient; + client: FilesClient; /** * Allow users to clear a file after uploading. * diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts index 8fc0c02e0f60e..3a4e19adf8114 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.test.ts @@ -11,6 +11,7 @@ import { TestScheduler } from 'rxjs/testing'; import type { FileKind, FileJSON } from '../../../common'; import { createMockFilesClient } from '../../mocks'; import type { FilesClient } from '../../types'; +import { ImageMetadataFactory } from '../util/image_metadata'; import { UploadState } from './upload_state'; @@ -21,6 +22,7 @@ describe('UploadState', () => { let filesClient: DeeplyMockedKeys; let uploadState: UploadState; let testScheduler: TestScheduler; + const imageMetadataFactory = (() => of(undefined)) as unknown as ImageMetadataFactory; beforeEach(() => { filesClient = createMockFilesClient(); @@ -28,7 +30,9 @@ describe('UploadState', () => { filesClient.upload.mockReturnValue(of(undefined) as any); uploadState = new UploadState( { id: 'test', http: {}, maxSizeBytes: 1000 } as FileKind, - filesClient + filesClient, + {}, + imageMetadataFactory ); testScheduler = getTestScheduler(); }); @@ -189,7 +193,8 @@ describe('UploadState', () => { uploadState = new UploadState( { id: 'test', http: {}, maxSizeBytes: 1000 } as FileKind, filesClient, - { allowRepeatedUploads: true } + { allowRepeatedUploads: true }, + imageMetadataFactory ); const file1 = { name: 'test' } as File; const file2 = { name: 'test 2.png' } as File; diff --git a/x-pack/plugins/files/public/components/upload_file/upload_state.ts b/x-pack/plugins/files/public/components/upload_file/upload_state.ts index d5fbc04512fdc..dd03eb7aee56a 100644 --- a/x-pack/plugins/files/public/components/upload_file/upload_state.ts +++ b/x-pack/plugins/files/public/components/upload_file/upload_state.ts @@ -29,6 +29,7 @@ import { } from 'rxjs'; import type { FileKind, FileJSON } from '../../../common/types'; import type { FilesClient } from '../../types'; +import { ImageMetadataFactory, getImageMetadata, isImage } from '../util'; import { i18nTexts } from './i18n_texts'; import { createStateSubject, type SimpleStateSubject, parseFileName } from './util'; @@ -68,7 +69,8 @@ export class UploadState { constructor( private readonly fileKind: FileKind, private readonly client: FilesClient, - private readonly opts: UploadOptions = { allowRepeatedUploads: false } + private readonly opts: UploadOptions = { allowRepeatedUploads: false }, + private readonly loadImageMetadata: ImageMetadataFactory = getImageMetadata ) { const latestFiles$ = this.files$$.pipe(switchMap((files$) => combineLatest(files$))); this.subscriptions = [ @@ -171,15 +173,17 @@ export class UploadState { const { name } = parseFileName(file.name); const mime = file.type || undefined; - - return from( - this.client.create({ - kind: this.fileKind.id, - name, - mimeType: mime, - meta: meta as Record, - }) - ).pipe( + const _meta = meta as Record; + + return from(isImage(file) ? this.loadImageMetadata(file) : of(undefined)).pipe( + mergeMap((imageMetadata) => + this.client.create({ + kind: this.fileKind.id, + name, + mimeType: mime, + meta: imageMetadata ? { ...imageMetadata, ..._meta } : _meta, + }) + ), mergeMap((result) => { uploadTarget = result.file; return race( @@ -240,10 +244,12 @@ export class UploadState { export const createUploadState = ({ fileKind, client, + imageMetadataFactory, ...options }: { fileKind: FileKind; client: FilesClient; + imageMetadataFactory?: ImageMetadataFactory; } & UploadOptions) => { - return new UploadState(fileKind, client, options); + return new UploadState(fileKind, client, options, imageMetadataFactory); }; diff --git a/x-pack/plugins/files/public/components/util/image_metadata.test.ts b/x-pack/plugins/files/public/components/util/image_metadata.test.ts new file mode 100644 index 0000000000000..16980aee00296 --- /dev/null +++ b/x-pack/plugins/files/public/components/util/image_metadata.test.ts @@ -0,0 +1,55 @@ +/* + * 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 { fitToBox } from './image_metadata'; +describe('util', () => { + describe('fitToBox', () => { + test('300x300', () => { + expect(fitToBox(300, 300)).toMatchInlineSnapshot(` + Object { + "height": 300, + "width": 300, + } + `); + }); + + test('300x150', () => { + expect(fitToBox(300, 150)).toMatchInlineSnapshot(` + Object { + "height": 150, + "width": 300, + } + `); + }); + + test('4500x9000', () => { + expect(fitToBox(4500, 9000)).toMatchInlineSnapshot(` + Object { + "height": 300, + "width": 150, + } + `); + }); + + test('1000x300', () => { + expect(fitToBox(1000, 300)).toMatchInlineSnapshot(` + Object { + "height": 90, + "width": 300, + } + `); + }); + + test('0x0', () => { + expect(fitToBox(0, 0)).toMatchInlineSnapshot(` + Object { + "height": 0, + "width": 0, + } + `); + }); + }); +}); diff --git a/x-pack/plugins/files/public/components/util/image_metadata.ts b/x-pack/plugins/files/public/components/util/image_metadata.ts new file mode 100644 index 0000000000000..9358dda9d05ad --- /dev/null +++ b/x-pack/plugins/files/public/components/util/image_metadata.ts @@ -0,0 +1,79 @@ +/* + * 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 bh from 'blurhash'; +import type { FileImageMetadata } from '../../../common'; + +export function isImage(file: Blob | File): boolean { + return file.type?.startsWith('image/'); +} + +export const boxDimensions = { + width: 300, + height: 300, +}; + +/** + * Calculate the size of an image, fitting to our limits see {@link boxDimensions}, + * while preserving the aspect ratio. + */ +export function fitToBox(width: number, height: number): { width: number; height: number } { + const offsetRatio = Math.abs( + Math.min( + // Find the aspect at which our box is smallest, if less than 1, it means we exceed the limits + Math.min(boxDimensions.width / width, boxDimensions.height / height), + // Values greater than 1 are within our limits + 1 + ) - 1 // Get the percentage we are exceeding. E.g., 0.3 - 1 = -0.7 means the image needs to shrink by 70% to fit + ); + return { + width: Math.floor(width - offsetRatio * width), + height: Math.floor(height - offsetRatio * height), + }; +} + +/** + * Get the native size of the image + */ +function loadImage(src: string): Promise { + return new Promise((res, rej) => { + const image = new window.Image(); + image.src = src; + image.onload = () => res(image); + image.onerror = rej; + }); +} + +/** + * Extract image metadata, assumes that file or blob as an image! + */ +export async function getImageMetadata(file: File | Blob): Promise { + const imgUrl = window.URL.createObjectURL(file); + try { + const image = await loadImage(imgUrl); + const canvas = document.createElement('canvas'); + const { width, height } = fitToBox(image.width, image.height); + canvas.width = width; + canvas.height = height; + const ctx = canvas.getContext('2d'); + if (!ctx) throw new Error('Could not get 2d canvas context!'); + ctx.drawImage(image, 0, 0, width, height); + const imgData = ctx.getImageData(0, 0, width, height); + return { + blurhash: bh.encode(imgData.data, imgData.width, imgData.height, 4, 4), + width: image.width, + height: image.height, + }; + } catch (e) { + // Don't error out if we cannot generate the blurhash + return undefined; + } finally { + window.URL.revokeObjectURL(imgUrl); + } +} + +export type ImageMetadataFactory = typeof getImageMetadata; diff --git a/x-pack/plugins/files/public/components/util/index.ts b/x-pack/plugins/files/public/components/util/index.ts new file mode 100644 index 0000000000000..e3e30fdb17bb3 --- /dev/null +++ b/x-pack/plugins/files/public/components/util/index.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { getImageMetadata, isImage, fitToBox } from './image_metadata'; +export type { ImageMetadataFactory } from './image_metadata'; diff --git a/x-pack/plugins/files/public/plugin.ts b/x-pack/plugins/files/public/plugin.ts index 8ebbd71cdbe1f..2f5481f8f2511 100644 --- a/x-pack/plugins/files/public/plugin.ts +++ b/x-pack/plugins/files/public/plugin.ts @@ -11,9 +11,10 @@ import { setFileKindsRegistry, FileKindsRegistryImpl, } from '../common/file_kinds_registry'; -import type { FilesClientFactory } from './types'; +import type { FilesClient, FilesClientFactory } from './types'; import { createFilesClient } from './files_client'; import { FileKind } from '../common'; +import { ScopedFilesClient } from '.'; /** * Public setup-phase contract @@ -50,11 +51,11 @@ export class FilesPlugin implements Plugin { setup(core: CoreSetup): FilesSetup { this.filesClientFactory = { - asScoped(fileKind: string) { - return createFilesClient({ fileKind, http: core.http }); + asScoped(fileKind: string) { + return createFilesClient({ fileKind, http: core.http }) as ScopedFilesClient; }, - asUnscoped() { - return createFilesClient({ http: core.http }); + asUnscoped() { + return createFilesClient({ http: core.http }) as FilesClient; }, }; return { diff --git a/x-pack/plugins/files/public/types.ts b/x-pack/plugins/files/public/types.ts index 25aab6e787b6b..1cc69ac4ed23e 100644 --- a/x-pack/plugins/files/public/types.ts +++ b/x-pack/plugins/files/public/types.ts @@ -60,13 +60,13 @@ interface GlobalEndpoints { /** * A client that can be used to manage a specific {@link FileKind}. */ -export interface FilesClient extends GlobalEndpoints { +export interface FilesClient extends GlobalEndpoints { /** * Create a new file object with the provided metadata. * * @param args - create file args */ - create: ClientMethodFrom; + create: ClientMethodFrom>; /** * Delete a file object and all associated share and content objects. * @@ -78,19 +78,19 @@ export interface FilesClient extends GlobalEndpoints { * * @param args - get file by ID args */ - getById: ClientMethodFrom; + getById: ClientMethodFrom>; /** * List all file objects, of a given {@link FileKind}. * * @param args - list files args */ - list: ClientMethodFrom; + list: ClientMethodFrom>; /** * Update a set of of metadata values of the file object. * * @param args - update file args */ - update: ClientMethodFrom; + update: ClientMethodFrom>; /** * Stream the contents of the file to Kibana server for storage. * @@ -151,8 +151,8 @@ export interface FilesClient extends GlobalEndpoints { listShares: ClientMethodFrom; } -export type FilesClientResponses = { - [K in keyof FilesClient]: Awaited>; +export type FilesClientResponses = { + [K in keyof FilesClient]: Awaited[K]>>; }; /** @@ -161,10 +161,10 @@ export type FilesClientResponses = { * More convenient if you want to re-use the same client for the same file kind * and not specify the kind every time. */ -export type ScopedFilesClient = { +export type ScopedFilesClient = { [K in keyof FilesClient]: K extends 'list' - ? (arg?: Omit[0], 'kind'>) => ReturnType - : (arg: Omit[0], 'kind'>) => ReturnType; + ? (arg?: Omit[K]>[0], 'kind'>) => ReturnType[K]> + : (arg: Omit[K]>[0], 'kind'>) => ReturnType[K]>; }; /** @@ -174,11 +174,11 @@ export interface FilesClientFactory { /** * Create a files client. */ - asUnscoped(): FilesClient; + asUnscoped(): FilesClient; /** * Create a {@link ScopedFileClient} for a given {@link FileKind}. * * @param fileKind - The {@link FileKind} to create a client for. */ - asScoped(fileKind: string): ScopedFilesClient; + asScoped(fileKind: string): ScopedFilesClient; } diff --git a/x-pack/plugins/files/server/routes/common.test.ts b/x-pack/plugins/files/server/routes/common.test.ts index 2c4d302d04625..1f9292e3ff07a 100644 --- a/x-pack/plugins/files/server/routes/common.test.ts +++ b/x-pack/plugins/files/server/routes/common.test.ts @@ -26,30 +26,30 @@ describe('getDownloadHeadersForFile', () => { const file = { data: { name: 'test', mimeType: undefined } } as unknown as File; test('no mime type and name from file object', () => { - expect(getDownloadHeadersForFile(file, undefined)).toEqual( + expect(getDownloadHeadersForFile({ file, fileName: undefined })).toEqual( expectHeaders({ contentType: 'application/octet-stream', contentDisposition: 'test' }) ); }); test('no mime type and name (without ext)', () => { - expect(getDownloadHeadersForFile(file, 'myfile')).toEqual( + expect(getDownloadHeadersForFile({ file, fileName: 'myfile' })).toEqual( expectHeaders({ contentType: 'application/octet-stream', contentDisposition: 'myfile' }) ); }); test('no mime type and name (with ext)', () => { - expect(getDownloadHeadersForFile(file, 'myfile.png')).toEqual( + expect(getDownloadHeadersForFile({ file, fileName: 'myfile.png' })).toEqual( expectHeaders({ contentType: 'image/png', contentDisposition: 'myfile.png' }) ); }); test('mime type and no name', () => { const fileWithMime = { data: { ...file.data, mimeType: 'application/pdf' } } as File; - expect(getDownloadHeadersForFile(fileWithMime, undefined)).toEqual( + expect(getDownloadHeadersForFile({ file: fileWithMime, fileName: undefined })).toEqual( expectHeaders({ contentType: 'application/pdf', contentDisposition: 'test' }) ); }); test('mime type and name', () => { const fileWithMime = { data: { ...file.data, mimeType: 'application/pdf' } } as File; - expect(getDownloadHeadersForFile(fileWithMime, 'a cool file.pdf')).toEqual( + expect(getDownloadHeadersForFile({ file: fileWithMime, fileName: 'a cool file.pdf' })).toEqual( expectHeaders({ contentType: 'application/pdf', contentDisposition: 'a cool file.pdf' }) ); }); diff --git a/x-pack/plugins/files/server/routes/common.ts b/x-pack/plugins/files/server/routes/common.ts index 0730a6435de02..8e17a39511b53 100644 --- a/x-pack/plugins/files/server/routes/common.ts +++ b/x-pack/plugins/files/server/routes/common.ts @@ -8,7 +8,12 @@ import mime from 'mime'; import type { ResponseHeaders } from '@kbn/core/server'; import type { File } from '../../common/types'; -export function getDownloadHeadersForFile(file: File, fileName?: string): ResponseHeaders { +interface Args { + file: File; + fileName?: string; +} + +export function getDownloadHeadersForFile({ file, fileName }: Args): ResponseHeaders { return { 'content-type': (fileName && mime.getType(fileName)) ?? file.data.mimeType ?? 'application/octet-stream', diff --git a/x-pack/plugins/files/server/routes/file_kind/create.ts b/x-pack/plugins/files/server/routes/file_kind/create.ts index a134bdd292e98..78a7260771a16 100644 --- a/x-pack/plugins/files/server/routes/file_kind/create.ts +++ b/x-pack/plugins/files/server/routes/file_kind/create.ts @@ -23,7 +23,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition }>; export const handler: CreateHandler = async ({ fileKind, files }, req, res) => { const { fileService } = await files; diff --git a/x-pack/plugins/files/server/routes/file_kind/download.ts b/x-pack/plugins/files/server/routes/file_kind/download.ts index 85f8b5bd0a2d6..d4ae37ddb6623 100644 --- a/x-pack/plugins/files/server/routes/file_kind/download.ts +++ b/x-pack/plugins/files/server/routes/file_kind/download.ts @@ -40,7 +40,7 @@ export const handler: CreateHandler = async ({ files, fileKind }, req, const body: Response = await file.downloadContent(); return res.ok({ body, - headers: getDownloadHeadersForFile(file, fileName), + headers: getDownloadHeadersForFile({ file, fileName }), }); } catch (e) { if (e instanceof fileErrors.NoDownloadAvailableError) { diff --git a/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts b/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts index 00c5bd2312f89..4d86e05564fdf 100644 --- a/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts +++ b/x-pack/plugins/files/server/routes/file_kind/get_by_id.ts @@ -19,7 +19,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition }>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; diff --git a/x-pack/plugins/files/server/routes/file_kind/list.ts b/x-pack/plugins/files/server/routes/file_kind/list.ts index 3f1e36913bdc9..b6a869117b37f 100644 --- a/x-pack/plugins/files/server/routes/file_kind/list.ts +++ b/x-pack/plugins/files/server/routes/file_kind/list.ts @@ -18,7 +18,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition> }>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { diff --git a/x-pack/plugins/files/server/routes/file_kind/update.ts b/x-pack/plugins/files/server/routes/file_kind/update.ts index 733f9c9ce78c2..9621fc56c311c 100644 --- a/x-pack/plugins/files/server/routes/file_kind/update.ts +++ b/x-pack/plugins/files/server/routes/file_kind/update.ts @@ -26,7 +26,7 @@ const rt = { }), }; -export type Endpoint = CreateRouteDefinition; +export type Endpoint = CreateRouteDefinition }>; export const handler: CreateHandler = async ({ files, fileKind }, req, res) => { const { fileService } = await files; diff --git a/x-pack/plugins/files/server/routes/public_facing/download.ts b/x-pack/plugins/files/server/routes/public_facing/download.ts index bd739f93077fe..bd77cabaf7e4d 100644 --- a/x-pack/plugins/files/server/routes/public_facing/download.ts +++ b/x-pack/plugins/files/server/routes/public_facing/download.ts @@ -43,7 +43,7 @@ const handler: CreateHandler = async ({ files }, req, res) => { const body: Readable = await file.downloadContent(); return res.ok({ body, - headers: getDownloadHeadersForFile(file, fileName), + headers: getDownloadHeadersForFile({ file, fileName }), }); } catch (e) { if ( diff --git a/yarn.lock b/yarn.lock index d79a8afad43bb..8c11e9acf7d98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10852,6 +10852,11 @@ bluebird@3.7.2, bluebird@^3.3.5, bluebird@^3.5.5, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +blurhash@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-2.0.1.tgz#7f134ad0cf3cbb6bcceb81ea51b82e1423009dca" + integrity sha512-qAJW99ZIEVJqLKvR6EUtMavaalYiFgfHNvwO6eiqHE7RTBZYGQLPJvzs4WlnqSQPxZgqSPH/n4kRJIHzb/Y7dg== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" From a231f9c4fd7cde8ff19d41b1810b9e95f5aafd8c Mon Sep 17 00:00:00 2001 From: Ying Mao Date: Thu, 6 Oct 2022 10:21:09 -0400 Subject: [PATCH 166/174] [Response Ops][Alerting] Update stack rules to respect max alert limit (#141000) * wip * wip * Adding bucket selector clauses * Adding comparator script generator * Generating all the right queries * Skip condition check if group agg * Fixing functional test * Fixing comparator script * Fixing tests * Fixing tests * Renaming * Using limit services in es query rule executor Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/alert_types/es_query/executor.ts | 8 + .../index_threshold/action_context.test.ts | 26 +- .../index_threshold/action_context.ts | 8 +- .../alert_types/index_threshold/index.ts | 4 +- .../{alert_type.test.ts => rule_type.test.ts} | 124 +- .../{alert_type.ts => rule_type.ts} | 33 +- ...arams.test.ts => rule_type_params.test.ts} | 4 +- ...ert_type_params.ts => rule_type_params.ts} | 2 +- .../server/alert_types/lib/comparator.test.ts | 47 + .../server/alert_types/lib/comparator.ts | 39 + x-pack/plugins/stack_alerts/server/feature.ts | 2 +- x-pack/plugins/stack_alerts/server/index.ts | 2 +- .../triggers_actions_ui/common/data/index.ts | 1 + .../triggers_actions_ui/server/data/index.ts | 1 + .../server/data/lib/index.ts | 1 + .../server/data/lib/time_series_query.test.ts | 1606 ++++++++++++++++- .../server/data/lib/time_series_query.ts | 98 +- .../server/data/lib/time_series_types.ts | 7 + .../triggers_actions_ui/server/index.ts | 1 + .../common/lib/es_test_index_tool.ts | 3 + .../index_threshold/alert.ts | 39 + .../time_series_query_endpoint.ts | 9 + .../lib/create_test_data.ts | 1 + 23 files changed, 1918 insertions(+), 148 deletions(-) rename x-pack/plugins/stack_alerts/server/alert_types/index_threshold/{alert_type.test.ts => rule_type.test.ts} (73%) rename x-pack/plugins/stack_alerts/server/alert_types/index_threshold/{alert_type.ts => rule_type.ts} (88%) rename x-pack/plugins/stack_alerts/server/alert_types/index_threshold/{alert_type_params.test.ts => rule_type_params.test.ts} (98%) rename x-pack/plugins/stack_alerts/server/alert_types/index_threshold/{alert_type_params.ts => rule_type_params.ts} (98%) create mode 100644 x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.test.ts diff --git a/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts b/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts index 5f33eeb0af845..b52f5803405a7 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/es_query/executor.ts @@ -29,6 +29,8 @@ export async function executor( const currentTimestamp = new Date().toISOString(); const publicBaseUrl = core.http.basePath.publicBaseUrl ?? ''; + const alertLimit = alertFactory.alertLimit.getValue(); + const compareFn = ComparatorFns.get(params.thresholdComparator); if (compareFn == null) { throw new Error(getInvalidComparatorError(params.thresholdComparator)); @@ -91,6 +93,12 @@ export async function executor( if (firstValidTimefieldSort) { latestTimestamp = firstValidTimefieldSort; } + + // we only create one alert if the condition is met, so we would only ever + // reach the alert limit if the limit is less than 1 + alertFactory.alertLimit.setLimitReached(alertLimit < 1); + } else { + alertFactory.alertLimit.setLimitReached(false); } const { getRecoveredAlerts } = alertFactory.done(); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts index 4d8c1dc3d9b9f..0df74bb2f89c5 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.test.ts @@ -6,7 +6,7 @@ */ import { BaseActionContext, addMessages } from './action_context'; -import { ParamsSchema } from './alert_type_params'; +import { ParamsSchema } from './rule_type_params'; describe('ActionContext', () => { it('generates expected properties if aggField is null', async () => { @@ -28,10 +28,10 @@ describe('ActionContext', () => { value: 42, conditions: 'count greater than 4', }; - const context = addMessages({ name: '[alert-name]' }, base, params); - expect(context.title).toMatchInlineSnapshot(`"alert [alert-name] group [group] met threshold"`); + const context = addMessages({ name: '[rule-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot(`"alert [rule-name] group [group] met threshold"`); expect(context.message).toEqual( - `alert '[alert-name]' is active for group '[group]': + `alert '[rule-name]' is active for group '[group]': - Value: 42 - Conditions Met: count greater than 4 over 5m @@ -59,10 +59,10 @@ describe('ActionContext', () => { value: 42, conditions: 'avg([aggField]) greater than 4.2', }; - const context = addMessages({ name: '[alert-name]' }, base, params); - expect(context.title).toMatchInlineSnapshot(`"alert [alert-name] group [group] met threshold"`); + const context = addMessages({ name: '[rule-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot(`"alert [rule-name] group [group] met threshold"`); expect(context.message).toEqual( - `alert '[alert-name]' is active for group '[group]': + `alert '[rule-name]' is active for group '[group]': - Value: 42 - Conditions Met: avg([aggField]) greater than 4.2 over 5m @@ -89,10 +89,10 @@ describe('ActionContext', () => { value: 4, conditions: 'count between 4 and 5', }; - const context = addMessages({ name: '[alert-name]' }, base, params); - expect(context.title).toMatchInlineSnapshot(`"alert [alert-name] group [group] met threshold"`); + const context = addMessages({ name: '[rule-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot(`"alert [rule-name] group [group] met threshold"`); expect(context.message).toEqual( - `alert '[alert-name]' is active for group '[group]': + `alert '[rule-name]' is active for group '[group]': - Value: 4 - Conditions Met: count between 4 and 5 over 5m @@ -119,10 +119,10 @@ describe('ActionContext', () => { value: 'unknown', conditions: 'count between 4 and 5', }; - const context = addMessages({ name: '[alert-name]' }, base, params); - expect(context.title).toMatchInlineSnapshot(`"alert [alert-name] group [group] met threshold"`); + const context = addMessages({ name: '[rule-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot(`"alert [rule-name] group [group] met threshold"`); expect(context.message).toEqual( - `alert '[alert-name]' is active for group '[group]': + `alert '[rule-name]' is active for group '[group]': - Value: unknown - Conditions Met: count between 4 and 5 over 5m diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts index 94e7f9b8501a4..36ed27d8a7391 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/action_context.ts @@ -7,9 +7,9 @@ import { i18n } from '@kbn/i18n'; import { RuleExecutorOptions, AlertInstanceContext } from '@kbn/alerting-plugin/server'; -import { Params } from './alert_type_params'; +import { Params } from './rule_type_params'; -// alert type context provided to actions +// rule type context provided to actions type RuleInfo = Pick; @@ -21,10 +21,10 @@ export interface ActionContext extends BaseActionContext { } export interface BaseActionContext extends AlertInstanceContext { - // the aggType used in the alert + // the aggType used in the rule // the value of the aggField, if used, otherwise 'all documents' group: string; - // the date the alert was run as an ISO date + // the date the rule was run as an ISO date date: string; // the value that met the threshold value: number | string; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts index 065ef6b5ee22c..449c6528798a6 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts @@ -7,7 +7,7 @@ import { Logger } from '@kbn/core/server'; import { AlertingSetup, StackAlertsStartDeps } from '../../types'; -import { getAlertType } from './alert_type'; +import { getRuleType } from './rule_type'; // future enhancement: make these configurable? export const MAX_INTERVALS = 1000; @@ -22,5 +22,5 @@ interface RegisterParams { export function register(params: RegisterParams) { const { logger, data, alerting } = params; - alerting.registerType(getAlertType(logger, data)); + alerting.registerType(getRuleType(logger, data)); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts similarity index 73% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts rename to x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts index 1751fc6fc2344..656a1e5f275e5 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.test.ts @@ -6,34 +6,44 @@ */ import uuid from 'uuid'; +import sinon from 'sinon'; import type { Writable } from '@kbn/utility-types'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { RuleExecutorServices } from '@kbn/alerting-plugin/server'; -import { getAlertType, ActionGroupId } from './alert_type'; +import { getRuleType, ActionGroupId } from './rule_type'; import { ActionContext } from './action_context'; -import { Params } from './alert_type_params'; +import { Params } from './rule_type_params'; +import { TIME_SERIES_BUCKET_SELECTOR_FIELD } from '@kbn/triggers-actions-ui-plugin/server'; import { RuleExecutorServicesMock, alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { Comparator } from '../../../common/comparator_types'; -describe('alertType', () => { +let fakeTimer: sinon.SinonFakeTimers; + +describe('ruleType', () => { const logger = loggingSystemMock.create().get(); const data = { timeSeriesQuery: jest.fn(), }; const alertServices: RuleExecutorServicesMock = alertsMock.createRuleExecutorServices(); - const alertType = getAlertType(logger, Promise.resolve(data)); + const ruleType = getRuleType(logger, Promise.resolve(data)); + + beforeAll(() => { + fakeTimer = sinon.useFakeTimers(); + }); afterEach(() => { data.timeSeriesQuery.mockReset(); }); - it('alert type creation structure is the expected value', async () => { - expect(alertType.id).toBe('.index-threshold'); - expect(alertType.name).toBe('Index threshold'); - expect(alertType.actionGroups).toEqual([{ id: 'threshold met', name: 'Threshold met' }]); + afterAll(() => fakeTimer.restore()); - expect(alertType.actionVariables).toMatchInlineSnapshot(` + it('rule type creation structure is the expected value', async () => { + expect(ruleType.id).toBe('.index-threshold'); + expect(ruleType.name).toBe('Index threshold'); + expect(ruleType.actionGroups).toEqual([{ id: 'threshold met', name: 'Threshold met' }]); + + expect(ruleType.actionVariables).toMatchInlineSnapshot(` Object { "context": Array [ Object { @@ -123,11 +133,11 @@ describe('alertType', () => { threshold: [0], }; - expect(alertType.validate?.params?.validate(params)).toBeTruthy(); + expect(ruleType.validate?.params?.validate(params)).toBeTruthy(); }); it('validator fails with invalid params', async () => { - const paramsSchema = alertType.validate?.params; + const paramsSchema = ruleType.validate?.params; if (!paramsSchema) throw new Error('params validator not set'); const params: Partial> = { @@ -168,7 +178,7 @@ describe('alertType', () => { threshold: [1], }; - await alertType.executor({ + await ruleType.executor({ alertId: uuid.v4(), executionId: uuid.v4(), startedAt: new Date(), @@ -234,7 +244,7 @@ describe('alertType', () => { threshold: [1], }; - await alertType.executor({ + await ruleType.executor({ alertId: uuid.v4(), executionId: uuid.v4(), startedAt: new Date(), @@ -300,7 +310,7 @@ describe('alertType', () => { threshold: [1], }; - await alertType.executor({ + await ruleType.executor({ alertId: uuid.v4(), executionId: uuid.v4(), startedAt: new Date(), @@ -342,4 +352,90 @@ describe('alertType', () => { expect(customAlertServices.alertFactory.create).not.toHaveBeenCalled(); }); + + it('should correctly pass comparator script to timeSeriesQuery', async () => { + data.timeSeriesQuery.mockImplementation((...args) => { + return { + results: [ + { + group: 'all documents', + metrics: [['2021-07-14T14:49:30.978Z', 0]], + }, + ], + }; + }); + const params: Params = { + index: 'index-name', + timeField: 'time-field', + aggType: 'foo', + groupBy: 'all', + timeWindowSize: 5, + timeWindowUnit: 'm', + thresholdComparator: Comparator.LT, + threshold: [1], + }; + + await ruleType.executor({ + alertId: uuid.v4(), + executionId: uuid.v4(), + startedAt: new Date(), + previousStartedAt: new Date(), + services: alertServices as unknown as RuleExecutorServices< + {}, + ActionContext, + typeof ActionGroupId + >, + params, + state: { + latestTimestamp: undefined, + }, + spaceId: uuid.v4(), + name: uuid.v4(), + tags: [], + createdBy: null, + updatedBy: null, + rule: { + name: uuid.v4(), + tags: [], + consumer: '', + producer: '', + ruleTypeId: '', + ruleTypeName: '', + enabled: true, + schedule: { + interval: '1h', + }, + actions: [], + createdBy: null, + updatedBy: null, + createdAt: new Date(), + updatedAt: new Date(), + throttle: null, + notifyWhen: null, + }, + }); + + expect(data.timeSeriesQuery).toHaveBeenCalledWith( + expect.objectContaining({ + query: { + aggField: undefined, + aggType: 'foo', + dateEnd: '1970-01-01T00:00:00.000Z', + dateStart: '1970-01-01T00:00:00.000Z', + groupBy: 'all', + index: 'index-name', + interval: undefined, + termField: undefined, + termSize: undefined, + timeField: 'time-field', + timeWindowSize: 5, + timeWindowUnit: 'm', + }, + condition: { + conditionScript: `${TIME_SERIES_BUCKET_SELECTOR_FIELD} < 1L`, + resultLimit: 1000, + }, + }) + ); + }); }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts similarity index 88% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts rename to x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts index 58d680794aedf..3b3480407fcfc 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type.ts @@ -10,21 +10,23 @@ import { Logger } from '@kbn/core/server'; import { CoreQueryParamsSchemaProperties, TimeSeriesQuery, + TIME_SERIES_BUCKET_SELECTOR_FIELD, } from '@kbn/triggers-actions-ui-plugin/server'; import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types'; -import { Params, ParamsSchema } from './alert_type_params'; +import { Params, ParamsSchema } from './rule_type_params'; import { ActionContext, BaseActionContext, addMessages } from './action_context'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { ComparatorFns, getHumanReadableComparator } from '../lib'; +import { getComparatorScript } from '../lib/comparator'; export const ID = '.index-threshold'; export const ActionGroupId = 'threshold met'; -export function getAlertType( +export function getRuleType( logger: Logger, data: Promise ): RuleType { - const alertTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', { + const ruleTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', { defaultMessage: 'Index threshold', }); @@ -92,7 +94,7 @@ export function getAlertType( } ); - const alertParamsVariables = Object.keys(CoreQueryParamsSchemaProperties).map( + const ruleParamsVariables = Object.keys(CoreQueryParamsSchemaProperties).map( (propKey: string) => { return { name: propKey, @@ -103,7 +105,7 @@ export function getAlertType( return { id: ID, - name: alertTypeName, + name: ruleTypeName, actionGroups: [{ id: ActionGroupId, name: actionGroupName }], defaultActionGroupId: ActionGroupId, validate: { @@ -121,7 +123,7 @@ export function getAlertType( params: [ { name: 'threshold', description: actionVariableContextThresholdLabel }, { name: 'thresholdComparator', description: actionVariableContextThresholdComparatorLabel }, - ...alertParamsVariables, + ...ruleParamsVariables, ], }, minimumLicenseRequired: 'basic', @@ -137,6 +139,8 @@ export function getAlertType( const { alertId: ruleId, name, services, params } = options; const { alertFactory, scopedClusterClient } = services; + const alertLimit = alertFactory.alertLimit.getValue(); + const compareFn = ComparatorFns.get(params.thresholdComparator); if (compareFn == null) { throw new Error( @@ -173,9 +177,19 @@ export function getAlertType( logger, esClient, query: queryParams, + condition: { + resultLimit: alertLimit, + conditionScript: getComparatorScript( + params.thresholdComparator, + params.threshold, + TIME_SERIES_BUCKET_SELECTOR_FIELD + ), + }, }); logger.debug(`rule ${ID}:${ruleId} "${name}" query result: ${JSON.stringify(result)}`); + const isGroupAgg = !!queryParams.termField; + const unmetGroupValues: Record = {}; const agg = params.aggField ? `${params.aggType}(${params.aggField})` : `${params.aggType}`; @@ -196,7 +210,10 @@ export function getAlertType( continue; } - const met = compareFn(value, params.threshold); + // group aggregations use the bucket selector agg to compare conditions + // within the ES query, so only 'met' results are returned, therefore we don't need + // to use the compareFn + const met = isGroupAgg ? true : compareFn(value, params.threshold); if (!met) { unmetGroupValues[alertId] = value; @@ -219,6 +236,8 @@ export function getAlertType( logger.debug(`scheduled actionGroup: ${JSON.stringify(actionContext)}`); } + alertFactory.alertLimit.setLimitReached(result.truncated); + const { getRecoveredAlerts } = services.alertFactory.done(); for (const recoveredAlert of getRecoveredAlerts()) { const alertId = recoveredAlert.getId(); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type_params.test.ts similarity index 98% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts rename to x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type_params.test.ts index 7bcf84db20a1f..c4dd8f2149255 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type_params.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ParamsSchema, Params } from './alert_type_params'; +import { ParamsSchema, Params } from './rule_type_params'; import { ObjectType, TypeOf } from '@kbn/config-schema'; import type { Writable } from '@kbn/utility-types'; import { CoreQueryParams, MAX_GROUPS } from '@kbn/triggers-actions-ui-plugin/server'; @@ -22,7 +22,7 @@ const DefaultParams: Writable> = { threshold: [0], }; -describe('alertType Params validate()', () => { +describe('ruleType Params validate()', () => { runTests(ParamsSchema, DefaultParams); // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type_params.ts similarity index 98% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts rename to x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type_params.ts index 5783b6a64ab83..9018d915f4e48 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/rule_type_params.ts @@ -15,7 +15,7 @@ import { ComparatorFnNames } from '../lib'; import { Comparator } from '../../../common/comparator_types'; import { getComparatorSchemaType } from '../lib/comparator'; -// alert type parameters +// rule type parameters export type Params = TypeOf; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.test.ts new file mode 100644 index 0000000000000..62447c12fdf49 --- /dev/null +++ b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getComparatorScript } from './comparator'; +import { Comparator } from '../../../common/comparator_types'; + +describe('getComparatorScript', () => { + it('correctly returns script when comparator is LT', () => { + expect(getComparatorScript(Comparator.LT, [10], 'fieldName')).toEqual(`fieldName < 10L`); + }); + it('correctly returns script when comparator is LT_OR_EQ', () => { + expect(getComparatorScript(Comparator.LT_OR_EQ, [10], 'fieldName')).toEqual(`fieldName <= 10L`); + }); + it('correctly returns script when comparator is GT', () => { + expect(getComparatorScript(Comparator.GT, [10], 'fieldName')).toEqual(`fieldName > 10L`); + }); + it('correctly returns script when comparator is GT_OR_EQ', () => { + expect(getComparatorScript(Comparator.GT_OR_EQ, [10], 'fieldName')).toEqual(`fieldName >= 10L`); + }); + it('correctly returns script when comparator is BETWEEN', () => { + expect(getComparatorScript(Comparator.BETWEEN, [10, 100], 'fieldName')).toEqual( + `fieldName >= 10L && fieldName <= 100L` + ); + }); + it('correctly returns script when comparator is NOT_BETWEEN', () => { + expect(getComparatorScript(Comparator.NOT_BETWEEN, [10, 100], 'fieldName')).toEqual( + `fieldName < 10L || fieldName > 100L` + ); + }); + it('correctly returns script when threshold is float', () => { + expect(getComparatorScript(Comparator.LT, [3.5454], 'fieldName')).toEqual(`fieldName < 3.5454`); + }); + it('throws error when threshold is empty', () => { + expect(() => { + getComparatorScript(Comparator.LT, [], 'fieldName'); + }).toThrowErrorMatchingInlineSnapshot(`"Threshold value required"`); + }); + it('throws error when comparator requires two thresholds and two thresholds are not defined', () => { + expect(() => { + getComparatorScript(Comparator.BETWEEN, [1], 'fieldName'); + }).toThrowErrorMatchingInlineSnapshot(`"Threshold values required"`); + }); +}); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts index 524e3a7554dc2..dbdc310b88038 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/lib/comparator.ts @@ -34,6 +34,45 @@ export const ComparatorFns = new Map([ ], ]); +export const getComparatorScript = ( + comparator: Comparator, + threshold: number[], + fieldName: string +) => { + if (threshold.length === 0) { + throw new Error('Threshold value required'); + } + + function getThresholdString(thresh: number) { + return Number.isInteger(thresh) ? `${thresh}L` : `${thresh}`; + } + + switch (comparator) { + case Comparator.LT: + return `${fieldName} < ${getThresholdString(threshold[0])}`; + case Comparator.LT_OR_EQ: + return `${fieldName} <= ${getThresholdString(threshold[0])}`; + case Comparator.GT: + return `${fieldName} > ${getThresholdString(threshold[0])}`; + case Comparator.GT_OR_EQ: + return `${fieldName} >= ${getThresholdString(threshold[0])}`; + case Comparator.BETWEEN: + if (threshold.length < 2) { + throw new Error('Threshold values required'); + } + return `${fieldName} >= ${getThresholdString( + threshold[0] + )} && ${fieldName} <= ${getThresholdString(threshold[1])}`; + case Comparator.NOT_BETWEEN: + if (threshold.length < 2) { + throw new Error('Threshold values required'); + } + return `${fieldName} < ${getThresholdString( + threshold[0] + )} || ${fieldName} > ${getThresholdString(threshold[1])}`; + } +}; + export const getComparatorSchemaType = (validate: (comparator: Comparator) => string | void) => schema.oneOf( [ diff --git a/x-pack/plugins/stack_alerts/server/feature.ts b/x-pack/plugins/stack_alerts/server/feature.ts index f7257651f2aeb..9005a8435657b 100644 --- a/x-pack/plugins/stack_alerts/server/feature.ts +++ b/x-pack/plugins/stack_alerts/server/feature.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { KibanaFeatureConfig } from '@kbn/features-plugin/common'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { TRANSFORM_RULE_TYPE } from '@kbn/transform-plugin/common'; -import { ID as IndexThreshold } from './alert_types/index_threshold/alert_type'; +import { ID as IndexThreshold } from './alert_types/index_threshold/rule_type'; import { GEO_CONTAINMENT_ID as GeoContainment } from './alert_types/geo_containment/alert_type'; import { ES_QUERY_ID as ElasticsearchQuery } from './alert_types/es_query/constants'; import { STACK_ALERTS_FEATURE_ID } from '../common'; diff --git a/x-pack/plugins/stack_alerts/server/index.ts b/x-pack/plugins/stack_alerts/server/index.ts index 8f18ac9a4df9a..5483124209028 100644 --- a/x-pack/plugins/stack_alerts/server/index.ts +++ b/x-pack/plugins/stack_alerts/server/index.ts @@ -8,7 +8,7 @@ import { get } from 'lodash'; import { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; import { AlertingBuiltinsPlugin } from './plugin'; import { configSchema, Config } from '../common/config'; -export { ID as INDEX_THRESHOLD_ID } from './alert_types/index_threshold/alert_type'; +export { ID as INDEX_THRESHOLD_ID } from './alert_types/index_threshold/rule_type'; export const config: PluginConfigDescriptor = { exposeToBrowser: {}, diff --git a/x-pack/plugins/triggers_actions_ui/common/data/index.ts b/x-pack/plugins/triggers_actions_ui/common/data/index.ts index e368c77fe5479..e0c0f240c3b2f 100644 --- a/x-pack/plugins/triggers_actions_ui/common/data/index.ts +++ b/x-pack/plugins/triggers_actions_ui/common/data/index.ts @@ -7,6 +7,7 @@ export interface TimeSeriesResult { results: TimeSeriesResultRow[]; + truncated: boolean; } export interface TimeSeriesResultRow { diff --git a/x-pack/plugins/triggers_actions_ui/server/data/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/index.ts index c65f44162ecd7..aae400a7469ea 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/index.ts @@ -11,6 +11,7 @@ import { registerRoutes } from './routes'; export type { TimeSeriesQuery, CoreQueryParams } from './lib'; export { + TIME_SERIES_BUCKET_SELECTOR_FIELD, CoreQueryParamsSchemaProperties, validateCoreQueryBody, validateTimeWindowUnits, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts index c76eb1cafa867..b99f278837f26 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts @@ -6,6 +6,7 @@ */ export type { TimeSeriesQuery } from './time_series_query'; +export { TIME_SERIES_BUCKET_SELECTOR_FIELD } from './time_series_query'; export type { CoreQueryParams } from './core_query_types'; export { CoreQueryParamsSchemaProperties, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index dc037dde8f499..5e2550e15f35d 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -5,9 +5,6 @@ * 2.0. */ -// test error conditions of calling timeSeriesQuery - postive results tested in FT - -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import { Logger } from '@kbn/core/server'; import { TimeSeriesQuery, timeSeriesQuery, getResultFromEs } from './time_series_query'; @@ -20,9 +17,9 @@ const DefaultQueryParams: TimeSeriesQuery = { aggField: undefined, timeWindowSize: 5, timeWindowUnit: 'm', - dateStart: undefined, - dateEnd: undefined, - interval: undefined, + dateStart: '2021-04-22T15:19:31Z', + dateEnd: '2021-04-22T15:20:31Z', + interval: '1m', groupBy: 'all', termField: undefined, termSize: undefined, @@ -37,6 +34,10 @@ describe('timeSeriesQuery', () => { query: DefaultQueryParams, }; + beforeEach(() => { + jest.clearAllMocks(); + }); + it('fails as expected when the callCluster call fails', async () => { esClient.search.mockRejectedValue(new Error('woopsie')); await timeSeriesQuery(params); @@ -48,141 +49,1560 @@ describe('timeSeriesQuery', () => { }); it('fails as expected when the query params are invalid', async () => { - params.query = { ...params.query, dateStart: 'x' }; - expect(timeSeriesQuery(params)).rejects.toThrowErrorMatchingInlineSnapshot( - `"invalid date format for dateStart: \\"x\\""` + expect( + timeSeriesQuery({ ...params, query: { ...params.query, dateStart: 'x' } }) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"invalid date format for dateStart: \\"x\\""`); + }); + + it('should create correct query when aggType=count and termField is undefined (count over all) and selector params are undefined', async () => { + await timeSeriesQuery(params); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType=count and termField is undefined (count over all) and selector params are defined', async () => { + await timeSeriesQuery({ + ...params, + condition: { + resultLimit: 1000, + conditionScript: `params.compareValue > 1`, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType=count and termField is specified (count over top N termField) and selector params are undefined', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + termField: 'the-term', + termSize: 10, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + groupAgg: { + terms: { + field: 'the-term', + size: 10, + }, + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType=count and termField is specified (count over top N termField) and selector params are defined', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + termField: 'the-term', + termSize: 10, + }, + condition: { + resultLimit: 1000, + conditionScript: `params.compareValue > 1`, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + groupAgg: { + terms: { + field: 'the-term', + size: 10, + }, + aggs: { + conditionSelector: { + bucket_selector: { + buckets_path: { + compareValue: '_count', + }, + script: `params.compareValue > 1`, + }, + }, + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + }, + groupAggCount: { + stats_bucket: { + buckets_path: 'groupAgg._count', + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType!=count and termField is undefined (aggregate metric over all) and selector params are undefined', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + aggType: 'avg', + aggField: 'avg-field', + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + aggs: { + metricAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + }, + sortValueAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType!=count and termField is undefined (aggregate metric over all) and selector params are defined', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + aggType: 'avg', + aggField: 'avg-field', + }, + condition: { + resultLimit: 1000, + conditionScript: `params.compareValue > 1`, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + aggs: { + metricAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + }, + sortValueAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType!=count and termField is specified (aggregate metric over top N termField) and selector params are undefined', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + aggType: 'avg', + aggField: 'avg-field', + termField: 'the-field', + termSize: 20, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + groupAgg: { + terms: { + field: 'the-field', + order: { + sortValueAgg: 'desc', + }, + size: 20, + }, + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + aggs: { + metricAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + }, + sortValueAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should create correct query when aggType!=count and termField is specified (aggregate metric over top N termField) and selector params are defined', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + aggType: 'avg', + aggField: 'avg-field', + termField: 'the-field', + termSize: 20, + }, + condition: { + resultLimit: 1000, + conditionScript: `params.compareValue > 1`, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + groupAgg: { + terms: { + field: 'the-field', + order: { + sortValueAgg: 'desc', + }, + size: 20, + }, + aggs: { + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + aggs: { + metricAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + }, + conditionSelector: { + bucket_selector: { + buckets_path: { + compareValue: 'sortValueAgg', + }, + script: 'params.compareValue > 1', + }, + }, + sortValueAgg: { + avg: { + field: 'avg-field', + }, + }, + }, + }, + groupAggCount: { + stats_bucket: { + buckets_path: 'groupAgg._count', + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } + ); + }); + + it('should correctly apply the resultLimit if specified', async () => { + await timeSeriesQuery({ + ...params, + query: { + ...params.query, + termField: 'the-term', + termSize: 100, + }, + condition: { + resultLimit: 5, + conditionScript: `params.compareValue > 1`, + }, + }); + expect(esClient.search).toHaveBeenCalledWith( + { + allow_no_indices: true, + body: { + aggs: { + groupAgg: { + terms: { + field: 'the-term', + size: 6, + }, + aggs: { + conditionSelector: { + bucket_selector: { + buckets_path: { + compareValue: '_count', + }, + script: `params.compareValue > 1`, + }, + }, + dateAgg: { + date_range: { + field: 'time-field', + format: 'strict_date_time', + ranges: [ + { + from: '2021-04-22T15:14:31.000Z', + to: '2021-04-22T15:19:31.000Z', + }, + { + from: '2021-04-22T15:15:31.000Z', + to: '2021-04-22T15:20:31.000Z', + }, + ], + }, + }, + }, + }, + groupAggCount: { + stats_bucket: { + buckets_path: 'groupAgg._count', + }, + }, + }, + query: { + bool: { + filter: { + range: { + 'time-field': { + format: 'strict_date_time', + gte: '2021-04-22T15:14:31.000Z', + lt: '2021-04-22T15:20:31.000Z', + }, + }, + }, + }, + }, + size: 0, + }, + ignore_unavailable: true, + index: 'index-name', + }, + { ignore: [404], meta: true } ); }); }); describe('getResultFromEs', () => { - it('correctly parses time series results for count aggregation', () => { + it('correctly parses time series results for count over all aggregation', () => { + // results should be same whether isConditionInQuery is true or false expect( - getResultFromEs(true, false, { - took: 0, - timed_out: false, - _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, - hits: { total: { value: 0, relation: 'eq' }, hits: [] }, - aggregations: { - dateAgg: { - buckets: [ - { - key: '2021-04-22T15:14:31.075Z-2021-04-22T15:19:31.075Z', - from: 1619104471075, - from_as_string: '2021-04-22T15:14:31.075Z', - to: 1619104771075, - to_as_string: '2021-04-22T15:19:31.075Z', - doc_count: 0, - }, - ], + getResultFromEs({ + isCountAgg: true, + isGroupAgg: false, + isConditionInQuery: true, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + dateAgg: { + buckets: [ + { + key: '2022-09-20T00:14:31.000Z-2022-09-20T23:19:31.000Z', + from: 1663632871000, + from_as_string: '2022-09-20T00:14:31.000Z', + to: 1663715971000, + to_as_string: '2022-09-20T23:19:31.000Z', + doc_count: 481, + }, + ], + }, }, }, - } as estypes.SearchResponse) + }) ).toEqual({ results: [ { group: 'all documents', - metrics: [['2021-04-22T15:19:31.075Z', 0]], + metrics: [['2022-09-20T23:19:31.000Z', 481]], }, ], + truncated: false, + }); + + expect( + getResultFromEs({ + isCountAgg: true, + isGroupAgg: false, + isConditionInQuery: false, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + dateAgg: { + buckets: [ + { + key: '2022-09-20T00:14:31.000Z-2022-09-20T23:19:31.000Z', + from: 1663632871000, + from_as_string: '2022-09-20T00:14:31.000Z', + to: 1663715971000, + to_as_string: '2022-09-20T23:19:31.000Z', + doc_count: 481, + }, + ], + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'all documents', + metrics: [['2022-09-20T23:19:31.000Z', 481]], + }, + ], + truncated: false, }); }); - it('correctly parses time series results with no aggregation data for count aggregation', () => { + it('correctly parses time series results with no aggregation data for count over all aggregation', () => { // this could happen with cross cluster searches when cluster permissions are incorrect // the query completes but doesn't return any aggregations + + // results should be same whether isConditionInQuery is true or false expect( - getResultFromEs(true, false, { - took: 0, - timed_out: false, - _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, - _clusters: { total: 1, successful: 1, skipped: 0 }, - hits: { total: { value: 0, relation: 'eq' }, hits: [] }, - } as estypes.SearchResponse) + getResultFromEs({ + isCountAgg: true, + isGroupAgg: false, + isConditionInQuery: true, + esResult: { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + }, + }) ).toEqual({ results: [], + truncated: false, + }); + + expect( + getResultFromEs({ + isCountAgg: true, + isGroupAgg: false, + isConditionInQuery: false, + esResult: { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + }, + }) + ).toEqual({ + results: [], + truncated: false, }); }); - it('correctly parses time series results for group aggregation', () => { + it('correctly parses time series results for count over top N termField aggregation when isConditionInQuery = false', () => { expect( - getResultFromEs(false, true, { - took: 1, - timed_out: false, - _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, - hits: { total: { value: 298, relation: 'eq' }, hits: [] }, - aggregations: { - groupAgg: { - doc_count_error_upper_bound: 0, - sum_other_doc_count: 0, - buckets: [ - { - key: 'host-2', - doc_count: 149, - sortValueAgg: { value: 0.5000000018251423 }, - dateAgg: { - buckets: [ - { - key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', - from: 1619104723191, - from_as_string: '2021-04-22T15:18:43.191Z', - to: 1619105023191, - to_as_string: '2021-04-22T15:23:43.191Z', - doc_count: 149, - metricAgg: { value: 0.5000000018251423 }, - }, - ], + getResultFromEs({ + isCountAgg: true, + isGroupAgg: true, + isConditionInQuery: false, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + }, + ], + }, }, - }, - { - key: 'host-1', - doc_count: 149, - sortValueAgg: { value: 0.5000000011000857 }, - dateAgg: { - buckets: [ - { - key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', - from: 1619104723191, - from_as_string: '2021-04-22T15:18:43.191Z', - to: 1619105023191, - to_as_string: '2021-04-22T15:23:43.191Z', - doc_count: 149, - metricAgg: { value: 0.5000000011000857 }, - }, - ], + { + key: 'host-1', + doc_count: 53, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 53, + }, + ], + }, }, - }, - ], + ], + }, }, }, - } as estypes.SearchResponse) + }) ).toEqual({ results: [ { group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 149]], + }, + { + group: 'host-1', + metrics: [['2021-04-22T15:23:43.191Z', 53]], + }, + ], + truncated: false, + }); + }); + + it('correctly parses time series results for count over top N termField aggregation when isConditionInQuery = true', () => { + expect( + getResultFromEs({ + isCountAgg: true, + isGroupAgg: true, + isConditionInQuery: true, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 53, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 53, + }, + ], + }, + }, + ], + }, + groupAggCount: { + count: 2, + min: 90, + max: 90, + avg: 90, + sum: 180, + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 149]], + }, + { + group: 'host-1', + metrics: [['2021-04-22T15:23:43.191Z', 53]], + }, + ], + truncated: false, + }); + }); + + it('correctly returns truncated status for time series results for count over top N termField aggregation when isConditionInQuery = true', () => { + expect( + getResultFromEs({ + isCountAgg: true, + isGroupAgg: true, + isConditionInQuery: true, + resultLimit: 5, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 53, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 53, + }, + ], + }, + }, + { + key: 'host-3', + doc_count: 40, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 40, + }, + ], + }, + }, + { + key: 'host-6', + doc_count: 55, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 55, + }, + ], + }, + }, + { + key: 'host-9', + doc_count: 54, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 54, + }, + ], + }, + }, + { + key: 'host-11', + doc_count: 2, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 2, + }, + ], + }, + }, + ], + }, + groupAggCount: { + count: 6, + min: 90, + max: 90, + avg: 90, + sum: 180, + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 149]], + }, + { + group: 'host-1', + metrics: [['2021-04-22T15:23:43.191Z', 53]], + }, + { + group: 'host-3', + metrics: [['2021-04-22T15:23:43.191Z', 40]], + }, + { + group: 'host-6', + metrics: [['2021-04-22T15:23:43.191Z', 55]], + }, + { + group: 'host-9', + metrics: [['2021-04-22T15:23:43.191Z', 54]], + }, + ], + truncated: true, + }); + }); + + it('correctly parses time series results with no aggregation data for count over top N termField aggregation', () => { + // this could happen with cross cluster searches when cluster permissions are incorrect + // the query completes but doesn't return any aggregations + + // results should be same whether isConditionInQuery is true or false + expect( + getResultFromEs({ + isCountAgg: true, + isGroupAgg: true, + isConditionInQuery: true, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + }, + }) + ).toEqual({ + results: [], + truncated: false, + }); + + expect( + getResultFromEs({ + isCountAgg: true, + isGroupAgg: true, + isConditionInQuery: false, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + }, + }) + ).toEqual({ + results: [], + truncated: false, + }); + }); + + it('correctly parses time series results for aggregate metric over all aggregation', () => { + // results should be same whether isConditionInQuery is true or false + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: false, + isConditionInQuery: true, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2022-09-20T00:14:31.000Z-2022-09-20T23:19:31.000Z', + from: 1663632871000, + from_as_string: '2022-09-20T00:14:31.000Z', + to: 1663715971000, + to_as_string: '2022-09-20T23:19:31.000Z', + doc_count: 481, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'all documents', + metrics: [['2022-09-20T23:19:31.000Z', 0.5000000018251423]], + }, + ], + truncated: false, + }); + + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: false, + isConditionInQuery: false, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2022-09-20T00:14:31.000Z-2022-09-20T23:19:31.000Z', + from: 1663632871000, + from_as_string: '2022-09-20T00:14:31.000Z', + to: 1663715971000, + to_as_string: '2022-09-20T23:19:31.000Z', + doc_count: 481, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'all documents', + metrics: [['2022-09-20T23:19:31.000Z', 0.5000000018251423]], + }, + ], + truncated: false, + }); + }); + + it('correctly parses time series results with no aggregation data for aggregate metric over all aggregation', () => { + // this could happen with cross cluster searches when cluster permissions are incorrect + // the query completes but doesn't return any aggregations + + // results should be same whether isConditionInQuery is true or false + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: false, + isConditionInQuery: true, + esResult: { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + }, + }) + ).toEqual({ + results: [], + truncated: false, + }); + + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: false, + isConditionInQuery: false, + esResult: { + took: 0, + timed_out: false, + _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, + _clusters: { total: 1, successful: 1, skipped: 0 }, + hits: { total: { value: 0, relation: 'eq' }, hits: [] }, + }, + }) + ).toEqual({ + results: [], + truncated: false, + }); + }); + + it('correctly parses time series results for aggregate metric over top N termField aggregation when isConditionInQuery = false', () => { + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: true, + isConditionInQuery: false, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + sortValueAgg: { value: 0.7100000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.7100000018251423 }, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 53, + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 53, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + ], + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 0.7100000018251423]], + }, + { + group: 'host-1', + metrics: [['2021-04-22T15:23:43.191Z', 0.5000000018251423]], + }, + ], + truncated: false, + }); + }); + + it('correctly parses time series results for aggregate metric over top N termField aggregation when isConditionInQuery = true', () => { + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: true, + isConditionInQuery: true, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + sortValueAgg: { value: 0.7100000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.7100000018251423 }, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 53, + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 53, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + ], + }, + groupAggCount: { + count: 2, + min: 75, + max: 90, + avg: 82.5, + sum: 165, + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 0.7100000018251423]], + }, + { + group: 'host-1', metrics: [['2021-04-22T15:23:43.191Z', 0.5000000018251423]], }, + ], + truncated: false, + }); + }); + + it('correctly returns truncated status for time series results for aggregate metrics over top N termField aggregation when isConditionInQuery = true', () => { + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: true, + isConditionInQuery: true, + resultLimit: 5, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + aggregations: { + groupAgg: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'host-2', + doc_count: 149, + sortValueAgg: { value: 0.7100000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 149, + metricAgg: { value: 0.7100000018251423 }, + }, + ], + }, + }, + { + key: 'host-1', + doc_count: 53, + sortValueAgg: { value: 0.5000000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 53, + metricAgg: { value: 0.5000000018251423 }, + }, + ], + }, + }, + { + key: 'host-3', + doc_count: 40, + sortValueAgg: { value: 0.4900000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 40, + metricAgg: { value: 0.4900000018251423 }, + }, + ], + }, + }, + { + key: 'host-6', + doc_count: 55, + sortValueAgg: { value: 0.4600000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 55, + metricAgg: { value: 0.4600000018251423 }, + }, + ], + }, + }, + { + key: 'host-9', + doc_count: 54, + sortValueAgg: { value: 0.3300000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 54, + metricAgg: { value: 0.3300000018251423 }, + }, + ], + }, + }, + { + key: 'host-11', + doc_count: 2, + sortValueAgg: { value: 0.1200000018251423 }, + dateAgg: { + buckets: [ + { + key: '2021-04-22T15:18:43.191Z-2021-04-22T15:23:43.191Z', + from: 1619104723191, + from_as_string: '2021-04-22T15:18:43.191Z', + to: 1619105023191, + to_as_string: '2021-04-22T15:23:43.191Z', + doc_count: 2, + metricAgg: { value: 0.1200000018251423 }, + }, + ], + }, + }, + ], + }, + groupAggCount: { + count: 6, + min: 75, + max: 90, + avg: 82.5, + sum: 165, + }, + }, + }, + }) + ).toEqual({ + results: [ + { + group: 'host-2', + metrics: [['2021-04-22T15:23:43.191Z', 0.7100000018251423]], + }, { group: 'host-1', - metrics: [['2021-04-22T15:23:43.191Z', 0.5000000011000857]], + metrics: [['2021-04-22T15:23:43.191Z', 0.5000000018251423]], + }, + { + group: 'host-3', + metrics: [['2021-04-22T15:23:43.191Z', 0.4900000018251423]], + }, + { + group: 'host-6', + metrics: [['2021-04-22T15:23:43.191Z', 0.4600000018251423]], + }, + { + group: 'host-9', + metrics: [['2021-04-22T15:23:43.191Z', 0.3300000018251423]], }, ], + truncated: true, }); }); - it('correctly parses time series results with no aggregation data for group aggregation', () => { + it('correctly parses time series results with no aggregation data for aggregate metric over top N termField aggregation', () => { // this could happen with cross cluster searches when cluster permissions are incorrect // the query completes but doesn't return any aggregations + + // results should be same whether isConditionInQuery is true or false + expect( + getResultFromEs({ + isCountAgg: false, + isGroupAgg: true, + isConditionInQuery: true, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + }, + }) + ).toEqual({ + results: [], + truncated: false, + }); + expect( - getResultFromEs(false, true, { - took: 0, - timed_out: false, - _shards: { total: 0, successful: 0, skipped: 0, failed: 0 }, - _clusters: { total: 1, successful: 1, skipped: 0 }, - hits: { total: { value: 0, relation: 'eq' }, hits: [] }, - } as estypes.SearchResponse) + getResultFromEs({ + isCountAgg: false, + isGroupAgg: true, + isConditionInQuery: false, + esResult: { + took: 1, + timed_out: false, + _shards: { total: 1, successful: 1, skipped: 0, failed: 0 }, + hits: { total: { value: 481, relation: 'eq' }, max_score: null, hits: [] }, + }, + }) ).toEqual({ results: [], + truncated: false, }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index 6f5ebbe34a891..885be0bf59f5b 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -12,19 +12,28 @@ import { getEsErrorMessage } from '@kbn/alerting-plugin/server'; import { DEFAULT_GROUPS } from '..'; import { getDateRangeInfo } from './date_range_info'; -import { TimeSeriesQuery, TimeSeriesResult, TimeSeriesResultRow } from './time_series_types'; +import { + TimeSeriesQuery, + TimeSeriesResult, + TimeSeriesResultRow, + TimeSeriesCondition, +} from './time_series_types'; export type { TimeSeriesQuery, TimeSeriesResult } from './time_series_types'; +export const TIME_SERIES_BUCKET_SELECTOR_PATH_NAME = 'compareValue'; +export const TIME_SERIES_BUCKET_SELECTOR_FIELD = `params.${TIME_SERIES_BUCKET_SELECTOR_PATH_NAME}`; + export interface TimeSeriesQueryParameters { logger: Logger; esClient: ElasticsearchClient; query: TimeSeriesQuery; + condition?: TimeSeriesCondition; } export async function timeSeriesQuery( params: TimeSeriesQueryParameters ): Promise { - const { logger, esClient, query: queryParams } = params; + const { logger, esClient, query: queryParams, condition: conditionParams } = params; const { index, timeWindowSize, timeWindowUnit, interval, timeField, dateStart, dateEnd } = queryParams; @@ -62,6 +71,22 @@ export async function timeSeriesQuery( const isCountAgg = aggType === 'count'; const isGroupAgg = !!termField; + const includeConditionInQuery = !!conditionParams; + + // Cap the maximum number of terms returned to the resultLimit if defined + // Use resultLimit + 1 because we're using the bucket selector aggregation + // to apply the threshold condition to the ES query. We don't seem to be + // able to get the true cardinality from the bucket selector (i.e., get + // the number of buckets that matched the selector condition without actually + // retrieving the bucket data). By using resultLimit + 1, we can count the number + // of buckets returned and if the value is greater than resultLimit, we know that + // there is additional alert data that we're not returning. + let terms = termSize || DEFAULT_GROUPS; + terms = includeConditionInQuery + ? terms > conditionParams.resultLimit + ? conditionParams.resultLimit + 1 + : terms + : terms; let aggParent = esQuery.body; @@ -71,9 +96,18 @@ export async function timeSeriesQuery( groupAgg: { terms: { field: termField, - size: termSize || DEFAULT_GROUPS, + size: terms, }, }, + ...(includeConditionInQuery + ? { + groupAggCount: { + stats_bucket: { + buckets_path: 'groupAgg._count', + }, + }, + } + : {}), }; // if not count add an order @@ -82,6 +116,17 @@ export async function timeSeriesQuery( aggParent.aggs.groupAgg.terms.order = { sortValueAgg: sortOrder, }; + } else if (includeConditionInQuery) { + aggParent.aggs.groupAgg.aggs = { + conditionSelector: { + bucket_selector: { + buckets_path: { + [TIME_SERIES_BUCKET_SELECTOR_PATH_NAME]: '_count', + }, + script: conditionParams.conditionScript, + }, + }, + }; } aggParent = aggParent.aggs.groupAgg; @@ -89,6 +134,7 @@ export async function timeSeriesQuery( // next, add the time window aggregation aggParent.aggs = { + ...aggParent.aggs, dateAgg: { date_range: { field: timeField, @@ -105,6 +151,17 @@ export async function timeSeriesQuery( field: aggField, }, }; + + if (isGroupAgg && includeConditionInQuery) { + aggParent.aggs.conditionSelector = { + bucket_selector: { + buckets_path: { + [TIME_SERIES_BUCKET_SELECTOR_PATH_NAME]: 'sortValueAgg', + }, + script: conditionParams.conditionScript, + }, + }; + } } aggParent = aggParent.aggs.dateAgg; @@ -133,19 +190,35 @@ export async function timeSeriesQuery( } catch (err) { // console.log('time_series_query.ts error\n', JSON.stringify(err, null, 4)); logger.warn(`${logPrefix} error: ${getEsErrorMessage(err)}`); - return { results: [] }; + return { results: [], truncated: false }; } // console.log('time_series_query.ts response\n', JSON.stringify(esResult, null, 4)); logger.debug(`${logPrefix} result: ${JSON.stringify(esResult)}`); - return getResultFromEs(isCountAgg, isGroupAgg, esResult); + return getResultFromEs({ + isCountAgg, + isGroupAgg, + isConditionInQuery: includeConditionInQuery, + esResult, + resultLimit: conditionParams?.resultLimit, + }); } -export function getResultFromEs( - isCountAgg: boolean, - isGroupAgg: boolean, - esResult: estypes.SearchResponse -): TimeSeriesResult { +interface GetResultFromEsParams { + isCountAgg: boolean; + isGroupAgg: boolean; + isConditionInQuery: boolean; + esResult: estypes.SearchResponse; + resultLimit?: number; +} + +export function getResultFromEs({ + isCountAgg, + isGroupAgg, + isConditionInQuery, + esResult, + resultLimit, +}: GetResultFromEsParams): TimeSeriesResult { const aggregations = esResult?.aggregations || {}; // add a fake 'all documents' group aggregation, if a group aggregation wasn't used @@ -161,11 +234,16 @@ export function getResultFromEs( // @ts-expect-error specify aggregations type explicitly const groupBuckets = aggregations.groupAgg?.buckets || []; + // @ts-expect-error specify aggregations type explicitly + const numGroupsTotal = aggregations.groupAggCount?.count ?? 0; const result: TimeSeriesResult = { results: [], + truncated: isConditionInQuery && resultLimit ? numGroupsTotal > resultLimit : false, }; for (const groupBucket of groupBuckets) { + if (resultLimit && result.results.length === resultLimit) break; + const groupName: string = `${groupBucket?.key}`; const dateBuckets = groupBucket?.dateAgg?.buckets || []; const groupResult: TimeSeriesResultRow = { diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts index d59d99c59419f..491bea6522dec 100644 --- a/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts @@ -45,6 +45,13 @@ export const TimeSeriesQuerySchema = schema.object( } ); +export const TimeSeriesConditionSchema = schema.object({ + resultLimit: schema.number(), + conditionScript: schema.string({ minLength: 1 }), +}); + +export type TimeSeriesCondition = TypeOf; + // using direct type not allowed, circular reference, so body is typed to unknown function validateBody(anyParams: unknown): string | undefined { // validate core query parts, return if it fails validation (returning string) diff --git a/x-pack/plugins/triggers_actions_ui/server/index.ts b/x-pack/plugins/triggers_actions_ui/server/index.ts index 5b81caa235a29..e96ced7c7583c 100644 --- a/x-pack/plugins/triggers_actions_ui/server/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/index.ts @@ -17,6 +17,7 @@ export { MAX_INTERVALS, MAX_GROUPS, DEFAULT_GROUPS, + TIME_SERIES_BUCKET_SELECTOR_FIELD, } from './data'; export const config: PluginConfigDescriptor = { diff --git a/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts b/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts index f66ad0bcd46e1..2af943785e612 100644 --- a/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts +++ b/x-pack/test/alerting_api_integration/common/lib/es_test_index_tool.ts @@ -50,6 +50,9 @@ export class ESTestIndexTool { testedValue: { type: 'long', }, + testedValueFloat: { + type: 'float', + }, testedValueUnsigned: { type: 'unsigned_long', }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index ab6a3023141a8..c06eb41759d63 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -265,6 +265,45 @@ export default function ruleTests({ getService }: FtrProviderContext) { expect(inGroup2).to.be.greaterThan(0); }); + it('runs correctly: max grouped on float', async () => { + await createRule({ + name: 'never fire', + aggType: 'max', + aggField: 'testedValueFloat', + groupBy: 'top', + termField: 'group', + termSize: 2, + thresholdComparator: '<', + threshold: [3.235423], + }); + + await createRule({ + name: 'always fire', + aggType: 'max', + aggField: 'testedValueFloat', + groupBy: 'top', + termField: 'group', + termSize: 2, // two actions will fire each interval + thresholdComparator: '>=', + threshold: [200.2354364], + }); + + // create some more documents in the first group + await createEsDocumentsInGroups(1); + + const docs = await waitForDocs(4); + + for (const doc of docs) { + const { name, message } = doc._source.params; + + expect(name).to.be('always fire'); + + const messagePattern = + /alert 'always fire' is active for group \'group-\d\':\n\n- Value: 234.2534637451172\n- Conditions Met: max\(testedValueFloat\) is greater than or equal to 200.2354364 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + expect(message).to.match(messagePattern); + } + }); + it('runs correctly: max grouped on unsigned long', async () => { await createRule({ name: 'never fire', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts index 5eee05a916da3..61fec5cf7e481 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts @@ -82,6 +82,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider const expected = { results: [{ group: 'all documents', metrics: [[START_DATE_PLUS_YEAR, 0]] }], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -95,6 +96,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider const expected = { results: [{ group: 'all documents', metrics: [[START_DATE_MINUS_YEAR, 0]] }], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -108,6 +110,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider const expected = { results: [{ group: 'all documents', metrics: [[START_DATE, 6]] }], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -130,6 +133,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider ], }, ], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -154,6 +158,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider ], }, ], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -185,6 +190,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider ], }, ], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -220,6 +226,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider ], }, ], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); @@ -289,6 +296,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider }); const expected = { results: [{ group: 'all documents', metrics: [[START_DATE, 6]] }], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); }); @@ -303,6 +311,7 @@ export default function timeSeriesQueryEndpointTests({ getService }: FtrProvider }); const expected = { results: [], + truncated: false, }; expect(await runQueryExpect(query, 200)).eql(expected); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/lib/create_test_data.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/lib/create_test_data.ts index 8fbb4d0bc6805..58df573fa629e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/lib/create_test_data.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/lib/create_test_data.ts @@ -106,6 +106,7 @@ async function createEsDocument( date: new Date(epochMillis).toISOString(), date_epoch_millis: epochMillis, testedValue, + testedValueFloat: 234.2534643, testedValueUnsigned: '18446744073709551615', '@timestamp': new Date(epochMillis).toISOString(), ...(group ? { group } : {}), From 42f0868a0451b3ae34539d030ae975278914dbf9 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Thu, 6 Oct 2022 17:23:53 +0300 Subject: [PATCH 167/174] Fix Failing test: Jest Tests.x-pack/plugins/lens/common/expressions/time_scale - time_scale should work with relative time range (#142837) Closes: #142820 --- .../common/expressions/time_scale/time_scale.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts index fceabbd5542b3..a2b7d730766c7 100644 --- a/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts +++ b/x-pack/plugins/lens/common/expressions/time_scale/time_scale.test.ts @@ -22,7 +22,11 @@ describe('time_scale', () => { context?: ExecutionContext ) => Promise; - const timeScale = getTimeScale(createDatatableUtilitiesMock, () => 'UTC'); + const timeScale = getTimeScale( + createDatatableUtilitiesMock, + () => 'UTC', + () => new Date('2010-01-04T06:30:30') + ); const emptyTable: Datatable = { type: 'datatable', @@ -390,7 +394,6 @@ describe('time_scale', () => { ...emptyTable, rows: [ { - date: moment('2010-01-01T00:00:00.000Z').valueOf(), metric: 300, }, ], @@ -419,7 +422,6 @@ describe('time_scale', () => { ...emptyTable, rows: [ { - date: moment().subtract('1d').valueOf(), metric: 300, }, ], @@ -432,14 +434,14 @@ describe('time_scale', () => { { getSearchContext: () => ({ timeRange: { - from: 'now-10d', + from: 'now-2d', to: 'now', }, }), } as unknown as ExecutionContext ); - expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([30]); + expect(result.rows.map(({ scaledMetric }) => scaledMetric)).toEqual([150]); }); it('should apply fn for non-histogram fields (with Reduced time range)', async () => { From 8efefb11ef54f1735a2a98f9df57cb4148dc6fae Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Thu, 6 Oct 2022 10:25:32 -0400 Subject: [PATCH 168/174] synthetics - unskip flaky test (#142801) --- .../api_integration/apis/uptime/rest/add_monitor_project.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts index 9b0ae053566b2..2be3509a61cf6 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/add_monitor_project.ts @@ -19,8 +19,7 @@ import { PrivateLocationTestService } from './services/private_location_test_ser import { comparePolicies, getTestProjectSyntheticsPolicy } from './sample_data/test_policy'; export default function ({ getService }: FtrProviderContext) { - // FLAKY: https://github.com/elastic/kibana/issues/142110 - describe.skip('AddProjectMonitors', function () { + describe('AddProjectMonitors', function () { this.tags('skipCloud'); const supertest = getService('supertest'); @@ -708,6 +707,7 @@ export default function ({ getService }: FtrProviderContext) { ...projectMonitors, keep_stale: false, monitors: testMonitors, + project: 'test-project-2', }); const messages = await parseStreamApiResponse( @@ -715,6 +715,7 @@ export default function ({ getService }: FtrProviderContext) { JSON.stringify({ ...projectMonitors, keep_stale: false, + project: 'test-project-2', }) ); From 4142f65ba63d41b0e66bce2023c08488ca964116 Mon Sep 17 00:00:00 2001 From: Marius Dragomir Date: Thu, 6 Oct 2022 10:37:06 -0400 Subject: [PATCH 169/174] change reporting url due to sample data dashboard change (#142794) --- .../apps/reporting/reporting_watcher.js | 4 ++-- .../apps/reporting/reporting_watcher_png.js | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js index 869d56fe90047..ca6ad964d617f 100644 --- a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js +++ b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher.js @@ -29,7 +29,7 @@ export default function ({ getService, getPageObjects }) { const watch = { id }; const interval = 10; const emails = REPORTING_TEST_EMAILS.split(','); - + // http://localhost:5601/api/reporting/generate/printablePdfV2?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A2024%2Cwidth%3A1920%29%2Cid%3Apreserve_layout%29%2ClocatorParams%3A%21%28%28id%3ADASHBOARD_APP_LOCATOR%2Cparams%3A%28dashboardId%3A%27722b74f0-b882-11e8-a6d9-e546fe2bba5f%27%2CpreserveSavedFilters%3A%21t%2CtimeRange%3A%28from%3Anow-7d%2Cto%3Anow%29%2CuseHash%3A%21f%2CviewMode%3Aview%29%29%29%2CobjectType%3Adashboard%2Ctitle%3A%27%5BeCommerce%5D%20Revenue%20Dashboard%27%2Cversion%3A%278.6.0-SNAPSHOT%27%29 // https://localhost:5601/api/reporting/generate/printablePdf?jobParams=(objectType:dashboard,queryString:%27_g%3D(refreshInterval%3A(display%3AOff%2Cpause%3A!!f%2Cvalue%3A0)%2Ctime%3A(from%3Anow-7d%2Cmode%3Aquick%2Cto%3Anow))%26_a%3D(description%3A%2527%2527%2Cfilters%3A!!()%2CfullScreenMode%3A!!f%2Coptions%3A(darkTheme%3A!!f)%2Cpanels%3A!!((col%3A1%2Cid%3ASystem-Navigation%2CpanelIndex%3A9%2Crow%3A1%2Csize_x%3A8%2Csize_y%3A1%2Ctype%3Avisualization)%2C(col%3A1%2Cid%3Ac6f2ffd0-4d17-11e7-a196-69b9a7a020a9%2CpanelIndex%3A11%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A7%2Cid%3Afe064790-1b1f-11e7-bec4-a5e9ec5cab8b%2CpanelIndex%3A12%2Crow%3A4%2Csize_x%3A6%2Csize_y%3A5%2Ctype%3Avisualization)%2C(col%3A1%2Cid%3A%2527855899e0-1b1c-11e7-b09e-037021c4f8df%2527%2CpanelIndex%3A13%2Crow%3A4%2Csize_x%3A6%2Csize_y%3A5%2Ctype%3Avisualization)%2C(col%3A1%2Cid%3A%25277cdb1330-4d1a-11e7-a196-69b9a7a020a9%2527%2CpanelIndex%3A14%2Crow%3A9%2Csize_x%3A12%2Csize_y%3A6%2Ctype%3Avisualization)%2C(col%3A9%2Cid%3A%2527522ee670-1b92-11e7-bec4-a5e9ec5cab8b%2527%2CpanelIndex%3A16%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A11%2Cid%3A%25271aae9140-1b93-11e7-8ada-3df93aab833e%2527%2CpanelIndex%3A17%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A7%2Cid%3A%2527825fdb80-4d1d-11e7-b5f2-2b7c1895bf32%2527%2CpanelIndex%3A18%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A5%2Cid%3Ad3166e80-1b91-11e7-bec4-a5e9ec5cab8b%2CpanelIndex%3A19%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A3%2Cid%3A%252783e12df0-1b91-11e7-bec4-a5e9ec5cab8b%2527%2CpanelIndex%3A20%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A9%2Cid%3Ae9d22060-4d64-11e7-aa29-87a97a796de6%2CpanelIndex%3A21%2Crow%3A1%2Csize_x%3A4%2Csize_y%3A1%2Ctype%3Avisualization))%2Cquery%3A(language%3Alucene%2Cquery%3A(query_string%3A(analyze_wildcard%3A!!t%2Cquery%3A%2527*%2527)))%2CtimeRestore%3A!!f%2Ctitle%3A%2527Metricbeat%2Bsystem%2Boverview%2527%2CuiState%3A(P-11%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-12%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-14%3A(vis%3A(defaultColors%3A(%25270%2525%2B-%2B8.75%2525%2527%3A%2527rgb(247%2C252%2C245)%2527%2C%252717.5%2525%2B-%2B26.25%2525%2527%3A%2527rgb(116%2C196%2C118)%2527%2C%252726.25%2525%2B-%2B35%2525%2527%3A%2527rgb(35%2C139%2C69)%2527%2C%25278.75%2525%2B-%2B17.5%2525%2527%3A%2527rgb(199%2C233%2C192)%2527)))%2CP-16%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-2%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-3%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527))))%2CviewMode%3Aview)%27,savedObjectId:Metricbeat-system-overview) // https://localhost:5601/api/reporting/generate/printablePdf?jobParams=(objectType:dashboard,queryString:%27_g%3D()%26_a%3D(description%3A%2527%2527%2Cfilters%3A!!()%2CfullScreenMode%3A!!f%2Coptions%3A(darkTheme%3A!!f)%2Cpanels%3A!!((col%3A1%2Cid%3ASystem-Navigation%2CpanelIndex%3A9%2Crow%3A1%2Csize_x%3A12%2Csize_y%3A1%2Ctype%3Avisualization)%2C(col%3A1%2Cid%3Ac6f2ffd0-4d17-11e7-a196-69b9a7a020a9%2CpanelIndex%3A11%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A7%2Cid%3Afe064790-1b1f-11e7-bec4-a5e9ec5cab8b%2CpanelIndex%3A12%2Crow%3A4%2Csize_x%3A6%2Csize_y%3A5%2Ctype%3Avisualization)%2C(col%3A1%2Cid%3A%2527855899e0-1b1c-11e7-b09e-037021c4f8df%2527%2CpanelIndex%3A13%2Crow%3A4%2Csize_x%3A6%2Csize_y%3A5%2Ctype%3Avisualization)%2C(col%3A1%2Cid%3A%25277cdb1330-4d1a-11e7-a196-69b9a7a020a9%2527%2CpanelIndex%3A14%2Crow%3A9%2Csize_x%3A12%2Csize_y%3A6%2Ctype%3Avisualization)%2C(col%3A9%2Cid%3A%2527522ee670-1b92-11e7-bec4-a5e9ec5cab8b%2527%2CpanelIndex%3A16%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A11%2Cid%3A%25271aae9140-1b93-11e7-8ada-3df93aab833e%2527%2CpanelIndex%3A17%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A7%2Cid%3A%2527825fdb80-4d1d-11e7-b5f2-2b7c1895bf32%2527%2CpanelIndex%3A18%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A5%2Cid%3Ad3166e80-1b91-11e7-bec4-a5e9ec5cab8b%2CpanelIndex%3A19%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization)%2C(col%3A3%2Cid%3A%252783e12df0-1b91-11e7-bec4-a5e9ec5cab8b%2527%2CpanelIndex%3A20%2Crow%3A2%2Csize_x%3A2%2Csize_y%3A2%2Ctype%3Avisualization))%2Cquery%3A(language%3Alucene%2Cquery%3A(query_string%3A(analyze_wildcard%3A!!t%2Cdefault_field%3A%2527*%2527%2Cquery%3A%2527*%2527)))%2CtimeRestore%3A!!f%2Ctitle%3A%2527%255BMetricbeat%2BSystem%255D%2BOverview%2527%2CuiState%3A(P-11%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-12%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-14%3A(vis%3A(defaultColors%3A(%25270%2525%2B-%2B8.75%2525%2527%3A%2527rgb(247%2C252%2C245)%2527%2C%252717.5%2525%2B-%2B26.25%2525%2527%3A%2527rgb(116%2C196%2C118)%2527%2C%252726.25%2525%2B-%2B35%2525%2527%3A%2527rgb(35%2C139%2C69)%2527%2C%25278.75%2525%2B-%2B17.5%2525%2527%3A%2527rgb(199%2C233%2C192)%2527)))%2CP-16%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-2%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527)))%2CP-3%3A(vis%3A(defaultColors%3A(%25270%2B-%2B100%2527%3A%2527rgb(0%2C104%2C55)%2527))))%2CviewMode%3Aview)%27,savedObjectId:Metricbeat-system-overview) // https://localhost:5601 @@ -40,7 +40,7 @@ export default function ({ getService, getPageObjects }) { KIBANAIP + ':' + servers.kibana.port + - '/api/reporting/generate/printablePdf?jobParams=%28browserTimezone%3AEurope%2FParis%2Clayout%3A%28dimensions%3A%28height%3A2052%2Cwidth%3A2542.666748046875%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Adashboard%2CrelativeUrls%3A%21%28%27%2Fapp%2Fdashboards%23%2Fview%2F722b74f0-b882-11e8-a6d9-e546fe2bba5f%3F_g%3D%28filters%3A%21%21%28%29%29%26_a%3D%28description%3A%21%27Analyze%2520mock%2520eCommerce%2520orders%2520and%2520revenue%21%27%2Cfilters%3A%21%21%28%29%2CfullScreenMode%3A%21%21f%2Coptions%3A%28hidePanelTitles%3A%21%21f%2CuseMargins%3A%21%21t%29%2Cpanels%3A%21%21%28%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A10%2Ci%3A%21%275%21%27%2Cw%3A24%2Cx%3A0%2Cy%3A22%29%2Cid%3A%21%2745e07720-b890-11e8-a6d9-e546fe2bba5f%21%27%2CpanelIndex%3A%21%275%21%27%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3A%21%277%21%27%2Cw%3A12%2Cx%3A36%2Cy%3A15%29%2Cid%3Ab80e6540-b891-11e8-a6d9-e546fe2bba5f%2CpanelIndex%3A%21%277%21%27%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A18%2Ci%3A%21%2710%21%27%2Cw%3A48%2Cx%3A0%2Cy%3A55%29%2Cid%3A%21%273ba638e0-b894-11e8-a6d9-e546fe2bba5f%21%27%2CpanelIndex%3A%21%2710%21%27%2Ctype%3Asearch%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChiddenLayers%3A%21%21%28%29%2CisLayerTOCOpen%3A%21%21f%2CmapBuffer%3A%28maxLat%3A66.51326%2CmaxLon%3A90%2CminLat%3A0%2CminLon%3A-135%29%2CmapCenter%3A%28lat%3A45.88578%2Clon%3A-15.07605%2Czoom%3A2.11%29%2CopenTOCDetails%3A%21%21%28%29%29%2CgridData%3A%28h%3A14%2Ci%3A%21%2711%21%27%2Cw%3A24%2Cx%3A0%2Cy%3A32%29%2Cid%3A%21%272c9c1f60-1909-11e9-919b-ffe5949a18d2%21%27%2CpanelIndex%3A%21%2711%21%27%2Ctype%3Amap%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Aa71cf076-6895-491c-8878-63592e429ed5%2Cw%3A18%2Cx%3A0%2Cy%3A0%29%2Cid%3Ac00d1f90-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Aa71cf076-6895-491c-8878-63592e429ed5%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Aadc0a2f4-481c-45eb-b422-0ea59a3e5163%2Cw%3A30%2Cx%3A18%2Cy%3A0%29%2Cid%3Ac3378480-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Aadc0a2f4-481c-45eb-b422-0ea59a3e5163%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChidePanelTitles%3A%21%21f%29%2CgridData%3A%28h%3A8%2Ci%3A%21%277077b79f-2a99-4fcb-bbd4-456982843278%21%27%2Cw%3A24%2Cx%3A0%2Cy%3A7%29%2Cid%3Ac762b7a0-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%277077b79f-2a99-4fcb-bbd4-456982843278%21%27%2Ctitle%3A%21%27%2525%2520of%2520target%2520revenue%2520%28%2410k%29%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A8%2Ci%3A%21%2719a3c101-ad2e-4421-a71b-a4734ec1f03e%21%27%2Cw%3A12%2Cx%3A24%2Cy%3A7%29%2Cid%3Ace02e260-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%2719a3c101-ad2e-4421-a71b-a4734ec1f03e%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A8%2Ci%3A%21%27491469e7-7d24-4216-aeb3-bca00e5c8c1b%21%27%2Cw%3A12%2Cx%3A36%2Cy%3A7%29%2Cid%3Ad5f90030-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%27491469e7-7d24-4216-aeb3-bca00e5c8c1b%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Aa1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef%2Cw%3A24%2Cx%3A0%2Cy%3A15%29%2Cid%3Adde978b0-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Aa1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Ada51079b-952f-43dc-96e6-6f9415a3708b%2Cw%3A12%2Cx%3A24%2Cy%3A15%29%2Cid%3Ae3902840-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Ada51079b-952f-43dc-96e6-6f9415a3708b%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A10%2Ci%3A%21%2764fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b%21%27%2Cw%3A24%2Cx%3A24%2Cy%3A22%29%2Cid%3Aeddf7850-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%2764fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A14%2Ci%3Abd330ede-2eef-4e2a-8100-22a21abf5038%2Cw%3A24%2Cx%3A24%2Cy%3A32%29%2Cid%3Aff6a21b0-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Abd330ede-2eef-4e2a-8100-22a21abf5038%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChidePanelTitles%3A%21%21f%29%2CgridData%3A%28h%3A9%2Ci%3Ab897d4be-cf83-46fb-a111-c7fbec9ef403%2Cw%3A24%2Cx%3A0%2Cy%3A46%29%2Cid%3A%21%2703071e90-f5eb-11eb-a78e-83aac3c38a60%21%27%2CpanelIndex%3Ab897d4be-cf83-46fb-a111-c7fbec9ef403%2Ctitle%3A%21%27Top%2520products%2520this%2520week%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChidePanelTitles%3A%21%21f%2CtimeRange%3A%28from%3Anow-2w%2Cto%3Anow-1w%29%29%2CgridData%3A%28h%3A9%2Ci%3Ae0f68f93-30f2-4da7-889a-6cd128a68d3f%2Cw%3A24%2Cx%3A24%2Cy%3A46%29%2Cid%3A%21%2706379e00-f5eb-11eb-a78e-83aac3c38a60%21%27%2CpanelIndex%3Ae0f68f93-30f2-4da7-889a-6cd128a68d3f%2Ctitle%3A%21%27Top%2520products%2520last%2520week%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%29%2Cquery%3A%28language%3Akuery%2Cquery%3A%21%27%21%27%29%2Ctags%3A%21%21%28%29%2CtimeRestore%3A%21%21t%2Ctitle%3A%21%27%255BeCommerce%255D%2520Revenue%2520Dashboard%21%27%2CviewMode%3Aview%29%27%29%2Ctitle%3A%27%5BeCommerce%5D%20Revenue%20Dashboard%27%2Cversion%3A%278.0.0%27%29'; + '/api/reporting/generate/printablePdfV2?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A2024%2Cwidth%3A1920%29%2Cid%3Apreserve_layout%29%2ClocatorParams%3A%21%28%28id%3ADASHBOARD_APP_LOCATOR%2Cparams%3A%28dashboardId%3A%27722b74f0-b882-11e8-a6d9-e546fe2bba5f%27%2CpreserveSavedFilters%3A%21t%2CtimeRange%3A%28from%3Anow-7d%2Cto%3Anow%29%2CuseHash%3A%21f%2CviewMode%3Aview%29%29%29%2CobjectType%3Adashboard%2Ctitle%3A%27%5BeCommerce%5D%20Revenue%20Dashboard%27%2Cversion%3A%278.6.0-SNAPSHOT%27%29'; const body = { trigger: { diff --git a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js index d793390488596..9b8f17c422272 100644 --- a/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js +++ b/x-pack/test/stack_functional_integration/apps/reporting/reporting_watcher_png.js @@ -26,6 +26,7 @@ export default ({ getService, getPageObjects }) => { describe('PNG Reporting watch', () => { let id = 'watcher_png_report-'; id = id + new Date().getTime(); // For debugging. + // http://localhost:5601/api/reporting/generate/pngV2?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A2024%2Cwidth%3A1920%29%2Cid%3Apreserve_layout%29%2ClocatorParams%3A%28id%3ADASHBOARD_APP_LOCATOR%2Cparams%3A%28dashboardId%3A%27722b74f0-b882-11e8-a6d9-e546fe2bba5f%27%2CpreserveSavedFilters%3A%21t%2CtimeRange%3A%28from%3Anow-7d%2Cto%3Anow%29%2CuseHash%3A%21f%2CviewMode%3Aview%29%29%2CobjectType%3Adashboard%2Ctitle%3A%27%5BeCommerce%5D%20Revenue%20Dashboard%27%2Cversion%3A%278.6.0-SNAPSHOT%27%29 const watch = { id }; const reportingUrl = servers.kibana.protocol + @@ -33,7 +34,7 @@ export default ({ getService, getPageObjects }) => { KIBANAIP + ':' + servers.kibana.port + - '/api/reporting/generate/png?jobParams=%28browserTimezone%3AEurope%2FParis%2Clayout%3A%28dimensions%3A%28height%3A2052%2Cwidth%3A2542.666748046875%29%2Cid%3Apreserve_layout%2Cselectors%3A%28itemsCountAttribute%3Adata-shared-items-count%2CrenderComplete%3A%5Bdata-shared-item%5D%2Cscreenshot%3A%5Bdata-shared-items-container%5D%2CtimefilterDurationAttribute%3Adata-shared-timefilter-duration%29%29%2CobjectType%3Adashboard%2CrelativeUrl%3A%27%2Fapp%2Fdashboards%23%2Fview%2F722b74f0-b882-11e8-a6d9-e546fe2bba5f%3F_g%3D%28filters%3A%21%21%28%29%29%26_a%3D%28description%3A%21%27Analyze%2520mock%2520eCommerce%2520orders%2520and%2520revenue%21%27%2Cfilters%3A%21%21%28%29%2CfullScreenMode%3A%21%21f%2Coptions%3A%28hidePanelTitles%3A%21%21f%2CuseMargins%3A%21%21t%29%2Cpanels%3A%21%21%28%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A10%2Ci%3A%21%275%21%27%2Cw%3A24%2Cx%3A0%2Cy%3A22%29%2Cid%3A%21%2745e07720-b890-11e8-a6d9-e546fe2bba5f%21%27%2CpanelIndex%3A%21%275%21%27%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3A%21%277%21%27%2Cw%3A12%2Cx%3A36%2Cy%3A15%29%2Cid%3Ab80e6540-b891-11e8-a6d9-e546fe2bba5f%2CpanelIndex%3A%21%277%21%27%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A18%2Ci%3A%21%2710%21%27%2Cw%3A48%2Cx%3A0%2Cy%3A55%29%2Cid%3A%21%273ba638e0-b894-11e8-a6d9-e546fe2bba5f%21%27%2CpanelIndex%3A%21%2710%21%27%2Ctype%3Asearch%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChiddenLayers%3A%21%21%28%29%2CisLayerTOCOpen%3A%21%21f%2CmapBuffer%3A%28maxLat%3A66.51326%2CmaxLon%3A90%2CminLat%3A0%2CminLon%3A-135%29%2CmapCenter%3A%28lat%3A45.88578%2Clon%3A-15.07605%2Czoom%3A2.11%29%2CopenTOCDetails%3A%21%21%28%29%29%2CgridData%3A%28h%3A14%2Ci%3A%21%2711%21%27%2Cw%3A24%2Cx%3A0%2Cy%3A32%29%2Cid%3A%21%272c9c1f60-1909-11e9-919b-ffe5949a18d2%21%27%2CpanelIndex%3A%21%2711%21%27%2Ctype%3Amap%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Aa71cf076-6895-491c-8878-63592e429ed5%2Cw%3A18%2Cx%3A0%2Cy%3A0%29%2Cid%3Ac00d1f90-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Aa71cf076-6895-491c-8878-63592e429ed5%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Aadc0a2f4-481c-45eb-b422-0ea59a3e5163%2Cw%3A30%2Cx%3A18%2Cy%3A0%29%2Cid%3Ac3378480-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Aadc0a2f4-481c-45eb-b422-0ea59a3e5163%2Ctype%3Avisualization%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChidePanelTitles%3A%21%21f%29%2CgridData%3A%28h%3A8%2Ci%3A%21%277077b79f-2a99-4fcb-bbd4-456982843278%21%27%2Cw%3A24%2Cx%3A0%2Cy%3A7%29%2Cid%3Ac762b7a0-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%277077b79f-2a99-4fcb-bbd4-456982843278%21%27%2Ctitle%3A%21%27%2525%2520of%2520target%2520revenue%2520%28%2410k%29%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A8%2Ci%3A%21%2719a3c101-ad2e-4421-a71b-a4734ec1f03e%21%27%2Cw%3A12%2Cx%3A24%2Cy%3A7%29%2Cid%3Ace02e260-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%2719a3c101-ad2e-4421-a71b-a4734ec1f03e%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A8%2Ci%3A%21%27491469e7-7d24-4216-aeb3-bca00e5c8c1b%21%27%2Cw%3A12%2Cx%3A36%2Cy%3A7%29%2Cid%3Ad5f90030-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%27491469e7-7d24-4216-aeb3-bca00e5c8c1b%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Aa1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef%2Cw%3A24%2Cx%3A0%2Cy%3A15%29%2Cid%3Adde978b0-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Aa1b03eb9-a36b-4e12-aa1b-bb29b5d6c4ef%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A7%2Ci%3Ada51079b-952f-43dc-96e6-6f9415a3708b%2Cw%3A12%2Cx%3A24%2Cy%3A15%29%2Cid%3Ae3902840-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Ada51079b-952f-43dc-96e6-6f9415a3708b%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A10%2Ci%3A%21%2764fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b%21%27%2Cw%3A24%2Cx%3A24%2Cy%3A22%29%2Cid%3Aeddf7850-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3A%21%2764fd5dcf-30c5-4f5a-a78c-70b1fbf87e5b%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%29%2CgridData%3A%28h%3A14%2Ci%3Abd330ede-2eef-4e2a-8100-22a21abf5038%2Cw%3A24%2Cx%3A24%2Cy%3A32%29%2Cid%3Aff6a21b0-f5ea-11eb-a78e-83aac3c38a60%2CpanelIndex%3Abd330ede-2eef-4e2a-8100-22a21abf5038%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChidePanelTitles%3A%21%21f%29%2CgridData%3A%28h%3A9%2Ci%3Ab897d4be-cf83-46fb-a111-c7fbec9ef403%2Cw%3A24%2Cx%3A0%2Cy%3A46%29%2Cid%3A%21%2703071e90-f5eb-11eb-a78e-83aac3c38a60%21%27%2CpanelIndex%3Ab897d4be-cf83-46fb-a111-c7fbec9ef403%2Ctitle%3A%21%27Top%2520products%2520this%2520week%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%2C%28embeddableConfig%3A%28enhancements%3A%28%29%2ChidePanelTitles%3A%21%21f%2CtimeRange%3A%28from%3Anow-2w%2Cto%3Anow-1w%29%29%2CgridData%3A%28h%3A9%2Ci%3Ae0f68f93-30f2-4da7-889a-6cd128a68d3f%2Cw%3A24%2Cx%3A24%2Cy%3A46%29%2Cid%3A%21%2706379e00-f5eb-11eb-a78e-83aac3c38a60%21%27%2CpanelIndex%3Ae0f68f93-30f2-4da7-889a-6cd128a68d3f%2Ctitle%3A%21%27Top%2520products%2520last%2520week%21%27%2Ctype%3Alens%2Cversion%3A%21%278.0.0%21%27%29%29%2Cquery%3A%28language%3Akuery%2Cquery%3A%21%27%21%27%29%2Ctags%3A%21%21%28%29%2CtimeRestore%3A%21%21t%2Ctitle%3A%21%27%255BeCommerce%255D%2520Revenue%2520Dashboard%21%27%2CviewMode%3Aview%29%27%2Ctitle%3A%27%5BeCommerce%5D%20Revenue%20Dashboard%27%2Cversion%3A%278.0.0%27%29'; + '/api/reporting/generate/pngV2?jobParams=%28browserTimezone%3AAmerica%2FNew_York%2Clayout%3A%28dimensions%3A%28height%3A2024%2Cwidth%3A1920%29%2Cid%3Apreserve_layout%29%2ClocatorParams%3A%28id%3ADASHBOARD_APP_LOCATOR%2Cparams%3A%28dashboardId%3A%27722b74f0-b882-11e8-a6d9-e546fe2bba5f%27%2CpreserveSavedFilters%3A%21t%2CtimeRange%3A%28from%3Anow-7d%2Cto%3Anow%29%2CuseHash%3A%21f%2CviewMode%3Aview%29%29%2CobjectType%3Adashboard%2Ctitle%3A%27%5BeCommerce%5D%20Revenue%20Dashboard%27%2Cversion%3A%278.6.0-SNAPSHOT%27%29'; const emails = REPORTING_TEST_EMAILS.split(','); const interval = 10; const body = { From 022e59f241049b90eb7a1e45814a290b48d5ee2d Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 6 Oct 2022 15:46:03 +0100 Subject: [PATCH 170/174] Bugfix: Refresh search results when clearing category filter (#142853) --- .../integrations/hooks/use_local_search.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx index 58d3a847d3efc..7539197e8462f 100644 --- a/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx +++ b/x-pack/plugins/fleet/public/applications/integrations/hooks/use_local_search.tsx @@ -6,7 +6,7 @@ */ import { Search as LocalSearch, PrefixIndexStrategy } from 'js-search'; -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; import type { IntegrationCardItem } from '../../../../common/types/models'; @@ -16,13 +16,11 @@ export const fieldsToSearch = ['name', 'title']; export function useLocalSearch(packageList: IntegrationCardItem[]) { const localSearchRef = useRef(new LocalSearch(searchIdField)); - useEffect(() => { - const localSearch = new LocalSearch(searchIdField); - localSearch.indexStrategy = new PrefixIndexStrategy(); - fieldsToSearch.forEach((field) => localSearch.addIndex(field)); - localSearch.addDocuments(packageList); - localSearchRef.current = localSearch; - }, [packageList]); + const localSearch = new LocalSearch(searchIdField); + localSearch.indexStrategy = new PrefixIndexStrategy(); + fieldsToSearch.forEach((field) => localSearch.addIndex(field)); + localSearch.addDocuments(packageList); + localSearchRef.current = localSearch; return localSearchRef; } From f880edc50c0abfe7a7566ce805d62a512c508182 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Thu, 6 Oct 2022 17:47:18 +0300 Subject: [PATCH 171/174] [Cases] Fix bulk actions alignment (#142830) * Fix alignment * Remove utility bar * Fix tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../public/components/all_cases/table.tsx | 4 +- .../components/all_cases/utility_bar.tsx | 126 +++++++++------ .../__snapshots__/utility_bar.test.tsx.snap | 29 ---- .../utility_bar_group.test.tsx.snap | 9 -- .../utility_bar_section.test.tsx.snap | 11 -- .../utility_bar_text.test.tsx.snap | 7 - .../public/components/utility_bar/index.ts | 13 -- .../public/components/utility_bar/styles.tsx | 144 ------------------ .../utility_bar/utility_bar.test.tsx | 103 ------------- .../components/utility_bar/utility_bar.tsx | 20 --- .../utility_bar/utility_bar_action.test.tsx | 42 ----- .../utility_bar/utility_bar_action.tsx | 38 ----- .../utility_bar_bulk_actions.test.tsx | 89 ----------- .../utility_bar/utility_bar_bulk_actions.tsx | 67 -------- .../utility_bar/utility_bar_group.test.tsx | 26 ---- .../utility_bar/utility_bar_group.tsx | 20 --- .../utility_bar/utility_bar_section.test.tsx | 28 ---- .../utility_bar/utility_bar_section.tsx | 20 --- .../utility_bar/utility_bar_spacer.tsx | 20 --- .../utility_bar/utility_bar_text.test.tsx | 24 --- .../utility_bar/utility_bar_text.tsx | 21 --- x-pack/test/functional/services/cases/list.ts | 5 +- 22 files changed, 86 insertions(+), 780 deletions(-) delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/index.ts delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/styles.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx delete mode 100644 x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx diff --git a/x-pack/plugins/cases/public/components/all_cases/table.tsx b/x-pack/plugins/cases/public/components/all_cases/table.tsx index b85f4ae1826d5..1f7382351e802 100644 --- a/x-pack/plugins/cases/public/components/all_cases/table.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/table.tsx @@ -82,7 +82,7 @@ export const CasesTable: FunctionComponent = ({ ) : ( -
+ <> = ({ sorting={sorting} hasActions={false} /> -
+ ); }; CasesTable.displayName = 'CasesTable'; diff --git a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx index 415472574f25b..6daf9cb665116 100644 --- a/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx +++ b/x-pack/plugins/cases/public/components/all_cases/utility_bar.tsx @@ -6,18 +6,18 @@ */ import React, { FunctionComponent, useCallback, useState } from 'react'; -import { EuiContextMenu } from '@elastic/eui'; import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '../utility_bar'; + EuiButtonEmpty, + EuiContextMenu, + EuiFlexGroup, + EuiFlexItem, + EuiPopover, + EuiText, + useEuiTheme, +} from '@elastic/eui'; import * as i18n from './translations'; import { Case } from '../../../common/ui/types'; import { useRefreshCases } from './use_on_refresh_cases'; -import { UtilityBarBulkActions } from '../utility_bar/utility_bar_bulk_actions'; import { useBulkActions } from './use_bulk_actions'; import { useCasesContext } from '../cases_context/use_cases_context'; @@ -30,6 +30,7 @@ interface Props { export const CasesTableUtilityBar: FunctionComponent = React.memo( ({ isSelectorView, totalCases, selectedCases, deselectCases }) => { + const { euiTheme } = useEuiTheme(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]); const closePopover = useCallback(() => setIsPopoverOpen(false), []); @@ -56,47 +57,82 @@ export const CasesTableUtilityBar: FunctionComponent = React.memo( return ( <> - - - - - {i18n.SHOWING_CASES(totalCases)} - - - + + + + {i18n.SHOWING_CASES(totalCases)} + + + + {!isSelectorView && showBulkActions && ( <> - - {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} - - - - + + + {i18n.SHOWING_SELECTED_CASES(selectedCases.length)} + + + + + {i18n.BULK_ACTIONS} + + } + > + + + )} - - {i18n.REFRESH} - - - - + + + {i18n.REFRESH} + + + + + {modals} ); diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap deleted file mode 100644 index 83c8a16ea0290..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar.test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBar it renders 1`] = ` - - - - - Test text - - - - - Test action - - - - - - - Test action - - - - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap deleted file mode 100644 index 8ef7ee1cfe842..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_group.test.tsx.snap +++ /dev/null @@ -1,9 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarGroup it renders 1`] = ` - - - Test text - - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap deleted file mode 100644 index 2fe3b8ac5c7aa..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_section.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarSection it renders 1`] = ` - - - - Test text - - - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap b/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap deleted file mode 100644 index cf635ffa49c4c..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/__snapshots__/utility_bar_text.test.tsx.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`UtilityBarText it renders 1`] = ` - - Test text - -`; diff --git a/x-pack/plugins/cases/public/components/utility_bar/index.ts b/x-pack/plugins/cases/public/components/utility_bar/index.ts deleted file mode 100644 index 830f3cb043ba9..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/index.ts +++ /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. - */ - -export { UtilityBar } from './utility_bar'; -export { UtilityBarAction } from './utility_bar_action'; -export { UtilityBarGroup } from './utility_bar_group'; -export { UtilityBarSection } from './utility_bar_section'; -export { UtilityBarSpacer } from './utility_bar_spacer'; -export { UtilityBarText } from './utility_bar_text'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/styles.tsx b/x-pack/plugins/cases/public/components/utility_bar/styles.tsx deleted file mode 100644 index 4c4427ba23471..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/styles.tsx +++ /dev/null @@ -1,144 +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 styled, { css } from 'styled-components'; - -/** - * UTILITY BAR - */ - -export interface BarProps { - border?: boolean; -} - -export interface BarSectionProps { - grow?: boolean; -} - -export interface BarGroupProps { - grow?: boolean; -} - -export const Bar = styled.aside.attrs({ - className: 'casesUtilityBar', -})` - ${({ border, theme }) => css` - ${border && - css` - border-bottom: ${theme.eui.euiBorderThin}; - padding-bottom: ${theme.eui.euiSizeS}; - `} - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { - display: flex; - justify-content: space-between; - } - `} -`; -Bar.displayName = 'Bar'; - -export const BarSection = styled.div.attrs({ - className: 'casesUtilityBar__section', -})` - ${({ grow, theme }) => css` - & + & { - margin-top: ${theme.eui.euiSizeS}; - } - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { - display: flex; - flex-wrap: wrap; - } - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.l}) { - & + & { - margin-top: 0; - margin-left: ${theme.eui.euiSize}; - } - } - ${grow && - css` - flex: 1; - `} - `} -`; -BarSection.displayName = 'BarSection'; - -export const BarGroup = styled.div.attrs({ - className: 'casesUtilityBar__group', -})` - ${({ grow, theme }) => css` - align-items: flex-start; - display: flex; - flex-wrap: wrap; - - & + & { - margin-top: ${theme.eui.euiSizeS}; - } - - @media only screen and (min-width: ${theme.eui.euiBreakpoints.m}) { - border-right: ${theme.eui.euiBorderThin}; - flex-wrap: nowrap; - margin-right: ${theme.eui.euiSizeM}; - padding-right: ${theme.eui.euiSizeM}; - - & + & { - margin-top: 0; - } - - &:last-child { - border-right: none; - margin-right: 0; - padding-right: 0; - } - } - - & > * { - margin-right: ${theme.eui.euiSize}; - - &:last-child { - margin-right: 0; - } - } - ${grow && - css` - flex: 1; - `} - `} -`; -BarGroup.displayName = 'BarGroup'; - -export const BarText = styled.p.attrs({ - className: 'casesUtilityBar__text', -})` - ${({ theme }) => css` - color: ${theme.eui.euiTextSubduedColor}; - font-size: ${theme.eui.euiFontSizeXS}; - line-height: ${theme.eui.euiLineHeight}; - white-space: nowrap; - `} -`; -BarText.displayName = 'BarText'; - -export const BarAction = styled.div.attrs({ - className: 'casesUtilityBar__action', -})` - ${({ theme }) => css` - font-size: ${theme.eui.euiFontSizeXS}; - line-height: ${theme.eui.euiLineHeight}; - `} -`; -BarAction.displayName = 'BarAction'; - -export const BarSpacer = styled.div.attrs({ - className: 'casesUtilityBar__spacer', -})` - ${() => css` - flex: 1; - `} -`; -BarSpacer.displayName = 'BarSpacer'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx deleted file mode 100644 index 52486e32905db..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.test.tsx +++ /dev/null @@ -1,103 +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 { euiDarkVars } from '@kbn/ui-theme'; -import { mount, shallow } from 'enzyme'; -import React from 'react'; -import { TestProviders } from '../../common/mock'; - -import { - UtilityBar, - UtilityBarAction, - UtilityBarGroup, - UtilityBarSection, - UtilityBarText, -} from '.'; - -describe('UtilityBar', () => { - test('it renders', () => { - const wrapper = shallow( - - - - - {'Test text'} - - - - {'Test action'} - - - - - - {'Test action'} - - - - - ); - - expect(wrapper.find('UtilityBar')).toMatchSnapshot(); - }); - - test('it applies border styles when border is true', () => { - const wrapper = mount( - - - - - {'Test text'} - - - - {'Test action'} - - - - - - {'Test action'} - - - - - ); - const casesUtilityBar = wrapper.find('.casesUtilityBar').first(); - - expect(casesUtilityBar).toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(casesUtilityBar).toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeS); - }); - - test('it DOES NOT apply border styles when border is false', () => { - const wrapper = mount( - - - - - {'Test text'} - - - - {'Test action'} - - - - - - {'Test action'} - - - - - ); - const casesUtilityBar = wrapper.find('.casesUtilityBar').first(); - - expect(casesUtilityBar).not.toHaveStyleRule('border-bottom', euiDarkVars.euiBorderThin); - expect(casesUtilityBar).not.toHaveStyleRule('padding-bottom', euiDarkVars.euiSizeS); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx deleted file mode 100644 index ff47459d437be..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { Bar, BarProps } from './styles'; - -interface UtilityBarProps extends BarProps { - children: React.ReactNode; -} - -export const UtilityBar = React.memo(({ border, children }) => ( - {children} -)); - -UtilityBar.displayName = 'UtilityBar'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx deleted file mode 100644 index 881f4e922bcab..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { AppMockRenderer, createAppMockRenderer } from '../../common/mock'; -import { UtilityBarAction } from '.'; - -describe('UtilityBarAction', () => { - let appMockRenderer: AppMockRenderer; - const dataTestSubj = 'test-bar-action'; - - beforeEach(() => { - jest.clearAllMocks(); - appMockRenderer = createAppMockRenderer(); - }); - - test('it renders', () => { - const res = appMockRenderer.render( - - {'Test action'} - - ); - - expect(res.getByTestId(dataTestSubj)).toBeInTheDocument(); - expect(res.getByText('Test action')).toBeInTheDocument(); - }); - - test('it renders a popover', () => { - const res = appMockRenderer.render( - - {'Test action'} - - ); - - expect(res.getByTestId(`${dataTestSubj}-link-icon`)).toBeInTheDocument(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx deleted file mode 100644 index b0748f1dd7c9f..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_action.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { LinkIcon, LinkIconProps } from '../link_icon'; -import { BarAction } from './styles'; - -export interface UtilityBarActionProps extends LinkIconProps { - dataTestSubj?: string; -} - -export const UtilityBarAction = React.memo( - ({ dataTestSubj, children, color, disabled, href, iconSide, iconSize, iconType, onClick }) => { - return ( - - - {children} - - - ); - } -); - -UtilityBarAction.displayName = 'UtilityBarAction'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx deleted file mode 100644 index fa3372cf52331..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.test.tsx +++ /dev/null @@ -1,89 +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 userEvent from '@testing-library/user-event'; -import React from 'react'; -import { act } from 'react-dom/test-utils'; - -import { AppMockRenderer, createAppMockRenderer, TestProviders } from '../../common/mock'; -import { UtilityBarBulkActions } from './utility_bar_bulk_actions'; - -describe('UtilityBarBulkActions', () => { - let appMockRenderer: AppMockRenderer; - const closePopover = jest.fn(); - const onButtonClick = jest.fn(); - const dataTestSubj = 'test-bar-action'; - - beforeEach(() => { - jest.clearAllMocks(); - appMockRenderer = createAppMockRenderer(); - }); - - it('renders', () => { - const res = appMockRenderer.render( - - - {'Test bulk actions'} - - - ); - - expect(res.getByTestId(dataTestSubj)).toBeInTheDocument(); - expect(res.getByText('button title')).toBeInTheDocument(); - }); - - it('renders a popover', async () => { - const res = appMockRenderer.render( - - - {'Test bulk actions'} - - - ); - - expect(res.getByText('Test bulk actions')).toBeInTheDocument(); - }); - - it('calls onButtonClick', async () => { - const res = appMockRenderer.render( - - - {'Test bulk actions'} - - - ); - - expect(res.getByText('Test bulk actions')).toBeInTheDocument(); - - act(() => { - userEvent.click(res.getByText('button title')); - }); - - expect(onButtonClick).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.tsx deleted file mode 100644 index afeb93cc221ea..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_bulk_actions.tsx +++ /dev/null @@ -1,67 +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 { EuiPopover } from '@elastic/eui'; -import React from 'react'; -import { LinkIcon, LinkIconProps } from '../link_icon'; - -import { BarAction } from './styles'; - -export interface UtilityBarActionProps extends Omit { - isPopoverOpen: boolean; - buttonTitle: string; - closePopover: () => void; - onButtonClick: () => void; - dataTestSubj?: string; -} - -export const UtilityBarBulkActions = React.memo( - ({ - dataTestSubj, - children, - color, - disabled, - href, - iconSide, - iconSize, - iconType, - isPopoverOpen, - onButtonClick, - buttonTitle, - closePopover, - }) => { - return ( - - - {buttonTitle} - - } - > - {children} - - - ); - } -); - -UtilityBarBulkActions.displayName = 'UtilityBarBulkActions'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx deleted file mode 100644 index bf7bb34ab5b64..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarGroup, UtilityBarText } from '.'; - -describe('UtilityBarGroup', () => { - test('it renders', () => { - const wrapper = shallow( - - - {'Test text'} - - - ); - - expect(wrapper.find('UtilityBarGroup')).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx deleted file mode 100644 index ef83d6effc8a3..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_group.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { BarGroup, BarGroupProps } from './styles'; - -export interface UtilityBarGroupProps extends BarGroupProps { - children: React.ReactNode; -} - -export const UtilityBarGroup = React.memo(({ grow, children }) => ( - {children} -)); - -UtilityBarGroup.displayName = 'UtilityBarGroup'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx deleted file mode 100644 index 142cca8fc9e32..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.test.tsx +++ /dev/null @@ -1,28 +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 { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarGroup, UtilityBarSection, UtilityBarText } from '.'; - -describe('UtilityBarSection', () => { - test('it renders', () => { - const wrapper = shallow( - - - - {'Test text'} - - - - ); - - expect(wrapper.find('UtilityBarSection')).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx deleted file mode 100644 index c84219cc63488..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_section.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { BarSection, BarSectionProps } from './styles'; - -export interface UtilityBarSectionProps extends BarSectionProps { - children: React.ReactNode; -} - -export const UtilityBarSection = React.memo(({ grow, children }) => ( - {children} -)); - -UtilityBarSection.displayName = 'UtilityBarSection'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx deleted file mode 100644 index 11b3be8d656e4..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_spacer.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { BarSpacer } from './styles'; - -export interface UtilityBarSpacerProps { - dataTestSubj?: string; -} - -export const UtilityBarSpacer = React.memo(({ dataTestSubj }) => ( - -)); - -UtilityBarSpacer.displayName = 'UtilityBarSpacer'; diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx deleted file mode 100644 index afeae82a19e85..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.test.tsx +++ /dev/null @@ -1,24 +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 { shallow } from 'enzyme'; -import React from 'react'; - -import { TestProviders } from '../../common/mock'; -import { UtilityBarText } from '.'; - -describe('UtilityBarText', () => { - test('it renders', () => { - const wrapper = shallow( - - {'Test text'} - - ); - - expect(wrapper.find('UtilityBarText')).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx b/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx deleted file mode 100644 index c0be3cbfbe202..0000000000000 --- a/x-pack/plugins/cases/public/components/utility_bar/utility_bar_text.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; - -import { BarText } from './styles'; - -export interface UtilityBarTextProps { - children: string | JSX.Element; - dataTestSubj?: string; -} - -export const UtilityBarText = React.memo(({ children, dataTestSubj }) => ( - {children} -)); - -UtilityBarText.displayName = 'UtilityBarText'; diff --git a/x-pack/test/functional/services/cases/list.ts b/x-pack/test/functional/services/cases/list.ts index 15e2e40b0ca71..89b09ddde5879 100644 --- a/x-pack/test/functional/services/cases/list.ts +++ b/x-pack/test/functional/services/cases/list.ts @@ -159,7 +159,7 @@ export function CasesTableServiceProvider( }, async refreshTable() { - await testSubjects.click('all-cases-refresh'); + await testSubjects.click('all-cases-refresh-link-icon'); }, async openRowActions(index: number) { @@ -177,7 +177,8 @@ export function CasesTableServiceProvider( async selectAllCasesAndOpenBulkActions() { await testSubjects.setCheckbox('checkboxSelectAll', 'check'); - const button = await find.byCssSelector('[aria-label="Bulk actions"]'); + await testSubjects.existOrFail('case-table-bulk-actions-link-icon'); + const button = await testSubjects.find('case-table-bulk-actions-link-icon'); await button.click(); }, From c723fd825d759d9b6207573557c2de5b8d23c1c3 Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Thu, 6 Oct 2022 10:56:42 -0400 Subject: [PATCH 172/174] [8.6][ML Inference] New API to fetch ML inference errors (#142799) * Add ML inference PL creation flow * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Add exists check, clean up code a bit * Fix dest name * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Separate concerns * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Remove i18n due to linter error, fix src field ref * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Add/update unit tests * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * Refactor error handling * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Add sub-pipeline to parent ML PL * Add unit tests and docs * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Refactor error handling * Wrap logic into higher level function * Add route test * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * API to fetch inference errors * Minor style changes * Add unit tests * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../get_inference_errors.test.ts | 87 +++++++++++++++++++ .../get_inference_errors.ts | 81 +++++++++++++++++ .../routes/enterprise_search/indices.test.ts | 63 ++++++++++++++ .../routes/enterprise_search/indices.ts | 26 ++++++ 4 files changed, 257 insertions(+) create mode 100644 x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts create mode 100644 x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts new file mode 100644 index 0000000000000..d9939afedaa5e --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ElasticsearchClient } from '@kbn/core/server'; + +import { getMlInferenceErrors } from './get_inference_errors'; + +describe('getMlInferenceErrors', () => { + const indexName = 'my-index'; + + const mockClient = { + search: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should fetch aggregations and transform them', async () => { + mockClient.search.mockImplementation(() => + Promise.resolve({ + aggregations: { + errors: { + buckets: [ + { + key: 'Error message 1', + doc_count: 100, + max_error_timestamp: { + value: 1664977836100, + value_as_string: '2022-10-05T13:50:36.100Z', + }, + }, + { + key: 'Error message 2', + doc_count: 200, + max_error_timestamp: { + value: 1664977836200, + value_as_string: '2022-10-05T13:50:36.200Z', + }, + }, + ], + }, + }, + }) + ); + + const actualResult = await getMlInferenceErrors( + indexName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual([ + { + message: 'Error message 1', + doc_count: 100, + timestamp: '2022-10-05T13:50:36.100Z', + }, + { + message: 'Error message 2', + doc_count: 200, + timestamp: '2022-10-05T13:50:36.200Z', + }, + ]); + expect(mockClient.search).toHaveBeenCalledTimes(1); + }); + + it('should return an empty array if there are no aggregates', async () => { + mockClient.search.mockImplementation(() => + Promise.resolve({ + aggregations: { + errors: [], + }, + }) + ); + + const actualResult = await getMlInferenceErrors( + indexName, + mockClient as unknown as ElasticsearchClient + ); + + expect(actualResult).toEqual([]); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.ts new file mode 100644 index 0000000000000..1ced837f42f22 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/lib/ml_inference_pipeline/get_inference_errors.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 { + AggregationsMultiBucketAggregateBase, + AggregationsStringRareTermsBucketKeys, +} from '@elastic/elasticsearch/lib/api/types'; + +import { ElasticsearchClient } from '@kbn/core/server'; + +export interface MlInferenceError { + message: string; + doc_count: number; + timestamp: string | undefined; // Date string +} + +export interface ErrorAggregationBucket extends AggregationsStringRareTermsBucketKeys { + max_error_timestamp: { + value: number | null; + value_as_string?: string; + }; +} + +/** + * Fetches an aggregate of distinct ML inference errors from the target index, along with the most + * recent error's timestamp and affected document count for each bucket. + * @param indexName the index to get the errors from. + * @param esClient the Elasticsearch Client to use to fetch the errors. + */ +export const getMlInferenceErrors = async ( + indexName: string, + esClient: ElasticsearchClient +): Promise => { + const searchResult = await esClient.search< + unknown, + { + errors: AggregationsMultiBucketAggregateBase; + } + >({ + index: indexName, + body: { + aggs: { + errors: { + terms: { + field: '_ingest.inference_errors.message.enum', + order: { + max_error_timestamp: 'desc', + }, + size: 20, + }, + aggs: { + max_error_timestamp: { + max: { + field: '_ingest.inference_errors.timestamp', + }, + }, + }, + }, + }, + size: 0, + }, + }); + + const errorBuckets = searchResult.aggregations?.errors.buckets; + if (!errorBuckets) { + return []; + } + + // Buckets are either in an array or in a Record, we transform them to an array + const buckets = Array.isArray(errorBuckets) ? errorBuckets : Object.values(errorBuckets); + + return buckets.map((bucket) => ({ + message: bucket.key, + doc_count: bucket.doc_count, + timestamp: bucket.max_error_timestamp?.value_as_string, + })); +}; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts index 435fb0892019f..64711a6ac20be 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.test.ts @@ -23,10 +23,14 @@ jest.mock('../../lib/indices/delete_ml_inference_pipeline', () => ({ jest.mock('../../lib/indices/exists_index', () => ({ indexOrAliasExists: jest.fn(), })); +jest.mock('../../lib/ml_inference_pipeline/get_inference_errors', () => ({ + getMlInferenceErrors: jest.fn(), +})); import { deleteMlInferencePipeline } from '../../lib/indices/delete_ml_inference_pipeline'; import { indexOrAliasExists } from '../../lib/indices/exists_index'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; +import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; import { createAndReferenceMlInferencePipeline } from '../../utils/create_ml_inference_pipeline'; import { ElasticsearchResponseError } from '../../utils/identify_exceptions'; @@ -40,6 +44,7 @@ describe('Enterprise Search Managed Indices', () => { putPipeline: jest.fn(), simulate: jest.fn(), }, + search: jest.fn(), }, }; @@ -47,6 +52,64 @@ describe('Enterprise Search Managed Indices', () => { elasticsearch: { client: mockClient }, }; + describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/errors', () => { + beforeEach(() => { + const context = { + core: Promise.resolve(mockCore), + } as unknown as jest.Mocked; + + mockRouter = new MockRouter({ + context, + method: 'get', + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/errors', + }); + + registerIndexRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('fails validation without index_name', () => { + const request = { + params: {}, + }; + mockRouter.shouldThrow(request); + }); + + it('fetches ML inference errors', async () => { + const errorsResult = [ + { + message: 'Error message 1', + doc_count: 100, + timestamp: '2022-10-05T13:50:36.100Z', + }, + { + message: 'Error message 2', + doc_count: 200, + timestamp: '2022-10-05T13:50:36.200Z', + }, + ]; + + (getMlInferenceErrors as jest.Mock).mockImplementationOnce(() => { + return Promise.resolve(errorsResult); + }); + + await mockRouter.callRoute({ + params: { indexName: 'my-index-name' }, + }); + + expect(getMlInferenceErrors).toHaveBeenCalledWith('my-index-name', mockClient.asCurrentUser); + + expect(mockRouter.response.ok).toHaveBeenCalledWith({ + body: { + errors: errorsResult, + }, + headers: { 'content-type': 'application/json' }, + }); + }); + }); + describe('GET /internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors', () => { beforeEach(() => { const context = { diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts index ef6f8131ee2c1..38aef14fdaa81 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/indices.ts @@ -9,6 +9,7 @@ import { IngestPutPipelineRequest, IngestSimulateRequest, } from '@elastic/elasticsearch/lib/api/types'; + import { schema } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; @@ -27,6 +28,7 @@ import { fetchIndex } from '../../lib/indices/fetch_index'; import { fetchIndices } from '../../lib/indices/fetch_indices'; import { fetchMlInferencePipelineProcessors } from '../../lib/indices/fetch_ml_inference_pipeline_processors'; import { generateApiKey } from '../../lib/indices/generate_api_key'; +import { getMlInferenceErrors } from '../../lib/ml_inference_pipeline/get_inference_errors'; import { createIndexPipelineDefinitions } from '../../lib/pipelines/create_pipeline_definitions'; import { getCustomPipelines } from '../../lib/pipelines/get_custom_pipelines'; import { getPipeline } from '../../lib/pipelines/get_pipeline'; @@ -524,6 +526,30 @@ export function registerIndexRoutes({ }) ); + router.get( + { + path: '/internal/enterprise_search/indices/{indexName}/ml_inference/errors', + validate: { + params: schema.object({ + indexName: schema.string(), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const indexName = decodeURIComponent(request.params.indexName); + const { client } = (await context.core).elasticsearch; + + const errors = await getMlInferenceErrors(indexName, client.asCurrentUser); + + return response.ok({ + body: { + errors, + }, + headers: { 'content-type': 'application/json' }, + }); + }) + ); + router.put( { path: '/internal/enterprise_search/indices/{indexName}/ml_inference/pipeline_processors/{pipelineName}', From 0d9a9f3771df63db399323f70d53f4843abf78ef Mon Sep 17 00:00:00 2001 From: Nav <13634519+navarone-feekery@users.noreply.github.com> Date: Thu, 6 Oct 2022 17:17:57 +0200 Subject: [PATCH 173/174] [Enterprise Search] Remove link to nowhere (#142860) --- .../crawl_requests_panel/crawl_requests_panel.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx index f5474d41aff81..75ff689819ff8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/crawler/crawl_requests_panel/crawl_requests_panel.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { useValues } from 'kea'; -import { EuiCode, EuiLink, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; +import { EuiCode, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataPanel } from '../../../../../shared/data_panel/data_panel'; @@ -45,14 +45,6 @@ export const CrawlRequestsPanel: React.FC = () => { 'Requests originating from the crawler can be identified by the following User Agent. This is configured in your enterprise-search.yml file.', } )}{' '} - - {i18n.translate( - 'xpack.enterpriseSearch.crawler.crawlRequestsPanel.userAgentDocumentationLink', - { - defaultMessage: 'Learn more about Elastic crawler user agents', - } - )} - {data ? data.userAgent : ''} From 4997fdf5d56aad25b3026b97413b1c8d6ca6ad3f Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 6 Oct 2022 10:18:22 -0500 Subject: [PATCH 174/174] Disable bazel cache on CI (#142873) --- .buildkite/scripts/common/setup_bazel.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.buildkite/scripts/common/setup_bazel.sh b/.buildkite/scripts/common/setup_bazel.sh index de381010b8aa3..a60543e36c941 100755 --- a/.buildkite/scripts/common/setup_bazel.sh +++ b/.buildkite/scripts/common/setup_bazel.sh @@ -11,7 +11,7 @@ cat < $KIBANA_DIR/.bazelrc build --build_metadata=ROLE=CI EOF -BAZEL_CACHE_MODE=${BAZEL_CACHE_MODE:-gcs} +BAZEL_CACHE_MODE=none if [[ "$BAZEL_CACHE_MODE" == "gcs" ]]; then echo "[bazel] enabling caching with GCS buckets"