From 0092f0ac532798f15d1a8aae10a403afa96c82e5 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 27 Oct 2022 14:35:33 +0200 Subject: [PATCH 01/28] [ML] Explain Log Rate Spikes: Limit fields for frequent_items agg. (#143974) Limits the fields we pass on to the frequent_items aggregation to 15. This is a trade off between speed and quality of the grouping result. The amount of fields we pass on to the agg grow the time it takes to get frequent items more than linearly and we risk timeouts with more fields. --- .../routes/queries/fetch_frequent_items.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) 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 aaf9af283c3e1..362cae07273e5 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 @@ -13,6 +13,8 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { Logger } from '@kbn/logging'; import type { ChangePoint, FieldValuePair } from '@kbn/ml-agg-utils'; +const FREQUENT_ITEMS_FIELDS_LIMIT = 15; + interface FrequentItemsAggregation extends estypes.AggregationsSamplerAggregation { fi: { buckets: Array<{ key: Record; doc_count: number; support: number }>; @@ -59,10 +61,19 @@ export async function fetchFrequentItems( emitError: (m: string) => void, abortSignal?: AbortSignal ) { - // get unique fields from change points - const fields = [...new Set(changePoints.map((t) => t.fieldName))]; + // Sort change points by ascending p-value, necessary to apply the field limit correctly. + const sortedChangePoints = changePoints.slice().sort((a, b) => { + return (a.pValue ?? 0) - (b.pValue ?? 0); + }); + + // Get up to 15 unique fields from change points with retained order + const fields = sortedChangePoints.reduce((p, c) => { + if (p.length < FREQUENT_ITEMS_FIELDS_LIMIT && !p.some((d) => d === c.fieldName)) { + p.push(c.fieldName); + } + return p; + }, []); - // TODO add query params const query = { bool: { minimum_should_match: 2, @@ -77,7 +88,7 @@ export async function fetchFrequentItems( }, }, ], - should: changePoints.map((t) => { + should: sortedChangePoints.map((t) => { return { term: { [t.fieldName]: t.fieldValue } }; }), }, @@ -117,16 +128,18 @@ export async function fetchFrequentItems( }, }; + const esBody = { + query, + aggs, + size: 0, + track_total_hits: true, + }; + const body = await client.search( { index, size: 0, - body: { - query, - aggs, - size: 0, - track_total_hits: true, - }, + body: esBody, }, { signal: abortSignal, maxRetries: 0 } ); @@ -167,7 +180,7 @@ export async function fetchFrequentItems( Object.entries(fis.key).forEach(([key, value]) => { result.set[key] = value[0]; - const pValue = changePoints.find( + const pValue = sortedChangePoints.find( (t) => t.fieldName === key && t.fieldValue === value[0] )?.pValue; From bbbf9f89854b628a27aa304ca32af0009466c995 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 27 Oct 2022 08:52:35 -0400 Subject: [PATCH 02/28] Adding content type (#143800) Co-authored-by: Larry Gregory --- x-pack/examples/screenshotting_example/server/plugin.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/examples/screenshotting_example/server/plugin.ts b/x-pack/examples/screenshotting_example/server/plugin.ts index 9ca74c6e16353..16a766558ff3f 100644 --- a/x-pack/examples/screenshotting_example/server/plugin.ts +++ b/x-pack/examples/screenshotting_example/server/plugin.ts @@ -38,6 +38,7 @@ export class ScreenshottingExamplePlugin implements Plugin { ); return response.ok({ + headers: { 'content-type': 'application/json' }, body: JSON.stringify({ metrics, image: results[0]?.screenshots[0]?.data.toString('base64'), From 460cf89d5f1bd148958c7d92c9aa6b1e9b2e76d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 27 Oct 2022 15:19:01 +0200 Subject: [PATCH 03/28] [Guided onboarding] Add a config for test guide (#143973) * [Guided onboarding] Add a guide config for testing and update the example plugin to use it instead of search * [Guided onboarding] Update the API tests with the test guide config * [Guided onboarding] Address CR comments * [Guided onboarding] Delete unneeded code in examples * Update src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts Co-authored-by: Alison Goryachev Co-authored-by: Alison Goryachev --- .../public/components/app.tsx | 2 +- .../public/components/main.tsx | 11 +- .../public/components/step_one.tsx | 10 +- .../public/components/step_three.tsx | 20 +- .../public/components/step_two.tsx | 55 +---- .../components/landing_page/use_case_card.tsx | 3 +- packages/kbn-guided-onboarding/src/types.ts | 11 +- .../public/constants/guides_config/index.ts | 2 + .../constants/guides_config/test_guide.ts | 68 ++++++ .../public/services/api.mocks.ts | 78 +++--- .../public/services/api.test.ts | 225 ++++++------------ .../guided_onboarding/public/services/api.ts | 2 +- .../public/services/helpers.test.ts | 31 ++- 13 files changed, 232 insertions(+), 286 deletions(-) create mode 100644 src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts diff --git a/examples/guided_onboarding_example/public/components/app.tsx b/examples/guided_onboarding_example/public/components/app.tsx index a5252920c27fa..ae55f3d3811dc 100755 --- a/examples/guided_onboarding_example/public/components/app.tsx +++ b/examples/guided_onboarding_example/public/components/app.tsx @@ -59,7 +59,7 @@ 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 a65fd2324d34b..4c9481d423e4c 100644 --- a/examples/guided_onboarding_example/public/components/main.tsx +++ b/examples/guided_onboarding_example/public/components/main.tsx @@ -259,6 +259,7 @@ export const Main = (props: MainProps) => { { value: 'observability', text: 'observability' }, { value: 'security', text: 'security' }, { value: 'search', text: 'search' }, + { value: 'testGuide', text: 'test guide' }, ]} value={selectedGuide} onChange={(e) => { @@ -294,7 +295,7 @@ export const Main = (props: MainProps) => {

@@ -316,6 +317,14 @@ export const Main = (props: MainProps) => { /> + + history.push('stepThree')}> + + + diff --git a/examples/guided_onboarding_example/public/components/step_one.tsx b/examples/guided_onboarding_example/public/components/step_one.tsx index 3441b4d8e5d99..fd5cb132b6b91 100644 --- a/examples/guided_onboarding_example/public/components/step_one.tsx +++ b/examples/guided_onboarding_example/public/components/step_one.tsx @@ -32,7 +32,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => const [isTourStepOpen, setIsTourStepOpen] = useState(false); const isTourActive = useObservable( - guidedOnboardingApi!.isGuideStepActive$('search', 'add_data'), + guidedOnboardingApi!.isGuideStepActive$('testGuide', 'step1'), false ); useEffect(() => { @@ -45,7 +45,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>

@@ -56,7 +56,7 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) =>

@@ -72,12 +72,12 @@ export const StepOne = ({ guidedOnboarding }: GuidedOnboardingExampleAppDeps) => onFinish={() => setIsTourStepOpen(false)} step={1} stepsTotal={1} - title="Step Add data" + title="Step 1" anchorPosition="rightUp" > { - await guidedOnboardingApi?.completeGuideStep('search', 'add_data'); + await guidedOnboardingApi?.completeGuideStep('testGuide', 'step1'); }} > Complete step 1 diff --git a/examples/guided_onboarding_example/public/components/step_three.tsx b/examples/guided_onboarding_example/public/components/step_three.tsx index ffe9d87993611..eefb38165beed 100644 --- a/examples/guided_onboarding_example/public/components/step_three.tsx +++ b/examples/guided_onboarding_example/public/components/step_three.tsx @@ -30,7 +30,7 @@ export const StepThree = (props: StepThreeProps) => { useEffect(() => { const subscription = guidedOnboardingApi - ?.isGuideStepActive$('search', 'search_experience') + ?.isGuideStepActive$('testGuide', 'step3') .subscribe((isStepActive) => { setIsTourStepOpen(isStepActive); }); @@ -53,9 +53,17 @@ export const StepThree = (props: StepThreeProps) => {

+

+

+

@@ -73,12 +81,12 @@ export const StepThree = (props: StepThreeProps) => { }} step={1} stepsTotal={1} - title="Step Build search experience" + title="Step 3" anchorPosition="rightUp" > { - await guidedOnboardingApi?.completeGuideStep('search', 'search_experience'); + await guidedOnboardingApi?.completeGuideStep('testGuide', 'step3'); }} > 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 07f4fd7e63e0c..89c0c37e46e4a 100644 --- a/examples/guided_onboarding_example/public/components/step_two.tsx +++ b/examples/guided_onboarding_example/public/components/step_two.tsx @@ -6,37 +6,17 @@ * Side Public License, v 1. */ -import React, { useEffect, useState } from 'react'; +import React from 'react'; -import { EuiButton, EuiSpacer, EuiText, EuiTitle, EuiTourStep } from '@elastic/eui'; +import { EuiText, EuiTitle } 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 StepTwoProps { - guidedOnboarding: GuidedOnboardingPluginStart; -} - -export const StepTwo = (props: StepTwoProps) => { - const { - guidedOnboarding: { guidedOnboardingApi }, - } = props; - - const [isTourStepOpen, setIsTourStepOpen] = useState(false); - - useEffect(() => { - const subscription = guidedOnboardingApi - ?.isGuideStepActive$('search', 'browse_docs') - .subscribe((isStepActive) => { - setIsTourStepOpen(isStepActive); - }); - return () => subscription?.unsubscribe(); - }, [guidedOnboardingApi]); - +export const StepTwo = () => { return ( <> @@ -54,36 +34,11 @@ export const StepTwo = (props: StepTwoProps) => {

- - -

Click this button to complete step 2.

- - } - isStepOpen={isTourStepOpen} - minWidth={300} - onFinish={() => { - setIsTourStepOpen(false); - }} - step={1} - stepsTotal={1} - title="Step Browse documents" - anchorPosition="rightUp" - > - { - await guidedOnboardingApi?.completeGuideStep('search', 'browse_docs'); - }} - > - Complete step 2 - -
); diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx index cc16977d966a7..574b9b18bf2b3 100644 --- a/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx +++ b/packages/kbn-guided-onboarding/src/components/landing_page/use_case_card.tsx @@ -9,7 +9,6 @@ import React, { ReactNode } from 'react'; import { EuiCard, EuiText, EuiImage } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { GuideId } from '../../types'; type UseCaseConstants = { [key in UseCase]: { @@ -53,7 +52,7 @@ const constants: UseCaseConstants = { export type UseCase = 'search' | 'observability' | 'security'; export interface UseCaseCardProps { - useCase: GuideId; + useCase: UseCase; title: string; description: string; footer: ReactNode; diff --git a/packages/kbn-guided-onboarding/src/types.ts b/packages/kbn-guided-onboarding/src/types.ts index 9a307464cefb8..6b919835da2e7 100644 --- a/packages/kbn-guided-onboarding/src/types.ts +++ b/packages/kbn-guided-onboarding/src/types.ts @@ -6,13 +6,14 @@ * Side Public License, v 1. */ -export type GuideId = 'observability' | 'security' | 'search'; +export type GuideId = 'observability' | 'security' | 'search' | 'testGuide'; -export type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; -export type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases'; -export type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience'; +type ObservabilityStepIds = 'add_data' | 'view_dashboard' | 'tour_observability'; +type SecurityStepIds = 'add_data' | 'rules' | 'alertsCases'; +type SearchStepIds = 'add_data' | 'browse_docs' | 'search_experience'; +type TestGuideIds = 'step1' | 'step2' | 'step3'; -export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds; +export type GuideStepIds = ObservabilityStepIds | SecurityStepIds | SearchStepIds | TestGuideIds; export interface GuideState { guideId: GuideId; diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/index.ts b/src/plugins/guided_onboarding/public/constants/guides_config/index.ts index 9ce81cf9d4698..e2ab4f7e7747f 100644 --- a/src/plugins/guided_onboarding/public/constants/guides_config/index.ts +++ b/src/plugins/guided_onboarding/public/constants/guides_config/index.ts @@ -10,9 +10,11 @@ import type { GuidesConfig } from '../../types'; import { securityConfig } from './security'; import { observabilityConfig } from './observability'; import { searchConfig } from './search'; +import { testGuideConfig } from './test_guide'; export const guidesConfig: GuidesConfig = { security: securityConfig, observability: observabilityConfig, search: searchConfig, + testGuide: testGuideConfig, }; diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts b/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts new file mode 100644 index 0000000000000..b357ad497c6b4 --- /dev/null +++ b/src/plugins/guided_onboarding/public/constants/guides_config/test_guide.ts @@ -0,0 +1,68 @@ +/* + * Copyright 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 { GuideConfig } from '../../types'; + +export const testGuideConfig: GuideConfig = { + title: 'Test guide for development', + description: `This guide is used to test the guided onboarding UI while in development and to run automated tests for the API and UI components.`, + guideName: 'Testing example', + docs: { + text: 'Testing example docs', + url: 'example.com', + }, + steps: [ + { + id: 'step1', + title: 'Step 1 (completed via an API request)', + descriptionList: [ + `This step is directly completed by clicking the button that uses the API function 'completeGuideStep`, + 'Navigate to /guidedOnboardingExample/stepOne to complete the step.', + ], + location: { + appID: 'guidedOnboardingExample', + path: 'stepOne', + }, + integration: 'testIntegration', + }, + { + id: 'step2', + title: 'Step 2 (manual completion after navigation)', + descriptionList: [ + 'This step is set to ready_to_complete on page navigation.', + 'After that click the popover on the guide button in the header and mark the step done', + ], + location: { + appID: 'guidedOnboardingExample', + path: 'stepTwo', + }, + manualCompletion: { + title: 'Manual completion step title', + description: + 'Mark the step complete by opening the panel and clicking the button "Mark done"', + readyToCompleteOnNavigation: true, + }, + }, + { + id: 'step3', + title: 'Step 3 (manual completion after click)', + descriptionList: [ + 'This step is completed by clicking a button on the page and then clicking the popover on the guide button in the header and marking the step done', + ], + manualCompletion: { + title: 'Manual completion step title', + description: + 'Mark the step complete by opening the panel and clicking the button "Mark done"', + }, + location: { + appID: 'guidedOnboardingExample', + path: 'stepThree', + }, + }, + ], +}; diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts index 21bb257cad68f..2294607f91b38 100644 --- a/src/plugins/guided_onboarding/public/services/api.mocks.ts +++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts @@ -6,84 +6,78 @@ * Side Public License, v 1. */ -import type { GuideState } from '@kbn/guided-onboarding'; +import type { GuideState, GuideId, GuideStepIds } from '@kbn/guided-onboarding'; -export const searchAddDataActiveState: GuideState = { - guideId: 'search', +export const testGuide: GuideId = 'testGuide'; +export const testGuideFirstStep: GuideStepIds = 'step1'; +export const testGuideManualCompletionStep = 'step2'; +export const testGuideLastStep: GuideStepIds = 'step3'; +export const testIntegration = 'testIntegration'; +export const wrongIntegration = 'notTestIntegration'; + +export const testGuideStep1ActiveState: GuideState = { + guideId: 'testGuide', isActive: true, status: 'in_progress', steps: [ { - id: 'add_data', + id: 'step1', status: 'active', }, { - id: 'browse_docs', + id: 'step2', status: 'inactive', }, { - id: 'search_experience', + id: 'step3', status: 'inactive', }, ], }; -export const securityAddDataInProgressState: GuideState = { - guideId: 'security', - status: 'in_progress', - isActive: true, +export const testGuideStep1InProgressState: GuideState = { + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', - status: 'in_progress', - }, - { - id: 'rules', - status: 'inactive', - }, - { - id: 'alertsCases', - status: 'inactive', + id: testGuideStep1ActiveState.steps[0].id, + status: 'in_progress', // update the first step status }, + testGuideStep1ActiveState.steps[1], + testGuideStep1ActiveState.steps[2], ], }; -export const securityRulesActiveState: GuideState = { - guideId: 'security', - isActive: true, - status: 'in_progress', +export const testGuideStep2ActiveState: GuideState = { + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', + ...testGuideStep1ActiveState.steps[0], status: 'complete', }, { - id: 'rules', + id: testGuideStep1ActiveState.steps[1].id, status: 'active', }, - { - id: 'alertsCases', - status: 'inactive', - }, + testGuideStep1ActiveState.steps[2], ], }; -export const noGuideActiveState: GuideState = { - guideId: 'security', - status: 'in_progress', - isActive: false, +export const testGuideStep2InProgressState: GuideState = { + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', - status: 'in_progress', - }, - { - id: 'rules', - status: 'inactive', + ...testGuideStep1ActiveState.steps[0], + status: 'complete', }, { - id: 'alertsCases', - status: 'inactive', + id: testGuideStep1ActiveState.steps[1].id, + status: 'in_progress', }, + testGuideStep1ActiveState.steps[2], ], }; + +export const testGuideNotActiveState: GuideState = { + ...testGuideStep1ActiveState, + isActive: false, +}; diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts index 2296304166648..56a5755f0ee55 100644 --- a/src/plugins/guided_onboarding/public/services/api.test.ts +++ b/src/plugins/guided_onboarding/public/services/api.test.ts @@ -12,20 +12,20 @@ import type { GuideState } from '@kbn/guided-onboarding'; import { firstValueFrom, Subscription } from 'rxjs'; import { API_BASE_PATH } from '../../common/constants'; -import { guidesConfig } from '../constants/guides_config'; import { ApiService } from './api'; import { - noGuideActiveState, - searchAddDataActiveState, - securityAddDataInProgressState, - securityRulesActiveState, + testGuide, + testGuideFirstStep, + testGuideManualCompletionStep, + testGuideStep1ActiveState, + testGuideStep1InProgressState, + testGuideStep2ActiveState, + testGuideNotActiveState, + testIntegration, + wrongIntegration, + testGuideStep2InProgressState, } from './api.mocks'; -const searchGuide = 'search'; -const firstStep = guidesConfig[searchGuide].steps[0].id; -const endpointIntegration = 'endpoint'; -const kubernetesIntegration = 'kubernetes'; - describe('GuidedOnboarding ApiService', () => { let httpClient: jest.Mocked; let apiService: ApiService; @@ -34,7 +34,7 @@ describe('GuidedOnboarding ApiService', () => { beforeEach(() => { httpClient = httpServiceMock.createStartContract({ basePath: '/base/path' }); httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1ActiveState], }); apiService = new ApiService(); apiService.setup(httpClient); @@ -57,10 +57,10 @@ describe('GuidedOnboarding ApiService', () => { }); it('broadcasts the updated state', async () => { - await apiService.activateGuide(searchGuide, searchAddDataActiveState); + await apiService.activateGuide(testGuide, testGuideStep1ActiveState); const state = await firstValueFrom(apiService.fetchActiveGuideState$()); - expect(state).toEqual(searchAddDataActiveState); + expect(state).toEqual(testGuideStep1ActiveState); }); }); @@ -74,12 +74,12 @@ describe('GuidedOnboarding ApiService', () => { describe('deactivateGuide', () => { it('deactivates an existing guide', async () => { - await apiService.deactivateGuide(searchAddDataActiveState); + await apiService.deactivateGuide(testGuideStep1ActiveState); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - ...searchAddDataActiveState, + ...testGuideStep1ActiveState, isActive: false, }), }); @@ -88,17 +88,7 @@ describe('GuidedOnboarding ApiService', () => { describe('updateGuideState', () => { it('sends a request to the put API', async () => { - const updatedState: GuideState = { - ...searchAddDataActiveState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', // update the first step status - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }; + const updatedState: GuideState = testGuideStep1InProgressState; await apiService.updateGuideState(updatedState, false); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { @@ -109,20 +99,11 @@ describe('GuidedOnboarding ApiService', () => { describe('isGuideStepActive$', () => { it('returns true if the step has been started', (done) => { - const updatedState: GuideState = { - ...searchAddDataActiveState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }; + const updatedState: GuideState = testGuideStep1InProgressState; apiService.updateGuideState(updatedState, false); + subscription = apiService - .isGuideStepActive$(searchGuide, firstStep) + .isGuideStepActive$(testGuide, testGuideFirstStep) .subscribe((isStepActive) => { if (isStepActive) { done(); @@ -131,9 +112,8 @@ describe('GuidedOnboarding ApiService', () => { }); it('returns false if the step is not been started', (done) => { - apiService.updateGuideState(searchAddDataActiveState, false); subscription = apiService - .isGuideStepActive$(searchGuide, firstStep) + .isGuideStepActive$(testGuide, testGuideFirstStep) .subscribe((isStepActive) => { if (!isStepActive) { done(); @@ -144,56 +124,44 @@ describe('GuidedOnboarding ApiService', () => { describe('activateGuide', () => { it('activates a new guide', async () => { - await apiService.activateGuide(searchGuide); + // update the mock to no active guides + httpClient.get.mockResolvedValue({ + state: [], + }); + apiService.setup(httpClient); + + await apiService.activateGuide(testGuide); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - isActive: true, - status: 'not_started', - steps: [ - { - id: 'add_data', - status: 'active', - }, - { - id: 'browse_docs', - status: 'inactive', - }, - { - id: 'search_experience', - status: 'inactive', - }, - ], - guideId: searchGuide, - }), + body: JSON.stringify({ ...testGuideStep1ActiveState, status: 'not_started' }), }); }); it('reactivates a guide that has already been started', async () => { - await apiService.activateGuide(searchGuide, searchAddDataActiveState); + await apiService.activateGuide(testGuide, testGuideStep1ActiveState); expect(httpClient.put).toHaveBeenCalledTimes(1); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify(searchAddDataActiveState), + body: JSON.stringify(testGuideStep1ActiveState), }); }); }); describe('completeGuide', () => { const readyToCompleteGuideState: GuideState = { - ...searchAddDataActiveState, + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', + ...testGuideStep1ActiveState.steps[0], status: 'complete', }, { - id: 'browse_docs', + ...testGuideStep1ActiveState.steps[1], status: 'complete', }, { - id: 'search_experience', + ...testGuideStep1ActiveState.steps[2], status: 'complete', }, ], @@ -204,7 +172,7 @@ describe('GuidedOnboarding ApiService', () => { }); it('updates the selected guide and marks it as complete', async () => { - await apiService.completeGuide(searchGuide); + await apiService.completeGuide(testGuide); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ @@ -222,51 +190,39 @@ describe('GuidedOnboarding ApiService', () => { it('returns undefined if the selected guide has uncompleted steps', async () => { const incompleteGuideState: GuideState = { - ...searchAddDataActiveState, + ...testGuideStep1ActiveState, steps: [ { - id: 'add_data', + ...testGuideStep1ActiveState.steps[0], status: 'complete', }, { - id: 'browse_docs', + ...testGuideStep1ActiveState.steps[1], status: 'complete', }, { - id: 'search_experience', + ...testGuideStep1ActiveState.steps[2], status: 'in_progress', }, ], }; await apiService.updateGuideState(incompleteGuideState, false); - const completedState = await apiService.completeGuide(searchGuide); + const completedState = await apiService.completeGuide(testGuide); expect(completedState).not.toBeDefined(); }); }); describe('startGuideStep', () => { beforeEach(async () => { - await apiService.updateGuideState(searchAddDataActiveState, false); + await apiService.updateGuideState(testGuideStep1ActiveState, false); }); it('updates the selected step and marks it as in_progress', async () => { - await apiService.startGuideStep(searchGuide, firstStep); + await apiService.startGuideStep(testGuide, testGuideFirstStep); expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify({ - ...searchAddDataActiveState, - isActive: true, - status: 'in_progress', - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }), + body: JSON.stringify(testGuideStep1InProgressState), }); }); @@ -278,76 +234,35 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuideStep', () => { it(`completes the step when it's in progress`, async () => { - const updatedState: GuideState = { - ...searchAddDataActiveState, - steps: [ - { - id: searchAddDataActiveState.steps[0].id, - status: 'in_progress', // Mark a step as in_progress in order to test the "completeGuideStep" behavior - }, - searchAddDataActiveState.steps[1], - searchAddDataActiveState.steps[2], - ], - }; - await apiService.updateGuideState(updatedState, false); + await apiService.updateGuideState(testGuideStep1InProgressState, false); - await apiService.completeGuideStep(searchGuide, firstStep); + await apiService.completeGuideStep(testGuide, testGuideFirstStep); // 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: searchAddDataActiveState.steps[0].id, - status: 'complete', - }, - { - id: searchAddDataActiveState.steps[1].id, - status: 'active', - }, - searchAddDataActiveState.steps[2], - ], - }), + body: JSON.stringify({ ...testGuideStep2ActiveState }), }); }); it(`marks the step as 'ready_to_complete' if it's configured for manual completion`, async () => { - const securityRulesInProgressState = { - ...securityRulesActiveState, - steps: [ - securityRulesActiveState.steps[0], - { - id: securityRulesActiveState.steps[1].id, - status: 'in_progress', - }, - securityRulesActiveState.steps[2], - ], - }; httpClient.get.mockResolvedValue({ - state: [securityRulesInProgressState], + state: [testGuideStep2InProgressState], }); apiService.setup(httpClient); - await apiService.completeGuideStep('security', 'rules'); + await apiService.completeGuideStep(testGuide, testGuideManualCompletionStep); expect(httpClient.put).toHaveBeenCalledTimes(1); // Verify the completed step now has a "ready_to_complete" status, and the subsequent step is "inactive" expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, { body: JSON.stringify({ - ...securityRulesInProgressState, + ...testGuideStep2InProgressState, steps: [ - securityRulesInProgressState.steps[0], - { - id: securityRulesInProgressState.steps[1].id, - status: 'ready_to_complete', - }, - { - id: securityRulesInProgressState.steps[2].id, - status: 'inactive', - }, + testGuideStep2InProgressState.steps[0], + { ...testGuideStep2InProgressState.steps[1], status: 'ready_to_complete' }, + testGuideStep2InProgressState.steps[2], ], }), }); @@ -359,12 +274,8 @@ describe('GuidedOnboarding ApiService', () => { }); it('does nothing if the step is not in progress', async () => { - httpClient.get.mockResolvedValue({ - state: [searchAddDataActiveState], - }); - apiService.setup(httpClient); - - await apiService.completeGuideStep(searchGuide, firstStep); + // by default the state set in beforeEach is test guide, step 1 active + await apiService.completeGuideStep(testGuide, testGuideFirstStep); expect(httpClient.put).toHaveBeenCalledTimes(0); }); }); @@ -372,11 +283,11 @@ describe('GuidedOnboarding ApiService', () => { describe('isGuidedOnboardingActiveForIntegration$', () => { it('returns true if the integration is part of the active step', (done) => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); subscription = apiService - .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .isGuidedOnboardingActiveForIntegration$(testIntegration) .subscribe((isIntegrationInGuideStep) => { if (isIntegrationInGuideStep) { done(); @@ -384,13 +295,13 @@ describe('GuidedOnboarding ApiService', () => { }); }); - it('returns false if another integration is part of the active step', (done) => { + it('returns false if the current step has a different integration', (done) => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); subscription = apiService - .isGuidedOnboardingActiveForIntegration$(kubernetesIntegration) + .isGuidedOnboardingActiveForIntegration$(wrongIntegration) .subscribe((isIntegrationInGuideStep) => { if (!isIntegrationInGuideStep) { done(); @@ -400,11 +311,11 @@ describe('GuidedOnboarding ApiService', () => { it('returns false if no guide is active', (done) => { httpClient.get.mockResolvedValue({ - state: [noGuideActiveState], + state: [testGuideNotActiveState], }); apiService.setup(httpClient); subscription = apiService - .isGuidedOnboardingActiveForIntegration$(endpointIntegration) + .isGuidedOnboardingActiveForIntegration$(testIntegration) .subscribe((isIntegrationInGuideStep) => { if (!isIntegrationInGuideStep) { done(); @@ -416,35 +327,35 @@ describe('GuidedOnboarding ApiService', () => { describe('completeGuidedOnboardingForIntegration', () => { it(`completes the step if it's active for the integration`, async () => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); - await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + await apiService.completeGuidedOnboardingForIntegration(testIntegration); expect(httpClient.put).toHaveBeenCalledTimes(1); // this assertion depends on the guides config expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, { - body: JSON.stringify(securityRulesActiveState), + body: JSON.stringify(testGuideStep2ActiveState), }); }); it(`does nothing if the step has a different integration`, async () => { httpClient.get.mockResolvedValue({ - state: [securityAddDataInProgressState], + state: [testGuideStep1InProgressState], }); apiService.setup(httpClient); - await apiService.completeGuidedOnboardingForIntegration(kubernetesIntegration); + await apiService.completeGuidedOnboardingForIntegration(wrongIntegration); expect(httpClient.put).not.toHaveBeenCalled(); }); it(`does nothing if no guide is active`, async () => { httpClient.get.mockResolvedValue({ - state: [noGuideActiveState], + state: [testGuideNotActiveState], }); apiService.setup(httpClient); - await apiService.completeGuidedOnboardingForIntegration(endpointIntegration); + await apiService.completeGuidedOnboardingForIntegration(testIntegration); expect(httpClient.put).not.toHaveBeenCalled(); }); }); diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts index 688e72fa83243..cd33f9505c546 100644 --- a/src/plugins/guided_onboarding/public/services/api.ts +++ b/src/plugins/guided_onboarding/public/services/api.ts @@ -147,10 +147,10 @@ export class ApiService implements GuidedOnboardingApi { }); const updatedGuide: GuideState = { + guideId, isActive: true, status: 'not_started', steps: updatedSteps, - guideId, }; return await this.updateGuideState(updatedGuide, true); diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts index 9dc7519a02019..82720c4f9d223 100644 --- a/src/plugins/guided_onboarding/public/services/helpers.test.ts +++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts @@ -6,51 +6,50 @@ * Side Public License, v 1. */ -import { guidesConfig } from '../constants/guides_config'; import { isIntegrationInGuideStep, isLastStep } from './helpers'; import { - noGuideActiveState, - securityAddDataInProgressState, - securityRulesActiveState, + testGuide, + testGuideFirstStep, + testGuideLastStep, + testGuideNotActiveState, + testGuideStep1InProgressState, + testGuideStep2InProgressState, + testIntegration, + wrongIntegration, } from './api.mocks'; -const searchGuide = 'search'; -const firstStep = guidesConfig[searchGuide].steps[0].id; -const lastStep = guidesConfig[searchGuide].steps[guidesConfig[searchGuide].steps.length - 1].id; - describe('GuidedOnboarding ApiService helpers', () => { - // this test suite depends on the guides config describe('isLastStepActive', () => { it('returns true if the passed params are for the last step', () => { - const result = isLastStep(searchGuide, lastStep); + const result = isLastStep(testGuide, testGuideLastStep); expect(result).toBe(true); }); it('returns false if the passed params are not for the last step', () => { - const result = isLastStep(searchGuide, firstStep); + const result = isLastStep(testGuide, testGuideFirstStep); expect(result).toBe(false); }); }); describe('isIntegrationInGuideStep', () => { it('return true if the integration is defined in the guide step config', () => { - const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'endpoint'); + const result = isIntegrationInGuideStep(testGuideStep1InProgressState, testIntegration); expect(result).toBe(true); }); it('returns false if a different integration is defined in the guide step', () => { - const result = isIntegrationInGuideStep(securityAddDataInProgressState, 'kubernetes'); + const result = isIntegrationInGuideStep(testGuideStep1InProgressState, wrongIntegration); expect(result).toBe(false); }); it('returns false if no integration is defined in the guide step', () => { - const result = isIntegrationInGuideStep(securityRulesActiveState, 'endpoint'); + const result = isIntegrationInGuideStep(testGuideStep2InProgressState, testIntegration); expect(result).toBe(false); }); it('returns false if no guide is active', () => { - const result = isIntegrationInGuideStep(noGuideActiveState, 'endpoint'); + const result = isIntegrationInGuideStep(testGuideNotActiveState, testIntegration); expect(result).toBe(false); }); it('returns false if no integration passed', () => { - const result = isIntegrationInGuideStep(securityAddDataInProgressState); + const result = isIntegrationInGuideStep(testGuideStep1InProgressState); expect(result).toBe(false); }); }); From 7bc63e07afb19760ee9a258cbc2f57d0aa5abaae Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Thu, 27 Oct 2022 15:20:43 +0200 Subject: [PATCH 04/28] Add context.alertDetailsUrl to connector template when configuring a Rule (#142854) --- x-pack/plugins/alerting/server/types.ts | 18 +-- .../lib/adapters/framework/adapter_types.ts | 8 +- .../server/lib/alerting/common/messages.ts | 10 +- .../infra/server/lib/alerting/common/utils.ts | 51 ++++++-- .../inventory_metric_threshold_executor.ts | 111 +++++++++++------- ...er_inventory_metric_threshold_rule_type.ts | 12 +- .../metric_threshold_executor.ts | 61 ++++++---- .../register_metric_threshold_rule_type.ts | 12 +- .../alerting/metric_threshold/test_mocks.ts | 4 - .../plugins/infra/server/lib/infra_types.ts | 10 +- x-pack/plugins/infra/server/plugin.ts | 1 + x-pack/plugins/observability/server/plugin.ts | 3 + .../server/utils/create_lifecycle_executor.ts | 38 ++++-- .../utils/create_lifecycle_rule_type.test.ts | 33 +++--- .../utils/lifecycle_alert_services.mock.ts | 1 + 15 files changed, 250 insertions(+), 123 deletions(-) diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index f1917a079a26d..9326f30dd7828 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -90,21 +90,21 @@ export interface RuleExecutorOptions< InstanceContext extends AlertInstanceContext = never, ActionGroupIds extends string = never > { - alertId: string; + alertId: string; // Is actually the Rule ID. Will be updated as part of https://github.com/elastic/kibana/issues/100115 + createdBy: string | null; executionId: string; - startedAt: Date; - previousStartedAt: Date | null; - services: RuleExecutorServices; + logger: Logger; + name: string; params: Params; - state: State; + previousStartedAt: Date | null; rule: SanitizedRuleConfig; + services: RuleExecutorServices; spaceId: string; - namespace?: string; - name: string; + startedAt: Date; + state: State; tags: string[]; - createdBy: string | null; updatedBy: string | null; - logger: Logger; + namespace?: string; } export interface RuleParamsAndRefs { diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts index 64d389a1c0bf7..55b847d33f87d 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/adapter_types.ts @@ -22,16 +22,18 @@ import { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import { PluginSetupContract as AlertingPluginContract } from '@kbn/alerting-plugin/server'; import { MlPluginSetup } from '@kbn/ml-plugin/server'; import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server'; +import { ObservabilityPluginSetup } from '@kbn/observability-plugin/server'; export interface InfraServerPluginSetupDeps { + alerting: AlertingPluginContract; data: DataPluginSetup; home: HomeServerPluginSetup; + features: FeaturesPluginSetup; + ruleRegistry: RuleRegistryPluginSetupContract; + observability: ObservabilityPluginSetup; spaces: SpacesPluginSetup; usageCollection: UsageCollectionSetup; visTypeTimeseries: VisTypeTimeseriesSetup; - features: FeaturesPluginSetup; - alerting: AlertingPluginContract; - ruleRegistry: RuleRegistryPluginSetupContract; ml?: MlPluginSetup; } diff --git a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts index 80dae7ffac959..644c31813deae 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/messages.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/messages.ts @@ -169,6 +169,14 @@ export const alertStateActionVariableDescription = i18n.translate( } ); +export const alertDetailUrlActionVariableDescription = i18n.translate( + 'xpack.infra.metrics.alerting.alertDetailUrlActionVariableDescription', + { + defaultMessage: + 'Link to the view within Elastic that shows further details and context surrounding this alert', + } +); + export const reasonActionVariableDescription = i18n.translate( 'xpack.infra.metrics.alerting.reasonActionVariableDescription', { @@ -211,7 +219,7 @@ export const viewInAppUrlActionVariableDescription = i18n.translate( 'xpack.infra.metrics.alerting.viewInAppUrlActionVariableDescription', { defaultMessage: - 'Link to the view or feature within Elastic that can be used to investigate the alert and its context further', + 'Link to the view or feature within Elastic that can assist with further investigation', } ); diff --git a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts index 2618af72168cd..ced80c75a3ef1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/infra/server/lib/alerting/common/utils.ts @@ -9,8 +9,11 @@ import { isEmpty, isError } from 'lodash'; import { schema } from '@kbn/config-schema'; import { Logger, LogMeta } from '@kbn/logging'; import type { IBasePath } from '@kbn/core/server'; +import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; +import { ObservabilityConfig } from '@kbn/observability-plugin/server'; import { ALERT_RULE_PARAMETERS, TIMESTAMP } from '@kbn/rule-data-utils'; import { parseTechnicalFields } from '@kbn/rule-registry-plugin/common/parse_technical_fields'; +import { LINK_TO_METRICS_EXPLORER } from '../../../../common/alerting/metrics'; import { getInventoryViewInAppUrl } from '../../../../common/alerting/metrics/alert_link'; import { AlertExecutionDetails, @@ -83,18 +86,30 @@ export const createScopedLogger = ( }; }; -export const getViewInAppUrl = (basePath: IBasePath, relativeViewInAppUrl: string) => - basePath.publicBaseUrl - ? new URL(basePath.prepend(relativeViewInAppUrl), basePath.publicBaseUrl).toString() - : relativeViewInAppUrl; +export const getAlertDetailsPageEnabledForApp = ( + config: ObservabilityConfig['unsafe']['alertDetails'] | null, + appName: keyof ObservabilityConfig['unsafe']['alertDetails'] +): boolean => { + if (!config) return false; -export const getViewInAppUrlInventory = ( - criteria: InventoryMetricConditions[], - nodeType: string, - timestamp: string, - basePath: IBasePath -) => { + return config[appName].enabled; +}; + +export const getViewInInventoryAppUrl = ({ + basePath, + criteria, + nodeType, + spaceId, + timestamp, +}: { + basePath: IBasePath; + criteria: InventoryMetricConditions[]; + nodeType: string; + spaceId: string; + timestamp: string; +}) => { const { metric, customMetric } = criteria[0]; + const fields = { [`${ALERT_RULE_PARAMETERS}.criteria.metric`]: [metric], [`${ALERT_RULE_PARAMETERS}.criteria.customMetric.id`]: [customMetric?.id], @@ -104,6 +119,18 @@ export const getViewInAppUrlInventory = ( [TIMESTAMP]: timestamp, }; - const relativeViewInAppUrl = getInventoryViewInAppUrl(parseTechnicalFields(fields, true)); - return getViewInAppUrl(basePath, relativeViewInAppUrl); + return addSpaceIdToPath( + basePath.publicBaseUrl, + spaceId, + getInventoryViewInAppUrl(parseTechnicalFields(fields, true)) + ); }; + +export const getViewInMetricsAppUrl = (basePath: IBasePath, spaceId: string) => + addSpaceIdToPath(basePath.publicBaseUrl, spaceId, LINK_TO_METRICS_EXPLORER); + +export const getAlertDetailsUrl = ( + basePath: IBasePath, + spaceId: string, + alertUuid: string | null +) => addSpaceIdToPath(basePath.publicBaseUrl, spaceId, `/app/observability/alerts/${alertUuid}`); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 2e51d2e8291b5..20b804a2cc7db 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -30,7 +30,12 @@ import { buildNoDataAlertReason, stateToAlertMessage, } from '../common/messages'; -import { createScopedLogger, getViewInAppUrlInventory } from '../common/utils'; +import { + createScopedLogger, + getAlertDetailsUrl, + getViewInInventoryAppUrl, + UNGROUPED_FACTORY_KEY, +} from '../common/utils'; import { evaluateCondition, ConditionResult } from './evaluate_condition'; type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf< @@ -61,12 +66,18 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = InventoryMetricThresholdAlertState, InventoryMetricThresholdAlertContext, InventoryMetricThresholdAllowedActionGroups - >(async ({ services, params, alertId, executionId, startedAt }) => { + >(async ({ services, params, alertId, executionId, spaceId, startedAt }) => { const startTime = Date.now(); + const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; + if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const logger = createScopedLogger(libs.logger, 'inventoryRule', { alertId, executionId }); - const { alertWithLifecycle, savedObjectsClient, getAlertStartedDate } = services; + + const esClient = services.scopedClusterClient.asCurrentUser; + + const { alertWithLifecycle, savedObjectsClient, getAlertStartedDate, getAlertUuid } = services; const alertFactory: InventoryMetricThresholdAlertFactory = (id, reason, additionalContext) => alertWithLifecycle({ id, @@ -85,23 +96,28 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = logger.error(e.message); const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); - const alert = alertFactory('*', reason); - const indexedStartedDate = getAlertStartedDate('*') ?? startedAt.toISOString(); - const viewInAppUrl = getViewInAppUrlInventory( - criteria, - nodeType, - indexedStartedDate, - libs.basePath - ); + const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); + const indexedStartedDate = + getAlertStartedDate(UNGROUPED_FACTORY_KEY) ?? startedAt.toISOString(); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + alert.scheduleActions(actionGroupId, { - group: '*', + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), reason, timestamp: startedAt.toISOString(), - viewInAppUrl, value: null, - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), }); + return {}; } } @@ -109,7 +125,7 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const [, , { logViews }] = await libs.getStartServices(); const logQueryFields: LogQueryFields | undefined = await logViews - .getClient(savedObjectsClient, services.scopedClusterClient.asCurrentUser) + .getClient(savedObjectsClient, esClient) .getResolvedLogView(sourceId) .then( ({ indices }) => ({ indexPattern: indices }), @@ -120,18 +136,19 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const results = await Promise.all( criteria.map((condition) => evaluateCondition({ - condition, - nodeType, - source, - logQueryFields, - esClient: services.scopedClusterClient.asCurrentUser, compositeSize, - filterQuery, + condition, + esClient, executionTimestamp: startedAt, + filterQuery, logger, + logQueryFields, + nodeType, + source, }) ) ); + let scheduledActionsCount = 0; const inventoryItems = Object.keys(first(results)!); for (const group of inventoryItems) { @@ -190,25 +207,28 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const alert = alertFactory(group, reason, additionalContext); const indexedStartedDate = getAlertStartedDate(group) ?? startedAt.toISOString(); - const viewInAppUrl = getViewInAppUrlInventory( - criteria, - nodeType, - indexedStartedDate, - libs.basePath - ); + const alertUuid = getAlertUuid(group); + scheduledActionsCount++; const context = { - group, + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[nextState], + group, reason, + metric: mapToConditionsLookup(criteria, (c) => c.metric), timestamp: startedAt.toISOString(), - viewInAppUrl, + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), value: mapToConditionsLookup(results, (result) => formatMetric(result[group].metric, result[group].currentValue) ), - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), ...additionalContext, }; alert.scheduleActions(actionGroupId, context); @@ -217,24 +237,27 @@ export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) = const { getRecoveredAlerts } = services.alertFactory.done(); const recoveredAlerts = getRecoveredAlerts(); + for (const alert of recoveredAlerts) { const recoveredAlertId = alert.getId(); const indexedStartedDate = getAlertStartedDate(recoveredAlertId) ?? startedAt.toISOString(); - const viewInAppUrl = getViewInAppUrlInventory( - criteria, - nodeType, - indexedStartedDate, - libs.basePath - ); - const context = { - group: recoveredAlertId, + const alertUuid = getAlertUuid(recoveredAlertId); + + alert.setContext({ + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.OK], - timestamp: startedAt.toISOString(), - viewInAppUrl, - threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + group: recoveredAlertId, metric: mapToConditionsLookup(criteria, (c) => c.metric), - }; - alert.setContext(context); + threshold: mapToConditionsLookup(criteria, (c) => c.threshold), + timestamp: startedAt.toISOString(), + viewInAppUrl: getViewInInventoryAppUrl({ + basePath: libs.basePath, + criteria, + nodeType, + timestamp: indexedStartedDate, + spaceId, + }), + }); } const stopTime = Date.now(); diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index b3b1f5ba21c65..030628f59ad38 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -24,6 +24,7 @@ import { } from '../../../../common/inventory_models/types'; import { InfraBackendLibs } from '../../infra_types'; import { + alertDetailUrlActionVariableDescription, alertStateActionVariableDescription, cloudActionVariableDescription, containerActionVariableDescription, @@ -39,7 +40,11 @@ import { valueActionVariableDescription, viewInAppUrlActionVariableDescription, } from '../common/messages'; -import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils'; +import { + getAlertDetailsPageEnabledForApp, + oneOfLiterals, + validateIsStringElasticsearchJSONFilter, +} from '../common/utils'; import { createInventoryMetricThresholdExecutor, FIRED_ACTIONS, @@ -72,6 +77,8 @@ export async function registerMetricInventoryThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs ) { + const config = libs.getAlertDetailsConfig(); + alertingPlugin.registerType({ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, name: i18n.translate('xpack.infra.metrics.inventory.alertName', { @@ -102,6 +109,9 @@ export async function registerMetricInventoryThresholdRuleType( context: [ { name: 'group', description: groupActionVariableDescription }, { name: 'alertState', description: alertStateActionVariableDescription }, + ...(getAlertDetailsPageEnabledForApp(config, 'metrics') + ? [{ name: 'alertDetailsUrl', description: alertDetailUrlActionVariableDescription }] + : []), { name: 'reason', description: reasonActionVariableDescription }, { name: 'timestamp', description: timestampActionVariableDescription }, { name: 'value', description: valueActionVariableDescription }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 14b5fe8e75614..200ad68aa81d1 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -26,8 +26,12 @@ import { // buildRecoveredAlertReason, stateToAlertMessage, } from '../common/messages'; -import { UNGROUPED_FACTORY_KEY, getViewInAppUrl, createScopedLogger } from '../common/utils'; -import { LINK_TO_METRICS_EXPLORER } from '../../../../common/alerting/metrics'; +import { + createScopedLogger, + getAlertDetailsUrl, + getViewInMetricsAppUrl, + UNGROUPED_FACTORY_KEY, +} from '../common/utils'; import { EvaluatedRuleParams, evaluateRule } from './lib/evaluate_rule'; @@ -67,11 +71,16 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => MetricThresholdAllowedActionGroups >(async function (options) { const startTime = Date.now(); - const { services, params, state, startedAt, alertId, executionId } = options; + + const { services, params, state, startedAt, alertId, executionId, spaceId } = options; + const { criteria } = params; if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions'); + const logger = createScopedLogger(libs.logger, 'metricThresholdRule', { alertId, executionId }); - const { alertWithLifecycle, savedObjectsClient } = services; + + const { alertWithLifecycle, savedObjectsClient, getAlertUuid } = services; + const alertFactory: MetricThresholdAlertFactory = (id, reason) => alertWithLifecycle({ id, @@ -100,15 +109,19 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const actionGroupId = FIRED_ACTIONS.id; // Change this to an Error action group when able const reason = buildInvalidQueryAlertReason(params.filterQueryText); const alert = alertFactory(UNGROUPED_FACTORY_KEY, reason); + const alertUuid = getAlertUuid(UNGROUPED_FACTORY_KEY); + alert.scheduleActions(actionGroupId, { - group: UNGROUPED_FACTORY_KEY, + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.ERROR], + group: UNGROUPED_FACTORY_KEY, + metric: mapToConditionsLookup(criteria, (c) => c.metric), reason, - viewInAppUrl: getViewInAppUrl(libs.basePath, LINK_TO_METRICS_EXPLORER), timestamp, value: null, - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), }); + return { lastRunTimestamp: startedAt.valueOf(), missingGroups: [], @@ -157,6 +170,7 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => const hasGroups = !isEqual(groups, [UNGROUPED_FACTORY_KEY]); let scheduledActionsCount = 0; + // The key of `groups` is the alert instance ID. for (const group of groups) { // AND logic; all criteria must be across the threshold const shouldAlertFire = alertResults.every((result) => result[group]?.shouldFire); @@ -227,40 +241,45 @@ export const createMetricThresholdExecutor = (libs: InfraBackendLibs) => ? WARNING_ACTIONS.id : FIRED_ACTIONS.id; const alert = alertFactory(`${group}`, reason); + const alertUuid = getAlertUuid(group); scheduledActionsCount++; + alert.scheduleActions(actionGroupId, { - group, + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[nextState], + group, + metric: mapToConditionsLookup(criteria, (c) => c.metric), reason, - viewInAppUrl: getViewInAppUrl(libs.basePath, LINK_TO_METRICS_EXPLORER), + threshold: mapToConditionsLookup( + alertResults, + (result) => formatAlertResult(result[group]).threshold + ), timestamp, value: mapToConditionsLookup( alertResults, (result) => formatAlertResult(result[group]).currentValue ), - threshold: mapToConditionsLookup( - alertResults, - (result) => formatAlertResult(result[group]).threshold - ), - metric: mapToConditionsLookup(criteria, (c) => c.metric), + viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), }); } } const { getRecoveredAlerts } = services.alertFactory.done(); const recoveredAlerts = getRecoveredAlerts(); + for (const alert of recoveredAlerts) { const recoveredAlertId = alert.getId(); - const viewInAppUrl = getViewInAppUrl(libs.basePath, LINK_TO_METRICS_EXPLORER); - const context = { - group: recoveredAlertId, + const alertUuid = getAlertUuid(recoveredAlertId); + + alert.setContext({ + alertDetailsUrl: getAlertDetailsUrl(libs.basePath, spaceId, alertUuid), alertState: stateToAlertMessage[AlertStates.OK], + group: recoveredAlertId, + metric: mapToConditionsLookup(criteria, (c) => c.metric), timestamp: startedAt.toISOString(), - viewInAppUrl, threshold: mapToConditionsLookup(criteria, (c) => c.threshold), - metric: mapToConditionsLookup(criteria, (c) => c.metric), - }; - alert.setContext(context); + viewInAppUrl: getViewInMetricsAppUrl(libs.basePath, spaceId), + }); } const stopTime = Date.now(); diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts index 0ebb427819f74..6538fb25b6c8c 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts @@ -13,6 +13,7 @@ import { Comparator, METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/a import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api'; import { InfraBackendLibs } from '../../infra_types'; import { + alertDetailUrlActionVariableDescription, alertStateActionVariableDescription, groupActionVariableDescription, metricActionVariableDescription, @@ -22,7 +23,11 @@ import { valueActionVariableDescription, viewInAppUrlActionVariableDescription, } from '../common/messages'; -import { oneOfLiterals, validateIsStringElasticsearchJSONFilter } from '../common/utils'; +import { + getAlertDetailsPageEnabledForApp, + oneOfLiterals, + validateIsStringElasticsearchJSONFilter, +} from '../common/utils'; import { createMetricThresholdExecutor, FIRED_ACTIONS, @@ -41,6 +46,8 @@ export async function registerMetricThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs ) { + const config = libs.getAlertDetailsConfig(); + const baseCriterion = { threshold: schema.arrayOf(schema.number()), comparator: oneOfLiterals(Object.values(Comparator)), @@ -93,6 +100,9 @@ export async function registerMetricThresholdRuleType( actionVariables: { context: [ { name: 'group', description: groupActionVariableDescription }, + ...(getAlertDetailsPageEnabledForApp(config, 'metrics') + ? [{ name: 'alertDetailsUrl', description: alertDetailUrlActionVariableDescription }] + : []), { name: 'alertState', description: alertStateActionVariableDescription }, { name: 'reason', description: reasonActionVariableDescription }, { name: 'timestamp', description: timestampActionVariableDescription }, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts index 37e59700b0488..3d3c7a17cd1dd 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts @@ -4,10 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import * as utils from '../common/utils'; -jest - .spyOn(utils, 'getViewInAppUrl') - .mockReturnValue('http://localhost:5601/eyg/app/metrics/explorer'); const bucketsA = (from: number) => [ { diff --git a/x-pack/plugins/infra/server/lib/infra_types.ts b/x-pack/plugins/infra/server/lib/infra_types.ts index a4636daa7986e..4801fb49651f6 100644 --- a/x-pack/plugins/infra/server/lib/infra_types.ts +++ b/x-pack/plugins/infra/server/lib/infra_types.ts @@ -8,6 +8,7 @@ import { Logger } from '@kbn/logging'; import type { IBasePath } from '@kbn/core/server'; import { handleEsError } from '@kbn/es-ui-shared-plugin/server'; +import { ObservabilityConfig } from '@kbn/observability-plugin/server'; import { RulesServiceSetup } from '../services/rules'; import { InfraConfig, InfraPluginStartServicesAccessor } from '../types'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; @@ -24,14 +25,15 @@ export interface InfraDomainLibs { } export interface InfraBackendLibs extends InfraDomainLibs { + basePath: IBasePath; configuration: InfraConfig; framework: KibanaFramework; - sources: InfraSources; - sourceStatus: InfraSourceStatus; - handleEsError: typeof handleEsError; logsRules: RulesServiceSetup; metricsRules: RulesServiceSetup; + sources: InfraSources; + sourceStatus: InfraSourceStatus; + getAlertDetailsConfig: () => ObservabilityConfig['unsafe']['alertDetails']; getStartServices: InfraPluginStartServicesAccessor; + handleEsError: typeof handleEsError; logger: Logger; - basePath: IBasePath; } diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index bfd7113ec4dc0..a7fa9ceacd3c9 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -170,6 +170,7 @@ export class InfraServerPlugin logsRules: this.logsRules.setup(core, plugins), metricsRules: this.metricsRules.setup(core, plugins), getStartServices: () => core.getStartServices(), + getAlertDetailsConfig: () => plugins.observability.getAlertDetailsConfig(), logger: this.logger, basePath: core.http.basePath, }; diff --git a/x-pack/plugins/observability/server/plugin.ts b/x-pack/plugins/observability/server/plugin.ts index ff5fd246bea1b..dd2a07f848db3 100644 --- a/x-pack/plugins/observability/server/plugin.ts +++ b/x-pack/plugins/observability/server/plugin.ts @@ -157,6 +157,9 @@ export class ObservabilityPlugin implements Plugin { }); return { + getAlertDetailsConfig() { + return config.unsafe.alertDetails; + }, getScopedAnnotationsClient: async (...args: Parameters) => { const api = await annotationsApiPromise; return api?.getScopedAnnotationsClient(...args); 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 160e06d03e92a..615201fe44d99 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 @@ -68,7 +68,8 @@ export interface LifecycleAlertServices< ActionGroupIds extends string = never > { alertWithLifecycle: LifecycleAlertService; - getAlertStartedDate: (alertId: string) => string | null; + getAlertStartedDate: (alertInstanceId: string) => string | null; + getAlertUuid: (alertInstanceId: string) => string | null; } export type LifecycleRuleExecutor< @@ -88,6 +89,12 @@ export type LifecycleRuleExecutor< > ) => Promise; +/* + `alertId` will at some point be renamed to `ruleId` as that more + accurately describes the meaning of the variable. + See https://github.com/elastic/kibana/issues/100115 +*/ + const trackedAlertStateRt = rt.type({ alertId: rt.string, alertUuid: rt.string, @@ -159,6 +166,8 @@ export const createLifecycleExecutor = const currentAlerts: Record = {}; + const newAlertUuids: Record = {}; + const lifecycleAlertServices: LifecycleAlertServices< InstanceState, InstanceContext, @@ -169,6 +178,15 @@ export const createLifecycleExecutor = return alertFactory.create(id); }, getAlertStartedDate: (alertId: string) => state.trackedAlerts[alertId]?.started ?? null, + getAlertUuid: (alertId: string) => { + if (!state.trackedAlerts[alertId]) { + const alertUuid = v4(); + newAlertUuids[alertId] = alertUuid; + return alertUuid; + } + + return state.trackedAlerts[alertId].alertUuid; + }, }; const nextWrappedState = await wrappedExecutor({ @@ -203,9 +221,9 @@ export const createLifecycleExecutor = commonRuleFields ); result.forEach((hit) => { - const alertId = hit._source ? hit._source[ALERT_INSTANCE_ID] : void 0; - if (alertId && hit._source) { - trackedAlertsDataMap[alertId] = { + const alertInstanceId = hit._source ? hit._source[ALERT_INSTANCE_ID] : void 0; + if (alertInstanceId && hit._source) { + trackedAlertsDataMap[alertInstanceId] = { indexName: hit._index, fields: hit._source, }; @@ -226,10 +244,12 @@ export const createLifecycleExecutor = const isRecovered = !currentAlerts[alertId]; const isActive = !isRecovered; - const { alertUuid, started } = state.trackedAlerts[alertId] ?? { - alertUuid: v4(), - started: commonRuleFields[TIMESTAMP], - }; + const { alertUuid, started } = !isNew + ? state.trackedAlerts[alertId] + : { + alertUuid: newAlertUuids[alertId] || v4(), + started: commonRuleFields[TIMESTAMP], + }; const event: ParsedTechnicalFields & ParsedExperimentalFields = { ...alertData?.fields, @@ -249,8 +269,8 @@ export const createLifecycleExecutor = [ALERT_WORKFLOW_STATUS]: alertData?.fields[ALERT_WORKFLOW_STATUS] ?? 'open', [EVENT_KIND]: 'signal', [EVENT_ACTION]: isNew ? 'open' : isActive ? 'active' : 'close', - [VERSION]: ruleDataClient.kibanaVersion, [TAGS]: options.tags, + [VERSION]: ruleDataClient.kibanaVersion, ...(isRecovered ? { [ALERT_END]: commonRuleFields[TIMESTAMP] } : {}), }; 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 f71c7391cec77..132eb096e0aaa 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 @@ -40,6 +40,11 @@ function createRule(shouldWriteAlerts: boolean = true) { name: 'warning', }, ], + actionVariables: { + context: [], + params: [], + state: [], + }, defaultActionGroupId: 'warning', executor: async ({ services }) => { nextAlerts.forEach((alert) => { @@ -48,17 +53,17 @@ function createRule(shouldWriteAlerts: boolean = true) { nextAlerts = []; }, id: 'ruleTypeId', - minimumLicenseRequired: 'basic', isExportable: true, + minimumLicenseRequired: 'basic', name: 'ruleTypeName', producer: 'producer', - actionVariables: { - context: [], - params: [], - state: [], - }, validate: { - params: schema.object({}, { unknowns: 'allow' }), + params: schema.object( + {}, + { + unknowns: 'allow', + } + ), }, }); @@ -92,10 +97,12 @@ function createRule(shouldWriteAlerts: boolean = true) { state = ((await type.executor({ alertId: 'alertId', createdBy: 'createdBy', + executionId: 'b33f65d7-6e8b-4aae-8d20-c93613dec9f9', + logger: loggerMock.create(), name: 'name', + namespace: 'namespace', params: {}, previousStartedAt, - startedAt, rule: { actions: [], consumer: 'consumer', @@ -118,20 +125,18 @@ function createRule(shouldWriteAlerts: boolean = true) { services: { alertFactory, savedObjectsClient: {} as any, - uiSettingsClient: {} as any, scopedClusterClient: {} as any, - shouldWriteAlerts: () => shouldWriteAlerts, - shouldStopExecution: () => false, search: {} as any, searchSourceClient: {} as ISearchStartSearchSource, + shouldStopExecution: () => false, + shouldWriteAlerts: () => shouldWriteAlerts, + uiSettingsClient: {} as any, }, spaceId: 'spaceId', + startedAt, state, tags: ['tags'], updatedBy: 'updatedBy', - namespace: 'namespace', - executionId: 'b33f65d7-6e8b-4aae-8d20-c93613dec9f9', - logger: loggerMock.create(), })) ?? {}) as Record; previousStartedAt = startedAt; diff --git a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts index 5465e7a7922c5..a383110394da7 100644 --- a/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts +++ b/x-pack/plugins/rule_registry/server/utils/lifecycle_alert_services.mock.ts @@ -36,4 +36,5 @@ export const createLifecycleAlertServicesMock = < ): LifecycleAlertServices => ({ alertWithLifecycle: ({ id }) => alertServices.alertFactory.create(id), getAlertStartedDate: jest.fn((id: string) => null), + getAlertUuid: jest.fn((id: string) => null), }); From 2efad9d15abecfc4a24f453af797125196c25c88 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 27 Oct 2022 15:32:11 +0200 Subject: [PATCH 05/28] [Lens][Unified Field list] Add functional tests to fields lists and summary popover (#143747) * :recycle: Add testId handlers * :white_check_mark: Add functional tests * :lipstick: Wrap unsupported messages with testId * :wrench: Enable creation of dataViews without timefield * :white_check_mark: Extends tests for other dataview types + runtime fields * :white_check_mark: Add more checks on top values charts * :ok_hand: Integrated feedback * :bug: Fix testIds and added some logging --- .../components/field_stats/field_stats.tsx | 8 +- .../datasources/form_based/field_item.tsx | 37 +-- .../form_based/fields_accordion.tsx | 8 +- .../text_based/fields_accordion.tsx | 8 +- .../apps/lens/group1/fields_list.ts | 233 ++++++++++++++++++ .../test/functional/apps/lens/group1/index.ts | 1 + .../test/functional/page_objects/lens_page.ts | 16 +- 7 files changed, 287 insertions(+), 24 deletions(-) create mode 100644 x-pack/test/functional/apps/lens/group1/fields_list.ts diff --git a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx index c70f1df820252..07d35b78b58a2 100755 --- a/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx +++ b/src/plugins/unified_field_list/public/components/field_stats/field_stats.tsx @@ -75,7 +75,7 @@ export interface FieldStatsProps { 'data-test-subj'?: string; overrideMissingContent?: (params: { element: JSX.Element; - noDataFound?: boolean; + reason: 'no-data' | 'unsupported'; }) => JSX.Element | null; overrideFooter?: (params: { element: JSX.Element; @@ -304,7 +304,7 @@ const FieldStatsComponent: React.FC = ({ return overrideMissingContent ? overrideMissingContent({ - noDataFound: false, + reason: 'unsupported', element: messageNoAnalysis, }) : messageNoAnalysis; @@ -338,7 +338,7 @@ const FieldStatsComponent: React.FC = ({ return overrideMissingContent ? overrideMissingContent({ - noDataFound: true, + reason: 'no-data', element: messageNoData, }) : messageNoData; @@ -358,12 +358,14 @@ const FieldStatsComponent: React.FC = ({ defaultMessage: 'Top values', }), id: 'topValues', + 'data-test-subj': `${dataTestSubject}-buttonGroup-topValuesButton`, }, { label: i18n.translate('unifiedFieldList.fieldStats.fieldDistributionLabel', { defaultMessage: 'Distribution', }), id: 'histogram', + 'data-test-subj': `${dataTestSubject}-buttonGroup-distributionButton`, }, ]} onChange={(optionId: string) => { diff --git a/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx b/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx index 5ebfecc4cc95a..e2ee0559b3808 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/field_item.tsx @@ -190,6 +190,9 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { initialFocus=".lnsFieldItem__fieldPanel" className="lnsFieldItem__popoverAnchor" data-test-subj="lnsFieldListPanelField" + panelProps={{ + 'data-test-subj': 'lnsFieldListPanelFieldContent', + }} container={document.querySelector('.application') || undefined} button={ { - if (params?.noDataFound) { + if (params.reason === 'no-data') { // TODO: should we replace this with a default message "Analysis is not available for this field?" const isUsingSampling = core.uiSettings.get('lens:useFieldExistenceSampling'); return ( - <> - - {isUsingSampling - ? i18n.translate('xpack.lens.indexPattern.fieldStatsSamplingNoData', { - defaultMessage: - 'Lens is unable to create visualizations with this field because it does not contain data in the first 500 documents that match your filters. To create a visualization, drag and drop a different field.', - }) - : i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { - defaultMessage: - 'Lens is unable to create visualizations with this field because it does not contain data. To create a visualization, drag and drop a different field.', - })} - - + + {isUsingSampling + ? i18n.translate('xpack.lens.indexPattern.fieldStatsSamplingNoData', { + defaultMessage: + 'Lens is unable to create visualizations with this field because it does not contain data in the first 500 documents that match your filters. To create a visualization, drag and drop a different field.', + }) + : i18n.translate('xpack.lens.indexPattern.fieldStatsNoData', { + defaultMessage: + 'Lens is unable to create visualizations with this field because it does not contain data. To create a visualization, drag and drop a different field.', + })} + + ); + } + if (params.reason === 'unsupported') { + return ( + + {params.element} + ); } - return params.element; }} /> diff --git a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx b/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx index 105c9583e300d..d6b4c73b51082 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/fields_accordion.tsx @@ -169,14 +169,18 @@ export const FieldsAccordion = memo(function InnerFieldsAccordion({ } if (hasLoaded) { return ( - + {fieldsCount} ); } return ; - }, [showExistenceFetchError, showExistenceFetchTimeout, hasLoaded, isFiltered, fieldsCount]); + }, [showExistenceFetchError, showExistenceFetchTimeout, hasLoaded, isFiltered, id, fieldsCount]); return ( { if (hasLoaded) { return ( - + {fields.length} ); } return ; - }, [fields.length, hasLoaded, isFiltered]); + }, [fields.length, hasLoaded, id, isFiltered]); return ( <> diff --git a/x-pack/test/functional/apps/lens/group1/fields_list.ts b/x-pack/test/functional/apps/lens/group1/fields_list.ts new file mode 100644 index 0000000000000..3d571483bf9ac --- /dev/null +++ b/x-pack/test/functional/apps/lens/group1/fields_list.ts @@ -0,0 +1,233 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'header']); + const find = getService('find'); + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const filterBar = getService('filterBar'); + const fieldEditor = getService('fieldEditor'); + const retry = getService('retry'); + + describe('lens fields list tests', () => { + for (const datasourceType of ['form-based', 'ad-hoc', 'ad-hoc-no-timefield']) { + describe(`${datasourceType} datasource`, () => { + before(async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + + if (datasourceType !== 'form-based') { + await PageObjects.lens.createAdHocDataView( + '*stash*', + datasourceType !== 'ad-hoc-no-timefield' + ); + retry.try(async () => { + const selectedPattern = await PageObjects.lens.getDataPanelIndexPattern(); + expect(selectedPattern).to.eql('*stash*'); + }); + } + + if (datasourceType !== 'ad-hoc-no-timefield') { + await PageObjects.lens.goToTimeRange(); + } + + await retry.try(async () => { + await PageObjects.lens.clickAddField(); + await fieldEditor.setName('runtime_string'); + await fieldEditor.enableValue(); + await fieldEditor.typeScript("emit('abc')"); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + }); + + it('should show all fields as available', async () => { + expect( + await (await testSubjects.find('lnsIndexPatternAvailableFields-count')).getVisibleText() + ).to.eql(53); + }); + + it('should show a histogram and top values popover for numeric field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('number'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket'); + expect(topValuesRows.length).to.eql(11); + // check for the Other entry + expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n96.7%'); + // switch to date histogram + await testSubjects.click('lnsFieldListPanel-buttonGroup-distributionButton'); + // check for date histogram chart + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(true); + }); + + it('should show a top values popover for a keyword field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket'); + expect(topValuesRows.length).to.eql(11); + // check for the Other entry + expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n99.9%'); + // check no date histogram + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(false); + }); + + it('should show a date histogram popover for a date field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('date'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for date histogram chart + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(true); + // check no top values chart + await testSubjects.missingOrFail('lnsFieldListPanel-buttonGroup-topValuesButton'); + }); + + it('should show a placeholder message about geo points field', async () => { + const [fieldId] = await PageObjects.lens.findFieldIdsByType('geo_point'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + const message = await testSubjects.getVisibleText('lnsFieldListPanel-missingFieldStats'); + expect(message).to.eql('Analysis is not available for this field.'); + }); + + it('should show stats for a numeric runtime field', async () => { + await PageObjects.lens.searchField('runtime'); + await PageObjects.lens.waitForField('runtime_number'); + const [fieldId] = await PageObjects.lens.findFieldIdsByType('number'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + // check values + const topValuesRows = await testSubjects.findAll('lnsFieldListPanel-topValues-bucket'); + expect(topValuesRows.length).to.eql(11); + // check for the Other entry + expect(await topValuesRows[10].getVisibleText()).to.eql('Other\n96.7%'); + // switch to date histogram + await testSubjects.click('lnsFieldListPanel-buttonGroup-distributionButton'); + // check for date histogram chart + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(true); + }); + + it('should show stats for a keyword runtime field', async () => { + await PageObjects.lens.searchField('runtime'); + await PageObjects.lens.waitForField('runtime_string'); + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + // check for popover + await testSubjects.exists('lnsFieldListPanel-title'); + // check for top values chart + await testSubjects.existOrFail('lnsFieldListPanel-topValues'); + // check no date histogram + expect( + await find.existsByCssSelector( + '[data-test-subj="lnsFieldListPanelFieldContent"] .echChart' + ) + ).to.eql(false); + await PageObjects.lens.searchField(''); + }); + + it('should change popover content if user defines a filter that affects field values', async () => { + // check the current records count for stats + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await testSubjects.click(fieldId); + const valuesCount = parseInt( + (await testSubjects.getVisibleText('lnsFieldListPanel-statsFooter')) + .replaceAll(/(Calculated from | records\.)/g, '') + .replace(',', ''), + 10 + ); + // define a filter + await filterBar.addFilter('geo.src', 'is', 'CN'); + await retry.waitFor('Wait for the filter to take effect', async () => { + await testSubjects.click(fieldId); + // check for top values chart has changed compared to the previous test + const newValuesCount = parseInt( + (await testSubjects.getVisibleText('lnsFieldListPanel-statsFooter')) + .replaceAll(/(Calculated from | records\.)/g, '') + .replace(',', ''), + 10 + ); + return newValuesCount < valuesCount; + }); + }); + + // One Fields cap's limitation is to not know when an index has no fields based on filters + it('should detect fields have no data in popup if filter excludes them', async () => { + await filterBar.removeAllFilters(); + await filterBar.addFilter('bytes', 'is', '-1'); + // check via popup fields have no data + const [fieldId] = await PageObjects.lens.findFieldIdsByType('string'); + await log.debug(`Opening field stats for ${fieldId}`); + await retry.try(async () => { + await testSubjects.click(fieldId); + expect(await testSubjects.find('lnsFieldListPanel-missingFieldStats')).to.be.ok(); + // close the popover + await testSubjects.click(fieldId); + }); + }); + + if (datasourceType !== 'ad-hoc-no-timefield') { + it('should move some fields as empty when the time range excludes them', async () => { + // remove the filter + await filterBar.removeAllFilters(); + // tweak the time range to 17 Sept 2015 to 18 Sept 2015 + await PageObjects.lens.goToTimeRange( + 'Sep 17, 2015 @ 06:31:44.000', + 'Sep 18, 2015 @ 06:31:44.000' + ); + // check all fields are empty now + expect( + await (await testSubjects.find('lnsIndexPatternEmptyFields-count')).getVisibleText() + ).to.eql(52); + // check avaialble count is 0 + expect( + await ( + await testSubjects.find('lnsIndexPatternAvailableFields-count') + ).getVisibleText() + ).to.eql(1); + }); + } + }); + } + }); +} diff --git a/x-pack/test/functional/apps/lens/group1/index.ts b/x-pack/test/functional/apps/lens/group1/index.ts index 47f08a59e7341..302289319adbf 100644 --- a/x-pack/test/functional/apps/lens/group1/index.ts +++ b/x-pack/test/functional/apps/lens/group1/index.ts @@ -79,6 +79,7 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./table_dashboard')); loadTestFile(require.resolve('./table')); loadTestFile(require.resolve('./text_based_languages')); + loadTestFile(require.resolve('./fields_list')); loadTestFile(require.resolve('./layer_actions')); } }); diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 8dd95aa107929..c814b5b161fcd 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -1335,9 +1335,9 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.click('indexPattern-add-field'); }, - async createAdHocDataView(name: string) { + async createAdHocDataView(name: string, hasTimeField?: boolean) { await testSubjects.click('lns-dataView-switch-link'); - await PageObjects.unifiedSearch.createNewDataView(name, true); + await PageObjects.unifiedSearch.createNewDataView(name, true, hasTimeField); }, async switchToTextBasedLanguage(language: string) { @@ -1638,5 +1638,17 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }) ); }, + + async findFieldIdsByType( + type: 'string' | 'number' | 'date' | 'geo_point' | 'ip_range', + group: 'available' | 'empty' | 'meta' = 'available' + ) { + const groupCapitalized = `${group[0].toUpperCase()}${group.slice(1).toLowerCase()}`; + const allFieldsForType = await find.allByCssSelector( + `[data-test-subj="lnsIndexPattern${groupCapitalized}Fields"] .lnsFieldItem--${type}` + ); + // map to testSubjId + return Promise.all(allFieldsForType.map((el) => el.getAttribute('data-test-subj'))); + }, }); } From eff4ce0cd531f832fc7019574e02281ccfac0a7d Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Thu, 27 Oct 2022 15:56:54 +0200 Subject: [PATCH 06/28] [Infrastructure UI] Add unified search to hosts table (#143850) * Add unified search to hosts table * Add saved query support * Adjust error handling * Minor refactoring and unit tests * Revert changes to translations * CR fixes --- .../hosts/components/hosts_container.tsx | 39 ++++++ .../metrics/hosts/components/hosts_table.tsx | 82 +++++++++++- .../hosts/components/unified_search_bar.tsx | 77 +++++++++++ .../metrics/hosts/hooks/use_data_view.test.ts | 85 +++++++++++++ .../metrics/hosts/hooks/use_data_view.ts | 34 ++++- .../metrics/hosts/hooks/use_unified_search.ts | 105 +++++++++++++++ .../pages/metrics/hosts/hosts_content.tsx | 120 ------------------ .../public/pages/metrics/hosts/index.tsx | 8 +- .../translations/translations/fr-FR.json | 14 +- .../translations/translations/ja-JP.json | 14 +- .../translations/translations/zh-CN.json | 14 +- 11 files changed, 435 insertions(+), 157 deletions(-) create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts create mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts delete mode 100644 x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx new file mode 100644 index 0000000000000..036d22d8b7c5f --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/hosts_container.tsx @@ -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 React from 'react'; +import { EuiSpacer } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { InfraLoadingPanel } from '../../../../components/loading'; +import { useMetricsDataViewContext } from '../hooks/use_data_view'; +import { UnifiedSearchBar } from './unified_search_bar'; +import { HostsTable } from './hosts_table'; + +export const HostContainer = () => { + const { metricsDataView, isDataViewLoading, hasFailedLoadingDataView } = + useMetricsDataViewContext(); + + if (isDataViewLoading) { + return ( + + ); + } + + return hasFailedLoadingDataView || !metricsDataView ? null : ( + <> + + + + + ); +}; 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 d045c594f0ee6..759c65ca84b2e 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 @@ -7,16 +7,86 @@ import React from 'react'; import { EuiInMemoryTable } from '@elastic/eui'; -import type { SnapshotNode } from '../../../../../common/http_api'; +import { i18n } from '@kbn/i18n'; import { HostsTableColumns } from './hosts_table_columns'; +import { NoData } from '../../../../components/empty_states'; +import { InfraLoadingPanel } from '../../../../components/loading'; import { useHostTable } from '../hooks/use_host_table'; +import { useSnapshot } from '../../inventory_view/hooks/use_snaphot'; +import type { SnapshotMetricType } from '../../../../../common/inventory_models/types'; +import type { InfraTimerangeInput } from '../../../../../common/http_api'; +import { useUnifiedSearchContext } from '../hooks/use_unified_search'; +import { useSourceContext } from '../../../../containers/metrics_source'; -interface Props { - nodes: SnapshotNode[]; -} +const HOST_METRICS: Array<{ type: SnapshotMetricType }> = [ + { type: 'rx' }, + { type: 'tx' }, + { type: 'memory' }, + { type: 'cpuCores' }, + { type: 'memoryTotal' }, +]; + +export const HostsTable = () => { + const { sourceId } = useSourceContext(); + const { esQuery, dateRangeTimestamp } = useUnifiedSearchContext(); + + const timeRange: InfraTimerangeInput = { + from: dateRangeTimestamp.from, + to: dateRangeTimestamp.to, + interval: '1m', + ignoreLookback: true, + }; + + // Snapshot endpoint internally uses the indices stored in source.configuration.metricAlias. + // For the Unified Search, we create a data view, which for now will be built off of source.configuration.metricAlias too + // if we introduce data view selection, we'll have to change this hook and the endpoint to accept a new parameter for the indices + const { loading, nodes, reload } = useSnapshot( + esQuery && JSON.stringify(esQuery), + HOST_METRICS, + [], + 'host', + sourceId, + dateRangeTimestamp.to, + '', + '', + true, + timeRange + ); -export const HostsTable: React.FunctionComponent = ({ nodes }) => { const items = useHostTable(nodes); + const noData = items.length === 0; - return ; + return ( + <> + {loading ? ( + + ) : noData ? ( +
+ { + reload(); + }} + testString="noMetricsDataPrompt" + /> +
+ ) : ( + + )} + + ); }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx new file mode 100644 index 0000000000000..ec9879579908e --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/components/unified_search_bar.tsx @@ -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 React from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import type { SavedQuery } from '@kbn/data-plugin/public'; +import type { InfraClientStartDeps } from '../../../../types'; +import { useUnifiedSearchContext } from '../hooks/use_unified_search'; + +interface Props { + dataView: DataView; +} + +export const UnifiedSearchBar = ({ dataView }: Props) => { + const { + services: { unifiedSearch }, + } = useKibana(); + const { + unifiedSearchDateRange, + unifiedSearchQuery, + submitFilterChange, + saveQuery, + clearSavedQUery, + } = useUnifiedSearchContext(); + + const { SearchBar } = unifiedSearch.ui; + + const onFilterChange = (filters: Filter[]) => { + onQueryChange({ filters }); + }; + + const onQuerySubmit = (payload: { dateRange: TimeRange; query?: Query }) => { + onQueryChange({ payload }); + }; + + const onClearSavedQuery = () => { + clearSavedQUery(); + }; + + const onQuerySave = (savedQuery: SavedQuery) => { + saveQuery(savedQuery); + }; + + const onQueryChange = ({ + payload, + filters, + }: { + payload?: { dateRange: TimeRange; query?: Query }; + filters?: Filter[]; + }) => { + submitFilterChange(payload?.query, payload?.dateRange, filters); + }; + + return ( + + ); +}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts new file mode 100644 index 0000000000000..2a2bb57b102ff --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.test.ts @@ -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 { useDataView } from './use_data_view'; +import { renderHook } from '@testing-library/react-hooks'; +import { KibanaReactContextValue, useKibana } from '@kbn/kibana-react-plugin/public'; +import { coreMock, notificationServiceMock } from '@kbn/core/public/mocks'; +import type { DataView } from '@kbn/data-views-plugin/public'; +import { DataViewsServicePublic } from '@kbn/data-views-plugin/public/types'; +import { InfraClientStartDeps } from '../../../../types'; +import { CoreStart } from '@kbn/core/public'; + +jest.mock('@kbn/i18n'); +jest.mock('@kbn/kibana-react-plugin/public'); + +let dataViewMock: jest.Mocked; +const useKibanaMock = useKibana as jest.MockedFunction; +const notificationMock = notificationServiceMock.createStartContract(); +const prop = { metricAlias: 'test' }; + +const mockUseKibana = () => { + useKibanaMock.mockReturnValue({ + services: { + ...coreMock.createStart(), + notifications: notificationMock, + dataViews: dataViewMock, + } as Partial & Partial, + } as unknown as KibanaReactContextValue & Partial>); +}; + +const mockDataView = { + id: 'mock-id', + title: 'mock-title', + timeFieldName: 'mock-time-field-name', + isPersisted: () => false, + getName: () => 'mock-data-view', + toSpec: () => ({}), +} as jest.Mocked; + +describe('useHostTable hook', () => { + beforeEach(() => { + dataViewMock = { + createAndSave: jest.fn(), + find: jest.fn(), + } as Partial as jest.Mocked; + + mockUseKibana(); + }); + + it('should find an existing Data view', async () => { + dataViewMock.find.mockReturnValue(Promise.resolve([mockDataView])); + const { result, waitForNextUpdate } = renderHook(() => useDataView(prop)); + + await waitForNextUpdate(); + expect(result.current.isDataViewLoading).toEqual(false); + expect(result.current.hasFailedLoadingDataView).toEqual(false); + expect(result.current.metricsDataView).toEqual(mockDataView); + }); + + it('should create a new Data view', async () => { + dataViewMock.find.mockReturnValue(Promise.resolve([])); + dataViewMock.createAndSave.mockReturnValue(Promise.resolve(mockDataView)); + const { result, waitForNextUpdate } = renderHook(() => useDataView(prop)); + + await waitForNextUpdate(); + expect(result.current.isDataViewLoading).toEqual(false); + expect(result.current.hasFailedLoadingDataView).toEqual(false); + expect(result.current.metricsDataView).toEqual(mockDataView); + }); + + it('should display a toast when it fails to load the data view', async () => { + dataViewMock.find.mockReturnValue(Promise.reject()); + const { result, waitForNextUpdate } = renderHook(() => useDataView(prop)); + + await waitForNextUpdate(); + expect(result.current.isDataViewLoading).toEqual(false); + expect(result.current.hasFailedLoadingDataView).toEqual(true); + expect(result.current.metricsDataView).toBeUndefined(); + expect(notificationMock.toasts.addDanger).toBeCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts index b60b2aa89db62..f927afa72890c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_data_view.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { useCallback, useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useCallback, useState, useEffect, useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import createContainer from 'constate'; import type { DataView } from '@kbn/data-views-plugin/public'; @@ -15,7 +16,7 @@ import { useTrackedPromise } from '../../../../utils/use_tracked_promise'; export const useDataView = ({ metricAlias }: { metricAlias: string }) => { const [metricsDataView, setMetricsDataView] = useState(); const { - services: { dataViews }, + services: { dataViews, notifications }, } = useKibana(); const [createDataViewRequest, createDataView] = useTrackedPromise( @@ -33,7 +34,7 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => { const [getDataViewRequest, getDataView] = useTrackedPromise( { - createPromise: (indexPattern: string): Promise => { + createPromise: (_indexPattern: string): Promise => { return dataViews.find(metricAlias, 1); }, onResolve: (response: DataView[]) => { @@ -58,17 +59,36 @@ export const useDataView = ({ metricAlias }: { metricAlias: string }) => { } }, [metricAlias, createDataView, getDataView]); - const hasFailedFetchingDataView = getDataViewRequest.state === 'rejected'; - const hasFailedCreatingDataView = createDataViewRequest.state === 'rejected'; + const isDataViewLoading = useMemo( + () => getDataViewRequest.state === 'pending' || createDataViewRequest.state === 'pending', + [getDataViewRequest.state, createDataViewRequest.state] + ); + + const hasFailedLoadingDataView = useMemo( + () => getDataViewRequest.state === 'rejected' || createDataViewRequest.state === 'rejected', + [getDataViewRequest.state, createDataViewRequest.state] + ); useEffect(() => { loadDataView(); }, [metricAlias, loadDataView]); + useEffect(() => { + if (hasFailedLoadingDataView && notifications) { + notifications.toasts.addDanger( + i18n.translate('xpack.infra.hostsTable.errorOnCreateOrLoadDataview', { + defaultMessage: + 'There was an error trying to load or create the Data View: {metricAlias}', + values: { metricAlias }, + }) + ); + } + }, [hasFailedLoadingDataView, notifications, metricAlias]); + return { metricsDataView, - hasFailedCreatingDataView, - hasFailedFetchingDataView, + isDataViewLoading, + hasFailedLoadingDataView, }; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts new file mode 100644 index 0000000000000..4b3d4e7a47df6 --- /dev/null +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -0,0 +1,105 @@ +/* + * Copyright 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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import createContainer from 'constate'; +import { useCallback, useReducer } from 'react'; +import { buildEsQuery, Filter, Query, TimeRange } from '@kbn/es-query'; +import DateMath from '@kbn/datemath'; +import type { SavedQuery } from '@kbn/data-plugin/public'; +import type { InfraClientStartDeps } from '../../../../types'; +import { useMetricsDataViewContext } from './use_data_view'; +import { useKibanaTimefilterTime } from '../../../../hooks/use_kibana_timefilter_time'; + +const DEFAULT_FROM_MINUTES_VALUE = 15; + +export const useUnifiedSearch = () => { + const [, forceUpdate] = useReducer((x: number) => x + 1, 0); + + const { metricsDataView } = useMetricsDataViewContext(); + const { services } = useKibana(); + const { + data: { query: queryManager }, + } = services; + + const [getTime, setTime] = useKibanaTimefilterTime({ + from: `now-${DEFAULT_FROM_MINUTES_VALUE}m`, + to: 'now', + }); + const { queryString, filterManager } = queryManager; + + const currentDate = new Date(); + const fromTS = + DateMath.parse(getTime().from)?.valueOf() ?? + new Date(currentDate.getMinutes() - DEFAULT_FROM_MINUTES_VALUE).getTime(); + const toTS = DateMath.parse(getTime().to)?.valueOf() ?? currentDate.getTime(); + + const currentTimeRange = { + from: fromTS, + to: toTS, + }; + + const submitFilterChange = useCallback( + (query?: Query, dateRange?: TimeRange, filters?: Filter[]) => { + if (filters) { + filterManager.setFilters(filters); + } + + setTime({ + ...getTime(), + ...dateRange, + }); + + queryString.setQuery({ ...queryString.getQuery(), ...query }); + // Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values + // This can be removed once we get the state from the URL + forceUpdate(); + }, + [filterManager, queryString, getTime, setTime] + ); + + const saveQuery = useCallback( + (newSavedQuery: SavedQuery) => { + const savedQueryFilters = newSavedQuery.attributes.filters ?? []; + const globalFilters = filterManager.getGlobalFilters(); + filterManager.setFilters([...savedQueryFilters, ...globalFilters]); + + // Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values + // This can be removed once we get the state from the URL + forceUpdate(); + }, + [filterManager] + ); + + const clearSavedQUery = useCallback(() => { + filterManager.setFilters(filterManager.getGlobalFilters()); + + // Unified search holds the all state, we need to force the hook to rerender so that it can return the most recent values + // This can be removed once we get the state from the URL + forceUpdate(); + }, [filterManager]); + + const buildQuery = useCallback(() => { + if (!metricsDataView) { + return null; + } + return buildEsQuery(metricsDataView, queryString.getQuery(), filterManager.getFilters()); + }, [filterManager, metricsDataView, queryString]); + + return { + dateRangeTimestamp: currentTimeRange, + esQuery: buildQuery(), + submitFilterChange, + saveQuery, + clearSavedQUery, + unifiedSearchQuery: queryString.getQuery() as Query, + unifiedSearchDateRange: getTime(), + unifiedSearchFilters: filterManager.getFilters(), + }; +}; + +export const UnifiedSearch = createContainer(useUnifiedSearch); +export const [UnifiedSearchProvider, useUnifiedSearchContext] = UnifiedSearch; 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 deleted file mode 100644 index 5ab4a062d7fc9..0000000000000 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/hosts_content.tsx +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { Query, TimeRange } from '@kbn/es-query'; -import { i18n } from '@kbn/i18n'; -import React, { useState, useCallback } from 'react'; -import { SearchBar } from '@kbn/unified-search-plugin/public'; -import { EuiSpacer } from '@elastic/eui'; -import { NoData } from '../../../components/empty_states'; -import { InfraLoadingPanel } from '../../../components/loading'; -import { useMetricsDataViewContext } from './hooks/use_data_view'; -import { HostsTable } from './components/hosts_table'; -import { useSourceContext } from '../../../containers/metrics_source'; -import { useSnapshot } from '../inventory_view/hooks/use_snaphot'; -import type { SnapshotMetricType } from '../../../../common/inventory_models/types'; - -export const HostsContent: React.FunctionComponent = () => { - const { source, sourceId } = useSourceContext(); - const [dateRange, setDateRange] = useState({ from: 'now-15m', to: 'now' }); - const [query, setQuery] = useState({ query: '', language: 'kuery' }); - const { metricsDataView, hasFailedCreatingDataView, hasFailedFetchingDataView } = - useMetricsDataViewContext(); - // needed to refresh the lens table when filters havent changed - - const onQuerySubmit = useCallback( - (payload: { dateRange: TimeRange; query?: Query }) => { - setDateRange(payload.dateRange); - if (payload.query) { - setQuery(payload.query); - } - }, - [setDateRange, setQuery] - ); - - const hostMetrics: Array<{ type: SnapshotMetricType }> = [ - { type: 'rx' }, - { type: 'tx' }, - { type: 'memory' }, - { type: 'cpuCores' }, - { type: 'memoryTotal' }, - ]; - - const { loading, nodes, reload } = useSnapshot( - '', // use the unified search query, supported type? - hostMetrics, - [], - 'host', - sourceId, - 1666710279338, // currentTime. need to add support for TimeRange? - '', - '', - true, - { - from: 1666710279338, // dynamic time range needs to be supported - interval: '1m', - lookbackSize: 5, - to: 1666711479338, - } - ); - - const noData = !loading && nodes && nodes.length === 0; - - return ( -
- {metricsDataView && !loading ? ( - noData ? ( - { - reload(); - }} - testString="noMetricsDataPrompt" - /> - ) : ( - <> - - - - - ) - ) : hasFailedCreatingDataView || hasFailedFetchingDataView ? ( -
-
There was an error trying to load or create the Data View:
- {source?.configuration.metricAlias} -
- ) : ( - - )} -
- ); -}; diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx b/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx index a5dfd7f2ddd0f..3321be0af193c 100644 --- a/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/hosts/index.tsx @@ -9,16 +9,16 @@ import { EuiErrorBoundary } from '@elastic/eui'; import React from 'react'; import { useTrackPageview } from '@kbn/observability-plugin/public'; import { APP_WRAPPER_CLASS } from '@kbn/core/public'; - import { SourceErrorPage } from '../../../components/source_error_page'; import { SourceLoadingPage } from '../../../components/source_loading_page'; import { useSourceContext } from '../../../containers/metrics_source'; import { useMetricsBreadcrumbs } from '../../../hooks/use_metrics_breadcrumbs'; import { MetricsPageTemplate } from '../page_template'; import { hostsTitle } from '../../../translations'; -import { HostsContent } from './hosts_content'; import { MetricsDataViewProvider } from './hooks/use_data_view'; import { fullHeightContentStyles } from '../../../page_template.styles'; +import { UnifiedSearchProvider } from './hooks/use_unified_search'; +import { HostContainer } from './components/hosts_container'; export const HostsPage = () => { const { @@ -56,7 +56,9 @@ export const HostsPage = () => { }} > - + + + diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 7beb42271c73f..3ebe3a63523d2 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -15595,15 +15595,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "Voir les instructions de configuration", "xpack.infra.homePage.settingsTabTitle": "Paramètres", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "Rechercher des données d'infrastructure… (par exemple host.name:host-1)", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "Total de la mémoire (moy.)", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "Utilisation de la mémoire (moy.)", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", "xpack.infra.hostsTable.nameColumnHeader": "Nom", - "xpack.infra.hostsTable.operatingSystemColumnHeader": "Système d'exploitation", "xpack.infra.hostsTable.numberOfCpusColumnHeader": "Nombre de processeurs", - "xpack.infra.hostsTable.diskLatencyColumnHeader": "", - "xpack.infra.hostsTable.averageTxColumnHeader": "", - "xpack.infra.hostsTable.averageRxColumnHeader": "", - "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "Total de la mémoire (moy.)", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "Système d'exploitation", "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", - "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "Utilisation de la mémoire (moy.)", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "Créer une règle d'inventaire", "xpack.infra.infra.nodeDetails.openAsPage": "Ouvrir en tant que page", @@ -33705,4 +33705,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "Présentation" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fcc2c1c49cdf8..a74277d17862a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15580,15 +15580,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "セットアップの手順を表示", "xpack.infra.homePage.settingsTabTitle": "設定", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "インフラストラクチャデータを検索…(例:host.name:host-1)", - "xpack.infra.hostsTable.nameColumnHeader": "名前", - "xpack.infra.hostsTable.operatingSystemColumnHeader": "オペレーティングシステム", - "xpack.infra.hostsTable.numberOfCpusColumnHeader": "CPU数", - "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "メモリ合計 (平均) ", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "メモリー使用状況(平均)", "xpack.infra.hostsTable.averageTxColumnHeader": "", "xpack.infra.hostsTable.averageRxColumnHeader": "", - "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "メモリ合計 (平均) ", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", + "xpack.infra.hostsTable.nameColumnHeader": "名前", + "xpack.infra.hostsTable.numberOfCpusColumnHeader": "CPU数", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "オペレーティングシステム", "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", - "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "メモリー使用状況(平均)", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "インベントリルールの作成", "xpack.infra.infra.nodeDetails.openAsPage": "ページとして開く", @@ -33679,4 +33679,4 @@ "xpack.painlessLab.title": "Painless Lab", "xpack.painlessLab.walkthroughButtonLabel": "実地検証" } -} +} \ No newline at end of file diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index ff49a2cd73d6c..6127534e13b93 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15601,15 +15601,15 @@ "xpack.infra.homePage.noMetricsIndicesInstructionsActionLabel": "查看设置说明", "xpack.infra.homePage.settingsTabTitle": "设置", "xpack.infra.homePage.toolbar.kqlSearchFieldPlaceholder": "搜索基础设施数据……(例如 host.name:host-1)", + "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "内存合计 (平均值)", + "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "内存使用率(平均值)", + "xpack.infra.hostsTable.averageRxColumnHeader": "", + "xpack.infra.hostsTable.averageTxColumnHeader": "", + "xpack.infra.hostsTable.diskLatencyColumnHeader": "", "xpack.infra.hostsTable.nameColumnHeader": "名称", - "xpack.infra.hostsTable.operatingSystemColumnHeader": "操作系统", "xpack.infra.hostsTable.numberOfCpusColumnHeader": "# 个 CPU", - "xpack.infra.hostsTable.diskLatencyColumnHeader": "", - "xpack.infra.hostsTable.averageTxColumnHeader": "", - "xpack.infra.hostsTable.averageRxColumnHeader": "", - "xpack.infra.hostsTable.averageMemoryTotalColumnHeader": "内存合计 (平均值)", + "xpack.infra.hostsTable.operatingSystemColumnHeader": "操作系统", "xpack.infra.hostsTable.servicesOnHostColumnHeader": "", - "xpack.infra.hostsTable.averageMemoryUsageColumnHeader": "内存使用率(平均值)", "xpack.infra.infra.nodeDetails.apmTabLabel": "APM", "xpack.infra.infra.nodeDetails.createAlertLink": "创建库存规则", "xpack.infra.infra.nodeDetails.openAsPage": "以页面形式打开", @@ -33716,4 +33716,4 @@ "xpack.painlessLab.title": "Painless 实验室", "xpack.painlessLab.walkthroughButtonLabel": "指导" } -} +} \ No newline at end of file From 028fa94e2cbe793da9520cf0eaaa28c6a9142b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Thu, 27 Oct 2022 16:11:30 +0200 Subject: [PATCH 07/28] Adds RBAC API checks for event filters (#144009) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../validators/event_filter_validator.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts index 7759caa20e1f9..2ff4a663560b7 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts @@ -48,8 +48,16 @@ export class EventFilterValidator extends BaseValidator { return item.listId === ENDPOINT_EVENT_FILTERS_LIST_ID; } + protected async validateHasWritePrivilege(): Promise { + return super.validateHasPrivilege('canWriteEventFilters'); + } + + protected async validateHasReadPrivilege(): Promise { + return super.validateHasPrivilege('canReadEventFilters'); + } + async validatePreCreateItem(item: CreateExceptionListItemOptions) { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); await this.validateEventFilterData(item); // user can always create a global entry so additional checks not needed @@ -67,7 +75,7 @@ export class EventFilterValidator extends BaseValidator { ): Promise { const updatedItem = _updatedItem as ExceptionItemLikeOptions; - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); await this.validateEventFilterData(updatedItem); try { @@ -96,27 +104,27 @@ export class EventFilterValidator extends BaseValidator { } async validatePreGetOneItem(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreSummary(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreDeleteItem(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreExport(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreSingleListFind(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreMultiListFind(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreImport(): Promise { From 83e85bd9607ad4506d7b5dc8a13ae0224c70d8db Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Thu, 27 Oct 2022 16:16:57 +0200 Subject: [PATCH 08/28] [Lens] Revisit Random sampling UI (#143929) * :lipstick: Revisit settings ui * Design suggestions (#18) Co-authored-by: Michael Marcialis --- .../datasources/form_based/layer_settings.tsx | 120 +++++++++++++----- 1 file changed, 86 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx index 7d02ac98f23a4..ec161ef996737 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx @@ -5,9 +5,20 @@ * 2.0. */ -import { EuiFormRow, EuiRange, EuiBetaBadge } from '@elastic/eui'; +import { + EuiFormRow, + EuiRange, + EuiFlexGroup, + EuiFlexItem, + EuiBetaBadge, + EuiText, + EuiLink, + EuiSpacer, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { css } from '@emotion/react'; import React from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; import type { DatasourceLayerSettingsProps } from '../../types'; import type { FormBasedPrivateState } from './types'; @@ -22,54 +33,95 @@ export function LayerSettingsPanel({ const currentSamplingIndex = samplingIndex > -1 ? samplingIndex : samplingValue.length - 1; return ( + +

+ + + + ), + }} + /> +

+ + } label={ <> {i18n.translate('xpack.lens.xyChart.randomSampling.label', { - defaultMessage: 'Sampling', + defaultMessage: 'Random sampling', })}{' '} } > - { - setState({ - ...state, - layers: { - ...state.layers, - [layerId]: { - ...state.layers[layerId], - sampling: samplingValue[Number(e.currentTarget.value)], - }, - }, - }); - }} - showInput={false} - showRange={false} - showTicks - step={1} - min={0} - max={samplingValue.length - 1} - ticks={samplingValue.map((v, i) => ({ label: `${v}`, value: i }))} - /> + + + + + + + + { + setState({ + ...state, + layers: { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + sampling: samplingValue[Number(e.currentTarget.value)], + }, + }, + }); + }} + showInput={false} + showRange={false} + showTicks + step={1} + min={0} + max={samplingValue.length - 1} + ticks={samplingValue.map((v, i) => ({ label: `${v * 100}%`, value: i }))} + /> + + + + + + +
); } From b1fb85bf0846fbbaa36b7587c487f4092a660fa7 Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Thu, 27 Oct 2022 17:30:51 +0300 Subject: [PATCH 09/28] fix (#144099) --- .../kbn-apm-config-loader/src/apm_config.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/kbn-apm-config-loader/src/apm_config.ts b/packages/kbn-apm-config-loader/src/apm_config.ts index 0e7b1b9546288..2127d612d583b 100644 --- a/packages/kbn-apm-config-loader/src/apm_config.ts +++ b/packages/kbn-apm-config-loader/src/apm_config.ts @@ -8,14 +8,12 @@ import { schema } from '@kbn/config-schema'; -export const apmConfigSchema = schema.object({ - apm: schema.object( - { - active: schema.maybe(schema.boolean()), - serverUrl: schema.maybe(schema.uri()), - secretToken: schema.maybe(schema.string()), - globalLabels: schema.object({}, { unknowns: 'allow' }), - }, - { unknowns: 'allow' } - ), -}); +export const apmConfigSchema = schema.object( + { + active: schema.maybe(schema.boolean()), + serverUrl: schema.maybe(schema.uri()), + secretToken: schema.maybe(schema.string()), + globalLabels: schema.object({}, { unknowns: 'allow' }), + }, + { unknowns: 'allow' } +); From cb306dfa073216b73ba2718a70442b20dde3c998 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Thu, 27 Oct 2022 17:39:00 +0300 Subject: [PATCH 10/28] Added support of saved search (#144095) --- .../components/visualize_byvalue_editor.tsx | 1 + .../components/visualize_editor.tsx | 1 + .../components/visualize_editor_common.tsx | 4 ++++ .../components/visualize_top_nav.tsx | 7 ++++++- .../utils/get_top_nav_config.tsx | 7 +++++++ .../utils/use/use_linked_search_updates.ts | 20 ++++++++++--------- 6 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx index b9ff8d98f2ced..8cc220e77c8bc 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_byvalue_editor.tsx @@ -110,6 +110,7 @@ export const VisualizeByValueEditor = ({ onAppLeave }: VisualizeAppProps) => { visEditorRef={visEditorRef} embeddableId={embeddableId} onAppLeave={onAppLeave} + eventEmitter={eventEmitter} /> ); }; diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx index 480f0c3d36ee1..221cdcc9d8e10 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor.tsx @@ -110,6 +110,7 @@ export const VisualizeEditor = ({ onAppLeave }: VisualizeAppProps) => { visEditorRef={visEditorRef} onAppLeave={onAppLeave} embeddableId={embeddableIdValue} + eventEmitter={eventEmitter} /> ); }; diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx index 4598d2d23e613..7fa6418aa261b 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_editor_common.tsx @@ -7,6 +7,7 @@ */ import './visualize_editor.scss'; +import { EventEmitter } from 'events'; import React, { RefObject, useCallback, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; @@ -48,6 +49,7 @@ interface VisualizeEditorCommonProps { originatingPath?: string; visualizationIdFromUrl?: string; embeddableId?: string; + eventEmitter?: EventEmitter; } export const VisualizeEditorCommon = ({ @@ -66,6 +68,7 @@ export const VisualizeEditorCommon = ({ visualizationIdFromUrl, embeddableId, visEditorRef, + eventEmitter, }: VisualizeEditorCommonProps) => { const { services } = useKibana(); @@ -148,6 +151,7 @@ export const VisualizeEditorCommon = ({ visualizationIdFromUrl={visualizationIdFromUrl} embeddableId={embeddableId} onAppLeave={onAppLeave} + eventEmitter={eventEmitter} /> )} {visInstance?.vis?.type?.stage === 'experimental' && diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx index 0111c9026397d..2deffa0c511b3 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx @@ -7,7 +7,7 @@ */ import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; - +import { EventEmitter } from 'events'; import { AppMountParameters, OverlayRef } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import useLocalStorage from 'react-use/lib/useLocalStorage'; @@ -40,6 +40,7 @@ interface VisualizeTopNavProps { visualizationIdFromUrl?: string; embeddableId?: string; onAppLeave: AppMountParameters['onAppLeave']; + eventEmitter?: EventEmitter; } const TopNav = ({ @@ -57,6 +58,7 @@ const TopNav = ({ visualizationIdFromUrl, embeddableId, onAppLeave, + eventEmitter, }: VisualizeTopNavProps) => { const { services } = useKibana(); const { TopNavMenu } = services.navigation.ui; @@ -116,6 +118,7 @@ const TopNav = ({ uiStateJSON?.vis, uiStateJSON?.table, vis.data.indexPattern, + eventEmitter, ]); const displayEditInLensItem = Boolean(vis.type.navigateToLens && editInLensConfig); @@ -140,6 +143,7 @@ const TopNav = ({ hideLensBadge, setNavigateToLens, showBadge: !hideTryInLensBadge && displayEditInLensItem, + eventEmitter, }, services ); @@ -162,6 +166,7 @@ const TopNav = ({ displayEditInLensItem, hideLensBadge, hideTryInLensBadge, + eventEmitter, ]); const [indexPatterns, setIndexPatterns] = useState([]); const showDatePicker = () => { diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index 36b92585f1096..cab3d41ff8266 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -8,6 +8,7 @@ import React from 'react'; import moment from 'moment'; +import EventEmitter from 'events'; import { i18n } from '@kbn/i18n'; import { EuiBetaBadgeProps } from '@elastic/eui'; import { parse } from 'query-string'; @@ -71,6 +72,7 @@ export interface TopNavConfigParams { hideLensBadge: () => void; setNavigateToLens: (flag: boolean) => void; showBadge: boolean; + eventEmitter?: EventEmitter; } const SavedObjectSaveModalDashboard = withSuspense(LazySavedObjectSaveModalDashboard); @@ -102,6 +104,7 @@ export const getTopNavConfig = ( hideLensBadge, setNavigateToLens, showBadge, + eventEmitter, }: TopNavConfigParams, { data, @@ -301,6 +304,10 @@ export const getTopNavConfig = ( }, }), run: async () => { + // lens doesn't support saved searches, should unlink before transition + if (eventEmitter && visInstance.vis.data.savedSearchId) { + eventEmitter.emit('unlinkFromSavedSearch', false); + } const updatedWithMeta = { ...editInLensConfig, savedObjectId: visInstance.vis.id, diff --git a/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts b/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts index 8d7f2a8ef61f4..ffd23ec06aea6 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/use/use_linked_search_updates.ts @@ -29,7 +29,7 @@ export const useLinkedSearchUpdates = ( // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { searchSource } = visInstance.vis.data; - const unlinkFromSavedSearch = () => { + const unlinkFromSavedSearch = (showToast: boolean = true) => { const searchSourceParent = savedSearch.searchSource; const searchSourceGrandparent = searchSourceParent?.getParent(); const currentIndex = searchSourceParent?.getField('index'); @@ -44,14 +44,16 @@ export const useLinkedSearchUpdates = ( parentFilters: (searchSourceParent?.getOwnField('filter') as Filter[]) || [], }); - services.toastNotifications.addSuccess( - i18n.translate('visualizations.linkedToSearch.unlinkSuccessNotificationText', { - defaultMessage: `Unlinked from saved search '{searchTitle}'`, - values: { - searchTitle: savedSearch.title, - }, - }) - ); + if (showToast) { + services.toastNotifications.addSuccess( + i18n.translate('visualizations.linkedToSearch.unlinkSuccessNotificationText', { + defaultMessage: `Unlinked from saved search '{searchTitle}'`, + values: { + searchTitle: savedSearch.title, + }, + }) + ); + } }; eventEmitter.on('unlinkFromSavedSearch', unlinkFromSavedSearch); From 9ae38803d8ce559db2bd47400ec083a8ff138cd0 Mon Sep 17 00:00:00 2001 From: Yaroslav Kuznietsov Date: Thu, 27 Oct 2022 18:00:09 +0300 Subject: [PATCH 11/28] [Lens][Agg based Heatmap] Navigate to Lens Agg based Heatmap. (#143820) * Added base code for converting heatmap to lens. * Added navigateToLens to visType. * Added canNavigateToLens event. * Fixed type. * Added basic support of heatmap converting to lens. * Added visType as arg. * Added validation according to the * Fixed heatmap std_dev. * Fixed failing xy. * Fixed tests. * Added config for legend. * Added support of converting color ranges. * Fixed palette for default ranges. * Refactored. * Added tests for convertToLens. * Added tests for getConfiguration. * Fixed problem * Added basic functional tests for heatmap. * Added functional test for not convertable case. * Added tests for not valid config and fixed one with valid. * Added test for custom ranges. * Added empty filters if x-axis is not defined. - Added empty filters if y-axis is defined, but x-axis is not and if no x/y-axis was defined. - Added/fixed tests. * Removed unused service. * Histogram problems fixed. * Fixed include/exclude regexp. * Fixed terms. --- .../heatmap_function.test.ts.snap | 1 + .../expression_functions/heatmap_function.ts | 1 + .../common/types/expression_functions.ts | 1 + .../expression_renderers/heatmap_renderer.tsx | 9 +- src/plugins/vis_types/heatmap/kibana.json | 39 +- .../configurations/index.test.ts | 114 +++++ .../convert_to_lens/configurations/index.ts | 54 +++ .../convert_to_lens/configurations/palette.ts | 52 +++ .../public/convert_to_lens/index.test.ts | 166 +++++++ .../heatmap/public/convert_to_lens/index.ts | 97 ++++ .../heatmap/public/convert_to_lens/types.ts | 17 + .../vis_types/heatmap/public/plugin.ts | 13 +- .../heatmap/public/sample_vis.test.mocks.ts | 11 +- .../vis_types/heatmap/public/services.ts | 13 + .../vis_types/heatmap/public/to_ast.test.ts | 4 +- .../vis_types/heatmap/public/utils/palette.ts | 9 +- .../heatmap/public/vis_type/heatmap.tsx | 7 + src/plugins/vis_types/pie/kibana.json | 39 +- .../table/public/convert_to_lens/index.ts | 1 + .../convert_to_lens/lib/buckets/index.test.ts | 20 +- .../convert_to_lens/lib/buckets/index.ts | 12 +- .../lib/configurations/index.ts | 2 +- .../lib/configurations/palette.ts | 25 +- .../convert_to_lens/lib/convert/formula.ts | 1 + .../lib/convert/last_value.test.ts | 16 +- .../convert_to_lens/lib/convert/last_value.ts | 8 +- .../lib/convert/metric.test.ts | 5 + .../convert_to_lens/lib/convert/metric.ts | 4 +- .../lib/convert/parent_pipeline.test.ts | 17 + .../lib/convert/parent_pipeline.ts | 12 +- .../lib/convert/percentage_mode.test.ts | 7 +- .../lib/convert/percentile.test.ts | 27 +- .../convert_to_lens/lib/convert/percentile.ts | 3 +- .../lib/convert/percentile_rank.test.ts | 29 +- .../lib/convert/percentile_rank.ts | 3 +- .../convert_to_lens/lib/convert/range.test.ts | 1 - .../convert_to_lens/lib/convert/range.ts | 13 +- .../lib/convert/sibling_pipeline.test.ts | 13 +- .../lib/convert/sibling_pipeline.ts | 11 +- .../lib/convert/std_deviation.test.ts | 44 +- .../lib/convert/std_deviation.ts | 4 +- .../lib/convert/supported_metrics.ts | 68 +-- .../convert_to_lens/lib/convert/terms.test.ts | 14 + .../convert_to_lens/lib/convert/terms.ts | 48 +- .../convert_to_lens/lib/convert/types.ts | 2 + .../lib/metrics/formula.test.ts | 29 +- .../convert_to_lens/lib/metrics/formula.ts | 28 +- .../lib/metrics/metrics.test.ts | 438 ++++++++++++------ .../convert_to_lens/lib/metrics/metrics.ts | 20 +- .../lib/metrics/percentage_formula.test.ts | 7 +- .../lib/metrics/percentage_formula.ts | 3 +- .../convert_to_lens/types/configurations.ts | 59 ++- .../common/convert_to_lens/types/params.ts | 2 +- .../common/convert_to_lens/utils.ts | 12 +- .../public/convert_to_lens/index.ts | 2 + .../public/convert_to_lens/schemas.test.ts | 4 +- .../public/convert_to_lens/schemas.ts | 8 +- .../public/convert_to_lens/utils.test.ts | 12 +- .../public/convert_to_lens/utils.ts | 5 +- .../public/visualizations/heatmap/types.ts | 2 +- .../visualizations/heatmap/visualization.tsx | 28 +- .../lens/open_in_lens/agg_based/heatmap.ts | 219 +++++++++ .../apps/lens/open_in_lens/agg_based/index.ts | 1 + 63 files changed, 1573 insertions(+), 363 deletions(-) create mode 100644 src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts create mode 100644 src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts create mode 100644 src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts create mode 100644 src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts create mode 100644 src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts create mode 100644 src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts create mode 100644 src/plugins/vis_types/heatmap/public/services.ts create mode 100644 x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap index 96b70e33021f4..1b644ef0a4938 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/__snapshots__/heatmap_function.test.ts.snap @@ -77,6 +77,7 @@ Object { "xAccessor": "col-1-2", "yAccessor": undefined, }, + "canNavigateToLens": false, "data": Object { "columns": Array [ Object { diff --git a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts index 548d4ec0ab49e..f0c309de19236 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/expression_functions/heatmap_function.ts @@ -232,6 +232,7 @@ export const heatmapFunction = (): HeatmapExpressionFunctionDefinition => ({ }, syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false, syncCursor: handlers?.isSyncCursorEnabled?.() ?? true, + canNavigateToLens: Boolean(handlers?.variables?.canNavigateToLens), }, }; }, diff --git a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts index 5aa1507f30b03..1bf5fe3bbb36b 100644 --- a/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_heatmap/common/types/expression_functions.ts @@ -94,6 +94,7 @@ export interface HeatmapExpressionProps { args: HeatmapArguments; syncTooltips: boolean; syncCursor: boolean; + canNavigateToLens?: boolean; } export interface HeatmapRender { diff --git a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx index 4b813fb93416f..b14ee1382deb2 100644 --- a/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx +++ b/src/plugins/chart_expressions/expression_heatmap/public/expression_renderers/heatmap_renderer.tsx @@ -61,9 +61,14 @@ export const heatmapRenderer: ( const visualizationType = extractVisualizationType(executionContext); if (containerType && visualizationType) { - plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, [ + const events = [ `render_${visualizationType}_${EXPRESSION_HEATMAP_NAME}`, - ]); + config.canNavigateToLens + ? `render_${visualizationType}_${EXPRESSION_HEATMAP_NAME}_convertable` + : undefined, + ].filter((event): event is string => Boolean(event)); + + plugins.usageCollection?.reportUiCounter(containerType, METRIC_TYPE.COUNT, events); } handlers.done(); diff --git a/src/plugins/vis_types/heatmap/kibana.json b/src/plugins/vis_types/heatmap/kibana.json index c8df98e2b343a..b7f4a3bacbb90 100644 --- a/src/plugins/vis_types/heatmap/kibana.json +++ b/src/plugins/vis_types/heatmap/kibana.json @@ -1,14 +1,27 @@ { - "id": "visTypeHeatmap", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "fieldFormats"], - "requiredBundles": ["visDefaultEditor"], - "extraPublicDirs": ["common/index"], - "owner": { - "name": "Vis Editors", - "githubTeam": "kibana-vis-editors" - }, - "description": "Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting." - } + "id": "visTypeHeatmap", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "charts", + "data", + "expressions", + "visualizations", + "usageCollection", + "fieldFormats", + "dataViews" + ], + "requiredBundles": [ + "visDefaultEditor", + "kibanaUtils" + ], + "extraPublicDirs": [ + "common/index" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Contains the heatmap implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy heatmap charts library advanced setting." +} \ No newline at end of file diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts new file mode 100644 index 0000000000000..3f60b6fde0a94 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.test.ts @@ -0,0 +1,114 @@ +/* + * Copyright 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 { AvgColumn, DateHistogramColumn } from '@kbn/visualizations-plugin/common/convert_to_lens'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { getConfiguration } from '.'; +import { sampleHeatmapVis } from '../../sample_vis.test.mocks'; +import { HeatmapVisParams } from '../../types'; + +describe('getConfiguration', () => { + const layerId = 'layer-id'; + let vis: Vis; + + const metric: AvgColumn = { + sourceField: 'price', + columnId: 'column-1', + operationType: 'average', + isBucketed: false, + isSplit: false, + dataType: 'string', + params: {}, + }; + const xColumn: DateHistogramColumn = { + sourceField: 'price', + columnId: 'column-2', + operationType: 'date_histogram', + isBucketed: true, + isSplit: false, + dataType: 'string', + params: { + interval: '1h', + }, + }; + + const yColumn: DateHistogramColumn = { + sourceField: 'price', + columnId: 'column-3', + operationType: 'date_histogram', + isBucketed: true, + isSplit: true, + dataType: 'string', + params: { + interval: '1h', + }, + }; + + beforeEach(() => { + vis = sampleHeatmapVis as unknown as Vis; + }); + + test('should return valid configuration', async () => { + const result = await getConfiguration(layerId, vis, { + metrics: [metric.columnId], + buckets: [xColumn.columnId, yColumn.columnId], + }); + expect(result).toEqual({ + gridConfig: { + isCellLabelVisible: true, + isXAxisLabelVisible: true, + isXAxisTitleVisible: true, + isYAxisLabelVisible: true, + isYAxisTitleVisible: true, + type: 'heatmap_grid', + }, + layerId, + layerType: 'data', + legend: { isVisible: undefined, position: 'right', type: 'heatmap_legend' }, + palette: { + accessor: 'column-1', + name: 'custom', + params: { + colorStops: [ + { color: '#F7FBFF', stop: 0 }, + { color: '#DEEBF7', stop: 12.5 }, + { color: '#C3DBEE', stop: 25 }, + { color: '#9CC8E2', stop: 37.5 }, + { color: '#6DAED5', stop: 50 }, + { color: '#4391C6', stop: 62.5 }, + { color: '#2271B3', stop: 75 }, + { color: '#0D5097', stop: 87.5 }, + ], + continuity: 'none', + maxSteps: 5, + name: 'custom', + progression: 'fixed', + rangeMax: 100, + rangeMin: 0, + rangeType: 'number', + reverse: false, + stops: [ + { color: '#F7FBFF', stop: 12.5 }, + { color: '#DEEBF7', stop: 25 }, + { color: '#C3DBEE', stop: 37.5 }, + { color: '#9CC8E2', stop: 50 }, + { color: '#6DAED5', stop: 62.5 }, + { color: '#4391C6', stop: 75 }, + { color: '#2271B3', stop: 87.5 }, + { color: '#0D5097', stop: 100 }, + ], + }, + type: 'palette', + }, + shape: 'heatmap', + valueAccessor: metric.columnId, + xAccessor: xColumn.columnId, + yAccessor: yColumn.columnId, + }); + }); +}); diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts new file mode 100644 index 0000000000000..2e7a3f161514a --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/index.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../../types'; +import { getPaletteForHeatmap } from './palette'; + +export const getConfiguration = async ( + layerId: string, + vis: Vis, + { + metrics, + buckets, + }: { + metrics: string[]; + buckets: string[]; + } +): Promise => { + const [valueAccessor] = metrics; + const [xAccessor, yAccessor] = buckets; + + const { params, uiState } = vis; + const state = uiState.get('vis', {}) ?? {}; + + const palette = await getPaletteForHeatmap(params); + return { + layerId, + layerType: 'data', + shape: 'heatmap', + legend: { + type: 'heatmap_legend', + isVisible: state.legendOpen, + position: params.legendPosition, + }, + gridConfig: { + type: 'heatmap_grid', + isCellLabelVisible: params.valueAxes?.[0].labels.show ?? false, + isXAxisLabelVisible: true, + isYAxisLabelVisible: true, + isYAxisTitleVisible: true, + isXAxisTitleVisible: true, + }, + valueAccessor, + xAccessor, + yAccessor, + palette: palette ? { ...palette, accessor: valueAccessor } : undefined, + }; +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts new file mode 100644 index 0000000000000..32187e184d4ef --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/configurations/palette.ts @@ -0,0 +1,52 @@ +/* + * Copyright 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 { Range } from '@kbn/expressions-plugin/common'; +import { convertToLensModule } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../../types'; +import { getStopsWithColorsFromColorsNumber } from '../../utils/palette'; + +type HeatmapVisParamsWithRanges = Omit & { + colorsRange: Exclude; +}; + +const isHeatmapVisParamsWithRanges = ( + params: HeatmapVisParams | HeatmapVisParamsWithRanges +): params is HeatmapVisParamsWithRanges => { + return Boolean(params.setColorRange && params.colorsRange && params.colorsRange.length); +}; + +export const getPaletteForHeatmap = async (params: HeatmapVisParams) => { + const { getPalette, getPaletteFromStopsWithColors, getPercentageModeConfig } = + await convertToLensModule; + + if (isHeatmapVisParamsWithRanges(params)) { + const percentageModeConfig = getPercentageModeConfig(params, false); + return getPalette(params, percentageModeConfig, params.percentageMode); + } + + const { color, stop = [] } = getStopsWithColorsFromColorsNumber( + params.colorsNumber, + params.colorSchema, + params.invertColors, + true + ); + const colorsRange: Range[] = [{ from: stop[0], to: stop[stop.length - 1], type: 'range' }]; + const { colorSchema, invertColors, percentageMode } = params; + const percentageModeConfig = getPercentageModeConfig( + { + colorsRange, + colorSchema, + invertColors, + percentageMode, + }, + false + ); + + return getPaletteFromStopsWithColors({ color, stop: stop ?? [] }, percentageModeConfig, true); +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts new file mode 100644 index 0000000000000..ef86b3829c248 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright 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 { ColorSchemas } from '@kbn/charts-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { convertToLens } from '.'; +import { HeatmapVisParams } from '../types'; + +const mockGetColumnsFromVis = jest.fn(); +const mockGetConfiguration = jest.fn().mockReturnValue({}); +const mockGetDataViewByIndexPatternId = jest.fn(); +const mockConvertToFiltersColumn = jest.fn(); + +jest.mock('../services', () => ({ + getDataViewsStart: jest.fn(() => ({ get: () => ({}), getDefault: () => ({}) })), +})); + +jest.mock('@kbn/visualizations-plugin/public', () => ({ + convertToLensModule: Promise.resolve({ + getColumnsFromVis: jest.fn(() => mockGetColumnsFromVis()), + convertToFiltersColumn: jest.fn(() => mockConvertToFiltersColumn()), + }), + getDataViewByIndexPatternId: jest.fn(() => mockGetDataViewByIndexPatternId()), +})); + +jest.mock('./configurations', () => ({ + getConfiguration: jest.fn(() => mockGetConfiguration()), +})); + +const params: HeatmapVisParams = { + addTooltip: false, + addLegend: false, + enableHover: true, + legendPosition: 'bottom', + lastRangeIsRightOpen: false, + percentageMode: false, + valueAxes: [], + colorSchema: ColorSchemas.Blues, + invertColors: false, + colorsNumber: 4, + setColorRange: true, +}; + +const vis = { + isHierarchical: () => false, + type: {}, + params, + data: {}, +} as unknown as Vis; + +const timefilter = { + getAbsoluteTime: () => {}, +} as any; + +describe('convertToLens', () => { + beforeEach(() => { + mockGetDataViewByIndexPatternId.mockReturnValue({ id: 'index-pattern' }); + mockConvertToFiltersColumn.mockReturnValue({ columnId: 'column-id-1' }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('should return null if timefilter is undefined', async () => { + const result = await convertToLens(vis); + expect(result).toBeNull(); + }); + + test('should return null if mockGetDataViewByIndexPatternId returns null', async () => { + mockGetDataViewByIndexPatternId.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetDataViewByIndexPatternId).toBeCalledTimes(1); + expect(mockGetColumnsFromVis).toBeCalledTimes(0); + expect(result).toBeNull(); + }); + + test('should return null if getColumnsFromVis returns null', async () => { + mockGetColumnsFromVis.mockReturnValue(null); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return null if metrics count is more than 1', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1', '2'], + buckets: { all: [] }, + columns: [{ columnId: '2' }, { columnId: '1' }], + }, + ]); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toBeNull(); + }); + + test('should return empty filters for x-axis if no buckets are specified', async () => { + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: [] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + { columnId: 'column-id-1' }, + ], + }, + ]); + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(result).toEqual( + expect.objectContaining({ + configuration: {}, + indexPatternIds: ['index-pattern'], + layers: [ + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '1', dataType: 'number' }, { columnId: 'column-id-1' }], + indexPatternId: 'index-pattern', + }), + ], + type: 'lnsHeatmap', + }) + ); + }); + + test('should return correct state for valid vis', async () => { + const config = { + layerType: 'data', + }; + + mockGetColumnsFromVis.mockReturnValue([ + { + metrics: ['1'], + buckets: { all: ['2'] }, + columns: [{ columnId: '1', dataType: 'number' }], + columnsWithoutReferenced: [ + { columnId: '1', meta: { aggId: 'agg-1' } }, + { columnId: '2', meta: { aggId: 'agg-2' } }, + ], + }, + ]); + mockGetConfiguration.mockReturnValue(config); + + const result = await convertToLens(vis, timefilter); + expect(mockGetColumnsFromVis).toBeCalledTimes(1); + expect(mockGetConfiguration).toBeCalledTimes(1); + expect(result?.type).toEqual('lnsHeatmap'); + expect(result?.layers.length).toEqual(1); + expect(result?.layers[0]).toEqual( + expect.objectContaining({ + columnOrder: [], + columns: [{ columnId: '1', dataType: 'number' }, { columnId: 'column-id-1' }], + indexPatternId: 'index-pattern', + }) + ); + expect(result?.configuration).toEqual(config); + }); +}); diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts new file mode 100644 index 0000000000000..546d497e80560 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/index.ts @@ -0,0 +1,97 @@ +/* + * Copyright 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 { Column, ColumnWithMeta } from '@kbn/visualizations-plugin/common'; +import { + convertToLensModule, + getDataViewByIndexPatternId, +} from '@kbn/visualizations-plugin/public'; +import uuid from 'uuid'; +import { getDataViewsStart } from '../services'; +import { getConfiguration } from './configurations'; +import { ConvertHeatmapToLensVisualization } from './types'; + +export const isColumnWithMeta = (column: Column): column is ColumnWithMeta => { + if ((column as ColumnWithMeta).meta) { + return true; + } + return false; +}; + +export const excludeMetaFromColumn = (column: Column) => { + if (isColumnWithMeta(column)) { + const { meta, ...rest } = column; + return rest; + } + return column; +}; + +export const convertToLens: ConvertHeatmapToLensVisualization = async (vis, timefilter) => { + if (!timefilter) { + return null; + } + + const dataViews = getDataViewsStart(); + const dataView = await getDataViewByIndexPatternId(vis.data.indexPattern?.id, dataViews); + + if (!dataView) { + return null; + } + + const { getColumnsFromVis, convertToFiltersColumn } = await convertToLensModule; + const layers = getColumnsFromVis(vis, timefilter, dataView, { + buckets: ['segment'], + splits: ['group'], + unsupported: ['split_row', 'split_column'], + }); + + if (layers === null) { + return null; + } + + const [layerConfig] = layers; + + const xColumn = layerConfig.columns.find(({ isBucketed, isSplit }) => isBucketed && !isSplit); + const xAxisColumn = + xColumn ?? + convertToFiltersColumn(uuid(), { filters: [{ input: { language: 'lucene', query: '*' } }] })!; + + if (xColumn?.columnId !== xAxisColumn?.columnId) { + layerConfig.buckets.all.push(xAxisColumn.columnId); + layerConfig.columns.push(xAxisColumn); + } + const yColumn = layerConfig.columns.find(({ isBucketed, isSplit }) => isBucketed && isSplit); + + if (!layerConfig.buckets.all.length || layerConfig.metrics.length > 1) { + return null; + } + + const layerId = uuid(); + + const indexPatternId = dataView.id!; + const configuration = await getConfiguration(layerId, vis, { + metrics: layerConfig.metrics, + buckets: [xAxisColumn.columnId, yColumn?.columnId].filter((c): c is string => + Boolean(c) + ), + }); + + return { + type: 'lnsHeatmap', + layers: [ + { + indexPatternId, + layerId, + columns: layerConfig.columns.map(excludeMetaFromColumn), + columnOrder: [], + }, + ], + configuration, + indexPatternIds: [indexPatternId], + }; +}; diff --git a/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts b/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts new file mode 100644 index 0000000000000..732b977dd7b59 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/convert_to_lens/types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { TimefilterContract } from '@kbn/data-plugin/public'; +import { NavigateToLensContext, HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import { Vis } from '@kbn/visualizations-plugin/public'; +import { HeatmapVisParams } from '../types'; + +export type ConvertHeatmapToLensVisualization = ( + vis: Vis, + timefilter?: TimefilterContract +) => Promise | null>; diff --git a/src/plugins/vis_types/heatmap/public/plugin.ts b/src/plugins/vis_types/heatmap/public/plugin.ts index 44357cceaa86b..ee7349145e7c6 100644 --- a/src/plugins/vis_types/heatmap/public/plugin.ts +++ b/src/plugins/vis_types/heatmap/public/plugin.ts @@ -6,14 +6,16 @@ * Side Public License, v 1. */ -import { CoreSetup } from '@kbn/core/public'; +import { CoreSetup, CoreStart } from '@kbn/core/public'; import type { VisualizationsSetup } from '@kbn/visualizations-plugin/public'; import type { ChartsPluginSetup } from '@kbn/charts-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; import { LEGACY_HEATMAP_CHARTS_LIBRARY } from '../common'; import { heatmapVisType } from './vis_type'; +import { setDataViewsStart } from './services'; /** @internal */ export interface VisTypeHeatmapSetupDependencies { @@ -28,6 +30,11 @@ export interface VisTypeHeatmapPluginStartDependencies { fieldFormats: FieldFormatsStart; } +/** @internal */ +export interface VisTypeHeatmapStartDependencies { + dataViews: DataViewsPublicPluginStart; +} + export class VisTypeHeatmapPlugin { setup( core: CoreSetup, @@ -44,5 +51,7 @@ export class VisTypeHeatmapPlugin { return {}; } - start() {} + start(core: CoreStart, { dataViews }: VisTypeHeatmapStartDependencies) { + setDataViewsStart(dataViews); + } } diff --git a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts index 6a33feb853221..89ede55b951ef 100644 --- a/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts +++ b/src/plugins/vis_types/heatmap/public/sample_vis.test.mocks.ts @@ -5,7 +5,9 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -export const sampleAreaVis = { + +const mockUiStateGet = jest.fn().mockReturnValue(() => {}); +export const sampleHeatmapVis = { type: { name: 'heatmap', title: 'Heatmap', @@ -1788,5 +1790,10 @@ export const sampleAreaVis = { }, }, isHierarchical: () => false, - uiState: {}, + uiState: { + vis: { + legendOpen: false, + }, + get: mockUiStateGet, + }, }; diff --git a/src/plugins/vis_types/heatmap/public/services.ts b/src/plugins/vis_types/heatmap/public/services.ts new file mode 100644 index 0000000000000..736ad70d49419 --- /dev/null +++ b/src/plugins/vis_types/heatmap/public/services.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 { createGetterSetter } from '@kbn/kibana-utils-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; + +export const [getDataViewsStart, setDataViewsStart] = + createGetterSetter('dataViews'); diff --git a/src/plugins/vis_types/heatmap/public/to_ast.test.ts b/src/plugins/vis_types/heatmap/public/to_ast.test.ts index d1e312755cf49..07585d9f2332f 100644 --- a/src/plugins/vis_types/heatmap/public/to_ast.test.ts +++ b/src/plugins/vis_types/heatmap/public/to_ast.test.ts @@ -7,7 +7,7 @@ */ import { Vis } from '@kbn/visualizations-plugin/public'; -import { sampleAreaVis } from './sample_vis.test.mocks'; +import { sampleHeatmapVis } from './sample_vis.test.mocks'; import { buildExpression } from '@kbn/expressions-plugin/public'; import { toExpressionAst } from './to_ast'; @@ -33,7 +33,7 @@ describe('heatmap vis toExpressionAst function', () => { } as any; beforeEach(() => { - vis = sampleAreaVis as any; + vis = sampleHeatmapVis as any; }); it('should match basic snapshot', () => { diff --git a/src/plugins/vis_types/heatmap/public/utils/palette.ts b/src/plugins/vis_types/heatmap/public/utils/palette.ts index aa978a2954e90..29109a55fd1e7 100644 --- a/src/plugins/vis_types/heatmap/public/utils/palette.ts +++ b/src/plugins/vis_types/heatmap/public/utils/palette.ts @@ -27,13 +27,20 @@ const getColor = ( export const getStopsWithColorsFromColorsNumber = ( colorsNumber: number | '', colorSchema: ColorSchemas, - invertColors: boolean = false + invertColors: boolean = false, + includeZeroElement: boolean = false ) => { const colors = []; const stops = []; if (!colorsNumber) { return { color: [] }; } + + if (includeZeroElement) { + colors.push(TRANSPARENT); + stops.push(0); + } + const step = 100 / colorsNumber; for (let i = 0; i < colorsNumber; i++) { colors.push(getColor(i, colorsNumber, colorSchema, invertColors)); diff --git a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx index e5a92ca03f5cc..336da6e2d8041 100644 --- a/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx +++ b/src/plugins/vis_types/heatmap/public/vis_type/heatmap.tsx @@ -16,6 +16,7 @@ import { HeatmapTypeProps, HeatmapVisParams, AxisType, ScaleType } from '../type import { toExpressionAst } from '../to_ast'; import { getHeatmapOptions } from '../editor/components'; import { SplitTooltip } from './split_tooltip'; +import { convertToLens } from '../convert_to_lens'; export const getHeatmapVisTypeDefinition = ({ showElasticChartsOptions = false, @@ -154,4 +155,10 @@ export const getHeatmapVisTypeDefinition = ({ ], }, requiresSearch: true, + navigateToLens: async (vis, timefilter) => (vis ? convertToLens(vis, timefilter) : null), + getExpressionVariables: async (vis, timeFilter) => { + return { + canNavigateToLens: Boolean(vis?.params ? await convertToLens(vis, timeFilter) : null), + }; + }, }); diff --git a/src/plugins/vis_types/pie/kibana.json b/src/plugins/vis_types/pie/kibana.json index 4c5ee6b50579e..d9dca861e33be 100644 --- a/src/plugins/vis_types/pie/kibana.json +++ b/src/plugins/vis_types/pie/kibana.json @@ -1,14 +1,27 @@ { - "id": "visTypePie", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": ["charts", "data", "expressions", "visualizations", "usageCollection", "expressionPartitionVis", "dataViews"], - "requiredBundles": ["visDefaultEditor", "kibanaUtils"], - "extraPublicDirs": ["common/index"], - "owner": { - "name": "Vis Editors", - "githubTeam": "kibana-vis-editors" - }, - "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting." - } + "id": "visTypePie", + "version": "kibana", + "ui": true, + "server": true, + "requiredPlugins": [ + "charts", + "data", + "expressions", + "visualizations", + "usageCollection", + "expressionPartitionVis", + "dataViews" + ], + "requiredBundles": [ + "visDefaultEditor", + "kibanaUtils" + ], + "extraPublicDirs": [ + "common/index" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + }, + "description": "Contains the pie chart implementation using the elastic-charts library. The goal is to eventually deprecate the old implementation and keep only this. Until then, the library used is defined by the Legacy charts library advanced setting." +} \ No newline at end of file diff --git a/src/plugins/vis_types/table/public/convert_to_lens/index.ts b/src/plugins/vis_types/table/public/convert_to_lens/index.ts index e69faccbfd7ec..ed23d612cb68c 100644 --- a/src/plugins/vis_types/table/public/convert_to_lens/index.ts +++ b/src/plugins/vis_types/table/public/convert_to_lens/index.ts @@ -73,6 +73,7 @@ export const convertToLens: ConvertTableToLensVisualization = async (vis, timefi return null; } const percentageColumn = getPercentageColumnFormulaColumn({ + visType: vis.type.name, agg: metricAgg as SchemaConfig, dataView, aggs: visSchemas.metric as Array>, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts index f0a8e4d32f7c3..02a6140625c07 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.test.ts @@ -8,7 +8,7 @@ import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { BUCKET_TYPES, METRIC_TYPES } from '@kbn/data-plugin/common'; -import { convertBucketToColumns } from '.'; +import { BucketAggs, convertBucketToColumns } from '.'; import { DateHistogramColumn, FiltersColumn, RangeColumn, TermsColumn } from '../../types'; import { AggBasedColumn, SchemaConfig } from '../../..'; @@ -27,7 +27,7 @@ jest.mock('../convert', () => ({ describe('convertBucketToColumns', () => { const field = stubLogstashDataView.fields[0].name; const dateField = stubLogstashDataView.fields.find((f) => f.type === 'date')!.name; - const bucketAggs: SchemaConfig[] = [ + const bucketAggs: Array> = [ { accessor: 0, label: '', @@ -152,6 +152,7 @@ describe('convertBucketToColumns', () => { }, }, ]; + const visType = 'heatmap'; afterEach(() => { jest.clearAllMocks(); @@ -167,7 +168,7 @@ describe('convertBucketToColumns', () => { >([ [ 'null if bucket agg type is not supported', - [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[6], aggs, metricColumns, visType }], () => {}, null, ], @@ -179,6 +180,7 @@ describe('convertBucketToColumns', () => { agg: { ...bucketAggs[0], aggParams: undefined }, aggs, metricColumns, + visType, }, ], () => {}, @@ -186,7 +188,7 @@ describe('convertBucketToColumns', () => { ], [ 'filters column if bucket agg is valid filters agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[0], aggs, metricColumns, visType }], () => { mockConvertToFiltersColumn.mockReturnValue({ operationType: 'filters', @@ -198,7 +200,7 @@ describe('convertBucketToColumns', () => { ], [ 'date histogram column if bucket agg is valid date histogram agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[1], aggs, metricColumns, visType }], () => { mockConvertToDateHistogramColumn.mockReturnValue({ operationType: 'date_histogram', @@ -210,7 +212,7 @@ describe('convertBucketToColumns', () => { ], [ 'date histogram column if bucket agg is valid terms agg with date field', - [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[3], aggs, metricColumns, visType }], () => { mockConvertToDateHistogramColumn.mockReturnValue({ operationType: 'date_histogram', @@ -222,7 +224,7 @@ describe('convertBucketToColumns', () => { ], [ 'terms column if bucket agg is valid terms agg with no date field', - [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[2], aggs, metricColumns, visType }], () => { mockConvertToTermsColumn.mockReturnValue({ operationType: 'terms', @@ -234,7 +236,7 @@ describe('convertBucketToColumns', () => { ], [ 'range column if bucket agg is valid histogram agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[4], aggs, metricColumns, visType }], () => { mockConvertToRangeColumn.mockReturnValue({ operationType: 'range', @@ -246,7 +248,7 @@ describe('convertBucketToColumns', () => { ], [ 'range column if bucket agg is valid range agg', - [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns }], + [{ dataView: stubLogstashDataView, agg: bucketAggs[5], aggs, metricColumns, visType }], () => { mockConvertToRangeColumn.mockReturnValue({ operationType: 'range', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts index 0f929189f3369..db02b1e09fdce 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/buckets/index.ts @@ -9,9 +9,8 @@ import { BUCKET_TYPES, IAggConfig, METRIC_TYPES } from '@kbn/data-plugin/common'; import type { DataView } from '@kbn/data-views-plugin/common'; import { convertToSchemaConfig } from '../../../vis_schemas'; -import { SchemaConfig } from '../../..'; +import { AggBasedColumn, SchemaConfig } from '../../..'; import { - AggBasedColumn, CommonBucketConverterArgs, convertToDateHistogramColumn, convertToFiltersColumn, @@ -26,6 +25,7 @@ export type BucketAggs = | BUCKET_TYPES.FILTERS | BUCKET_TYPES.RANGE | BUCKET_TYPES.HISTOGRAM; + const SUPPORTED_BUCKETS: string[] = [ BUCKET_TYPES.TERMS, BUCKET_TYPES.DATE_HISTOGRAM, @@ -39,7 +39,7 @@ const isSupportedBucketAgg = (agg: SchemaConfig): agg is SchemaConfig, + { agg, dataView, metricColumns, aggs, visType }: CommonBucketConverterArgs, { label, isSplit = false, @@ -76,7 +76,7 @@ export const getBucketColumns = ( if (field.type !== 'date') { return convertToTermsColumn( agg.aggId ?? '', - { agg, dataView, metricColumns, aggs }, + { agg, dataView, metricColumns, aggs, visType }, label, isSplit ); @@ -102,7 +102,9 @@ export const convertBucketToColumns = ( dataView, metricColumns, aggs, + visType, }: { + visType: string; agg: SchemaConfig | IAggConfig; dataView: DataView; metricColumns: AggBasedColumn[]; @@ -116,7 +118,7 @@ export const convertBucketToColumns = ( return null; } return getBucketColumns( - { agg: currentAgg, dataView, metricColumns, aggs }, + { agg: currentAgg, dataView, metricColumns, aggs, visType }, { label: getLabel(currentAgg), isSplit, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts index c4592f50836c5..b4934d0bb0c85 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { getPalette } from './palette'; +export { getPalette, getPaletteFromStopsWithColors } from './palette'; export { getPercentageModeConfig } from './percentage_mode'; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts index a89177c914996..3f81291fab201 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/configurations/palette.ts @@ -74,6 +74,21 @@ const convertToPercentColorStops = ( return { ...colorStops, stop }; }; +export const getPaletteFromStopsWithColors = ( + config: PaletteConfig, + percentageModeConfig: PercentageModeConfig, + isPercentPaletteSupported: boolean = false +) => { + const percentStopsWithColors = percentageModeConfig.isPercentageMode + ? convertToPercentColorStops(config, percentageModeConfig, isPercentPaletteSupported) + : config; + + return buildCustomPalette( + buildPaletteParams(percentStopsWithColors), + isPercentPaletteSupported && percentageModeConfig.isPercentageMode + ); +}; + export const getPalette = ( params: PaletteParams, percentageModeConfig: PercentageModeConfig, @@ -86,12 +101,10 @@ export const getPalette = ( } const stopsWithColors = getStopsWithColorsFromRanges(colorsRange, colorSchema, invertColors); - const percentStopsWithColors = percentageModeConfig.isPercentageMode - ? convertToPercentColorStops(stopsWithColors, percentageModeConfig, isPercentPaletteSupported) - : stopsWithColors; - return buildCustomPalette( - buildPaletteParams(percentStopsWithColors), - isPercentPaletteSupported && percentageModeConfig.isPercentageMode + return getPaletteFromStopsWithColors( + stopsWithColors, + percentageModeConfig, + isPercentPaletteSupported ); }; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts index 0ad2a4072e19d..e79be2ba51516 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/formula.ts @@ -21,6 +21,7 @@ export const createFormulaColumn = (formula: string, agg: SchemaConfig): Formula operationType: 'formula', ...createColumn(agg), references: [], + dataType: 'number', params: { ...params, ...getFormat() }, timeShift: agg.aggParams?.timeShift, meta: { aggId: createAggregationId(agg) }, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts index 55ba1e8b5e09d..c46055ca6a9ab 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.test.ts @@ -22,6 +22,7 @@ jest.mock('../utils', () => ({ })); describe('convertToLastValueColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const sortField = dataView.fields[0]; @@ -59,7 +60,13 @@ describe('convertToLastValueColumn', () => { test.each<[string, Parameters, Partial | null]>([ [ 'null if top hits size is more than 1', - [{ agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, dataView }], + [ + { + agg: { ...topHitAgg, aggParams: { ...topHitAgg.aggParams!, size: 2 } }, + dataView, + visType, + }, + ], null, ], [ @@ -74,6 +81,7 @@ describe('convertToLastValueColumn', () => { }, }, dataView, + visType, }, ], null, @@ -88,7 +96,7 @@ describe('convertToLastValueColumn', () => { test('should skip if top hit field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -97,14 +105,14 @@ describe('convertToLastValueColumn', () => { mockGetFieldByName.mockReturnValue(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toBeNull(); + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); expect(mockGetLabel).toBeCalledTimes(0); }); test('should return top hit column if top hit field is not present in index pattern', () => { - expect(convertToLastValueColumn({ agg: topHitAgg, dataView })).toEqual( + expect(convertToLastValueColumn({ agg: topHitAgg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts index 3162cf14e71c3..9525f4b41b7eb 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/last_value.ts @@ -25,7 +25,11 @@ const convertToLastValueParams = ( }; export const convertToLastValueColumn = ( - { agg, dataView }: CommonColumnConverterArgs, + { + visType, + agg, + dataView, + }: CommonColumnConverterArgs, reducedTimeRange?: string ): LastValueColumn | null => { const { aggParams } = agg; @@ -43,7 +47,7 @@ export const convertToLastValueColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts index 3be17abc46ac1..a0419d46df6b5 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.test.ts @@ -16,6 +16,7 @@ const mockGetFieldByName = jest.fn(); describe('convertToLastValueColumn', () => { const dataView = stubLogstashDataView; + const visType = 'heatmap'; const agg: SchemaConfig = { accessor: 0, @@ -42,6 +43,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.TOP_HITS], { agg, dataView, + visType, }) ).toBeNull(); }); @@ -54,6 +56,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { agg, dataView, + visType, }) ).toBeNull(); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -67,6 +70,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.COUNT], { agg, dataView, + visType, }) ).toEqual(expect.objectContaining({ operationType: 'count' })); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -80,6 +84,7 @@ describe('convertToLastValueColumn', () => { convertMetricAggregationColumnWithoutSpecialParams(SUPPORTED_METRICS[METRIC_TYPES.AVG], { agg, dataView, + visType, }) ).toEqual( expect.objectContaining({ diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts index eb21b9f0fe91d..dd6c8b02687b0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/metric.ts @@ -78,7 +78,7 @@ export const isMetricWithField = ( export const convertMetricAggregationColumnWithoutSpecialParams = ( aggregation: SupportedMetric, - { agg, dataView }: CommonColumnConverterArgs, + { visType, agg, dataView }: CommonColumnConverterArgs, reducedTimeRange?: string ): MetricAggregationColumnWithoutSpecialParams | null => { if (!isSupportedAggregationWithoutParams(aggregation.name)) { @@ -94,7 +94,7 @@ export const convertMetricAggregationColumnWithoutSpecialParams = ( } const field = dataView.getFieldByName(sourceField); - if (!isFieldValid(field, aggregation)) { + if (!isFieldValid(visType, field, aggregation)) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts index c28324533c837..65dd1cf40aaef 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.test.ts @@ -40,6 +40,7 @@ jest.mock('../metrics', () => ({ })); describe('convertToOtherParentPipelineAggColumns', () => { + const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -81,6 +82,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -95,6 +97,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -112,6 +115,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -129,6 +133,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -147,6 +152,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -170,6 +176,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -188,6 +195,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -229,6 +237,7 @@ describe('convertToOtherParentPipelineAggColumns', () => { }); describe('convertToCumulativeSumAggColumn', () => { + const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -280,6 +289,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: { ...aggs[1], aggParams: undefined } as SchemaConfig, + visType, }, ], () => { @@ -294,6 +304,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -308,6 +319,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -325,6 +337,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -342,6 +355,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -360,6 +374,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -383,6 +398,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { @@ -401,6 +417,7 @@ describe('convertToCumulativeSumAggColumn', () => { dataView: stubLogstashDataView, aggs, agg: aggs[1] as SchemaConfig, + visType, }, ], () => { diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts index ab41ceb259adb..0e0aef11316b2 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/parent_pipeline.ts @@ -38,7 +38,7 @@ export const convertToMovingAverageParams = ( }); export const convertToOtherParentPipelineAggColumns = ( - { agg, dataView, aggs }: ExtendedColumnConverterArgs, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, reducedTimeRange?: string ): FormulaColumn | [ParentPipelineAggColumn, AggBasedColumn] | null => { const { aggType } = agg; @@ -63,7 +63,7 @@ export const convertToOtherParentPipelineAggColumns = ( } if (PIPELINE_AGGS.includes(metric.aggType)) { - const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); if (!formula) { return null; } @@ -71,7 +71,7 @@ export const convertToOtherParentPipelineAggColumns = ( return createFormulaColumn(formula, agg); } - const subMetric = convertMetricToColumns(metric, dataView, aggs); + const subMetric = convertMetricToColumns({ agg: metric, dataView, aggs, visType }); if (subMetric === null) { return null; @@ -90,7 +90,7 @@ export const convertToOtherParentPipelineAggColumns = ( }; export const convertToCumulativeSumAggColumn = ( - { agg, dataView, aggs }: ExtendedColumnConverterArgs, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, reducedTimeRange?: string ): | FormulaColumn @@ -119,7 +119,7 @@ export const convertToCumulativeSumAggColumn = ( // create column for sum or count const subMetric = convertMetricAggregationColumnWithoutSpecialParams( subAgg, - { agg: metric as SchemaConfig, dataView }, + { agg: metric as SchemaConfig, dataView, visType }, reducedTimeRange ); @@ -144,7 +144,7 @@ export const convertToCumulativeSumAggColumn = ( subMetric, ]; } else { - const formula = getFormulaForPipelineAgg({ agg, aggs, dataView }); + const formula = getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); if (!formula) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts index 3b7e8ad7e797f..0ef5d07236d60 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentage_mode.test.ts @@ -18,6 +18,7 @@ jest.mock('../metrics/formula', () => ({ })); describe('convertToColumnInPercentageMode', () => { + const visType = 'heatmap'; const formula = 'average(some_field)'; const dataView = stubLogstashDataView; @@ -42,7 +43,7 @@ describe('convertToColumnInPercentageMode', () => { test('should return null if it is not possible to build the valid formula', () => { mockGetFormulaForAgg.mockReturnValue(null); - expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toBeNull(); + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, {})).toBeNull(); }); test('should return percentage mode over range formula if min and max was passed', () => { @@ -51,7 +52,7 @@ describe('convertToColumnInPercentageMode', () => { params: { format: { id: 'percent' }, formula: `((${formula}) - 0) / (100 - 0)` }, }; expect( - convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, { min: 0, max: 100 }) + convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, { min: 0, max: 100 }) ).toEqual(expect.objectContaining(formulaColumn)); }); @@ -60,7 +61,7 @@ describe('convertToColumnInPercentageMode', () => { operationType: 'formula', params: { format: { id: 'percent' }, formula: `(${formula}) / 10000` }, }; - expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg] }, {})).toEqual( + expect(convertToColumnInPercentageMode({ agg, dataView, aggs: [agg], visType }, {})).toEqual( expect.objectContaining(formulaColumn) ); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts index b4cf7f141e928..adfab7f55d1c4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.test.ts @@ -24,6 +24,7 @@ jest.mock('../utils', () => ({ })); describe('convertToPercentileColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = dataView.fields[0].displayName; const aggId = 'pr.10'; @@ -67,23 +68,27 @@ describe('convertToPercentileColumn', () => { test.each< [string, Parameters, Partial | null] >([ - ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView, visType }], null], [ 'null if no value', - [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView, visType }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView, visType }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView, visType }], null], + [ + 'null if aggId is invalid', + [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView, visType }], null, ], - ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], - ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], - ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], [ 'null if values are undefined', - [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView }], + [{ agg: { ...agg, aggParams: { percents: undefined, field } }, dataView, visType }], null, ], [ 'null if values are empty', - [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView }], + [{ agg: { ...agg, aggParams: { percents: [], field } }, dataView, visType }], null, ], ])('should return %s', (_, input, expected) => { @@ -96,7 +101,7 @@ describe('convertToPercentileColumn', () => { test('should return null if field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -105,13 +110,13 @@ describe('convertToPercentileColumn', () => { mockGetFieldByName.mockReturnValueOnce(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToPercentileColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return percentile rank column for percentiles', () => { - expect(convertToPercentileColumn({ agg, dataView })).toEqual( + expect(convertToPercentileColumn({ agg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', @@ -126,7 +131,7 @@ describe('convertToPercentileColumn', () => { }); test('should return percentile rank column for single percentile', () => { - expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect(convertToPercentileColumn({ agg: singlePercentileRankAgg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts index de9d4e088b636..9989db1c5dda7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile.ts @@ -51,6 +51,7 @@ const getPercent = ( export const convertToPercentileColumn = ( { + visType, agg, dataView, }: CommonColumnConverterArgs, @@ -74,7 +75,7 @@ export const convertToPercentileColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts index 8a696d51d871b..afeaa9899d107 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.test.ts @@ -24,6 +24,7 @@ jest.mock('../utils', () => ({ })); describe('convertToPercentileRankColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = dataView.fields[0].displayName; const aggId = 'pr.10'; @@ -71,23 +72,27 @@ describe('convertToPercentileRankColumn', () => { Partial | null ] >([ - ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView }], null], + ['null if no percents', [{ agg: { ...agg, aggId: 'pr' }, dataView, visType }], null], [ 'null if no value', - [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView }], + [{ agg: { ...singlePercentileRankAgg, aggParams: undefined }, dataView, visType }], + null, + ], + ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView, visType }], null], + ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView, visType }], null], + [ + 'null if aggId is invalid', + [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView, visType }], null, ], - ['null if no aggId', [{ agg: { ...agg, aggId: undefined }, dataView }], null], - ['null if no aggParams', [{ agg: { ...agg, aggParams: undefined }, dataView }], null], - ['null if aggId is invalid', [{ agg: { ...agg, aggId: 'pr.invalid' }, dataView }], null], [ 'null if values are undefined', - [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView }], + [{ agg: { ...agg, aggParams: { values: undefined, field } }, dataView, visType }], null, ], [ 'null if values are empty', - [{ agg: { ...agg, aggParams: { values: [], field } }, dataView }], + [{ agg: { ...agg, aggParams: { values: [], field } }, dataView, visType }], null, ], ])('should return %s', (_, input, expected) => { @@ -100,7 +105,7 @@ describe('convertToPercentileRankColumn', () => { test('should return null if field is not specified', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -109,13 +114,13 @@ describe('convertToPercentileRankColumn', () => { mockGetFieldByName.mockReturnValueOnce(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToPercentileRankColumn({ agg, dataView })).toBeNull(); + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return percentile rank column for percentile ranks', () => { - expect(convertToPercentileRankColumn({ agg, dataView })).toEqual( + expect(convertToPercentileRankColumn({ agg, dataView, visType })).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', @@ -130,7 +135,9 @@ describe('convertToPercentileRankColumn', () => { }); test('should return percentile rank column for single percentile rank', () => { - expect(convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView })).toEqual( + expect( + convertToPercentileRankColumn({ agg: singlePercentileRankAgg, dataView, visType }) + ).toEqual( expect.objectContaining({ dataType: 'number', label: 'someOtherLabel', diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts index 5124a26543552..8fb55789dd6a7 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/percentile_rank.ts @@ -50,6 +50,7 @@ const getPercent = ( export const convertToPercentileRankColumn = ( { + visType, agg, dataView, }: CommonColumnConverterArgs, @@ -69,7 +70,7 @@ export const convertToPercentileRankColumn = ( } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts index 8f535c28c8264..5a754fd1c9466 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.test.ts @@ -60,7 +60,6 @@ describe('convertToRangeColumn', () => { params: { type: RANGE_MODES.Histogram, maxBars: 'auto', - ranges: [], }, }, ], diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts index 6a9f96fd5ad1e..98200c321935c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/range.ts @@ -27,18 +27,17 @@ export const convertToRangeParams = ( return { type: RANGE_MODES.Histogram, maxBars: aggParams.maxBars ?? 'auto', - ranges: [], + includeEmptyRows: aggParams.min_doc_count, }; } else { return { type: RANGE_MODES.Range, maxBars: 'auto', - ranges: - aggParams.ranges?.map((range) => ({ - label: range.label, - from: range.from ?? null, - to: range.to ?? null, - })) ?? [], + ranges: aggParams.ranges?.map((range) => ({ + label: range.label, + from: range.from ?? null, + to: range.to ?? null, + })), }; } }; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts index 759620650b8a6..6adde7004b69a 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.test.ts @@ -23,6 +23,7 @@ jest.mock('../../../vis_schemas', () => ({ })); describe('convertToSiblingPipelineColumns', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const aggId = 'agg-id-1'; const agg: SchemaConfig = { @@ -46,7 +47,12 @@ describe('convertToSiblingPipelineColumns', () => { test('should return null if aggParams are not defined', () => { expect( - convertToSiblingPipelineColumns({ agg: { ...agg, aggParams: undefined }, aggs: [], dataView }) + convertToSiblingPipelineColumns({ + agg: { ...agg, aggParams: undefined }, + aggs: [], + dataView, + visType, + }) ).toBeNull(); expect(mockConvertMetricToColumns).toBeCalledTimes(0); }); @@ -57,6 +63,7 @@ describe('convertToSiblingPipelineColumns', () => { agg: { ...agg, aggParams: { customMetric: undefined } }, aggs: [], dataView, + visType, }) ).toBeNull(); expect(mockConvertMetricToColumns).toBeCalledTimes(0); @@ -64,7 +71,7 @@ describe('convertToSiblingPipelineColumns', () => { test('should return null if sibling agg is not supported', () => { mockConvertMetricToColumns.mockReturnValue(null); - expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toBeNull(); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView, visType })).toBeNull(); expect(mockConvertToSchemaConfig).toBeCalledTimes(1); expect(mockConvertMetricToColumns).toBeCalledTimes(1); }); @@ -72,7 +79,7 @@ describe('convertToSiblingPipelineColumns', () => { test('should return column', () => { const column = { operationType: 'formula' }; mockConvertMetricToColumns.mockReturnValue([column]); - expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView })).toEqual(column); + expect(convertToSiblingPipelineColumns({ agg, aggs: [], dataView, visType })).toEqual(column); expect(mockConvertToSchemaConfig).toBeCalledTimes(1); expect(mockConvertMetricToColumns).toBeCalledTimes(1); }); diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts index a8389cb8601e4..c77500a55d5d1 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/sibling_pipeline.ts @@ -22,11 +22,12 @@ export const convertToSiblingPipelineColumns = ( return null; } - const customMetricColumn = convertMetricToColumns( - { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, - columnConverterArgs.dataView, - columnConverterArgs.aggs - ); + const customMetricColumn = convertMetricToColumns({ + agg: { ...convertToSchemaConfig(aggParams.customMetric), label, aggId }, + dataView: columnConverterArgs.dataView, + aggs: columnConverterArgs.aggs, + visType: columnConverterArgs.visType, + }); if (!customMetricColumn) { return null; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts index cbb1f03a6dc2e..c786d6b8c3a6f 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.test.ts @@ -22,6 +22,7 @@ jest.mock('../utils', () => ({ })); describe('convertToStdDeviationFormulaColumns', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const stdLowerAggId = 'agg-id.std_lower'; const stdUpperAggId = 'agg-id.std_upper'; @@ -51,22 +52,25 @@ describe('convertToStdDeviationFormulaColumns', () => { test.each< [string, Parameters, Partial | null] - >([['null if no aggId is passed', [{ agg: { ...agg, aggId: undefined }, dataView }], null]])( - 'should return %s', - (_, input, expected) => { - if (expected === null) { - expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); - } else { - expect(convertToStdDeviationFormulaColumns(...input)).toEqual( - expect.objectContaining(expected) - ); - } + >([ + [ + 'null if no aggId is passed', + [{ agg: { ...agg, aggId: undefined }, dataView, visType }], + null, + ], + ])('should return %s', (_, input, expected) => { + if (expected === null) { + expect(convertToStdDeviationFormulaColumns(...input)).toBeNull(); + } else { + expect(convertToStdDeviationFormulaColumns(...input)).toEqual( + expect.objectContaining(expected) + ); } - ); + }); test('should return null if field is not present', () => { mockGetFieldNameFromField.mockReturnValue(null); - expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(convertToStdDeviationFormulaColumns({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(0); }); @@ -74,14 +78,14 @@ describe('convertToStdDeviationFormulaColumns', () => { test("should return null if field doesn't exist in dataView", () => { mockGetFieldByName.mockReturnValue(null); dataView.getFieldByName = mockGetFieldByName; - expect(convertToStdDeviationFormulaColumns({ agg, dataView })).toBeNull(); + expect(convertToStdDeviationFormulaColumns({ agg, dataView, visType })).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); }); test('should return null if agg id is invalid', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView }) + convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: 'some-id' }, dataView, visType }) ).toBeNull(); expect(mockGetFieldNameFromField).toBeCalledTimes(1); expect(dataView.getFieldByName).toBeCalledTimes(1); @@ -89,7 +93,11 @@ describe('convertToStdDeviationFormulaColumns', () => { test('should return formula column for lower std deviation', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdLowerAggId }, dataView }) + convertToStdDeviationFormulaColumns({ + agg: { ...agg, aggId: stdLowerAggId }, + dataView, + visType, + }) ).toEqual( expect.objectContaining({ label, @@ -102,7 +110,11 @@ describe('convertToStdDeviationFormulaColumns', () => { test('should return formula column for upper std deviation', () => { expect( - convertToStdDeviationFormulaColumns({ agg: { ...agg, aggId: stdUpperAggId }, dataView }) + convertToStdDeviationFormulaColumns({ + agg: { ...agg, aggId: stdUpperAggId }, + dataView, + visType, + }) ).toEqual( expect.objectContaining({ label, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts index f2c218d429bdf..fe4e854759d8f 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/std_deviation.ts @@ -50,7 +50,7 @@ export const getStdDeviationFormula = ( }; export const convertToStdDeviationFormulaColumns = ( - { agg, dataView }: CommonColumnConverterArgs, + { visType, agg, dataView }: CommonColumnConverterArgs, reducedTimeRange?: string ) => { const { aggId } = agg; @@ -64,7 +64,7 @@ export const convertToStdDeviationFormulaColumns = ( return null; } const field = dataView.getFieldByName(fieldName); - if (!isFieldValid(field, SUPPORTED_METRICS[agg.aggType])) { + if (!isFieldValid(visType, field, SUPPORTED_METRICS[agg.aggType])) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts index 17a8ccf26c369..61f3f3961b6dc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/supported_metrics.ts @@ -18,10 +18,12 @@ interface AggWithFormula { formula: string; } +type SupportedDataTypes = { [key: string]: readonly string[] } & { default: readonly string[] }; + export type AggOptions = { isFullReference: boolean; isFieldRequired: boolean; - supportedDataTypes: readonly string[]; + supportedDataTypes: SupportedDataTypes; } & (T extends Exclude ? Agg : AggWithFormula); // list of supported TSVB aggregation types in Lens @@ -62,9 +64,9 @@ export type SupportedMetrics = LocalSupportedMetrics & { [Key in UnsupportedSupportedMetrics]?: null; }; -const supportedDataTypesWithDate = ['number', 'date', 'histogram'] as const; -const supportedDataTypes = ['number', 'histogram'] as const; -const extendedSupportedDataTypes = [ +const supportedDataTypesWithDate: readonly string[] = ['number', 'date', 'histogram']; +const supportedDataTypes: readonly string[] = ['number', 'histogram']; +const extendedSupportedDataTypes: readonly string[] = [ 'string', 'boolean', 'number', @@ -74,44 +76,44 @@ const extendedSupportedDataTypes = [ 'date', 'date_range', 'murmur3', -] as const; +]; export const SUPPORTED_METRICS: SupportedMetrics = { avg: { name: 'average', isFullReference: false, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, cardinality: { name: 'unique_count', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, count: { name: 'count', isFullReference: false, isFieldRequired: false, - supportedDataTypes: [], + supportedDataTypes: { default: ['number'] }, }, moving_avg: { name: 'moving_average', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, derivative: { name: 'differences', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, cumulative_sum: { name: 'cumulative_sum', isFullReference: true, isFieldRequired: true, - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, avg_bucket: { name: 'formula', @@ -119,7 +121,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_average', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, max_bucket: { name: 'formula', @@ -127,7 +129,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_max', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, min_bucket: { name: 'formula', @@ -135,7 +137,7 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_min', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, sum_bucket: { name: 'formula', @@ -143,79 +145,91 @@ export const SUPPORTED_METRICS: SupportedMetrics = { isFieldRequired: true, isFormula: true, formula: 'overall_sum', - supportedDataTypes: ['number'], + supportedDataTypes: { default: ['number'] }, }, max: { name: 'max', isFullReference: false, isFieldRequired: true, - supportedDataTypes: supportedDataTypesWithDate, + supportedDataTypes: { + default: ['number'], + heatmap: ['number'], + line: ['number'], + area: ['number'], + histogram: ['number'], + }, }, min: { name: 'min', isFullReference: false, isFieldRequired: true, - supportedDataTypes: supportedDataTypesWithDate, + supportedDataTypes: { + default: supportedDataTypesWithDate, + heatmap: ['number'], + line: ['number'], + area: ['number'], + histogram: ['number'], + }, }, percentiles: { name: 'percentile', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, single_percentile: { name: 'percentile', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, percentile_ranks: { name: 'percentile_rank', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, single_percentile_rank: { name: 'percentile_rank', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, sum: { name: 'sum', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, top_hits: { name: 'last_value', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, top_metrics: { name: 'last_value', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, value_count: { name: 'count', isFullReference: false, isFieldRequired: true, - supportedDataTypes: extendedSupportedDataTypes, + supportedDataTypes: { default: extendedSupportedDataTypes }, }, std_dev: { name: 'standard_deviation', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, median: { name: 'median', isFullReference: false, isFieldRequired: true, - supportedDataTypes, + supportedDataTypes: { default: supportedDataTypes }, }, } as const; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts index d214ec74b09b1..516ad6b196095 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.test.ts @@ -23,6 +23,7 @@ jest.mock('../../../vis_schemas', () => ({ })); describe('convertToDateHistogramColumn', () => { + const visType = 'heatmap'; const aggId = `some-id`; const aggParams: AggParamsTerms = { field: stubLogstashDataView.fields[0].name, @@ -79,6 +80,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -95,6 +97,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -107,6 +110,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'alphabetical' }, orderDirection: 'asc', @@ -123,6 +128,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -135,6 +141,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'column', columnId: metricColumns[0].columnId }, orderAgg: metricColumns[0], @@ -152,6 +160,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -170,6 +179,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -188,6 +198,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -208,6 +219,7 @@ describe('convertToDateHistogramColumn', () => { dataView: stubLogstashDataView, aggs, metricColumns, + visType, }, '', false, @@ -220,6 +232,8 @@ describe('convertToDateHistogramColumn', () => { size: 5, include: [], exclude: [], + includeIsRegex: false, + excludeIsRegex: false, parentFormat: { id: 'terms' }, orderBy: { type: 'custom' }, orderAgg: metricColumns[0], diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts index 0a50390ec469e..a54a3857e20f6 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/terms.ts @@ -23,6 +23,7 @@ const getOrderByWithAgg = ({ agg, dataView, aggs, + visType, metricColumns, }: CommonBucketConverterArgs): OrderByWithAgg | null => { if (!agg.aggParams) { @@ -37,11 +38,12 @@ const getOrderByWithAgg = ({ if (!agg.aggParams.orderAgg) { return null; } - const orderMetricColumn = convertMetricToColumns( - convertToSchemaConfig(agg.aggParams.orderAgg), + const orderMetricColumn = convertMetricToColumns({ + agg: convertToSchemaConfig(agg.aggParams.orderAgg), dataView, - aggs - ); + aggs, + visType, + }); if (!orderMetricColumn) { return null; } @@ -68,35 +70,43 @@ const getOrderByWithAgg = ({ }; }; +const filterOutEmptyValues = (values: string | Array): number[] | string[] => { + if (typeof values === 'string') { + return Boolean(values) ? [values] : []; + } + + return values.filter((v): v is string | number => { + if (typeof v === 'string') { + return Boolean(v); + } + return true; + }) as string[] | number[]; +}; + export const convertToTermsParams = ({ agg, dataView, aggs, metricColumns, + visType, }: CommonBucketConverterArgs): TermsParams | null => { if (!agg.aggParams) { return null; } - const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns }); + const orderByWithAgg = getOrderByWithAgg({ agg, dataView, aggs, metricColumns, visType }); if (orderByWithAgg === null) { return null; } + const exclude = agg.aggParams.exclude ? filterOutEmptyValues(agg.aggParams.exclude) : []; + const include = agg.aggParams.include ? filterOutEmptyValues(agg.aggParams.include) : []; return { size: agg.aggParams.size ?? 10, - include: agg.aggParams.include - ? Array.isArray(agg.aggParams.include) - ? agg.aggParams.include - : [agg.aggParams.include] - : [], - includeIsRegex: agg.aggParams.includeIsRegex, - exclude: agg.aggParams.exclude - ? Array.isArray(agg.aggParams.exclude) - ? agg.aggParams.exclude - : [agg.aggParams.exclude] - : [], - excludeIsRegex: agg.aggParams.excludeIsRegex, + include, + exclude, + includeIsRegex: Boolean(include.length && agg.aggParams.includeIsRegex), + excludeIsRegex: Boolean(exclude.length && agg.aggParams.excludeIsRegex), otherBucket: agg.aggParams.otherBucket, orderDirection: agg.aggParams.order?.value ?? 'desc', parentFormat: { id: 'terms' }, @@ -107,7 +117,7 @@ export const convertToTermsParams = ({ export const convertToTermsColumn = ( aggId: string, - { agg, dataView, aggs, metricColumns }: CommonBucketConverterArgs, + { agg, dataView, aggs, metricColumns, visType }: CommonBucketConverterArgs, label: string, isSplit: boolean ): TermsColumn | null => { @@ -121,7 +131,7 @@ export const convertToTermsColumn = ( return null; } - const params = convertToTermsParams({ agg, dataView, aggs, metricColumns }); + const params = convertToTermsParams({ agg, dataView, aggs, metricColumns, visType }); if (!params) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts index 8e6f9ec9443bb..97ccba39303fc 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/convert/types.ts @@ -64,6 +64,7 @@ export interface CommonColumnConverterArgs< > { agg: SchemaConfig; dataView: DataView; + visType: string; } export interface ExtendedColumnConverterArgs< @@ -75,6 +76,7 @@ export interface ExtendedColumnConverterArgs< export interface CommonBucketConverterArgs< Agg extends SupportedAggregation = SupportedAggregation > { + visType: string; agg: SchemaConfig; dataView: DataView; metricColumns: AggBasedColumn[]; diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts index 95e128e22b092..72cd07ba03f7c 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.test.ts @@ -29,7 +29,7 @@ jest.mock('../utils', () => ({ })); const dataView = stubLogstashDataView; - +const visType = 'heatmap'; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ { @@ -97,7 +97,7 @@ describe('getFormulaForPipelineAgg', () => { test.each<[string, Parameters, () => void, string | null]>([ [ 'null if custom metric is invalid', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValue(null); }, @@ -105,7 +105,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'null if custom metric type is not supported', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValue({ aggType: METRIC_TYPES.GEO_BOUNDS, @@ -115,7 +115,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported pipeline agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg .mockReturnValueOnce({ @@ -135,7 +135,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported not pipeline agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.AVG, @@ -149,7 +149,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is parent pipeline agg and custom metric is valid and supported percentile rank agg', - [{ agg: aggs[0] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[0] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.PERCENTILE_RANKS, @@ -163,7 +163,7 @@ describe('getFormulaForPipelineAgg', () => { ], [ 'correct formula if agg is sibling pipeline agg and custom metric is valid and supported agg', - [{ agg: aggs[1] as SchemaConfig, aggs, dataView }], + [{ agg: aggs[1] as SchemaConfig, aggs, dataView, visType }], () => { mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ aggType: METRIC_TYPES.AVG, @@ -212,6 +212,7 @@ describe('getFormulaForPipelineAgg', () => { agg: aggs[1] as SchemaConfig, aggs, dataView, + visType, }); expect(agg).toBeNull(); }); @@ -244,6 +245,7 @@ describe('getFormulaForPipelineAgg', () => { agg: aggs[1] as SchemaConfig, aggs, dataView, + visType, }); expect(agg).toBeNull(); }); @@ -270,6 +272,7 @@ describe('getFormulaForAgg', () => { agg: { ...aggs[0], aggType: METRIC_TYPES.GEO_BOUNDS, aggParams: { field } }, aggs, dataView, + visType, }, ], () => {}, @@ -277,7 +280,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct pipeline formula if agg is valid pipeline agg', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockIsPipeline.mockReturnValue(true); mockGetMetricFromParentPipelineAgg.mockReturnValueOnce({ @@ -292,7 +295,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct percentile formula if agg is valid percentile agg', - [{ agg: aggs[2], aggs, dataView }], + [{ agg: aggs[2], aggs, dataView, visType }], () => { mockIsPercentileAgg.mockReturnValue(true); }, @@ -300,7 +303,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct percentile rank formula if agg is valid percentile rank agg', - [{ agg: aggs[3], aggs, dataView }], + [{ agg: aggs[3], aggs, dataView, visType }], () => { mockIsPercentileRankAgg.mockReturnValue(true); }, @@ -308,7 +311,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct standart deviation formula if agg is valid standart deviation agg', - [{ agg: aggs[4], aggs, dataView }], + [{ agg: aggs[4], aggs, dataView, visType }], () => { mockIsStdDevAgg.mockReturnValue(true); }, @@ -316,7 +319,7 @@ describe('getFormulaForAgg', () => { ], [ 'correct metric formula if agg is valid other metric agg', - [{ agg: aggs[5], aggs, dataView }], + [{ agg: aggs[5], aggs, dataView, visType }], () => {}, 'average(bytes)', ], @@ -395,6 +398,7 @@ describe('getFormulaForAgg', () => { >, aggs, dataView, + visType, }); expect(result).toBeNull(); }); @@ -467,6 +471,7 @@ describe('getFormulaForAgg', () => { >, aggs, dataView, + visType, }); expect(result).toBeNull(); } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts index 276ac54e2fc3d..4492cd58ac230 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/formula.ts @@ -66,7 +66,7 @@ const isDataViewField = (field: string | DataViewField): field is DataViewField return false; }; -const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { +const isValidAgg = (visType: string, agg: SchemaConfig, dataView: DataView) => { const aggregation = SUPPORTED_METRICS[agg.aggType]; if (!aggregation) { return false; @@ -77,7 +77,7 @@ const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { } const sourceField = getFieldNameFromField(agg.aggParams?.field); const field = dataView.getFieldByName(sourceField!); - if (!isFieldValid(field, aggregation)) { + if (!isFieldValid(visType, field, aggregation)) { return false; } } @@ -86,13 +86,14 @@ const isValidAgg = (agg: SchemaConfig, dataView: DataView) => { }; const getFormulaForAggsWithoutParams = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string | undefined, reducedTimeRange?: string ) => { const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -101,6 +102,7 @@ const getFormulaForAggsWithoutParams = ( }; const getFormulaForPercentileRanks = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string | undefined, @@ -108,7 +110,7 @@ const getFormulaForPercentileRanks = ( ) => { const value = Number(agg.aggId?.split('.')[1]); const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -117,6 +119,7 @@ const getFormulaForPercentileRanks = ( }; const getFormulaForPercentile = ( + visType: string, agg: SchemaConfig, dataView: DataView, selector: string, @@ -124,7 +127,7 @@ const getFormulaForPercentile = ( ) => { const percentile = Number(agg.aggId?.split('.')[1]); const op = SUPPORTED_METRICS[agg.aggType]; - if (!isValidAgg(agg, dataView) || !op) { + if (!isValidAgg(visType, agg, dataView) || !op) { return null; } @@ -138,6 +141,7 @@ const getFormulaForSubMetric = ({ agg, dataView, aggs, + visType, }: ExtendedColumnConverterArgs): string | null => { const op = SUPPORTED_METRICS[agg.aggType]; if (!op) { @@ -148,12 +152,13 @@ const getFormulaForSubMetric = ({ PARENT_PIPELINE_OPS.includes(op.name) || SIBLING_PIPELINE_AGGS.includes(agg.aggType as METRIC_TYPES) ) { - return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView }); + return getFormulaForPipelineAgg({ agg: agg as PipelineAggs, aggs, dataView, visType }); } if (METRIC_OPS_WITHOUT_PARAMS.includes(op.name)) { const metricAgg = agg as MetricAggsWithoutParams; return getFormulaForAggsWithoutParams( + visType, metricAgg, dataView, metricAgg.aggParams && 'field' in metricAgg.aggParams @@ -168,6 +173,7 @@ const getFormulaForSubMetric = ({ const percentileRanksAgg = agg as SchemaConfig; return getFormulaForPercentileRanks( + visType, percentileRanksAgg, dataView, percentileRanksAgg.aggParams?.field @@ -181,6 +187,7 @@ export const getFormulaForPipelineAgg = ({ agg, dataView, aggs, + visType, }: ExtendedColumnConverterArgs< | METRIC_TYPES.CUMULATIVE_SUM | METRIC_TYPES.DERIVATIVE @@ -205,6 +212,7 @@ export const getFormulaForPipelineAgg = ({ agg: metricAgg, aggs, dataView, + visType, }); if (subFormula === null) { return null; @@ -222,13 +230,15 @@ export const getFormulaForAgg = ({ agg, aggs, dataView, + visType, }: ExtendedColumnConverterArgs) => { if (isPipeline(agg)) { - return getFormulaForPipelineAgg({ agg, aggs, dataView }); + return getFormulaForPipelineAgg({ agg, aggs, dataView, visType }); } if (isPercentileAgg(agg)) { return getFormulaForPercentile( + visType, agg, dataView, getFieldNameFromField(agg.aggParams?.field) ?? '' @@ -237,6 +247,7 @@ export const getFormulaForAgg = ({ if (isPercentileRankAgg(agg)) { return getFormulaForPercentileRanks( + visType, agg, dataView, getFieldNameFromField(agg.aggParams?.field) ?? '' @@ -244,13 +255,14 @@ export const getFormulaForAgg = ({ } if (isStdDevAgg(agg) && agg.aggId) { - if (!isValidAgg(agg, dataView)) { + if (!isValidAgg(visType, agg, dataView)) { return null; } return getStdDeviationFormula(agg.aggId, getFieldNameFromField(agg.aggParams?.field) ?? ''); } return getFormulaForAggsWithoutParams( + visType, agg, dataView, isMetricWithField(agg) ? getFieldNameFromField(agg.aggParams?.field) ?? '' : '' diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts index 1cf3ff0b84064..c7674bf6603c0 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.test.ts @@ -9,6 +9,7 @@ import { METRIC_TYPES } from '@kbn/data-plugin/common'; import { stubLogstashDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { SchemaConfig } from '../../..'; +import { ExtendedColumnConverterArgs } from '../convert'; import { convertMetricToColumns } from './metrics'; const mockConvertMetricAggregationColumnWithoutSpecialParams = jest.fn(); @@ -37,6 +38,8 @@ jest.mock('../convert', () => ({ convertToColumnInPercentageMode: jest.fn(() => mockConvertToColumnInPercentageMode()), })); +const visType = 'heatmap'; + describe('convertMetricToColumns invalid cases', () => { const dataView = stubLogstashDataView; @@ -55,13 +58,18 @@ describe('convertMetricToColumns invalid cases', () => { mockConvertToCumulativeSumAggColumn.mockReturnValue(null); }); + const aggs: ExtendedColumnConverterArgs['aggs'] = []; + test.each<[string, Parameters, null, jest.Mock | undefined]>([ [ 'null if agg is not supported', [ - { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.GEO_BOUNDS } as unknown as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -70,9 +78,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg AVG is not valid', [ - { aggType: METRIC_TYPES.AVG } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -81,9 +92,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MIN is not valid', [ - { aggType: METRIC_TYPES.MIN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + aggs: [], + visType, + }, { isPercentageMode: false }, ], null, @@ -92,9 +106,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MAX is not valid', [ - { aggType: METRIC_TYPES.MAX } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -103,9 +120,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SUM is not valid', [ - { aggType: METRIC_TYPES.SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -114,9 +134,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg COUNT is not valid', [ - { aggType: METRIC_TYPES.COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -125,9 +148,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg CARDINALITY is not valid', [ - { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -136,9 +162,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg VALUE_COUNT is not valid', [ - { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -147,9 +176,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MEDIAN is not valid', [ - { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -158,9 +190,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg STD_DEV is not valid', [ - { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -169,9 +204,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg PERCENTILES is not valid', [ - { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -180,9 +218,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SINGLE_PERCENTILE is not valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -191,9 +232,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg PERCENTILE_RANKS is not valid', [ - { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -202,9 +246,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SINGLE_PERCENTILE_RANK is not valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -213,9 +260,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg TOP_HITS is not valid', [ - { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -224,9 +274,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg TOP_METRICS is not valid', [ - { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -235,9 +288,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg CUMULATIVE_SUM is not valid', [ - { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -246,9 +302,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg DERIVATIVE is not valid', [ - { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -257,9 +316,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MOVING_FN is not valid', [ - { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -268,9 +330,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SUM_BUCKET is not valid', [ - { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -279,9 +344,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MIN_BUCKET is not valid', [ - { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -290,9 +358,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg MAX_BUCKET is not valid', [ - { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -301,9 +372,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg AVG_BUCKET is not valid', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs: [], + visType, + }, { isPercentageMode: false }, ], null, @@ -312,9 +386,12 @@ describe('convertMetricToColumns invalid cases', () => { [ 'null if supported agg SERIAL_DIFF is not valid', [ - { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SERIAL_DIFF } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], null, @@ -330,6 +407,7 @@ describe('convertMetricToColumns invalid cases', () => { }); describe('convertMetricToColumns valid cases', () => { const dataView = stubLogstashDataView; + const aggs: ExtendedColumnConverterArgs['aggs'] = []; beforeEach(() => { jest.clearAllMocks(); @@ -353,9 +431,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg AVG is valid', [ - { aggType: METRIC_TYPES.AVG } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -364,9 +445,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MIN is valid', [ - { aggType: METRIC_TYPES.MIN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -375,9 +459,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MAX is valid', [ - { aggType: METRIC_TYPES.MAX } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -386,9 +473,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SUM is valid', [ - { aggType: METRIC_TYPES.SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -397,9 +487,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg COUNT is valid', [ - { aggType: METRIC_TYPES.COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -408,9 +501,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg CARDINALITY is valid', [ - { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CARDINALITY } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -419,9 +515,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg VALUE_COUNT is valid', [ - { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.VALUE_COUNT } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -430,9 +529,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MEDIAN is valid', [ - { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MEDIAN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -441,9 +543,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg STD_DEV is valid', [ - { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.STD_DEV } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -452,9 +557,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg PERCENTILES is valid', [ - { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILES } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -463,9 +571,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SINGLE_PERCENTILE is valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -474,9 +585,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg PERCENTILE_RANKS is valid', [ - { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.PERCENTILE_RANKS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -485,9 +599,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SINGLE_PERCENTILE_RANK is valid', [ - { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SINGLE_PERCENTILE_RANK } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -496,9 +613,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg TOP_HITS is valid', [ - { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_HITS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -507,9 +627,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg TOP_METRICS is valid', [ - { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.TOP_METRICS } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -518,9 +641,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg CUMULATIVE_SUM is valid', [ - { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.CUMULATIVE_SUM } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -529,9 +655,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg DERIVATIVE is valid', [ - { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.DERIVATIVE } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -540,9 +669,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MOVING_FN is valid', [ - { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MOVING_FN } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -551,9 +683,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg SUM_BUCKET is valid', [ - { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.SUM_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -562,9 +697,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MIN_BUCKET is valid', [ - { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MIN_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -573,9 +711,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg MAX_BUCKET is valid', [ - { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.MAX_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -584,9 +725,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'array of columns if supported agg AVG_BUCKET is valid', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: false }, ], result, @@ -595,9 +739,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'column in percentage mode without range if percentageMode is enabled ', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: true, min: 0, max: 100 }, ], result, @@ -606,9 +753,12 @@ describe('convertMetricToColumns valid cases', () => { [ 'column in percentage mode with range if percentageMode is enabled ', [ - { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, - dataView, - [], + { + agg: { aggType: METRIC_TYPES.AVG_BUCKET } as SchemaConfig, + dataView, + aggs, + visType, + }, { isPercentageMode: true, min: 0, max: 100 }, ], result, diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts index be4c92cd4ec7f..5d765a6f286ba 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/metrics.ts @@ -7,8 +7,7 @@ */ import { METRIC_TYPES } from '@kbn/data-plugin/common'; -import type { DataView } from '@kbn/data-views-plugin/common'; -import { PercentageModeConfig, SchemaConfig } from '../../..'; +import { PercentageModeConfig } from '../../..'; import { convertMetricAggregationColumnWithoutSpecialParams, convertToOtherParentPipelineAggColumns, @@ -20,14 +19,13 @@ import { convertToCumulativeSumAggColumn, AggBasedColumn, convertToColumnInPercentageMode, + ExtendedColumnConverterArgs, } from '../convert'; import { SUPPORTED_METRICS } from '../convert/supported_metrics'; import { getValidColumns } from '../utils'; export const convertMetricToColumns = ( - agg: SchemaConfig, - dataView: DataView, - aggs: Array>, + { agg, dataView, aggs, visType }: ExtendedColumnConverterArgs, percentageModeConfig: PercentageModeConfig = { isPercentageMode: false } ): AggBasedColumn[] | null => { const supportedAgg = SUPPORTED_METRICS[agg.aggType]; @@ -38,7 +36,7 @@ export const convertMetricToColumns = ( if (percentageModeConfig.isPercentageMode) { const { isPercentageMode, ...minMax } = percentageModeConfig; - const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs }, minMax); + const formulaColumn = convertToColumnInPercentageMode({ agg, dataView, aggs, visType }, minMax); return getValidColumns(formulaColumn); } @@ -54,6 +52,7 @@ export const convertMetricToColumns = ( const columns = convertMetricAggregationColumnWithoutSpecialParams(supportedAgg, { agg, dataView, + visType, }); return getValidColumns(columns); } @@ -61,6 +60,7 @@ export const convertMetricToColumns = ( const columns = convertToStdDeviationFormulaColumns({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -68,6 +68,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -75,6 +76,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -82,6 +84,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileRankColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -89,6 +92,7 @@ export const convertMetricToColumns = ( const columns = convertToPercentileRankColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -97,6 +101,7 @@ export const convertMetricToColumns = ( const columns = convertToLastValueColumn({ agg, dataView, + visType, }); return getValidColumns(columns); } @@ -105,6 +110,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } @@ -114,6 +120,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } @@ -125,6 +132,7 @@ export const convertMetricToColumns = ( agg, dataView, aggs, + visType, }); return getValidColumns(columns); } diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts index 9855ce44b6602..fe6204d1fb2a1 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.test.ts @@ -24,6 +24,7 @@ jest.mock('../convert', () => ({ })); describe('getPercentageColumnFormulaColumn', () => { + const visType = 'heatmap'; const dataView = stubLogstashDataView; const field = stubLogstashDataView.fields[0].name; const aggs: Array> = [ @@ -52,7 +53,7 @@ describe('getPercentageColumnFormulaColumn', () => { >([ [ 'null if cannot build formula for provided agg', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue(null); }, @@ -60,7 +61,7 @@ describe('getPercentageColumnFormulaColumn', () => { ], [ 'null if cannot create formula column for provided arguments', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue('test-formula'); mockCreateFormulaColumn.mockReturnValue(null); @@ -69,7 +70,7 @@ describe('getPercentageColumnFormulaColumn', () => { ], [ 'formula column if provided arguments are valid', - [{ agg: aggs[0], aggs, dataView }], + [{ agg: aggs[0], aggs, dataView, visType }], () => { mockGetFormulaForAgg.mockReturnValue('test-formula'); mockCreateFormulaColumn.mockImplementation((formula) => ({ diff --git a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts index 773851a770db4..8d7194d5c25df 100644 --- a/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts +++ b/src/plugins/visualizations/common/convert_to_lens/lib/metrics/percentage_formula.ts @@ -14,8 +14,9 @@ export const getPercentageColumnFormulaColumn = ({ agg, aggs, dataView, + visType, }: ExtendedColumnConverterArgs): FormulaColumn | null => { - const metricFormula = getFormulaForAgg({ agg, aggs, dataView }); + const metricFormula = getFormulaForAgg({ agg, aggs, dataView, visType }); if (!metricFormula) { return null; } diff --git a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts index f62f61f0c50ab..8a6e70669dcf4 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/configurations.ts @@ -28,6 +28,9 @@ import { GaugeCentralMajorModes, CollapseFunctions, } from '../constants'; +import { ExpressionValueVisDimension } from '../../expression_functions'; + +export type ChartShapes = 'heatmap'; export type CollapseFunction = typeof CollapseFunctions[number]; @@ -277,9 +280,63 @@ export type GaugeVisConfiguration = GaugeState & { layerType: typeof LayerTypes.DATA; }; +export interface HeatmapLegendConfig { + isVisible: boolean; + position: Position; + maxLines?: number; + shouldTruncate?: boolean; + legendSize?: LegendSize; + type: 'heatmap_legend'; +} + +export interface HeatmapGridConfig { + strokeWidth?: number; + strokeColor?: string; + isCellLabelVisible: boolean; + isYAxisLabelVisible: boolean; + isYAxisTitleVisible: boolean; + yTitle?: string; + isXAxisLabelVisible: boolean; + isXAxisTitleVisible: boolean; + xTitle?: string; + type: 'heatmap_grid'; +} +export interface HeatmapArguments { + percentageMode?: boolean; + lastRangeIsRightOpen?: boolean; + showTooltip?: boolean; + highlightInHover?: boolean; + palette?: PaletteOutput; + xAccessor?: string | ExpressionValueVisDimension; + yAccessor?: string | ExpressionValueVisDimension; + valueAccessor?: string | ExpressionValueVisDimension; + splitRowAccessor?: string | ExpressionValueVisDimension; + splitColumnAccessor?: string | ExpressionValueVisDimension; + legend: HeatmapLegendConfig; + gridConfig: HeatmapGridConfig; + ariaLabel?: string; +} + +export type HeatmapLayerState = HeatmapArguments & { + layerId: string; + layerType: LayerType; + valueAccessor?: string; + xAccessor?: string; + yAccessor?: string; + shape: ChartShapes; +}; + +export type Palette = PaletteOutput & { accessor: string }; + +export type HeatmapConfiguration = HeatmapLayerState & { + // need to store the current accessor to reset the color stops at accessor change + palette?: Palette; +}; + export type Configuration = | XYConfiguration | TableVisConfiguration | PartitionVisConfiguration | MetricVisConfiguration - | GaugeVisConfiguration; + | GaugeVisConfiguration + | HeatmapConfiguration; diff --git a/src/plugins/visualizations/common/convert_to_lens/types/params.ts b/src/plugins/visualizations/common/convert_to_lens/types/params.ts index d66822921fb19..4623506496382 100644 --- a/src/plugins/visualizations/common/convert_to_lens/types/params.ts +++ b/src/plugins/visualizations/common/convert_to_lens/types/params.ts @@ -55,7 +55,7 @@ interface Range { export interface RangeParams extends FormatParams { type: RangeMode; maxBars: 'auto' | number; - ranges: Range[]; + ranges?: Range[]; includeEmptyRows?: boolean; parentFormat?: { id: string; diff --git a/src/plugins/visualizations/common/convert_to_lens/utils.ts b/src/plugins/visualizations/common/convert_to_lens/utils.ts index 6a875bf63bea4..88c2802c421ec 100644 --- a/src/plugins/visualizations/common/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/common/convert_to_lens/utils.ts @@ -18,7 +18,17 @@ export const isAnnotationsLayer = ( export const getIndexPatternIds = (layers: Layer[]) => layers.map(({ indexPatternId }) => indexPatternId); +const isValidFieldType = ( + visType: string, + { supportedDataTypes }: SupportedMetric, + field: DataViewField +) => { + const availableDataTypes = supportedDataTypes[visType] ?? supportedDataTypes.default; + return availableDataTypes.includes(field.type); +}; + export const isFieldValid = ( + visType: string, field: DataViewField | undefined, aggregation: SupportedMetric ): field is DataViewField => { @@ -26,7 +36,7 @@ export const isFieldValid = ( return false; } - if (field && (!field.aggregatable || !aggregation.supportedDataTypes.includes(field.type))) { + if (field && (!field.aggregatable || !isValidFieldType(visType, aggregation, field))) { return false; } diff --git a/src/plugins/visualizations/public/convert_to_lens/index.ts b/src/plugins/visualizations/public/convert_to_lens/index.ts index 73509d49157ae..46fca64199ae1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/index.ts +++ b/src/plugins/visualizations/public/convert_to_lens/index.ts @@ -8,8 +8,10 @@ export { getColumnsFromVis } from './schemas'; export { + convertToFiltersColumn, getPercentageColumnFormulaColumn, getPalette, + getPaletteFromStopsWithColors, getPercentageModeConfig, createStaticValueColumn, } from '../../common/convert_to_lens/lib'; diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts index 54975d08b8486..aa338db367988 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.test.ts @@ -70,7 +70,9 @@ describe('getColumnsFromVis', () => { ); const aggConfig = new AggConfig(aggConfigs, {} as AggConfigOptions); - const vis = {} as Vis; + const vis = { + type: { name: 'heatmap' }, + } as Vis; beforeEach(() => { jest.clearAllMocks(); mockGetVisSchemas.mockReturnValue({}); diff --git a/src/plugins/visualizations/public/convert_to_lens/schemas.ts b/src/plugins/visualizations/public/convert_to_lens/schemas.ts index 3a225e540faae..1b44f7cdffda1 100644 --- a/src/plugins/visualizations/public/convert_to_lens/schemas.ts +++ b/src/plugins/visualizations/public/convert_to_lens/schemas.ts @@ -33,6 +33,7 @@ const areVisSchemasValid = (visSchemas: Schemas, unsupported: Array>, metricsForLayer: Array>, @@ -52,7 +53,7 @@ const createLayer = ( dropEmptyRowsInDateHistogram?: boolean ) => { const metricColumns = metricsForLayer.flatMap((m) => - convertMetricToColumns(m, dataView, allMetrics, percentageModeConfig) + convertMetricToColumns({ agg: m, dataView, aggs: allMetrics, visType }, percentageModeConfig) ); if (metricColumns.includes(null)) { return null; @@ -60,6 +61,7 @@ const createLayer = ( const metricColumnsWithoutNull = metricColumns as AggBasedColumn[]; const { customBucketColumns, customBucketsMap } = getCustomBucketColumns( + visType, customBucketsWithMetricIds, metricColumnsWithoutNull, dataView, @@ -72,6 +74,7 @@ const createLayer = ( } const bucketColumns = getBucketColumns( + visType, visSchemas, buckets, dataView, @@ -84,6 +87,7 @@ const createLayer = ( } const splitBucketColumns = getBucketColumns( + visType, visSchemas, splits, dataView, @@ -181,6 +185,7 @@ export const getColumnsFromVis = ( c.metricIds.some((m) => metricAggIds.includes(m)) ); const layer = createLayer( + vis.type.name, visSchemas, aggs, metrics, @@ -197,6 +202,7 @@ export const getColumnsFromVis = ( } } else { const layer = createLayer( + vis.type.name, visSchemas, aggs, aggs, diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts index 50f667430a8cb..8c36b28452271 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.test.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.test.ts @@ -213,6 +213,7 @@ describe('getBucketCollapseFn', () => { describe('getBucketColumns', () => { const dataView = stubLogstashDataView; + const visType = 'heatmap'; beforeEach(() => { jest.clearAllMocks(); @@ -228,7 +229,7 @@ describe('getBucketColumns', () => { [bucketKey]: [], }; - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([]); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toEqual([]); expect(mockConvertBucketToColumns).toBeCalledTimes(0); }); @@ -254,7 +255,7 @@ describe('getBucketColumns', () => { }; mockConvertBucketToColumns.mockReturnValueOnce(null); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toBeNull(); expect(mockConvertBucketToColumns).toBeCalledTimes(1); }); @@ -280,7 +281,7 @@ describe('getBucketColumns', () => { }; mockConvertBucketToColumns.mockReturnValueOnce([null]); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toBeNull(); + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toBeNull(); expect(mockConvertBucketToColumns).toBeCalledTimes(1); }); test('should return columns', () => { @@ -319,7 +320,7 @@ describe('getBucketColumns', () => { mockConvertBucketToColumns.mockReturnValue(returnValue); - expect(getBucketColumns(visSchemas, keys, dataView, false, [])).toEqual([ + expect(getBucketColumns(visType, visSchemas, keys, dataView, false, [])).toEqual([ ...returnValue, ...returnValue, ]); @@ -592,6 +593,8 @@ describe('sortColumns', () => { }); describe('getColumnIds', () => { + const visType = 'heatmap'; + const colId1 = '0_agg_id'; const colId2 = '1_agg_id'; const colId3 = '2_agg_id'; @@ -694,6 +697,7 @@ describe('getColumnIds', () => { }); expect( getCustomBucketColumns( + visType, customBucketsWithMetricIds, [ { columnId: 'col-3', meta: { aggId: '3' } }, diff --git a/src/plugins/visualizations/public/convert_to_lens/utils.ts b/src/plugins/visualizations/public/convert_to_lens/utils.ts index ba05d29cdeea9..531746ff86d87 100644 --- a/src/plugins/visualizations/public/convert_to_lens/utils.ts +++ b/src/plugins/visualizations/public/convert_to_lens/utils.ts @@ -63,6 +63,7 @@ export const getBucketCollapseFn = ( }; export const getBucketColumns = ( + visType: string, visSchemas: Schemas, keys: Array, dataView: DataView, @@ -78,6 +79,7 @@ export const getBucketColumns = ( { agg: m, dataView, + visType, metricColumns, aggs: visSchemas.metric as Array>, }, @@ -154,6 +156,7 @@ export const sortColumns = ( export const getColumnIds = (columns: AggBasedColumn[]) => columns.map(({ columnId }) => columnId); export const getCustomBucketColumns = ( + visType: string, customBucketsWithMetricIds: Array<{ customBucket: IAggConfig; metricIds: string[]; @@ -167,7 +170,7 @@ export const getCustomBucketColumns = ( const customBucketsMap: Record = {}; customBucketsWithMetricIds.forEach((customBucketWithMetricIds) => { const customBucketColumn = convertBucketToColumns( - { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs }, + { agg: customBucketWithMetricIds.customBucket, dataView, metricColumns, aggs, visType }, true, dropEmptyRowsInDateHistogram ); diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/types.ts b/x-pack/plugins/lens/public/visualizations/heatmap/types.ts index 08913ad25a7d3..6be8d3b6e8d95 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/types.ts +++ b/x-pack/plugins/lens/public/visualizations/heatmap/types.ts @@ -10,7 +10,7 @@ import type { HeatmapArguments } from '@kbn/expression-heatmap-plugin/common'; import type { LayerType } from '../../../common'; export type ChartShapes = 'heatmap'; -export type HeatmapLayerState = HeatmapArguments & { +export type HeatmapLayerState = Omit & { layerId: string; layerType: LayerType; valueAccessor?: string; diff --git a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx index b8e98d03843a9..fc8ef976548b4 100644 --- a/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/heatmap/visualization.tsx @@ -17,7 +17,8 @@ import { ThemeServiceStart } from '@kbn/core/public'; import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { VIS_EVENT_TO_TRIGGER } from '@kbn/visualizations-plugin/public'; import { LayerTypes } from '@kbn/expression-xy-plugin/public'; -import type { OperationMetadata, Visualization } from '../../types'; +import { HeatmapConfiguration } from '@kbn/visualizations-plugin/common'; +import type { OperationMetadata, Suggestion, Visualization } from '../../types'; import type { HeatmapVisualizationState } from './types'; import { getSuggestions } from './suggestions'; import { @@ -33,6 +34,7 @@ import { import { HeatmapToolbar } from './toolbar_component'; import { HeatmapDimensionEditor } from './dimension_editor'; import { getSafePaletteParams } from './utils'; +import { FormBasedPersistedState } from '../..'; const groupLabelForHeatmap = i18n.translate('xpack.lens.heatmapVisualization.heatmapGroupLabel', { defaultMessage: 'Magnitude', @@ -525,4 +527,28 @@ export const getHeatmapVisualization = ({ ] : undefined; }, + + getSuggestionFromConvertToLensContext({ suggestions, context }) { + const allSuggestions = suggestions as Array< + Suggestion + >; + const suggestion: Suggestion = { + ...allSuggestions[0], + datasourceState: { + ...allSuggestions[0].datasourceState, + layers: allSuggestions.reduce( + (acc, s) => ({ + ...acc, + ...s.datasourceState?.layers, + }), + {} + ), + }, + visualizationState: { + ...allSuggestions[0].visualizationState, + ...(context.configuration as HeatmapConfiguration), + }, + }; + return suggestion; + }, }); diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts new file mode 100644 index 0000000000000..8556ae601daf9 --- /dev/null +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/heatmap.ts @@ -0,0 +1,219 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const { visualize, lens, visChart, timePicker, visEditor } = getPageObjects([ + 'visualize', + 'lens', + 'visChart', + 'timePicker', + 'visEditor', + ]); + + describe('Heatmap', function describeIndexTests() { + const isNewChartsLibraryEnabled = true; + + before(async () => { + await visualize.initTests(isNewChartsLibraryEnabled); + }); + + beforeEach(async () => { + await visualize.navigateToNewAggBasedVisualization(); + await visualize.clickHeatmapChart(); + await visualize.clickNewSearch(); + await timePicker.setDefaultAbsoluteRange(); + }); + + it('should show the "Edit Visualization in Lens" menu item if no X-axis was specified', async () => { + await visChart.waitForVisualizationRenderingStabilized(); + + expect(await visualize.hasNavigateToLensButton()).to.eql(true); + }); + + it('should show the "Edit Visualization in Lens" menu item', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + expect(await visualize.hasNavigateToLensButton()).to.eql(true); + }); + + it('should convert to Lens', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + // assert axes + expect(debugState.axes!.x[0].labels).to.eql(['win 8', 'win xp', 'win 7', 'ios', 'osx']); + expect(debugState.axes!.y[0].labels).to.eql(['']); + expect(debugState.heatmap!.cells.length).to.eql(5); + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 25', + name: '0 - 25', + }, + { color: '#86CB66', key: '25 - 50', name: '25 - 50' }, + { + color: '#FEFEBD', + key: '50 - 75', + name: '50 - 75', + }, + { + color: '#F88D52', + key: '75 - 100', + name: '75 - 100', + }, + ]); + }); + + it('should convert to Lens if Y-axis is defined, but X-axis is not', async () => { + await visEditor.clickBucket('Y-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.axes!.x[0].labels).to.eql(['*']); + expect(debugState.axes!.y[0].labels).to.eql(['win 8', 'win xp', 'win 7', 'ios', 'osx']); + expect(debugState.heatmap!.cells.length).to.eql(5); + }); + + it('should respect heatmap colors number', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visEditor.clickOptionsTab(); + await visEditor.changeHeatmapColorNumbers(6); + await visEditor.clickGo(); + await visChart.waitForVisualizationRenderingStabilized(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 16.67', + name: '0 - 16.67', + }, + { + color: '#4CB15D', + key: '16.67 - 33.33', + name: '16.67 - 33.33', + }, + { + color: '#B7E075', + key: '33.33 - 50', + name: '33.33 - 50', + }, + { + color: '#FEFEBD', + key: '50 - 66.67', + name: '50 - 66.67', + }, + { + color: '#FDBF6F', + key: '66.67 - 83.33', + name: '66.67 - 83.33', + }, + { + color: '#EA5839', + key: '83.33 - 100', + name: '83.33 - 100', + }, + ]); + }); + + it('should show respect heatmap custom color ranges', async () => { + await visEditor.clickBucket('X-axis'); + await visEditor.selectAggregation('Terms'); + await visEditor.selectField('machine.os.raw'); + await visEditor.clickGo(); + + await visEditor.clickOptionsTab(); + await visEditor.clickOptionsTab(); + await visEditor.clickEnableCustomRanges(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + await visEditor.clickAddRange(); + + await visEditor.clickGo(); + await visChart.waitForVisualizationRenderingStabilized(); + + await visualize.navigateToLensFromAnotherVisulization(); + await lens.waitForVisualization('heatmapChart'); + const debugState = await lens.getCurrentChartDebugState('heatmapChart'); + + if (!debugState) { + throw new Error('Debug state is not available'); + } + + expect(debugState.legend!.items).to.eql([ + { + color: '#006837', + key: '0 - 100', + name: '0 - 100', + }, + { + color: '#65BC62', + key: '100 - 200', + name: '100 - 200', + }, + { + color: '#D8EF8C', + key: '200 - 300', + name: '200 - 300', + }, + { + color: '#FEDF8B', + key: '300 - 400', + name: '300 - 400', + }, + { + color: '#F36D43', + key: '400 - 500', + name: '400 - 500', + }, + { + color: '#A50026', + key: '500 - 600', + name: '500 - 600', + }, + ]); + }); + }); +} diff --git a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts index 87c9d025893a1..52ef856d53ef6 100644 --- a/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts +++ b/x-pack/test/functional/apps/lens/open_in_lens/agg_based/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./gauge')); loadTestFile(require.resolve('./goal')); loadTestFile(require.resolve('./table')); + loadTestFile(require.resolve('./heatmap')); }); } From a21ed37a4c6565a310d4e8ea4fd123c539f53457 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Thu, 27 Oct 2022 09:19:01 -0600 Subject: [PATCH 12/28] [Maps] nest security layers in layer group (#144055) * [Maps] nest security layers in layer group * update security layers * [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> --- .../security/create_layer_descriptors.test.ts | 30 +++++++++++++++++ .../security/create_layer_descriptors.ts | 30 +++++++++++++---- .../components/embeddables/__mocks__/mock.ts | 26 +++++++++++++++ .../components/embeddables/map_config.test.ts | 7 ++++ .../components/embeddables/map_config.ts | 33 +++++++++++++++++-- 5 files changed, 117 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.test.ts b/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.test.ts index 2006c3eed6c2a..48cd66c47b53d 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.test.ts +++ b/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.test.ts @@ -42,6 +42,7 @@ describe('createLayerDescriptor', () => { label: 'apm-*-transaction* | Source Point', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -119,6 +120,7 @@ describe('createLayerDescriptor', () => { label: 'apm-*-transaction* | Destination point', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -196,6 +198,7 @@ describe('createLayerDescriptor', () => { label: 'apm-*-transaction* | Line', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -248,6 +251,13 @@ describe('createLayerDescriptor', () => { type: 'GEOJSON_VECTOR', visible: true, }, + { + id: '12345', + label: 'apm-*-transaction*', + sourceDescriptor: null, + type: 'LAYER_GROUP', + visible: true, + }, ]); }); @@ -262,6 +272,7 @@ describe('createLayerDescriptor', () => { label: 'filebeat-* | Source Point', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -339,6 +350,7 @@ describe('createLayerDescriptor', () => { label: 'filebeat-* | Destination point', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -410,6 +422,7 @@ describe('createLayerDescriptor', () => { label: 'filebeat-* | Line', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -462,6 +475,13 @@ describe('createLayerDescriptor', () => { type: 'GEOJSON_VECTOR', visible: true, }, + { + id: '12345', + label: 'filebeat-*', + sourceDescriptor: null, + type: 'LAYER_GROUP', + visible: true, + }, ]); }); @@ -476,6 +496,7 @@ describe('createLayerDescriptor', () => { label: 'traces-apm-opbean-node | Source Point', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -553,6 +574,7 @@ describe('createLayerDescriptor', () => { label: 'traces-apm-opbean-node | Destination point', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -624,6 +646,7 @@ describe('createLayerDescriptor', () => { label: 'traces-apm-opbean-node | Line', maxZoom: 24, minZoom: 0, + parent: '12345', disableTooltips: false, sourceDescriptor: { applyGlobalQuery: true, @@ -676,6 +699,13 @@ describe('createLayerDescriptor', () => { type: 'GEOJSON_VECTOR', visible: true, }, + { + id: '12345', + label: 'traces-apm-opbean-node', + sourceDescriptor: null, + type: 'LAYER_GROUP', + visible: true, + }, ]); }); }); diff --git a/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.ts b/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.ts index f295464126c96..792d61b08b9b4 100644 --- a/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.ts +++ b/x-pack/plugins/maps/public/classes/layers/wizards/solution_layers/security/create_layer_descriptors.ts @@ -23,6 +23,7 @@ import { VECTOR_STYLES, } from '../../../../../../common/constants'; import { GeoJsonVectorLayer } from '../../../vector_layer'; +import { LayerGroup } from '../../../layer_group'; import { VectorStyle } from '../../../../styles/vector/vector_style'; import { ESSearchSource } from '../../../../sources/es_search_source'; import { ESPewPewSource } from '../../../../sources/es_pew_pew_source'; @@ -48,7 +49,11 @@ function getDestinationField(indexPatternTitle: string) { return isApmIndex(indexPatternTitle) ? 'server.geo.location' : 'destination.geo.location'; } -function createSourceLayerDescriptor(indexPatternId: string, indexPatternTitle: string) { +function createSourceLayerDescriptor( + indexPatternId: string, + indexPatternTitle: string, + parentId: string +) { const sourceDescriptor = ESSearchSource.createDescriptor({ indexPatternId, geoField: getSourceField(indexPatternTitle), @@ -96,12 +101,17 @@ function createSourceLayerDescriptor(indexPatternId: string, indexPatternTitle: defaultMessage: '{indexPatternTitle} | Source Point', values: { indexPatternTitle }, }), + parent: parentId, sourceDescriptor, style: VectorStyle.createDescriptor(styleProperties), }); } -function createDestinationLayerDescriptor(indexPatternId: string, indexPatternTitle: string) { +function createDestinationLayerDescriptor( + indexPatternId: string, + indexPatternTitle: string, + parentId: string +) { const sourceDescriptor = ESSearchSource.createDescriptor({ indexPatternId, geoField: getDestinationField(indexPatternTitle), @@ -149,12 +159,17 @@ function createDestinationLayerDescriptor(indexPatternId: string, indexPatternTi defaultMessage: '{indexPatternTitle} | Destination point', values: { indexPatternTitle }, }), + parent: parentId, sourceDescriptor, style: VectorStyle.createDescriptor(styleProperties), }); } -function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: string) { +function createLineLayerDescriptor( + indexPatternId: string, + indexPatternTitle: string, + parentId: string +) { const sourceDescriptor = ESPewPewSource.createDescriptor({ indexPatternId, sourceGeoField: getSourceField(indexPatternTitle), @@ -195,6 +210,7 @@ function createLineLayerDescriptor(indexPatternId: string, indexPatternTitle: st defaultMessage: '{indexPatternTitle} | Line', values: { indexPatternTitle }, }), + parent: parentId, sourceDescriptor, style: VectorStyle.createDescriptor(styleProperties), }); @@ -204,9 +220,11 @@ export function createSecurityLayerDescriptors( indexPatternId: string, indexPatternTitle: string ): LayerDescriptor[] { + const layerGroupDescriptor = LayerGroup.createDescriptor({ label: indexPatternTitle }); return [ - createSourceLayerDescriptor(indexPatternId, indexPatternTitle), - createDestinationLayerDescriptor(indexPatternId, indexPatternTitle), - createLineLayerDescriptor(indexPatternId, indexPatternTitle), + createSourceLayerDescriptor(indexPatternId, indexPatternTitle, layerGroupDescriptor.id), + createDestinationLayerDescriptor(indexPatternId, indexPatternTitle, layerGroupDescriptor.id), + createLineLayerDescriptor(indexPatternId, indexPatternTitle, layerGroupDescriptor.id), + layerGroupDescriptor, ]; } diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts index abcaa079d3b20..e63fff5009152 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts @@ -18,6 +18,14 @@ export const mockAPMIndexPatternIds: IndexPatternMapping[] = [ { title: 'traces-apm*,logs-apm*,metrics-apm*,apm-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, ]; +export const mockLayerGroup = { + id: 'uuid.v4()', + label: 'filebeat-*', + sourceDescriptor: null, + type: LAYER_TYPE.LAYER_GROUP, + visible: true, +}; + export const mockSourceLayer = { sourceDescriptor: { id: 'uuid.v4()', @@ -64,6 +72,7 @@ export const mockSourceLayer = { }, }, id: 'uuid.v4()', + parent: 'uuid.v4()', label: `filebeat-* | Source Point`, minZoom: 0, maxZoom: 24, @@ -121,6 +130,7 @@ export const mockDestinationLayer = { }, }, id: 'uuid.v4()', + parent: 'uuid.v4()', label: `filebeat-* | Destination Point`, minZoom: 0, maxZoom: 24, @@ -176,6 +186,7 @@ export const mockClientLayer = { }, }, id: 'uuid.v4()', + parent: 'uuid.v4()', label: `apm-* | Client Point`, minZoom: 0, maxZoom: 24, @@ -238,6 +249,7 @@ export const mockServerLayer = { }, }, id: 'uuid.v4()', + parent: 'uuid.v4()', label: `apm-* | Server Point`, minZoom: 0, maxZoom: 24, @@ -307,6 +319,7 @@ export const mockLineLayer = { }, }, id: 'uuid.v4()', + parent: 'uuid.v4()', label: `filebeat-* | Line`, minZoom: 0, maxZoom: 24, @@ -371,6 +384,7 @@ export const mockClientServerLineLayer = { }, }, id: 'uuid.v4()', + parent: 'uuid.v4()', label: `apm-* | Line`, minZoom: 0, maxZoom: 24, @@ -399,6 +413,7 @@ export const mockLayerList = [ mockLineLayer, mockDestinationLayer, mockSourceLayer, + mockLayerGroup, ]; export const mockLayerListDouble = [ @@ -416,9 +431,11 @@ export const mockLayerListDouble = [ mockLineLayer, mockDestinationLayer, mockSourceLayer, + mockLayerGroup, mockLineLayer, mockDestinationLayer, mockSourceLayer, + mockLayerGroup, ]; export const mockLayerListMixed = [ @@ -436,12 +453,21 @@ export const mockLayerListMixed = [ mockLineLayer, mockDestinationLayer, mockSourceLayer, + mockLayerGroup, mockClientServerLineLayer, mockServerLayer, mockClientLayer, + { + ...mockLayerGroup, + label: 'apm-*', + }, mockApmDataStreamClientServerLineLayer, mockApmDataStreamServerLayer, mockApmDataStreamClientLayer, + { + ...mockLayerGroup, + label: 'traces-apm*,logs-apm*,metrics-apm*,apm-*', + }, ]; export const mockAPMIndexPattern: IndexPatternSavedObject = { diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.test.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.test.ts index f122d0a93ce90..d476840e91063 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.test.ts +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.test.ts @@ -15,6 +15,7 @@ import { mockLayerList, mockLayerListDouble, mockLayerListMixed, + mockLayerGroup, mockLineLayer, mockServerLayer, mockSourceLayer, @@ -50,6 +51,7 @@ describe('map_config', () => { const layerList = getSourceLayer( mockIndexPatternIds[0].title, mockIndexPatternIds[0].id, + mockLayerGroup.id, lmc.default.source ); expect(layerList).toStrictEqual(mockSourceLayer); @@ -59,6 +61,7 @@ describe('map_config', () => { const layerList = getSourceLayer( mockAPMIndexPatternIds[0].title, mockAPMIndexPatternIds[0].id, + mockLayerGroup.id, lmc[mockAPMIndexPatternIds[0].title].source ); expect(layerList).toStrictEqual(mockClientLayer); @@ -70,6 +73,7 @@ describe('map_config', () => { const layerList = getDestinationLayer( mockIndexPatternIds[0].title, mockIndexPatternIds[0].id, + mockLayerGroup.id, lmc.default.destination ); expect(layerList).toStrictEqual(mockDestinationLayer); @@ -79,6 +83,7 @@ describe('map_config', () => { const layerList = getDestinationLayer( mockAPMIndexPatternIds[0].title, mockAPMIndexPatternIds[0].id, + mockLayerGroup.id, lmc[mockAPMIndexPatternIds[0].title].destination ); expect(layerList).toStrictEqual(mockServerLayer); @@ -90,6 +95,7 @@ describe('map_config', () => { const layerList = getLineLayer( mockIndexPatternIds[0].title, mockIndexPatternIds[0].id, + mockLayerGroup.id, lmc.default ); expect(layerList).toStrictEqual(mockLineLayer); @@ -99,6 +105,7 @@ describe('map_config', () => { const layerList = getLineLayer( mockAPMIndexPatternIds[0].title, mockAPMIndexPatternIds[0].id, + mockLayerGroup.id, lmc[mockAPMIndexPatternIds[0].title] ); expect(layerList).toStrictEqual(mockClientServerLineLayer); diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts index 0a0e926840035..701631d585169 100644 --- a/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts +++ b/x-pack/plugins/security_solution/public/network/components/embeddables/map_config.ts @@ -117,11 +117,29 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { type: LAYER_TYPE.EMS_VECTOR_TILE, }, ...indexPatternIds.reduce((acc: object[], { title, id }) => { + const layerGroupDescriptor = { + id: uuid.v4(), + label: title, + sourceDescriptor: null, + type: LAYER_TYPE.LAYER_GROUP, + visible: true, + }; return [ ...acc, - getLineLayer(title, id, lmc[title] ?? lmc.default), - getDestinationLayer(title, id, lmc[title]?.destination ?? lmc.default.destination), - getSourceLayer(title, id, lmc[title]?.source ?? lmc.default.source), + getLineLayer(title, id, layerGroupDescriptor.id, lmc[title] ?? lmc.default), + getDestinationLayer( + title, + id, + layerGroupDescriptor.id, + lmc[title]?.destination ?? lmc.default.destination + ), + getSourceLayer( + title, + id, + layerGroupDescriptor.id, + lmc[title]?.source ?? lmc.default.source + ), + layerGroupDescriptor, ]; }, []), ]; @@ -133,11 +151,13 @@ export const getLayerList = (indexPatternIds: IndexPatternMapping[]) => { * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Source point" * @param indexPatternId used as layer's indexPattern to query for data + * @param parentId * @param layerDetails layer-specific field details */ export const getSourceLayer = ( indexPatternTitle: string, indexPatternId: string, + parentId: string, layerDetails: LayerMappingDetails ) => ({ sourceDescriptor: { @@ -179,6 +199,7 @@ export const getSourceLayer = ( }, }, id: uuid.v4(), + parent: parentId, label: `${indexPatternTitle} | ${layerDetails.label}`, minZoom: 0, maxZoom: 24, @@ -195,12 +216,14 @@ export const getSourceLayer = ( * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Destination point" * @param indexPatternId used as layer's indexPattern to query for data + * @param parentId used as layer's indexPattern to query for data * @param layerDetails layer-specific field details * */ export const getDestinationLayer = ( indexPatternTitle: string, indexPatternId: string, + parentId: string, layerDetails: LayerMappingDetails ) => ({ sourceDescriptor: { @@ -243,6 +266,7 @@ export const getDestinationLayer = ( }, }, id: uuid.v4(), + parent: parentId, label: `${indexPatternTitle} | ${layerDetails.label}`, minZoom: 0, maxZoom: 24, @@ -258,11 +282,13 @@ export const getDestinationLayer = ( * * @param indexPatternTitle used as layer name in LayerToC UI: "${indexPatternTitle} | Line" * @param indexPatternId used as layer's indexPattern to query for data + * @param parentId * @param layerDetails layer-specific field details */ export const getLineLayer = ( indexPatternTitle: string, indexPatternId: string, + parentId: string, layerDetails: LayerMapping ) => ({ sourceDescriptor: { @@ -327,6 +353,7 @@ export const getLineLayer = ( }, }, id: uuid.v4(), + parent: parentId, label: `${indexPatternTitle} | ${i18n.LINE_LAYER}`, minZoom: 0, maxZoom: 24, From 985d4c4f35432a08e4d32995b4e84b252ad8fc48 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Oct 2022 17:44:24 +0200 Subject: [PATCH 13/28] Update cypress (#143755) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index bb7cd79238c33..e11e5cf635bbd 100644 --- a/package.json +++ b/package.json @@ -1328,13 +1328,13 @@ "cssnano": "^5.1.12", "cssnano-preset-default": "^5.2.12", "csstype": "^3.0.2", - "cypress": "^10.9.0", + "cypress": "^10.10.0", "cypress-axe": "^1.0.0", "cypress-file-upload": "^5.0.8", "cypress-multi-reporters": "^1.6.1", "cypress-pipe": "^2.0.0", "cypress-react-selector": "^3.0.0", - "cypress-real-events": "^1.7.1", + "cypress-real-events": "^1.7.2", "cypress-recurse": "^1.23.0", "debug": "^2.6.9", "delete-empty": "^2.0.0", diff --git a/yarn.lock b/yarn.lock index 9ed18bf2180b0..61203d5a244ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12811,20 +12811,20 @@ cypress-react-selector@^3.0.0: dependencies: resq "1.10.2" -cypress-real-events@^1.7.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.1.tgz#8f430d67c29ea4f05b9c5b0311780120cbc9b935" - integrity sha512-/Bg15RgJ0SYsuXc6lPqH08x19z6j2vmhWN4wXfJqm3z8BTAFiK2MvipZPzxT8Z0jJP0q7kuniWrLIvz/i/8lCQ== +cypress-real-events@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.7.2.tgz#d04e6d3f15117ef485eb49f9c9b361f3a3413002" + integrity sha512-tOANHbFRlqVL5Lu8OozvxTsrYgHwCnWmXAULGc1kdBF+k1gxrrvT/42uez3AhGoT+HcytyxieXAVt0jNP4yrvA== cypress-recurse@^1.23.0: version "1.23.0" resolved "https://registry.yarnpkg.com/cypress-recurse/-/cypress-recurse-1.23.0.tgz#f87334747516de6737bc4708754e8f429057bc6d" integrity sha512-CAsdvynhuR3SUEXVJRO2jBEnZRJ6nJp7nMXHwzV4UQq9Lap3Bj72AwcJK0cl51fJXcTaGDXYTQQ9zvGe3TyaQA== -cypress@^10.9.0: - version "10.9.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.9.0.tgz#273a61a6304766f9d6423e5ac8d4a9a11ed8b485" - integrity sha512-MjIWrRpc+bQM9U4kSSdATZWZ2hUqHGFEQTF7dfeZRa4MnalMtc88FIE49USWP2ZVtfy5WPBcgfBX+YorFqGElA== +cypress@^10.10.0: + version "10.11.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.11.0.tgz#e9fbdd7638bae3d8fb7619fd75a6330d11ebb4e8" + integrity sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" From e6a3507d94f8eeeddd32d68a7e155e01aea57d4e Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Thu, 27 Oct 2022 10:57:04 -0500 Subject: [PATCH 14/28] Bump chromedriver to 107 (#144073) --- package.json | 2 +- yarn.lock | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e11e5cf635bbd..6b392f582dc14 100644 --- a/package.json +++ b/package.json @@ -1319,7 +1319,7 @@ "callsites": "^3.1.0", "chance": "1.0.18", "chokidar": "^3.5.3", - "chromedriver": "^105.0.1", + "chromedriver": "^107.0.0", "clean-webpack-plugin": "^3.0.0", "compression-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^6.0.2", diff --git a/yarn.lock b/yarn.lock index 61203d5a244ef..7e87fd52427a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11677,13 +11677,14 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^105.0.1: - version "105.0.1" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-105.0.1.tgz#325cf05aca200328176438991d236ddb6c61711b" - integrity sha512-QqylH9mvl4Ybq3mmHsym7jeq/LhEi2sPtD8ffd9ixiDFdPRlh2F4vzrzK+myj1MiXb0TYJK7+OCcMEmsB3Sm/Q== +chromedriver@^107.0.0: + version "107.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-107.0.0.tgz#9443ceb6020190f1a0f96ae6b5fad5453c0cd582" + integrity sha512-/VpGc83szXYUu9gBhCl6tg6XvtVwj2RQjOZ4wDA5TPSqudTMgWcMbkjeZbCfHwReJ9Qqo0hJ1jipG1IXWDxg3g== dependencies: "@testim/chrome-version" "^1.1.3" axios "^0.27.2" + compare-versions "^5.0.1" del "^6.1.1" extract-zip "^2.0.1" https-proxy-agent "^5.0.1" @@ -12098,6 +12099,11 @@ compare-versions@3.5.1: resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393" integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg== +compare-versions@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.1.tgz#14c6008436d994c3787aba38d4087fabe858555e" + integrity sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ== + component-emitter@^1.2.0, component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" From a602fa8924041a48bb71552c7282f0d3c63826c9 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Thu, 27 Oct 2022 18:01:28 +0200 Subject: [PATCH 15/28] Adds SavedObjectsWarning to analytics results pages. (#144109) - Fixes the saved object sync warning that should be shown on the analytics result pages. - Adds a check if the jobs description is an empty string to avoid unnecessary whitespace rendering. --- .../exploration_page_wrapper.tsx | 2 +- .../outlier_exploration.tsx | 2 +- .../pages/analytics_exploration/page.tsx | 27 ++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx index 482c214f884a3..c10c3e67be443 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_page_wrapper/exploration_page_wrapper.tsx @@ -161,7 +161,7 @@ export const ExplorationPageWrapper: FC = ({ return ( <> - {typeof jobConfig?.description !== 'undefined' && ( + {typeof jobConfig?.description !== 'undefined' && jobConfig?.description !== '' && ( <> {jobConfig?.description} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx index 93ceccf2756dc..67af8f7089210 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx @@ -121,7 +121,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) = return ( <> - {typeof jobConfig?.description !== 'undefined' && ( + {typeof jobConfig?.description !== 'undefined' && jobConfig?.description !== '' && ( <> {jobConfig?.description} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx index b8ed840397675..0550226599eb1 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx @@ -25,6 +25,7 @@ import { } from '../components/analytics_selector'; import { AnalyticsEmptyPrompt } from '../analytics_management/components/empty_prompt'; import { useUrlState } from '../../../util/url_state'; +import { SavedObjectsWarning } from '../../../components/saved_objects_warning'; export const Page: FC<{ jobId: string; @@ -41,7 +42,9 @@ export const Page: FC<{ } = useMlApiContext(); const helpLink = docLinks.links.ml.dataFrameAnalytics; const jobIdToUse = jobId ?? analyticsId?.job_id; - const analysisTypeToUse = analysisType || analyticsId?.analysis_type; + const [analysisTypeToUse, setAnalysisTypeToUse] = useState< + DataFrameAnalysisConfigType | undefined + >(analysisType || analyticsId?.analysis_type); const [, setGlobalState] = useUrlState('_g'); @@ -55,6 +58,25 @@ export const Page: FC<{ } }; + // The inner components of the results page don't have a concept of reloading the full page. + // Because we might want to refresh though if a user has to fix unsynced saved objects, + // we achieve this here by unmounting the inner pages first by setting `analysisTypeToUse` + // to `undefined`. The `useEffect()` below will then check if `analysisTypeToUse` doesn't + // match the passed in analyis type and will update it once again, the re-mounted + // page will then again fetch the most recent results. + const refresh = () => { + setAnalysisTypeToUse(undefined); + }; + + useEffect( + function checkRefresh() { + if (analysisTypeToUse !== analysisType || analyticsId?.analysis_type) { + setAnalysisTypeToUse(analysisType || analyticsId?.analysis_type); + } + }, + [analyticsId, analysisType, analysisTypeToUse] + ); + useEffect(function checkJobs() { checkJobsExist(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -126,6 +148,9 @@ export const Page: FC<{ /> )} + + + {jobIdToUse && analysisTypeToUse ? (
{analysisTypeToUse === ANALYSIS_CONFIG_TYPE.OUTLIER_DETECTION && ( From 069937d92f2e2c98e12d62e231f67158a2664e5a Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Thu, 27 Oct 2022 09:01:37 -0700 Subject: [PATCH 16/28] [Reporting/CSV Export] _id field can not be formatted (#143807) --- .../generate_csv/generate_csv.ts | 16 ++- .../reporting/big_int_id_field/data.json.gz | Bin 0 -> 172 bytes .../reporting/big_int_id_field/mappings.json | 25 +++++ .../reporting/big_int_id_field.json | 96 ++++++++++++++++++ .../__snapshots__/download_csv_dashboard.snap | 9 ++ .../download_csv_dashboard.ts | 79 ++++++++++++++ 6 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 x-pack/test/functional/es_archives/reporting/big_int_id_field/data.json.gz create mode 100644 x-pack/test/functional/es_archives/reporting/big_int_id_field/mappings.json create mode 100644 x-pack/test/functional/fixtures/kbn_archiver/reporting/big_int_id_field.json diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts index eb62c4d114640..d287ec58530b9 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts @@ -164,11 +164,16 @@ export class CsvGenerator { cell = '-'; } - try { - // expected values are a string of JSON where the value(s) is in an array - cell = JSON.parse(cell); - } catch (e) { - // ignore + const isIdField = tableColumn === '_id'; // _id field can not be formatted or mutated + if (!isIdField) { + try { + // unwrap the value + // expected values are a string of JSON where the value(s) is in an array + // examples: "[""Jan 1, 2020 @ 04:00:00.000""]","[""username""]" + cell = JSON.parse(cell); + } catch (e) { + // ignore + } } // We have to strip singular array values out of their array wrapper, @@ -381,6 +386,7 @@ export class CsvGenerator { break; // empty report with just the header } + // FIXME: make tabifyDocs handle the formatting, to get the same formatting logic as Discover? const formatters = this.getFormatters(table); await this.generateRows(columns, table, builder, formatters, settings); diff --git a/x-pack/test/functional/es_archives/reporting/big_int_id_field/data.json.gz b/x-pack/test/functional/es_archives/reporting/big_int_id_field/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..c42d21903c912668b99e5fbd14340e9f6b5c486f GIT binary patch literal 172 zcmV;d08{@TiwFpOPFQ0C17u-zVJ>QOZ*Bmq=2B2lDyb|;RkBi0O36=F(g6vSCFYcZ zM5=)tAU87wBx__~WMp7zU~Xst24)5(V8tN$yp+@mkdl(r;*$8(oW$ai%w(8kaeir0 zGQu#Bb(y(9b&0tJP*V*I3=IqojX{QLgLqJ#K$Sps#fj;u@h}Ja2PlE1YPmqTmW!*J a0_U3!ZReW}ZReX0xbp$ahkr`@0ssIrL`dlX literal 0 HcmV?d00001 diff --git a/x-pack/test/functional/es_archives/reporting/big_int_id_field/mappings.json b/x-pack/test/functional/es_archives/reporting/big_int_id_field/mappings.json new file mode 100644 index 0000000000000..d2ee24696e0f1 --- /dev/null +++ b/x-pack/test/functional/es_archives/reporting/big_int_id_field/mappings.json @@ -0,0 +1,25 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "test_elastic", + "mappings": { + "properties": { + "timestamp": { + "format": "yyyyMMddHHmmss||yyyyMMddHHmmssZ||strict_date_optional_time||epoch_millis", + "type": "date" + }, + "message_type": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/x-pack/test/functional/fixtures/kbn_archiver/reporting/big_int_id_field.json b/x-pack/test/functional/fixtures/kbn_archiver/reporting/big_int_id_field.json new file mode 100644 index 0000000000000..770758f52d0d3 --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/reporting/big_int_id_field.json @@ -0,0 +1,96 @@ +{ + "attributes": { + "fieldAttrs": "{}", + "fieldFormatMap": "{}", + "fields": "[]", + "name": "test_elastic*", + "runtimeFieldMap": "{}", + "sourceFilters": "[]", + "timeFieldName": "timestamp", + "title": "test_elastic*", + "typeMeta": "{}" + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-25T20:55:46.970Z", + "id": "c424ce04-f440-4f48-aa0c-534da84d06f6", + "migrationVersion": { + "index-pattern": "8.0.0" + }, + "references": [], + "type": "index-pattern", + "updated_at": "2022-10-25T20:55:46.970Z", + "version": "WzIxOCwxXQ==" +} + +{ + "attributes": { + "columns": [], + "description": "", + "grid": {}, + "hideChart": false, + "isTextBasedQuery": false, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}" + }, + "sort": [ + [ + "timestamp", + "desc" + ] + ], + "timeRestore": false, + "title": "testsearch" + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-25T20:57:39.872Z", + "id": "a984aeb0-54a7-11ed-b3f3-41d5096a3cfd", + "migrationVersion": { + "search": "8.0.0" + }, + "references": [ + { + "id": "c424ce04-f440-4f48-aa0c-534da84d06f6", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern" + } + ], + "type": "search", + "updated_at": "2022-10-25T20:57:39.872Z", + "version": "WzI2MCwxXQ==" +} + +{ + "attributes": { + "description": "", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}" + }, + "optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"version\":\"8.6.0\",\"type\":\"search\",\"gridData\":{\"x\":0,\"y\":0,\"w\":48,\"h\":18,\"i\":\"7307be50-603d-4091-b4b9-e76a96c6a33a\"},\"panelIndex\":\"7307be50-603d-4091-b4b9-e76a96c6a33a\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_7307be50-603d-4091-b4b9-e76a96c6a33a\"}]", + "refreshInterval": { + "pause": true, + "value": 0 + }, + "timeFrom": "now-15y", + "timeRestore": true, + "timeTo": "2022-10-30T00:00:00.000Z", + "title": "rbbaf", + "version": 1 + }, + "coreMigrationVersion": "8.6.0", + "created_at": "2022-10-25T21:01:17.780Z", + "id": "b78b1350-54a7-11ed-b3f3-41d5096a3cfd", + "migrationVersion": { + "dashboard": "8.6.0" + }, + "references": [ + { + "id": "a984aeb0-54a7-11ed-b3f3-41d5096a3cfd", + "name": "7307be50-603d-4091-b4b9-e76a96c6a33a:panel_7307be50-603d-4091-b4b9-e76a96c6a33a", + "type": "search" + } + ], + "type": "dashboard", + "updated_at": "2022-10-25T21:01:17.780Z", + "version": "WzMzNiwxXQ==" +} \ No newline at end of file diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap index 43649d0ed7552..73c7a6ef4b542 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap +++ b/x-pack/test/reporting_api_integration/reporting_and_security/__snapshots__/download_csv_dashboard.snap @@ -1,5 +1,14 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Reporting APIs CSV Generation from SearchSource _id field is a big integer passes through the value without mutation 1`] = ` +"\\"_id\\",\\"_index\\",\\"_score\\",\\"message_type\\",timestamp +202209071000000604,\\"test_elastic\\",\\"-\\",OP,\\"Jan 1, 2020 @ 11:00:00.000\\" +202209071000000605,\\"test_elastic\\",\\"-\\",OP,\\"Jan 1, 2020 @ 11:00:00.000\\" +202209071000000606,\\"test_elastic\\",\\"-\\",OP,\\"Jan 1, 2020 @ 11:00:00.000\\" +202209071000000607,\\"test_elastic\\",\\"-\\",OP,\\"Jan 1, 2020 @ 11:00:00.000\\" +" +`; + exports[`Reporting APIs CSV Generation from SearchSource date formatting With filters and timebased data, default to UTC 1`] = ` "\\"@timestamp\\",clientip,extension \\"Sep 20, 2015 @ 10:26:48.725\\",\\"74.214.76.90\\",jpg diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts b/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts index 922ff565b4e29..3941037733c70 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/download_csv_dashboard.ts @@ -394,6 +394,85 @@ export default function ({ getService }: FtrProviderContext) { }); }); + describe('_id field is a big integer', () => { + before(async () => { + await Promise.all([ + esArchiver.load('x-pack/test/functional/es_archives/reporting/big_int_id_field'), + kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/reporting/big_int_id_field' + ), + ]); + }); + + after(async () => { + await Promise.all([ + esArchiver.unload('x-pack/test/functional/es_archives/reporting/big_int_id_field'), + kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/reporting/big_int_id_field' + ), + ]); + }); + it('passes through the value without mutation', async () => { + const { text } = (await generateAPI.getCSVFromSearchSource( + getMockJobParams({ + browserTimezone: 'UTC', + version: '8.6.0', + searchSource: { + query: { query: '', language: 'kuery' }, + fields: [{ field: '*', include_unmapped: 'true' }], + index: 'c424ce04-f440-4f48-aa0c-534da84d06f6', + sort: [{ timestamp: 'desc' }], + filter: [ + { + meta: { + index: 'c424ce04-f440-4f48-aa0c-534da84d06f6', + params: {}, + field: 'timestamp', + }, + query: { + range: { + timestamp: { + format: 'strict_date_optional_time', + gte: '2007-10-25T21:18:23.905Z', + lte: '2022-10-30T00:00:00.000Z', + }, + }, + }, + }, + ], + parent: { + query: { query: '', language: 'kuery' }, + filter: [], + parent: { + filter: [ + { + meta: { + index: 'c424ce04-f440-4f48-aa0c-534da84d06f6', + params: {}, + field: 'timestamp', + }, + query: { + range: { + timestamp: { + format: 'strict_date_optional_time', + gte: '2007-10-25T21:18:23.905Z', + lte: '2022-10-30T00:00:00.000Z', + }, + }, + }, + }, + ], + }, + }, + }, + columns: [], + title: 'testsearch', + }) + )) as supertest.Response; + expectSnapshot(text).toMatch(); + }); + }); + describe('validation', () => { it('Return a 404', async () => { const { body } = (await generateAPI.getCSVFromSearchSource( From 18584443c2a3ed01fdb40262ad29497fe63706b5 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Thu, 27 Oct 2022 09:03:34 -0700 Subject: [PATCH 17/28] Improve `needs-team` auto labeling regex (#143787) Co-authored-by: Tyler Smalley --- .github/relabel.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/relabel.yml b/.github/relabel.yml index a737be356ce81..eb9a2fd557f45 100644 --- a/.github/relabel.yml +++ b/.github/relabel.yml @@ -1,3 +1,3 @@ issues: - missingLabel: needs-team - regex: ^(\:ml)|(Team:.*)$ \ No newline at end of file + regex: (^\:ml$)|(^Team:.+$)|(^EUI$) From 15748c601e56e448966a8080d08799483a07f3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Thu, 27 Oct 2022 18:11:39 +0200 Subject: [PATCH 18/28] [Security Solution][Endpoint] Adds RBAC API checks for Blocklist (#144047) * Adds RBAC API checks for Blocklist * Change privilege to read for export method in all artifacts --- .../validators/blocklist_validator.ts | 24 ++++++++++++------- .../validators/event_filter_validator.ts | 2 +- .../host_isolation_exceptions_validator.ts | 2 +- .../validators/trusted_app_validator.ts | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts index eaad3e6fb09f8..0a7c29bb67c2b 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/blocklist_validator.ts @@ -213,10 +213,18 @@ export class BlocklistValidator extends BaseValidator { return item.listId === ENDPOINT_BLOCKLISTS_LIST_ID; } + protected async validateHasWritePrivilege(): Promise { + return super.validateHasPrivilege('canWriteBlocklist'); + } + + protected async validateHasReadPrivilege(): Promise { + return super.validateHasPrivilege('canReadBlocklist'); + } + async validatePreCreateItem( item: CreateExceptionListItemOptions ): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); item.entries = removeDuplicateEntryValues(item.entries as BlocklistConditionEntry[]); @@ -228,27 +236,27 @@ export class BlocklistValidator extends BaseValidator { } async validatePreDeleteItem(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); } async validatePreGetOneItem(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreMultiListFind(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreExport(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreSingleListFind(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreGetListSummary(): Promise { - await this.validateCanManageEndpointArtifacts(); + await this.validateHasReadPrivilege(); } async validatePreUpdateItem( @@ -257,7 +265,7 @@ export class BlocklistValidator extends BaseValidator { ): Promise { const updatedItem = _updatedItem as ExceptionItemLikeOptions; - await this.validateCanManageEndpointArtifacts(); + await this.validateHasWritePrivilege(); _updatedItem.entries = removeDuplicateEntryValues( _updatedItem.entries as BlocklistConditionEntry[] diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts index 2ff4a663560b7..d448ee0fb1f75 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/event_filter_validator.ts @@ -116,7 +116,7 @@ export class EventFilterValidator extends BaseValidator { } async validatePreExport(): Promise { - await this.validateHasWritePrivilege(); + await this.validateHasReadPrivilege(); } async validatePreSingleListFind(): Promise { diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts index 01809c2c28f68..b20a6db4c046c 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/host_isolation_exceptions_validator.ts @@ -105,7 +105,7 @@ export class HostIsolationExceptionsValidator extends BaseValidator { } async validatePreExport(): Promise { - await this.validateHasWritePrivilege(); + await this.validateHasReadPrivilege(); } async validatePreSingleListFind(): Promise { diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts index 86b11249af9bd..38dd3442f3b4f 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts @@ -207,7 +207,7 @@ export class TrustedAppValidator extends BaseValidator { } async validatePreExport(): Promise { - await this.validateHasWritePrivilege(); + await this.validateHasReadPrivilege(); } async validatePreSingleListFind(): Promise { From d583ecc1d1039c15f62844a16d0ebdb46bfc624e Mon Sep 17 00:00:00 2001 From: Rachel Shen Date: Thu, 27 Oct 2022 10:28:12 -0600 Subject: [PATCH 19/28] [Shared UX] Add deprecation message to kibana react Markdown (#143766) --- src/plugins/kibana_react/public/index.ts | 1 + src/plugins/kibana_react/public/markdown/index.tsx | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 3311f42bff55d..0d01bd9e0dcd2 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -72,6 +72,7 @@ export { ValidatedDualRange } from './validated_range'; export type { ToastInput, KibanaReactNotifications } from './notifications'; export { createNotifications } from './notifications'; +/** @deprecated use `Markdown` from `@kbn/shared-ux-markdown` */ export { Markdown, MarkdownSimple } from './markdown'; export { reactToUiComponent, uiToReactComponent } from './adapters'; diff --git a/src/plugins/kibana_react/public/markdown/index.tsx b/src/plugins/kibana_react/public/markdown/index.tsx index 99da8a3c8898c..d0c72d8db8d76 100644 --- a/src/plugins/kibana_react/public/markdown/index.tsx +++ b/src/plugins/kibana_react/public/markdown/index.tsx @@ -17,6 +17,7 @@ const Fallback = () => ( ); +/** @deprecated use `Markdown` from `@kbn/shared-ux-markdown` */ const LazyMarkdownSimple = React.lazy(() => import('./markdown_simple')); export const MarkdownSimple = (props: MarkdownSimpleProps) => ( }> @@ -24,6 +25,7 @@ export const MarkdownSimple = (props: MarkdownSimpleProps) => ( ); +/** @deprecated use `Markdown` from `@kbn/shared-ux-markdown` */ const LazyMarkdown = React.lazy(() => import('./markdown')); export const Markdown = (props: MarkdownProps) => ( }> From f648ef35da35fae85393975c3bb289f99c63897a Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 27 Oct 2022 19:09:54 +0200 Subject: [PATCH 20/28] [APM] Add waterfall to dependency operations (#143257) Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../index.tsx | 9 +- ...dependency_operation_detail_trace_list.tsx | 129 ++++++-------- ...pendency_operation_distribution_chart.tsx} | 0 .../index.tsx | 160 +++++++++++++++++- ..._redirect_to_available_span_sample.test.ts | 108 ++++++++++++ ...maybe_redirect_to_available_span_sample.ts | 49 ++++++ .../components/app/trace_explorer/index.tsx | 15 +- .../distribution/index.tsx | 3 +- .../waterfall_with_summary/index.tsx | 64 ++++--- .../maybe_view_trace_link.tsx | 3 +- .../span_flyout/sticky_span_properties.tsx | 3 +- .../waterfall/waterfall_item.tsx | 3 +- .../components/routing/home/dependencies.tsx | 13 ++ .../resetting_height_container.tsx | 35 ++++ .../components/shared/managed_table/index.tsx | 12 +- .../redirect_with_offset/index.test.tsx | 5 + .../apm/public/hooks/use_apm_router.ts | 18 +- .../use_transaction_trace_samples_fetcher.ts | 10 +- .../dependencies/get_top_dependency_spans.ts | 20 ++- .../translations/translations/fr-FR.json | 1 - .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - .../tests/dependencies/top_spans.spec.ts | 32 +--- 23 files changed, 507 insertions(+), 187 deletions(-) rename x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/{dependendecy_operation_distribution_chart.tsx => dependency_operation_distribution_chart.tsx} (100%) create mode 100644 x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.test.ts create mode 100644 x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts create mode 100644 x-pack/plugins/apm/public/components/shared/height_retainer/resetting_height_container.tsx diff --git a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx index ecef396bb0c5d..4cfb1a3ba9c06 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx @@ -23,6 +23,7 @@ import { ITableColumn, ManagedTable } from '../../../shared/managed_table'; import { getComparisonEnabled } from '../../../shared/time_comparison/get_comparison_enabled'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { DependencyOperationDetailLink } from '../../dependency_operation_detail_view/dependency_operation_detail_link'; +import { TransactionTab } from '../../transaction_details/waterfall_with_summary/transaction_tabs'; interface OperationStatisticsItem extends SpanMetricGroup { spanName: string; @@ -35,7 +36,13 @@ function OperationLink({ spanName }: { spanName: string }) { } + content={ + + } /> ); } diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx index da4c603ff283e..0cea6233edf95 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx @@ -9,22 +9,28 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, + EuiRadio, EuiText, EuiTitle, RIGHT_ALIGNMENT, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { useHistory } from 'react-router-dom'; import { ValuesType } from 'utility-types'; import { EventOutcome } from '../../../../common/event_outcome'; import { asMillisecondDuration } from '../../../../common/utils/formatters'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { FetcherResult, FETCH_STATUS } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; -import { useTimeRange } from '../../../hooks/use_time_range'; import { APIReturnType } from '../../../services/rest/create_call_apm_api'; -import { ITableColumn, ManagedTable } from '../../shared/managed_table'; +import { push } from '../../shared/links/url_helpers'; +import { + ITableColumn, + ManagedTable, + SortFunction, +} from '../../shared/managed_table'; import { ServiceLink } from '../../shared/service_link'; import { TimestampTooltip } from '../../shared/timestamp_tooltip'; @@ -32,15 +38,23 @@ type DependencySpan = ValuesType< APIReturnType<'GET /internal/apm/dependencies/operations/spans'>['spans'] >; -export function DependencyOperationDetailTraceList() { +export function DependencyOperationDetailTraceList({ + spanFetch, + sortFn, +}: { + spanFetch: FetcherResult< + APIReturnType<'GET /internal/apm/dependencies/operations/spans'> + >; + sortFn: SortFunction; +}) { const router = useApmRouter(); + const history = useHistory(); + const theme = useTheme(); const { query: { - dependencyName, - spanName, comparisonEnabled, environment, offset, @@ -49,8 +63,11 @@ export function DependencyOperationDetailTraceList() { refreshInterval, refreshPaused, kuery, - sampleRangeFrom, - sampleRangeTo, + sortField = '@timestamp', + sortDirection = 'desc', + pageSize = 10, + page = 1, + spanId, }, } = useApmParams('/dependencies/operation'); @@ -99,9 +116,24 @@ export function DependencyOperationDetailTraceList() { return href; } - const { start, end } = useTimeRange({ rangeFrom, rangeTo }); - const columns: Array> = [ + { + name: '', + field: 'spanId', + render: (_, { spanId: itemSpanId }) => { + return ( + { + push(history, { + query: { spanId: value ? itemSpanId : '' }, + }); + }} + checked={itemSpanId === spanId} + /> + ); + }, + }, { name: i18n.translate( 'xpack.apm.dependencyOperationDetailTraceListOutcomeColumn', @@ -121,38 +153,6 @@ export function DependencyOperationDetailTraceList() { return {outcome}; }, }, - { - name: i18n.translate( - 'xpack.apm.dependencyOperationDetailTraceListTraceIdColumn', - { defaultMessage: 'Trace' } - ), - field: 'traceId', - truncateText: true, - render: ( - _, - { - serviceName, - traceId, - transactionId, - transactionName, - transactionType, - } - ) => { - const href = getTraceLink({ - serviceName, - traceId, - transactionId, - transactionType, - transactionName, - }); - - return ( - - {traceId.substr(0, 6)} - - ); - }, - }, { name: i18n.translate( 'xpack.apm.dependencyOperationDetailTraceListServiceNameColumn', @@ -190,6 +190,7 @@ export function DependencyOperationDetailTraceList() { ), field: 'transactionName', truncateText: true, + width: '60%', render: ( _, { @@ -239,35 +240,6 @@ export function DependencyOperationDetailTraceList() { }, ]; - const { data = { spans: [] }, status } = useFetcher( - (callApmApi) => { - return callApmApi('GET /internal/apm/dependencies/operations/spans', { - params: { - query: { - dependencyName, - spanName, - start, - end, - environment, - kuery, - sampleRangeFrom, - sampleRangeTo, - }, - }, - }); - }, - [ - dependencyName, - spanName, - start, - end, - environment, - kuery, - sampleRangeFrom, - sampleRangeTo, - ] - ); - return ( @@ -281,15 +253,18 @@ export function DependencyOperationDetailTraceList() { diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependendecy_operation_distribution_chart.tsx b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_distribution_chart.tsx similarity index 100% rename from x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependendecy_operation_distribution_chart.tsx rename to x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_distribution_chart.tsx diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx index c824a61f019b2..742f6e27b9be3 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx @@ -6,25 +6,141 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import { omit, orderBy } from 'lodash'; +import React, { useEffect, useMemo, useRef } from 'react'; +import { useHistory } from 'react-router-dom'; +import type { DependencySpan } from '../../../../server/routes/dependencies/get_top_dependency_spans'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useApmParams } from '../../../hooks/use_apm_params'; import { useApmRouter } from '../../../hooks/use_apm_router'; import { useDependencyDetailOperationsBreadcrumb } from '../../../hooks/use_dependency_detail_operations_breadcrumb'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useTimeRange } from '../../../hooks/use_time_range'; import { DependencyMetricCharts } from '../../shared/dependency_metric_charts'; import { DetailViewHeader } from '../../shared/detail_view_header'; -import { DependencyOperationDistributionChart } from './dependendecy_operation_distribution_chart'; +import { ResettingHeightRetainer } from '../../shared/height_retainer/resetting_height_container'; +import { push, replace } from '../../shared/links/url_helpers'; +import { SortFunction } from '../../shared/managed_table'; +import { useWaterfallFetcher } from '../transaction_details/use_waterfall_fetcher'; +import { WaterfallWithSummary } from '../transaction_details/waterfall_with_summary'; import { DependencyOperationDetailTraceList } from './dependency_operation_detail_trace_list'; +import { DependencyOperationDistributionChart } from './dependency_operation_distribution_chart'; +import { maybeRedirectToAvailableSpanSample } from './maybe_redirect_to_available_span_sample'; export function DependencyOperationDetailView() { const router = useApmRouter(); + const history = useHistory(); + const { - query: { spanName, ...query }, + query, + query: { + spanName, + dependencyName, + sampleRangeFrom, + sampleRangeTo, + kuery, + environment, + rangeFrom, + rangeTo, + spanId, + waterfallItemId, + detailTab, + sortField = '@timestamp', + sortDirection = 'desc', + }, } = useApmParams('/dependencies/operation'); useDependencyDetailOperationsBreadcrumb(); + const { start, end } = useTimeRange({ rangeFrom, rangeTo }); + + const queryWithoutSpanName = omit(query, 'spanName'); + + const spanFetch = useFetcher( + (callApmApi) => { + return callApmApi('GET /internal/apm/dependencies/operations/spans', { + params: { + query: { + dependencyName, + spanName, + start, + end, + environment, + kuery, + sampleRangeFrom, + sampleRangeTo, + }, + }, + }); + }, + [ + dependencyName, + spanName, + start, + end, + environment, + kuery, + sampleRangeFrom, + sampleRangeTo, + ] + ); + + const getSortedSamples: SortFunction = ( + items, + localSortField, + localSortDirection + ) => { + return orderBy(items, localSortField, localSortDirection); + }; + + const samples = useMemo(() => { + return ( + getSortedSamples( + spanFetch.data?.spans ?? [], + sortField, + sortDirection + ).map((span) => ({ + spanId: span.spanId, + traceId: span.traceId, + transactionId: span.transactionId, + })) || [] + ); + }, [spanFetch.data?.spans, sortField, sortDirection]); + + const selectedSample = useMemo(() => { + return samples.find((sample) => sample.spanId === spanId); + }, [samples, spanId]); + + const waterfallFetch = useWaterfallFetcher({ + traceId: selectedSample?.traceId, + transactionId: selectedSample?.transactionId, + start, + end, + }); + + const queryRef = useRef(query); + + queryRef.current = query; + + useEffect(() => { + maybeRedirectToAvailableSpanSample({ + history, + page: queryRef.current.page ?? 0, + pageSize: queryRef.current.pageSize ?? 10, + replace, + samples, + spanFetchStatus: spanFetch.status, + spanId, + }); + }, [samples, spanId, history, queryRef, router, spanFetch.status]); + + const isWaterfallLoading = + spanFetch.status === FETCH_STATUS.NOT_INITIATED || + (spanFetch.status === FETCH_STATUS.LOADING && samples.length === 0) || + waterfallFetch.status === FETCH_STATUS.LOADING || + !waterfallFetch.waterfall.entryWaterfallTransaction; + return ( @@ -33,7 +149,9 @@ export function DependencyOperationDetailView() { 'xpack.apm.dependecyOperationDetailView.header.backLinkLabel', { defaultMessage: 'All operations' } )} - backHref={router.link('/dependencies/operations', { query })} + backHref={router.link('/dependencies/operations', { + query: queryWithoutSpanName, + })} title={spanName} /> @@ -50,7 +168,39 @@ export function DependencyOperationDetailView() { - + + + + + + + { + push(history, { query: { spanId: sample.spanId } }); + }} + onTabClick={(tab) => { + push(history, { + query: { + detailTab: tab, + }, + }); + }} + serviceName={ + waterfallFetch.waterfall.entryWaterfallTransaction?.doc.service + .name + } + waterfallItemId={waterfallItemId} + detailTab={detailTab} + selectedSample={selectedSample || null} + /> + diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.test.ts b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.test.ts new file mode 100644 index 0000000000000..a7c2b2b669bf3 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright 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 { range } from 'lodash'; +import { maybeRedirectToAvailableSpanSample } from './maybe_redirect_to_available_span_sample'; +import { replace as urlHelpersReplace } from '../../shared/links/url_helpers'; +import { History } from 'history'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; + +describe('maybeRedirectToAvailableSpanSample', () => { + const samples: Array<{ + spanId: string; + traceId: string; + transactionId: string; + }> = range(11).map((_, index) => ({ + spanId: (index + 1).toString(), + traceId: '', + transactionId: '', + })); + + let defaultParams: Omit< + Parameters[0], + 'replace' + > & { replace: jest.MockedFunction }; + + beforeEach(() => { + defaultParams = { + samples, + page: 0, + pageSize: 10, + history: { + location: { + search: '', + }, + } as History, + spanFetchStatus: FETCH_STATUS.SUCCESS, + replace: jest.fn(), + }; + }); + + it('does not redirect while loading', () => { + maybeRedirectToAvailableSpanSample({ + ...defaultParams, + spanId: undefined, + spanFetchStatus: FETCH_STATUS.LOADING, + }); + expect(defaultParams.replace).not.toHaveBeenCalled(); + }); + + it('redirects to the first available span if no span is selected', () => { + maybeRedirectToAvailableSpanSample({ + ...defaultParams, + spanId: undefined, + page: 1, + spanFetchStatus: FETCH_STATUS.SUCCESS, + }); + expect(defaultParams.replace).toHaveBeenCalled(); + + expect(defaultParams.replace.mock.calls[0][1].query).toEqual({ + spanId: samples[0].spanId, + page: '0', + }); + }); + + it('redirects to the first available span if the currently selected sample is not found', () => { + maybeRedirectToAvailableSpanSample({ + ...defaultParams, + page: 1, + spanId: '12', + spanFetchStatus: FETCH_STATUS.SUCCESS, + }); + expect(defaultParams.replace).toHaveBeenCalled(); + + expect(defaultParams.replace.mock.calls[0][1].query).toEqual({ + spanId: samples[0].spanId, + page: '0', + }); + }); + + it('does not redirect if the sample is found', () => { + maybeRedirectToAvailableSpanSample({ + ...defaultParams, + page: 0, + spanId: '1', + spanFetchStatus: FETCH_STATUS.SUCCESS, + }); + expect(defaultParams.replace).not.toHaveBeenCalled(); + }); + + it('redirects to the page of the currently selected sample', () => { + maybeRedirectToAvailableSpanSample({ + ...defaultParams, + page: 0, + spanId: '11', + spanFetchStatus: FETCH_STATUS.SUCCESS, + }); + + expect(defaultParams.replace).toHaveBeenCalled(); + + expect(defaultParams.replace.mock.calls[0][1].query).toEqual({ + page: '1', + spanId: '11', + }); + }); +}); diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts new file mode 100644 index 0000000000000..00ce95a255ea2 --- /dev/null +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/maybe_redirect_to_available_span_sample.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { History } from 'history'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { replace as urlHelpersReplace } from '../../shared/links/url_helpers'; + +export function maybeRedirectToAvailableSpanSample({ + spanFetchStatus, + spanId, + pageSize, + page, + replace, + samples, + history, +}: { + spanFetchStatus: FETCH_STATUS; + spanId?: string; + pageSize: number; + page: number; + replace: typeof urlHelpersReplace; + history: History; + samples: Array<{ spanId: string; traceId: string; transactionId: string }>; +}) { + if (spanFetchStatus !== FETCH_STATUS.SUCCESS) { + // we're still loading, don't do anything + return; + } + + const nextSpanId = + samples.find((sample) => sample.spanId === spanId)?.spanId || + samples[0]?.spanId || + ''; + + const indexOfNextSample = + samples.findIndex((sample) => sample.spanId === nextSpanId) ?? 0; + + const nextPageIndex = Math.floor((indexOfNextSample + 1) / (pageSize ?? 10)); + + if (page !== nextPageIndex || (spanId ?? '') !== nextSpanId) { + replace(history, { + query: { spanId: nextSpanId, page: nextPageIndex.toString() }, + }); + } +} diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx index 47287084386ad..cf3306ad0d376 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx @@ -20,10 +20,6 @@ import { useWaterfallFetcher } from '../transaction_details/use_waterfall_fetche import { WaterfallWithSummary } from '../transaction_details/waterfall_with_summary'; import { TraceSearchBox } from './trace_search_box'; -const INITIAL_DATA = { - traceSamples: [], -}; - export function TraceExplorer() { const [query, setQuery] = useState({ query: '', @@ -58,11 +54,7 @@ export function TraceExplorer() { rangeTo, }); - const { - data = INITIAL_DATA, - status, - error, - } = useFetcher( + const { data, status, error } = useFetcher( (callApmApi) => { return callApmApi('GET /internal/apm/traces/find', { params: { @@ -80,7 +72,7 @@ export function TraceExplorer() { ); useEffect(() => { - const nextSample = data.traceSamples[0]; + const nextSample = data?.traceSamples[0]; const nextWaterfallItemId = ''; history.replace({ ...history.location, @@ -141,7 +133,8 @@ export function TraceExplorer() { { push(history, { diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index 73b224d60aaa4..0cb5fa49117c5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -117,7 +117,8 @@ export function TransactionDistribution({ waterfallItemId={waterfallItemId} detailTab={detailTab as TransactionTab | undefined} waterfallFetchResult={waterfallFetchResult} - traceSamplesFetchResult={traceSamplesFetchResult} + traceSamplesFetchStatus={traceSamplesFetchResult.status} + traceSamples={traceSamplesFetchResult.data?.traceSamples} />
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx index 57d630393f9a9..537ada31df0e5 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx @@ -22,55 +22,67 @@ import { MaybeViewTraceLink } from './maybe_view_trace_link'; import { TransactionTab, TransactionTabs } from './transaction_tabs'; import { Environment } from '../../../../../common/environment_rt'; import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; -import { TraceSamplesFetchResult } from '../../../../hooks/use_transaction_trace_samples_fetcher'; import { WaterfallFetchResult } from '../use_waterfall_fetcher'; -interface Props { +interface Props { waterfallFetchResult: WaterfallFetchResult; - traceSamplesFetchResult: TraceSamplesFetchResult; + traceSamples?: TSample[]; + traceSamplesFetchStatus: FETCH_STATUS; environment: Environment; - onSampleClick: (sample: { transactionId: string; traceId: string }) => void; - onTabClick: (tab: string) => void; + onSampleClick: (sample: TSample) => void; + onTabClick: (tab: TransactionTab) => void; serviceName?: string; waterfallItemId?: string; detailTab?: TransactionTab; + selectedSample?: TSample | null; } -export function WaterfallWithSummary({ +export function WaterfallWithSummary({ waterfallFetchResult, - traceSamplesFetchResult, + traceSamples, + traceSamplesFetchStatus, environment, onSampleClick, onTabClick, serviceName, waterfallItemId, detailTab, -}: Props) { + selectedSample, +}: Props) { const [sampleActivePage, setSampleActivePage] = useState(0); + const isControlled = selectedSample !== undefined; + + const isLoading = + waterfallFetchResult.status === FETCH_STATUS.LOADING || + traceSamplesFetchStatus === FETCH_STATUS.LOADING; + const isSucceded = + waterfallFetchResult.status === FETCH_STATUS.SUCCESS && + traceSamplesFetchStatus === FETCH_STATUS.SUCCESS; + useEffect(() => { - setSampleActivePage(0); - }, [traceSamplesFetchResult.data.traceSamples]); + if (!isControlled) { + setSampleActivePage(0); + } + }, [traceSamples, isControlled]); const goToSample = (index: number) => { - setSampleActivePage(index); - const sample = traceSamplesFetchResult.data.traceSamples[index]; + const sample = traceSamples![index]; + if (!isControlled) { + setSampleActivePage(index); + } onSampleClick(sample); }; + const samplePageIndex = isControlled + ? selectedSample + ? traceSamples?.indexOf(selectedSample) + : 0 + : sampleActivePage; + const { entryWaterfallTransaction } = waterfallFetchResult.waterfall; - const isLoading = - waterfallFetchResult.status === FETCH_STATUS.LOADING || - traceSamplesFetchResult.status === FETCH_STATUS.LOADING; - const isSucceded = - waterfallFetchResult.status === FETCH_STATUS.SUCCESS && - traceSamplesFetchResult.status === FETCH_STATUS.SUCCESS; - if ( - !entryWaterfallTransaction && - traceSamplesFetchResult.data.traceSamples.length === 0 && - isSucceded - ) { + if (!entryWaterfallTransaction && traceSamples?.length === 0 && isSucceded) { return ( - {traceSamplesFetchResult.data.traceSamples.length > 0 && ( + {!!traceSamples?.length && ( diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx index 1621ea72b39a1..40b1944605b58 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/maybe_view_trace_link.tsx @@ -53,7 +53,8 @@ export function MaybeViewTraceLink({ query: { comparisonEnabled, offset }, } = useAnyOfApmParams( '/services/{serviceName}/transactions/view', - '/traces/explorer' + '/traces/explorer', + '/dependencies/operation' ); const latencyAggregationType = diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx index 0b500cb79a746..59abacb1c325c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/span_flyout/sticky_span_properties.tsx @@ -33,7 +33,8 @@ interface Props { export function StickySpanProperties({ span, transaction }: Props) { const { query } = useAnyOfApmParams( '/services/{serviceName}/transactions/view', - '/traces/explorer' + '/traces/explorer', + '/dependencies/operation' ); const { environment, comparisonEnabled, offset } = query; diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx index 9bfff6a4ea89b..0f03b430152f0 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx @@ -277,7 +277,8 @@ function RelatedErrors({ const theme = useTheme(); const { query } = useAnyOfApmParams( '/services/{serviceName}/transactions/view', - '/traces/explorer' + '/traces/explorer', + '/dependencies/operation' ); let kuery = `${TRACE_ID} : "${item.doc.trace.id}"`; diff --git a/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx b/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx index a20634c31912c..01109eedba483 100644 --- a/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx @@ -20,6 +20,7 @@ import { DependencyDetailView } from '../../app/dependency_detail_view'; import { DependenciesInventory } from '../../app/dependencies_inventory'; import { DependencyOperationDetailView } from '../../app/dependency_operation_detail_view'; import { useApmParams } from '../../../hooks/use_apm_params'; +import { TransactionTab } from '../../app/transaction_details/waterfall_with_summary/transaction_tabs'; export const DependenciesInventoryTitle = i18n.translate( 'xpack.apm.views.dependenciesInventory.title', @@ -73,13 +74,25 @@ export const dependencies = { query: t.intersection([ t.type({ spanName: t.string, + detailTab: t.union([ + t.literal(TransactionTab.timeline), + t.literal(TransactionTab.metadata), + t.literal(TransactionTab.logs), + ]), }), t.partial({ + spanId: t.string, sampleRangeFrom: toNumberRt, sampleRangeTo: toNumberRt, + waterfallItemId: t.string, }), ]), }), + defaults: { + query: { + detailTab: TransactionTab.timeline, + }, + }, element: , }, '/dependencies/overview': { diff --git a/x-pack/plugins/apm/public/components/shared/height_retainer/resetting_height_container.tsx b/x-pack/plugins/apm/public/components/shared/height_retainer/resetting_height_container.tsx new file mode 100644 index 0000000000000..00a1b9dd40c31 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/height_retainer/resetting_height_container.tsx @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useRef } from 'react'; + +export function ResettingHeightRetainer( + props: React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLDivElement + > & { reset?: boolean } +) { + const { reset, ...containerProps } = props; + const resetRef = useRef(reset); + const containerRef = useRef(null); + + const minHeightRef = useRef(0); + + if (resetRef.current !== reset) { + minHeightRef.current = reset ? 0 : containerRef.current?.clientHeight ?? 0; + + resetRef.current = reset; + } + + return ( +
+ ); +} diff --git a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx index a4814211399e3..41512f00d22b6 100644 --- a/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/managed_table/index.tsx @@ -36,11 +36,7 @@ interface Props { showPerPageOptions?: boolean; noItemsMessage?: React.ReactNode; sortItems?: boolean; - sortFn?: ( - items: T[], - sortField: string, - sortDirection: 'asc' | 'desc' - ) => T[]; + sortFn?: SortFunction; pagination?: boolean; isLoading?: boolean; error?: boolean; @@ -57,6 +53,12 @@ function defaultSortFn( return orderBy(items, sortField, sortDirection); } +export type SortFunction = ( + items: T[], + sortField: string, + sortDirection: 'asc' | 'desc' +) => T[]; + function UnoptimizedManagedTable(props: Props) { const history = useHistory(); const { diff --git a/x-pack/plugins/apm/public/components/shared/redirect_with_offset/index.test.tsx b/x-pack/plugins/apm/public/components/shared/redirect_with_offset/index.test.tsx index a47d3c76b500b..2a04856a7a1c4 100644 --- a/x-pack/plugins/apm/public/components/shared/redirect_with_offset/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/redirect_with_offset/index.test.tsx @@ -31,6 +31,11 @@ describe('RedirectWithOffset', () => { .spyOn(useApmPluginContextExports, 'useApmPluginContext') .mockReturnValue({ core: { + http: { + basePath: { + prepend: () => {}, + }, + }, uiSettings: { get: () => defaultSetting, }, diff --git a/x-pack/plugins/apm/public/hooks/use_apm_router.ts b/x-pack/plugins/apm/public/hooks/use_apm_router.ts index d10b6da857802..d479b3f1af414 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_router.ts +++ b/x-pack/plugins/apm/public/hooks/use_apm_router.ts @@ -6,6 +6,7 @@ */ import { useRouter } from '@kbn/typed-react-router-config'; +import { useMemo } from 'react'; import type { ApmRouter } from '../components/routing/apm_route_config'; import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; @@ -13,12 +14,13 @@ export function useApmRouter() { const router = useRouter(); const { core } = useApmPluginContext(); - const link = (...args: [any]) => { - return core.http.basePath.prepend('/app/apm' + router.link(...args)); - }; - - return { - ...router, - link, - } as unknown as ApmRouter; + return useMemo( + () => + ({ + ...router, + link: (...args: [any]) => + core.http.basePath.prepend('/app/apm' + router.link(...args)), + } as unknown as ApmRouter), + [core.http.basePath, router] + ); } diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts b/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts index 9bb2b68266827..510118b6a191c 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_trace_samples_fetcher.ts @@ -12,10 +12,6 @@ import { useApmServiceContext } from '../context/apm_service/use_apm_service_con import { useApmParams } from './use_apm_params'; import { useTimeRange } from './use_time_range'; -const INITIAL_DATA = { - traceSamples: [], -}; - export type TraceSamplesFetchResult = ReturnType< typeof useTransactionTraceSamplesFetcher >; @@ -41,11 +37,7 @@ export function useTransactionTraceSamplesFetcher({ urlParams: { transactionId, traceId, sampleRangeFrom, sampleRangeTo }, } = useLegacyUrlParams(); - const { - data = INITIAL_DATA, - status, - error, - } = useFetcher( + const { data, status, error } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType && transactionName) { return callApmApi( diff --git a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts index 5a2df933c7dba..83d7f2bf52b9e 100644 --- a/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts +++ b/x-pack/plugins/apm/server/routes/dependencies/get_top_dependency_spans.ts @@ -5,14 +5,14 @@ * 2.0. */ +import { ProcessorEvent } from '@kbn/observability-plugin/common'; import { kqlQuery, rangeQuery, termQuery, termsQuery, } from '@kbn/observability-plugin/server'; -import { compact, keyBy } from 'lodash'; -import { ProcessorEvent } from '@kbn/observability-plugin/common'; +import { keyBy } from 'lodash'; import { AGENT_NAME, EVENT_OUTCOME, @@ -20,6 +20,7 @@ import { SERVICE_NAME, SPAN_DESTINATION_SERVICE_RESOURCE, SPAN_DURATION, + SPAN_ID, SPAN_NAME, TRACE_ID, TRANSACTION_ID, @@ -29,6 +30,7 @@ import { import { Environment } from '../../../common/environment_rt'; import { EventOutcome } from '../../../common/event_outcome'; import { environmentQuery } from '../../../common/utils/environment_query'; +import { maybe } from '../../../common/utils/maybe'; import { AgentName } from '../../../typings/es_schemas/ui/fields/agent'; import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client'; @@ -36,11 +38,12 @@ const MAX_NUM_SPANS = 1000; export interface DependencySpan { '@timestamp': number; + spanId: string; spanName: string; serviceName: string; agentName: AgentName; traceId: string; - transactionId?: string; + transactionId: string; transactionType?: string; transactionName?: string; duration: number; @@ -84,6 +87,7 @@ export async function getTopDependencySpans({ ...kqlQuery(kuery), ...termQuery(SPAN_DESTINATION_SERVICE_RESOURCE, dependencyName), ...termQuery(SPAN_NAME, spanName), + { exists: { field: TRANSACTION_ID } }, ...((sampleRangeFrom ?? 0) >= 0 && (sampleRangeTo ?? 0) > 0 ? [ { @@ -100,6 +104,7 @@ export async function getTopDependencySpans({ }, }, _source: [ + SPAN_ID, TRACE_ID, TRANSACTION_ID, SPAN_NAME, @@ -114,7 +119,7 @@ export async function getTopDependencySpans({ }) ).hits.hits.map((hit) => hit._source); - const transactionIds = compact(spans.map((span) => span.transaction?.id)); + const transactionIds = spans.map((span) => span.transaction!.id); const transactions = ( await apmEventClient.search('get_transactions_for_dependency_spans', { @@ -143,19 +148,18 @@ export async function getTopDependencySpans({ ); return spans.map((span): DependencySpan => { - const transaction = span.transaction - ? transactionsById[span.transaction.id] - : undefined; + const transaction = maybe(transactionsById[span.transaction!.id]); return { '@timestamp': new Date(span['@timestamp']).getTime(), + spanId: span.span.id, spanName: span.span.name, serviceName: span.service.name, agentName: span.agent.name, duration: span.span.duration.us, traceId: span.trace.id, outcome: (span.event?.outcome || EventOutcome.unknown) as EventOutcome, - transactionId: transaction?.transaction.id, + transactionId: span.transaction!.id, transactionType: transaction?.transaction.type, transactionName: transaction?.transaction.name, }; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 3ebe3a63523d2..73cc46c87a0a8 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -7164,7 +7164,6 @@ "xpack.apm.dependencyOperationDetailTraceListOutcomeColumn": "Résultat", "xpack.apm.dependencyOperationDetailTraceListServiceNameColumn": "Service d'origine", "xpack.apm.dependencyOperationDetailTraceListTimestampColumn": "Horodatage", - "xpack.apm.dependencyOperationDetailTraceListTraceIdColumn": "Trace", "xpack.apm.dependencyOperationDetailTraceListTransactionNameColumn": "Nom de la transaction", "xpack.apm.dependencyOperationDistributionChart.allSpansLegendLabel": "Tous les intervalles", "xpack.apm.dependencyOperationDistributionChart.failedSpansLegendLabel": "Intervalles ayant échoué", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a74277d17862a..0cc4a0683b366 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7152,7 +7152,6 @@ "xpack.apm.dependencyOperationDetailTraceListOutcomeColumn": "成果", "xpack.apm.dependencyOperationDetailTraceListServiceNameColumn": "発生元サービス", "xpack.apm.dependencyOperationDetailTraceListTimestampColumn": "タイムスタンプ", - "xpack.apm.dependencyOperationDetailTraceListTraceIdColumn": "トレース", "xpack.apm.dependencyOperationDetailTraceListTransactionNameColumn": "トランザクション名", "xpack.apm.dependencyOperationDistributionChart.allSpansLegendLabel": "すべてのスパン", "xpack.apm.dependencyOperationDistributionChart.failedSpansLegendLabel": "失敗したスパン", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6127534e13b93..d9d116ef0aeb6 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7168,7 +7168,6 @@ "xpack.apm.dependencyOperationDetailTraceListOutcomeColumn": "结果", "xpack.apm.dependencyOperationDetailTraceListServiceNameColumn": "发起服务", "xpack.apm.dependencyOperationDetailTraceListTimestampColumn": "时间戳", - "xpack.apm.dependencyOperationDetailTraceListTraceIdColumn": "跟踪", "xpack.apm.dependencyOperationDetailTraceListTransactionNameColumn": "事务名称", "xpack.apm.dependencyOperationDistributionChart.allSpansLegendLabel": "所有跨度", "xpack.apm.dependencyOperationDistributionChart.failedSpansLegendLabel": "失败的跨度", diff --git a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts index e93af3051d451..fe244297f5d3c 100644 --- a/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts +++ b/x-pack/test/apm_api_integration/tests/dependencies/top_spans.spec.ts @@ -155,7 +155,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(javaSpans.length + goSpans.length).to.eql(spans.length); - expect(omit(javaSpans[0], 'traceId', 'transactionId')).to.eql({ + expect(omit(javaSpans[0], 'spanId', 'traceId', 'transactionId')).to.eql({ '@timestamp': 1609459200000, agentName: 'java', duration: 100000, @@ -166,7 +166,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { outcome: 'success', }); - expect(omit(goSpans[0], 'traceId', 'transactionId')).to.eql({ + expect(omit(goSpans[0], 'spanId', 'traceId', 'transactionId')).to.eql({ '@timestamp': 1609459200000, agentName: 'go', duration: 50000, @@ -223,34 +223,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { }); }); - describe('when requesting spans without a transaction', () => { - it('should return the spans without transaction metadata', async () => { - const response = await callApi({ - dependencyName: 'elasticsearch', - spanName: 'without transaction', - }); - - const { spans } = response.body; - - const spanNames = uniq(spans.map((span) => span.spanName)); - - expect(spanNames).to.eql(['without transaction']); - - expect(omit(spans[0], 'traceId')).to.eql({ - '@timestamp': 1609459200000, - agentName: 'java', - duration: 200000, - serviceName: 'java', - spanName: 'without transaction', - outcome: 'unknown', - }); - - expect(spans[0].transactionType).not.to.be.ok(); - expect(spans[0].transactionId).not.to.be.ok(); - expect(spans[0].transactionName).not.to.be.ok(); - }); - }); - describe('when requesting spans within a specific sample range', () => { it('returns only spans whose duration falls into the requested range', async () => { const response = await callApi({ From db6f06eaec98a7fdc0c0bbca8e6464bb70ef3506 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Thu, 27 Oct 2022 19:21:53 +0200 Subject: [PATCH 21/28] Bump nwsapi from v2.2.0 to v2.2.2 (#144001) --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7e87fd52427a2..a0b5043df251d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21468,9 +21468,9 @@ numeral@^2.0.6: integrity sha1-StCAk21EPCVhrtnyGX7//iX05QY= nwsapi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== nyc@15.1.0, nyc@^15.1.0: version "15.1.0" From 61505e5edd19ff4177e477d44c061c94d3c7406f Mon Sep 17 00:00:00 2001 From: nastasha-solomon <79124755+nastasha-solomon@users.noreply.github.com> Date: Thu, 27 Oct 2022 13:32:11 -0400 Subject: [PATCH 22/28] [8.5][DOCS] Add support for differential logs (#143242) Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> --- .../images/live-query-check-results.png | Bin 355410 -> 425717 bytes docs/osquery/osquery.asciidoc | 13 +++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/osquery/images/live-query-check-results.png b/docs/osquery/images/live-query-check-results.png index 6b84a3bf9f7ca5a6d9e8e521989035c7492b2a5f..f52a96a3d64f60027a44e0ebbce828977f9bd66a 100644 GIT binary patch literal 425717 zcmeFZXH-*b_brYUk*XewR8bHGX(GKT91C6P5{iI;^b&eiX$k^L?-3P{8W0FQC@o5t z79fq#LQCib2uW^u+r4MpbKm3d)BnT&e8?Dk5R$$3v*%iC&bii;*AMmYGoR%?OGih? zto7h`Lpr*%SUNiT*fXbTe}QqB%hS;vvvbzee5j?VdFi2#r=zo*10CJ9cYbfHb&lI| zb%Vq-rGC7OiHYIietBc={CVErg)e(a>3oJXkK7vCnk8KRR46#b>QdhIa`!zq_lLIk zXSn0u|A@Hcb$vfr9jqiCtgeO@U|l1vZ_F6{nracl%GMD*b`3#A>_gGYH;a+mJD zszJzIXwgw|8Gh7B|58T+#4v6NCG?fg4R;1~b_72bXKOnjTPM*v+BMdbPVtLuP&2=^ z**gkbOu7<%nd!!zZk3y<8ce`fm#BmKwKXaCn3?=CCw^yen5fMYiZ0ObxXJP35saz5 z^K9ewhrf@v$!yn7F|fHFBPSL+r+%AP%iEuGz5u)ZjJ2V`p|pe|BvuzX-d#T9BXx|a z#fu`xu*dfv??pjRx1cUjou-Q@45pXtck5$banTlC!NFY1QBRLfly-cE?pUld-AUTf zG1`lp_M)RZ{{HyC|8f@l{=~nJ>0^)Hyj++fLPvL-PV4tO#(~FHQ31KeV<`G*&HJ}c zemW!f^(A{qN21-g^Ln?WUwf`*Z+8FwvHSM~ZrOez7d!N=luUEG*TNjDR*%m8e&fmA zPY)!IU%oUQGB}-=hnmdGd+eVvIjktNUCHOWn?^_qrqu}wIRSWYklCEo-FUB1die+82Gnjb3Pn(CTWTUwnfWEFI(ht_ZyPtEh^is(^*wsKQ@P&9OBmr4k0nK>{7sj zL*>fu2)*N)nWFV#zoZ@`Sl{|>*3|2eZ3aL&y!w-~T%oJ@QoHZfUt^TFFL1E6TP4U# zGEaA3SJ?{6tdcI0S*?4FO&dJ%g4m)Y^QJZMVMTG+C5B?_orzAjwkv9^pD7rllo|v@ zE>t^(1&hvUI3#SO!4=#o!)~8tP6O@fV36uo%o zSGn=y7wMD2j0vJ_WtH7$8=T|Qym-p;zQ7MAjaucmQl!GiMJ9K$IVvM;UHsuqe26I3 z6z%HjFbACVB;Hv{zPTXl2Ge9Ipy;?quwtzHh^p0gQ})TG02dy}vehHK-Aula%A%}W zht9O?RJZC-r#Kw8?18M zkE?8Jy6 zu4w&af+FJ)Xl?)=of}fVE)vDljQ<&tR~2ynvd7QMxc82bi8{>{FXQua9ACvjJcuHb zc&~NS6*&@}$8R<%m35){QZjLkS*!!^lALaVl%?d4w z_}QwiV7?Jlnl(_fDQ9PuO>S*Wu$ihZX}}s0=ws-pYTBs^T)#INwxn^RYJYm-F*eCS zkHu5Z<~NQT)*Sv5&+t{$1=LN!9(VX!s(*hAbo<+(7n;x0DZN?9*TD5qTiFnu|^MeH~Z+JE0ndgs%Jko`vL>P zfh0D~#)u$)?LFeFk}P)sap|{8Rgd`9eIL&JumJ0Z#ry!79E+=3^YjBq!CH5dcM&$U zRJK0Ism4^_a!dRTs7_(T6Rg)9MW**<)~qM+ANV?FC|myQ?M<29#{VGiugDm+BV;(D z8=za=c^xGo6#|L86IXIWaLTrGQAsyvt-MDaZ6BU5N_5};bPBtQ!1UbkY#~@+4=z9k zZr~`M10M`J%qRyghR616h9G{u>qA+}3xolQHaQ2|hJ%gQ*!kC69jQS&8lmC4h!$6c zC-ELxBpcZVtFeREH32LEZ8xtVb0<`G7kY4hyPmtfZ>Y0WC{KQbr?un$@D;fdNcQ%d zQ#u{8KlbgrkgSf9ELO=Rxxm}?eY`AMZpwA8X?d<=(R;kzIOPFy0;@U;9#P9gKA!|$ z{bK#<&NkPZ3fK6pCil_pbuUBP;HN(Mdv1iwwyELkQktSk|LsFQdMxqOIkypHW6 z;$kz@_OA6=>^VE;;^h{98&Ll2SxDS1kJd>EAzGMkFZJLQRpH;-LKJHa2o5bE*6;vW z%R}<^zD`Hoj}Y=tc_*hEfI7Exa?9gtyzf+Wiq~4hU$K8IgCGZEQ&I+VPn;+1K^uYF zZ`Qx%8fYAzhyupA@j~-jK(H`MIOU!lKz0$7$TJvm-^aiXQSJm=&zP;(4G0qcMVNnN zJcE@xAEbE7YgSkQ;+{05#PG9_-NQ4`+ofTKF=xT~2CZuFguhWX(0l3Ii0@+9>R8_p zi_Dd-dR*5XOV1Jh!KAFn4l#>ta z17+M9Q>0e?w5Fxpr5h|{qe#;{H8u8~?;Wg8G;HuzpYh^0Ph8PS`D*W0{lP03zp<0t zAKpuI3>wSXl0dn`J2xG^JfCQ6#LMBJ$;f&+t^TRiD=q}gKQ^QVz9@HEA;JceP zQ>8U;NOqYD=s}SBUO3nON0ex*!7PP_DL%Q?TykT84m+vIkGowa>~BK#8n1+m5=JSVH(_tWMh?mPS3WpGm( zCt2yq(`54GKOp_Q_%VARY?;~Kb;`txwD%Ykbpp(9Li@sw3F*Kb2v;ay=-B6V+cY{) zra&|1X^*jH`@%-pkZ*aSWyWq>eM=Ve^g?iF^$eNPy#tQrx+Mw0ZM%+X z?U$*?Xhn)@EL?DmI6q@BCg-^MlE!if$_|5Z_NBXZ`oBh8UUaj4elRcFJvlxEw9Gf| z*F{8Yk{qBfRjq9Dz{PXMbqNLSX2m!9at7M+LD?v|&s3#?`)SXu{heePBSKw#_S)t| z+}kL_3$_EEM8;W3j96c*OPlT~q%TG`$)L};MGu}dRq51}$$2$wI-$qJ&mKl!cG6EE z1gO8e_pu(#CDvTc&taFjk;w;)>|-}wS~qBsmG69RHwA^buK7yy2B2KCv4$!9<>lJUmIA8mQWNwxX++%OWw&afD+HvA*TcAYfG&;dp3%N*;m? za*!Jxp;~LTC);RMio3%Iqhh+0eRP&<9B^+I4$%#8$Zu?8rb^Mcj$n-8>;{_E z8hxzR4~4oqt|tjEhPDsnma0tycRrvxRxzubYKQY`*d?Gpi}Yu1!0J{j0$qA*uK$WC zVKyTLIH;FD`IB>%e}U{z^&0tscuMN-sjeP&D-q8Jw)HnVqUnELfr{uOKVE*wjS~=? z;TW-to=Q@CoW8^jDhPC!sYJ@p%Fc6=qVV#c17pJX*Q0dX7SHivntTVfivzcY?_}Y< zZ7kA)DKidfH|!r0^F2KU7Q)ol5C#I7%p$OdTWThvtY%7gzMMyS>M}WzgWLLQ0tt7* zWUO`{K2zEt=(NcD6dzdY_zh_r~mPWPHerKI>L9Id$tQBO7 zxpA*(^(FT%$W*B-LGi^vnB2w>v>q1&(YM5EQpc%eOIW@?t_=+tO@ z0vmIozT`KwnqoaaWpQ&){9Wlw9CnL1ZYsaIkso7+0L6q}=twJJF_6UGDb)-jzz?nI%qIL;fDpH-AY5AQEkvS~K(sYN)Zj5uk) z&G?#w8V04N;{9xnBBim}YGfB;@wo*_CQt#59+IXzw^CZbj z(Lu^7#sq!)%$iu(}q!NPjayxHn(;QaTw_&BmstF@Ej(K_VzZ1})5OvtUA(2EY zC5YMowln@KL~cQzvw2*Jv$<7!IdI}tv3Ovq=c@IiteqlJK_pekw!5}VG{**6uow5G zy40imO%`Nrwb{1Gd}*irgj`RhdX{m59^$>wrQC2-Sa}S$Sa>%_8zH=>ML28TT(34P zCoO4x*3;x9)lPV#7T@BH8!AEdOnWv}sM!#wYF#0vF1*BnpE6%MVAJbXVB>O76Ylh6 zSisyiPeXWdmCnt<(h4AH9J`m>(`e9Q<(*(cTBuCQe0c71>kWqZh0}Z^;p%FiY?{p1 zzyUbRtst$!5iaYBDQMo7N~g=0M!^*%6vJb=5jy7~SsMz_XCJ$e^3~HLdThJFJjky@ zs5TptAyOF%oeB0BB9kG0_oZT7K`nw-HAWQkEZ9?DO z$$1UNMmXDvDo)gvb!MHH1Dzs&{iFI_LDTmX5GvU0>}5`FnE(u4>x{g6tS3UXv$V$q zz%;X9>YgaLxNH$BNvkiHnXKvte?*QCdIY;3z$S@7NCl56n4sy3K0@9f{Fyh`V@vy2 z3jl}te%@+A*tqOsXo$#|!H9N4^-Z_NQ{MLL_)^$aN5TW9{_ZMQ?lY~Feb$29&J`%T`_sWeng0rFwSy*zoU zPBTY$$Pb%5k4+HjyV&+2d}4(??THQpOYzpVdD$2d`%{Bu>hf>yEEo|jFJ28j$P2RF zC>Fcf(RF*{k>XewChyU?FK=GV^Hl~GOpO%1m~D^=Xq+1O(QPiJV0v61kq9#Tss|r| zC&1l73{u5gI`!b?mBX%H4JN%}e1vC^jFGIY6?n;S;iZ8lc))$j-*ec$qQ$+=Jx~ND zXF3@+y%Xnjl{itNIEYBioj2du$FV{|OG?g!8LtU}drmLMLp-O1JA0t_Jux!d*2PwY zhr;5pCvJWf1{rbE>Q>ga=2jSA#E=jdPvqiK*x^{m1&zbk-w0tTZw~|keyk+c#q5fz zTz<3Fl@J;AAO0xW;vNtGSsbG`ruPEp({UdV9vMzkif@~<>v`Aw%FTICG*owqwm3ha zVkAwByA#H0%0f&xPuITq#ZDdbjfaiA_9sAYdc0~e9z;~R>%y=k%|Lo}2kGOILtJL_ ziA;2sMP@Ok$GiiXffBa#A`81{6z4wsn8K{IG?xX+z@zHcX$2s_jnl4@mYN*G&k054K(MpwXSdW?IWQ_8E-P?>3$0e- z>H!|p3jBaPJ!CqMVSbho_Li#67@WrDn7?^UeO#EH4bBtO0GYr_Rt{=KiK?gVT$>*yx^oy{2q0kkiEYNg{SNdjPa@ODqUI;{UCH+@2AzoL1>*JsZ{_8Fbp- z0SV<);YmDfGt=O0mZ-32PRlAQ?|!fz{xw!Y^jS(Ibatr@*|I zi<1SNmtakL1Gw0paBSH8(e|`I`)qey zamcof2#*H7(H}9?7P6FPTq7dxBWSwz#zbx8Z26HM&aDaodBGN!CeM|grUyo5Zdmvu zpW)pw*&MstqSfQ$GqN~{^Hzq?*h{hIA`-BpbO~Z-W7e76JXVWbiEC!nBiKqyPn8m( z*#+_F-#P}IZ_AlRq29ahq&WfUd8@2pt1daF;_b{L?ia$Pp3Zua*I6~00t_-~{b)J= z&}XZwQtMxE9c7&Ocbw1rSwLSiF@PP-S7mI0$+;qwA2iOMX(CRfn^FtCtR7>3W1~0BTFlE3v z)OCD1_OlWw_kS_*jR`#rQ}UG6D{9R52YotExPn1QGc_*E_X(#1*ob>p@ZK0N`>sLk zEG{}ijcU8=&)WuA8~W0DGmmHCqyVDc`67X4#Y}F*S((!mU6qMpn2hPo1bmW#!9a`{ zE5_g9w*0Qvi7M&Pz`-(^nht?9BVqaG>K7b#-*;P-%}!@|a!bp0o~?CeyLDQZh_3Z{ z6%;AKZt^Su9fdqw>7-sg42}%3s#Efw z@}#*aOLm2D>pZd8=Mh_l{bD8-m7|R>OoPEA5`C0FhiluTbI{ys;%PsnkzJm0zDmmG zb1y$1U#92k`@6{XEqV;=m8*S&Z#WYK^xGTe+0G{1**AZuSka?Gvn;U!TF>_a40QWq zo<-~oMI~!CY&_N$pRF-A;z@~`8-aumgs7uTS6zwv?(J$hehda}GLpdWcu52Bc5ZhM zoGzN{ zP;(a%#zGAdi9CJ-{O3D9OJ4$5#gEyq@v2_w9D3r{Ws-!3I_*}0kXzRN`>$T8e(9CY zj`DIt?eLUKz$v@uQm11^{M-4%G{%^=pgCqyJ_2Dt{m<7| z5BijWGIs`PNwTP8?4W6@n|&9qCS5W`fvYMPPT{@n*Zf3Z)>g;4jEPoN4@SU@qw;2h zr|b%cx#IK@;#v;lv|@eLBh=d^P&{Zr&B{8=1ht$f=7@l5EvmZ9Cw-Y_)pE_VThWERn`<56Cz>3Z$;u_kRwy9% zAWz44XP(?Gw_=2LP(oTGO=r_orn`xlb}^#${C^en|9Om0{Lau^p%1}|^xnV@_oT#} z6qDC!QucR;3;`LR4pxovsV$D$yjWB1b<3E_D3I$ z2I;3c5VFt9@cV9nbKmudFsWx_%QZ{Tm_R(b#J%XHlafDEU=cpmMWas>VkzHQT36lm zeuV+vb}sz};2nQ*P{OzQ6Di9-?jK^rY$F9!FHll1tX7Ox_w>A{SbYJ^aKTlq&*>&A zTWgi3Q|8cdw}Fr?9Al1VY?p zIzWAu%_&|RrWq`(Frlq@VCYh0nu+Jg>`bdgJ}?IYXxk>+I7=L$*rx~;DpI@heWj&O=iRcaC5xe<0qm+~U@Q8b1OSmAz7>wxYCTU>#mf2EHAB59vYV>az3V|ZE~g_hKFfU?A(Rn865ZEKF*hghq%D8PL^ zMq10}O8EoBkKWcxbzLmYyLjHy+ZI7diChz{!!^FsV;|cyl8N_nNsC&H?W$T2smt2CP@{kYIstOLh&I@rT4qXj`+NGh9IGQmMVp94Y2{Gx_{i+u8 z#a{U)LkNrnr3nUXObX zQ~1qLB@QKsdm6)QKObWim*@N0g7tI7*^K3vqhQe<*= z+}bCQDn;{=ssYyVFWQj)PtPy*xr+m8KO6he>?@_>mmVIN7|Vf*>8yNQZL8b^&Mj*q z@{>-()*Jj6guS_@*Aika%vy0}Qfb#bG~JkSA6K*xoArfEQzLrm`MhNaw-yhC)hpufe_fHVV+M zFi%SJoUI97sPK%A5c;-&q$7Zny<9@O&74T#J_*Vm#*Y#Lo*Fh1he_SBjB7z)Lppi% zTD*@Ma99ykZ7`3MiAnL)X$LcA7#j~IBsShz;(C>Iwf;?RdKmvq;Lq7!q7X5 zPwDmI6M0{pK)yf4ezD=#qjCF8fGjG;d>`V%79gu995Q88b`oRv$|tfCUqf;QyuuEa zy3hI{VsQf~VcrIZ9h)m{dWOz2r{yAiAKbmxxXfj8qRvFKu{wh3>jLBoy{Yve%B;`F z51z~qZ;CnFTy25pOgWq1h~NA4#l3)|EWLXLQyLInhYUmxxi`h0!>w`e8nl-d)>`Kw zVnha}uCLNGd`rhnYalkZ`D)tBtu?bLMbj8QAeJC#n%3_gBK!>tNO|j$YgWKP<|kA1 zv7hdQ-Ar>R+dcBlb)&~f(3-GU-{9F6dgCUf`ih18&a;)(mpxR>#yBGTF_QruQU@Ntd7k74_WovUJJ^E{b8hZ+kE; z$l)MQHa2OE;}24}Sp!X+&=bx`zp+z7IW>0*TxsoP42Z0?_jD<4=%Z22P(8^)(Da)1 zm|PfTczvMnZkE(43r}Spa|laAU|{CkTuDeyic6JE>G@0ggUwGTC)_KbM=qGJ6%+c^g{73n++Wxb@8sm50J2dKc zsvJ$QKIvy~2&`?2qa3@8t1!^7xOv^M4)uojm?d9)GANe(d!q*FM>F;%K*uwbg!I%r&zaT(|qE1=}0aaB80)7n7M|fy#$&Ce{ zZ`kOLh;N1fvi{28f%(Ui#_bh2M62&oF}BQxbZ=KIC;npi9?&34&VA~) zkvjB2XAq0xV%5trypO?ng)4l0I+7U0AwF~E4pD@ivD|-DHyhOYePQ?E6TT@}^0p zKL+og#r69ev`ck=kEnY3BRTna1M{r}i`cNOe))Ko>F#d}v_{QUgDk_92Bm)sF~B8C zf&TSl$iO=L?iP#n54eFWfLB}IAEM%q8T4Ooy(qdi^yRw#RIZuTG(G?=9`u%o=Cj$9 zX8G4uWv74be0@PD-^+?(^=MOz_fNm%kN0y;hPHu3PGH1Y=tUx*(yigF+?anT+EnmY zY&B3GN+$pKcRoFM0U~bcnq z9BvQCYY+)5a(G6$kUtQ|KZDQn+k7+|VCDI^CClqEcW~3PSpfiEDQj)WG<~>~)W%9a z*sZ8e;``T$w$3k(mKg1I)R{$BK&HM-N9P3)Cb2`BtP&HyvW@vRko_6TQnJsFU8D-0 zAK8wlHYy^+5q#JkI4q{x9u__BJ{2Wi&&zh21$mV>q%Wi0!nID)L#p+k7R(FBD?{~b z-PpAVl2HQ@P3q9OV#DIuA}tsCWih2{av>+?*ai@hrZbRB*7W7Nd`@i_@e~F`^8#J8f%)9$N`X8N)`T9_kAicc*{@HT#>dPv7 z(^u{qcvaL*#gd5_c`$Wru#IbBATOi7wTRgjjJm$sO3atVOSq5#o~?Z7khN)sdX0bo z76AFfLAO4Bs*ag9XA$v}T;_kcS~J+*;O!Zsl~Nq#Y*ZZWJW>?PTCC5UlCtx-#xZs$ zc!w$MV4Gp30xq_#ojyA_puF~5@b;p$-{-3}l;tvTG%W~4bn|Zt>mEKa%v5kT+_ebV zUOgAQyLiqY69$)hHbf{eD%KyfhIg>4bEk^~tntuNo_du3N))0AInotV@1J^|MJYXGkoFzbU{* z-mPh}$QBc}YvBM2&hnCtEUz3K-8)p>pOXoa2_~Y5n-KOXDWADs>}(RJ38HXI$u+ih zm)95%HI(bd$ZWKxP&x;pNoohzwk|dV{z&c%v#tl!R}q>}8ufF<5!CRb+Rqv$k5;IC z_VYuk(bzgxff?}M%!nu)n+@{T-P7ryYKj;QB1XJ|^_L>hAyontu(2Pw^{DD6#R=4^`Q6&_;?A@>= z5t;*j+{xw|4qC+CZ*QZ90gh-+0*;GHiDHW=g>XGooq5%fe0!X~AdIwY5|Dv9DAxMA z^;#tVtUNLh7%0OaY6ABG%eJoF7w~(!w_87W#;U2@Xz8t3_->i*;+NrWZc_-t1L1|T zp_+G~({jLVlo+XlH0e{MIRdXg73Fsp#=^7vaBnbz(8pIgy(v)H+N6&wQCb`onh2xO zuGpLuoOsMBR~O*I0JA+Tu@@0G_bxWNBZi+Ff(d`}Y|>{eE?0f44~QL%*ax5$U8bZC z8$~p7gidlJL_jwN_N<8YD`l;T+ZVHCLrmqOcr+v7mO%Gca8tMVJFv3K1Cwgh)m*CHU62=WR>^AM>< zDwpX{)4Ej8JGD&F&69ER&}t*P+!XU=ospJ=uKVVpBc-2_7VVQ_L)Q3A@gXV!Y8ON# zN_UBd^_5l~`9IcjHCYs|tv0O;G{&a3Ak9II!UpB9PH;$C=N>9!_odt>A_c?q*f92obL~}_aMEuf=7r&)@TT(N;IQ(}_0PCC!SrO6tny@iX#PmfR6@fq2W>>WlXI3z5&pgVXOS0-<`FO;jDhPMJN zh}u+ABoTy|#eGfwbop8Hr#J_zsTI#R;`>!6POq75N|y!SE+5~o0oh#Sk2w6192=MB z6X7+mpQWzRuqVP9W&S$UuT~Z>BO9jvn=2n^7T`fPpz+N1h*uB$0;P4iskbjvR;$SV zKS&$feXi25N*9pNUS9(66`X>=P%5X?k6+v2w!bY;3Ase+oTg4XfgZb#?HUdisQ;g( z=ui6{L$#+&D=h6sp#C*r`WIIIlO=PdkVcaQ?Y#EIpWxKs2MW)=zql)~HD@#!B^kcd zV8SxJkS`SzXxFgbdBLDJ)Gw3(9c$(7u!CC>lf1q~XN+Edo-?3sJ==&QE0jzJMl}6! z6+bS0gQKiJdSV7P-!hPA@$76hJEhKA&g$ZypJ%8Xcli1RX*$fHtElZ!J`K(LN zTA4DL&Ub($6L?K*I%E;vc9#gbbC2VXEylQ1H*IWCfj(UFg#H+%yM^8n`@NVbu7fgH zgk5}p$H5Bm`qDb*{T5?GDssR;o$N+%spzd<4k*@7Fw@#iGqdhTwwd=s)TVN1X+1oU z5?xSTnpXLmwod5cfD{aa2xzmPEpm%_(k}2;Ocib*5B|3JafN*Ztv-1zU&oYg2^>qk zx$j!pD0g@OHcbg3@AmHb=f$hTr$0Gy9EMxIJR+EL_P@8D7%p-P~?|)Fg(wdF?{ODtcYQn>cV7)_K@tqhL9pQR$ zco}-lTW{-?;Hr@U$uyEa+*K(;(*q=hjnwVJ?0Lp_yVRq1L0STrTFEDVuMT!_7dQ1xuz{m9@keDLlKu*# z)gW2x$xYMyZm&2ZLClY_b%F~>b9)KP2C0$<`(!EanNQtH4ht%{eB!JWaH0IPGS0mo z^+?iQ!0RhTIN<_cv3{QEuyi%Bq+ssI13(2}>gKja=bv*uY1>QgUG{nQ2xjRww$_o# zTFfz4Uzs=FIXG%q&aF%fUU8_6MB`kXZ{6dT?l^)|?nYaI^ofS+$bNYYcYYYeHaw{a zxBk~t{;6#=5bR?Nk5j_IkS#)yeX z>;?>UW1_ggzx&Bt`QsWMmpyJ+f^DBz3V7J#`|e-_YV;X9KhLJpa>y!%vKt^w|6|3T zg(keta*D8LW=Pst3v|pUc*F7naPDQjG0-r1mLsXA6${-S{+xUO8yIAcDEMU0qPF;T zsM4xAIkChQ*G49Tvd(t|yVl(h^TAlu@GP_j%%P9e?TPm}~w+vhvtqGQy@`(5K#-TQuvA+GpfQ_0=u; zhPsmS#EW(lE-EmZ{z@r*o&j%YD+uV6LK=i`j+|z^H-3vM20p~QFg_oxHOM)K}j%mO5N0e&FX7b)wyZAX2%0n`NRF>14#hn29u<8l=9OD&LB@=y=%o= zM-;!3#$|HF^<2I6$hzY55(yW#EVz{YEw9IphG1N&>&5G|x-mv-NKl=QV;5^zH^q|A zi|u*Vqad~jcL+=Ml%JKwp(yOy|;v&nED7D0A9a*3%L6mGNx;NblI8wurW&Vq&oEKyD+pmDn9C zkLKdZfABKBur^SSh@n1F*!&RS{^1gpjR@GJwb z1w#T6$ip?jh{xTw?m^@Q0|hm=X1rX16F9%X3IX|nBlN9PNEh1u;N&YUy9YdXlh~EJ z+17g{+s@CdeJMiTYq@n(H&>zLlg=w5VGk_8$)Xj?=TtdNU4_Y4S z1yopOyg$d{koA`W*F2Ku7r@`=poTJnJI}>NtCN?&cR>q{xO_>aC}o$?#SgFqJ`!<- zM-Jti!krX!__|o5yc?h$kIjphICxYiSlPPEoqEw_?sJ9=7JX61@i*0NTGknPCWwp` z2&!ESGQkHPIE$F^SjOFHiiL@@1I-kka9Y_jp0YvoM6#;!4k*lyPBo47Qn>msp*4CPS97*m-BYHgUkd4} z2My1$mAx1r9S4BTCTvM~?cfM#{>kZC>8@H%Yu-n?^==?dR#k;s+URsM`XOU=oSR^V z1}-7u#20*9G3$gE3Yg-&%{H!laxCNDa;MO=!`2RufH7wGT#wKStM4 zC`UFRd8McYFYCUyRLB_}Mnp9ip=3QkDnZ(>3N{R<9%HB;3WNrG&EE91wuVcmAIuK& zYjN!r<#;k5ar)~WFn7-Umc=KAEt5d|H-$gk7>pgP!28o&Q(+37mI zs)JR0B5b0Ho{<1!e1{r`kA#Ak_hwi3* zx;*O_bPS2SV|Q&WQ<5)FC8It%Uo~WbAleX$%cF`PgqeB0Kdwxi0zPcOwbeTq404+cE+oU{?21&BQlOaf{SYs{=rtKlR94xu7*a zWjJ*#a;wcpMKzp&48mreKkiu_jCN3ORq)ko|J+qOL2YLY=bCY?#uvo$Ux&EnkrMb- zi{@O)H=deV?UcQ=9rD$XE)V<}t*&j>8ZJR2MmoZcM+rQu$|Xn6J?`e4F-J))(FG2K z-QCfQ`)!79#N>l6C$*UBS(%aMfVGz}h%3CZn@m&L1_`86#Kx%6pmDvTC!pMPf1r77 z-W}|$S&O0tCe3#NBLsrxs*_>L>tDZN%WI%HoX`rThZzzpLhLEv@5X1qO<=Wg_$aNC z;?*9vG%S7%Esr9nfq9KauiLQ^#cU)hJ(Z?Ez1CBYB4J~VMtZL=*U@Z>yF2lDEDBGk zA25;|NtY~sQ~mNUEA+pluiL;2pZCzq3;qt3PIYIPRJY#>nUnl~;=(D>=d`}N_r4@! z^fZ8lp=k#Ygd3k-U}X_UTYhV*|IfSXqWi8z&3-E!15_+)yMe?2b$M%fAPkNo#?b0) z!IQ2I+O`P?M2bP0KgxUc0(4`?V$FcSo2ar`#a5=;y$qewenHP4+8`9CBLS77<&@G- z_WqL&#p}_My!;drh7_mdF+paHF|TWcBD&P~y}48Z+f4Xri!32%Iuf?qh`pP@9$D8F z%6Itv-N!g?NxbjGkfsKcf}C$WP(Ws(F4+KxZgk=#XI1Y`<+VR{0$qfXP&GPg2JZCb zC7_bJMq^Y2LD5Ng%zUuN%{up_4{i`7pmy*qJX3z{$iu}{RmGQUr!})DG9HE z68Tc#pPGD&6Zte?b9MHc_vs76O>v2ye_c|Z@gEn($|aG^<;}EG<+QsQLuk9*xDu3Wy(U>MJer*hVssOEyJ#z%J-3s0GwbZ?+Pd z84FKOoMp*eRhgR>+&TYMZxHysfW9#PZY#%yDn7Nt=nvi6%3azu2|r`hZKhgga_ttC zV$yV+oAFIL6VE;}>nMKu40T<0a(Sd6>+6Dnh82kt9CO^c?Ov%-@)Z{{KX#N9=ch6( z6TWQe%kn-ZFF21l8Ml3msi+S2Hu4N}DS=q+9E5Mg+r(zrNI=i-eSLv&BxoI4@l&ro ziN)75R%>{dZ=!Ca%=zJ)I#JIfdSr<`Ly#0t z{x?e3Nk1U{EB2GFF4db)uCt2-(CS%sOsQW;`TNElIl$PIBwosWYTLIOZNnM1siid* zHG6W`fS0trWVLDeA>uIDLpBtWDb=ni2E8u+&i9LtUlns49PH z5pyGTqxfp)ay1#J0j3CYFNSBu*8!N{bQrEQn?vOZ-<}0Uox5@`Mp!33wZAU&uMPeo zUs{_KRgx3bc$PPK@tf`F=bv{CJUd@yXs8r5nYw&+O+p1QqME?~)Wa-y8OOeIZyP^l zyUz;YCZSdUuyt#kn(uh|H0&RXTf=o&`-GR_O5DoO`~$ zg0VD8zFV+$6y4Ff+`%@7tqQj7^jXA%BkP??Awpx+93Cgyz)P_kGQwob*X$lNTfMic zi{r%2wURw$n_n1)O}$L}b=tUe=v{`@=0P-eb3OjZ>)#NYaXpJo40JaBIop{?FL3kI z6RM)tdR6LfuMOpDC#d_GjMJFvDgbe~yg4=tVB&g+sFEdMLbv)|ko8ybnO1gH2W2+O z1wR8F2lMmxU3Lx>u+NV%1KU^?10TrUFhi)=fAvQ;)VIsY8iT?&x-wjNJ`7WisEKo{ zpqb;KsCZN}+5g0fO5QFW5vzAVWCXWT?&ZP0t9sGoO26#0SMz)KFJ4gFyQCXPja>gm ze}N`l)tQX08ZgXaKtpWBuF3WU2twkh%Oz|UG}t=f1mfYJF_=>FU3^=z(B`)pd+G#a zLWPO9!F{o1cWTMwSG-6SS`SI(Jddf#dz}owc3O|*V*d5bGR&-5=aI&$^~j?tutX5{ zlZ!440zS(=9jY4&il3BsWfvuk%TbU)h0#f~fwWdj3B=YFDInJ5BB%?pwh%^k-zpo# zx!35en5NZ1R#0I?D@~>@cEGpYu1}@@dV{hf~; z8j8A^$rI6G!EnHt0NTP-4AfO3>`vjTweBMJNFIHe@OB=17(2NSzLVRJZScoWMSJG1 z?|AKu!N(EvorBqXo*Br9Hn(?C-n5^gieyof+Fn7_qX!%={k4-3bf(sgKLab;qj$-) z>IS51sCNaN)V~6?>xx^Sm^E0S(SwfZZ=^Ac>Pof9SlRxMl?bQ4+{bWDS5*%*GN^9% z2Y#r{27??+&S(zcY)^Q`9Auyay>7yisoGq-TxP-ou5B7n$`h-A!?l?vnJ?aKSj6Us zKt6nMAl%uopg|3f)H{_C%c9yaO*ud-+6OzYk@;0a%7=do2E$#`5(iZS{{fZHE1NT2 z@g4Yj%*7ZMRngqg&5PKkHY)*ZZbfan`jbN+pfIuctgLx!sATq%JE3tW4E4^}CKjd{~+!9#44<0>aR9dHz#^or5WMi0_Xi zaejQ1(D3i-;1L|p416E}KUG+S+JS~G4HpV0HI7@j(rP{7m}|wDT7X|#`VpsW`ao6> zA@W@{2_iBd-aq#aT}|{DeQr+c*M0afVQ05`>bG0zw-T}^?A|23etlrB_2DNM^SuP< zF8c1riIA$_-G1vjZT?|zK-D09m8Chv(cfwE;x3Su6clk1I5kj+|9a9IWw#6rjjI_@ zlGmqnfwd2WQ;N`F@izd+r9%6|Js_t!n|u~J2(BvaL$aFL$Jw|scIN-}0K>E2Y2Dy+ zyn6i?CLD7r;`68L;8gY_gp5!r^!%kj^u&+PeE06*1}49Kp@}3pxqc1A_{R`paz36+ z%rO&|J=L#WL3liTyzCROEj+C3X<=G(AI;zE98gXY4So@gC2w1|tU>AH_}ftaSKx6VsZlpg9u5cR;=}J7#9%I`!n^Uk66?(YCLGiSC}zSIlx;hK8qlBUBD1TIjU| zt2wQ&LEDk9+~Y_oJLH^wCnQe1s>gbJ2~qrsoi@;c5Z;koRRJ#0M`ujU?VtZ&?7d}J zlgj(OO#GVFpZY%MHud+ReWl~;Y@g%#1qG})l}O3G6g75GeZ^ywfLk4FM2ADvY& z7EC{Fo+xXPFmqaosza}lJ1ulyDMGsZrn`nkz4XWmVu_;{M;FUfU_KR+lxX>@w3~hN z@Q0R8BXQyMRm^Qn;?uGGIe z%O7(+E6b#FC)UAh>*h8|&7mDEuLXG!tIyqnCIakSXt=tz{mr3NF07_ii{J%PwJzLs z)Loh=4kaHpbzEB;`V(=>wpiL~v8ZHWnBqdQMscX$p&hynYr)A>CEJm%{|sT?ycP_#!4?MKQMWi25~q))GK0n>wWEr zI2;!1SiaUO-f~vo+*$`XUVr79h7jSAhY#CJ%^FQ3_1G)kWjd6#ij95?vZ2#@^d^RV zl>cxik?uGPz6Gz5@mk!Z&U5?8t~@UyF0+96P$HIMF7*IJG*q6agv*7g&^x*9D%np3 zuD$3m$Tf?&Pb=x%@w4@DjKAx9wX8p>NkvKWG$?RfmLqRXNwUqC1&K;9ItJ7CFnmo; z`?s@V*!ixqDJ>Q^Lu1gsRb&g3KeU7(eq|5goK3o-k~&qbkTj*a`O=5=!al+O^UXe%qz&x78_bxCWG zOM6o?mv)q&wR&38CWZAHip1ScYRD}Y%gugIXNw>x@;s8fNNeC_c%^#%=c`k+BnR2b zXrC%%P07xI0Rrp9Ohc{3mF4QN8pk#`HIR-gmUnkfZ&>BR)gO4Pr1`J~+7UPE7;m@f zH86W_a=}X%rZw(A2UaEmuZe+UeTM8NTiC*y7}_m56t(mg3J|-wo!BJ<1$vd)R8kHC z**b4==hl3z%7|sT45N*n>2&x7HB#;wH~b2RNpF_cO20`MU6aKQQC{lzm#wGK&}GmtaK1ePzj7E;fwNHwJS+3lew=CvXvKuv8VYhrlfHNowY&@RZ{> z`V1nkCo60y_%6ZwC&3uTOVyLG{!i~;4%W76StA5{b>aOvYdZ>2%P{xcVDFU%4yeP; zYR+J^zAB<*J{faf~7)S?%;s6)7+8n3Jw1YqO(TXeEPH7K=Q0;iF{fzBuP(Dc|zBw^=r8 zgYx8Cy~hWQg5Ioj$`i9hA?uzQ-#o_yd|xH*uGfcbT76#Gnp6tau`Rp!uy#bs+vE1J zw=S3w$-RN5VNa(pmXf~VGS^_663i9+yA1E5Za%L}?qHv#7{O%AVn*w0$uk0LxU@tc z+DO85ZZsjL^MgiiXU29a97n+LUiH8LCyaS>^>f79U7VzKf2#ecH`N(Iw8=Y`Ffjfr z9&_p_0aOhqJUtgODO7%v6>9t&(>442F1cRwYt9n`o7Fe%ALC#XC{N@ z@|}Ni1%Fxa3+D!@>i`a_oCR8VF8S4)$l5L4b6?_Qce>Uyv(>%SkTIUNCABm5b!W|c z;>>&yJ26%!VDc&K3_H&>@KNS#2ZsdAcGl;&bL;BT7J{VlU&lk7E!2^zu{OF+Px5E( z06+6KH!iw2!Z}&)3r9S@$?M$JOk0&f-1-%%8*2Q%gJnJHI&7fsqMD2~@|M-cZU_&l zc6xrSaAznj8ZNrH=dwgQJss!TLq399Dy4!JW`mOmu#}TLkENIfjI?dFSZqJBgTGUo zt2K?#7khYPC4f%9S$Dj!kJjLgd(eZ6@)@7w-PcZubCu405*Rg(?a97}hM@U3vV}*lHdt&f4zO(`#%9LpH1YvYW52tN;5EJ5gH@0o zosXn_DQQ!&-3wA;$zXH>QDQCvM;aYNZOeBkvejEk^ygt+5l4FkTGC-!az&Qan~8Py zDy*=k4?!egUb>BefzgkPsJmZBQke)iY%ry;E+YAZwCFi` zREkOv!46R{pspJr$G-Z{l&MqF#q2na_Cr$+SX;3I7nC){mTX4^h4hnP(ln(bKmF>m zQ4k@oL#o9hYqPDpR?L-WekHebXYg$XGSzZ6g(u=i;O_v;>($Eh9kx!QQm9>rQO-IMgF3kU!GXAASqP+$1yg3b3V2mXy!PygiX^9Lv z0_TU+mPoVns#XE04Cn1*MekAc70zA$ju&A_q1~0R`j`50$I3#OT{XRIv6!h_wk9U| zwF^`b@67@8ZOaG_;)KTSws^7Bg#<8)x%%PT$%}#Yz+%n5?Trvidt!d9e`ymPzjkn@ z!W&l?Y2b4fQz0&BFun1TGlwhP*sy`(8|7Xk8GD^edZq%$@+}uCAIZ~^YcTqmVBQ!j zVe#gW_$-M}`rFlwzERbw=g+|a$90#%ZE^ddeA)U|abewBR}e<8^pR`Sg1}3CeJFj> z2e(9%a^#SF=}iik5l}Fi=tFGl4J_q#Ka{Uy$jntQ+Kv(HxWfqPPyR5^?+n8%p7mT@ z8Qu#3jRU45gV&{qz|c;V%qE7#+LW^`H3t;v?0lYS>(+;Rgx9FVaWUP3CuHY7QmB`!KKi2Th-~}HU^Ppzr_F!k5?r24dhE<~N1-hRr zOvY*Is_atk%n{PCX0;qi+~O);bp4FBf^SSkdVO!n0gM5M?pa>=dfVI7D7$Gjdjh3! ztuUfQQR*8M=*FGM96blMlcT|X21JdaD2j5ylndj#IJ91;Sj?b%_vbeX$Hr4}@a3xD zmEa)_E9NT#F`JUvpVDOqW3Q;tdewgGAX_qccoa6X?c+R{dr{3!*Bo7FJgxiOyF|D0 zIx~N2+jH{-ZywgIoOzu_Ea=i^zh9#15mwXF^Zn*wO2HC3X!Pi|gn@^zuV-KUMbc}r z4)~2+-{AEgZCOFz)XfDw?W4kH(+(`orx9JGmK21}#t^l#hucBxeGc@I?jb(E&M*p- zvP1MkV0vo^5oOPcGZlL&-ozqNGIw)YXv3pD$yr`VL9Gruy< z-c!F=GP|_0mYCSRfV%8_5CrMJ#jOUPp4d9=^I)Ofs~^)^mKvEIGru>q0IVLbJ9D^z z;nX)sBW)G4RJlWyV~~%p^^gM!t#E7=wz;diX1<_pm7_0I5vlLtUO{aWL7U6sM16=T_`wY+FW!uVzV>x`rh(yE>(CrA;}jqRcIKU{^@>n(QTr zRq`8((3N)E9#42waQZE)o!T%E1F0W?(e{6SfUJLV@Q|gRMSLUlE&=%oLgu#i%CV$B9zbM#B*jB`hnzp z1?Klxj}|#?i3GgWo|x;;2PK_)*`k{6ZBdmZt1#WWB`Kkw!glR51M6C)P_Hz7wx_ih zR%rXnB|X-k8&wNIHVKU@?%j*G&)f%1)@6Dzr%z8ar^V4g_+K%)DsQuMMWt&?t03U0 zR#K(Tg08egkJ1spSnKNr);C8zY}=3Ux@cMF>#_}XX>T;Bu;ILQ=0?lZy{`%Xv;lPZ zC7>?hj*+W(IBH$tzUJ?Ugb06Rl5c1ON>e5>{%o@G)EuYtgUPzk7x`piJkFY0JGs!- zLHnBVpa|2Ps(ZbYrX#JMg- zXP>GQpN$(Wd|V7oX=aEKR-?69%I{H+6?T0|dp0p+JR(v*H+lBkM zP4AkGf&t>ILcB80Jz0z?as`ou?T>ejCPfna;q;D64E~Fa-Q)h-%J)OUAdsPFX>pRF{SBbnC>RcN|JNks@$&DzUjr4-lb( zwz|?wze8j~t}z)*i5U&VC0H77d*Pn?@cH1~O*0!2H|Q{CKbp3`98A5TwKrPWXz|7P zaFNv=(xx+&kQW(_Xe2Dm8#}&)LuMtwYC-Q039u4U+=1~oIu z%w#@FXT`S7dTCv1&a6By8NEgqIA~+p+vI#)1f6od2vH?*^!tA2zWB!L8hr0AWdajw zoZMK(?VfCzL1!Q#)yw)tUw$N2ZAQ`er4O)uFX0DpOwZz|7psDIY9BlcXJ{(CC5uvN%b|qaOO<0l6Z>ZyD&9Pbja5c%O zOF=jkq3dFwY-cUcYH|mYNHw;(&;w1eHTC&jHPDSKp5e`s*da-q;@t&$K}%d+L|!FQsNe9e?gp$Wmgez&8gbt3E=-et36$@Z zYRT%9F5Bp2sAOrw*LhV1VbY)=+rrx>Fv~YZpubY)FVWXHUn?lh>ua2O*XmubMKhCGHG&~lY_QcI2jJL4+twIIbD2VwjQ45re zZpTQ-s*tH8%`LSt*)Uz!`L)vYddD`Y(Yf|L)0LT9dLfW%^h<}2R>+k{i8BM%&_-Wg zb4hv8K@A;9Px1O&=^0K2?EG*UlS*&2Nm{(i^H|$bp}Lt};yJ>pCqH+f=UyKcGd`#M z>INt+2hXdkCa$M7s%*`HsGBFl7p=%hMNmg~(Yg=AY!m4vJ%}P>PrOY!lQ|{=8EsyI zJowW+tlarFv}-?4QNOX~Ixr;n9J!v~1XF9eabBG(_H*Y=CNnZ~zQ_9yUJ0>O)^@UZMH15mnp4P z>1Sq@KjgWF`y?{4EHerEa>1aeBbDH_FrS6&MJD*<+?mN>v@IS#(vjZvltOpY#;;Nb zPNivQ@4wO4MwU0*9Ievcxn%)Tt*b5S)g55j1n=p!mnf2fx~*@;I`7J&V@Qxhmt)E~ z7Z2V=1W}65ZtRIz*kD>TWbNo>r2gaRsXlLoUep2MgxEwR}oBR@UG?2NQY)T=>VCFPNVihXa1@Bxr}+ z6N=`EzAHUj?i%oOHl3|Bsf@VsgCm1S@lC>`gdo~WRvup4(~&D3vaGTo@LWBQ zT#_u6%}^{}$jGdb%{3@Ji{S4?}aHe~fXWI|9@8Q8%XtDHBPhFd`%NIAF&D8#vKPdBW9!Hqf` zzx4AqX`;jq78OaM%*_(a%XjU_I)*ivr}S}p7f<`{JUo2S{C?ueE=LRu3>8jojt}4U zYhL$}$3T~c%WmiDzII9pSAP|*p(Ps87O+D*b3Hv!68QqZVZ_X|8;=)AC01#J}cLwf)BTWQ+b0c~b zOgx)_6uzTE28VA`7#%`D;GWnGp(=iqZhnuvZ-*V6PQY{)Tu z;{VIU`#*yWhEvp4kh^uxdvEbr%@3jHtf1>Y-IKrC$XVOSnN8os1$FXTsYRGnJww!j zT%X$=Vp7LM9nAIRZo}m9zx2=ld098SL58xCUi=-KIGe`~hjy)df5mnNu~dN@G0Wyq z`^qe6`3O35E$TnN`mPZ6c>hwO@cg}&^^yAn znorHH>4@cp{Rzn`IHr-Kdv~k+fF#j(*L?_dT1~aO<v)d$r8bUz$x%?e8ZY?FR93 zcJ{d7-XRo}R=!h`)Qb>ga$W4;14UKS=%!0Qx1`_C@A8>BO}DS2-#d!&~ zUn{;h4M;EQ5Npar6ko&TXd;guw4*V=j?V~Vo*nBpNQT?Wo%%4CQM&UJV zb9Z0La09s~x;i+t1=u=QULi?~b1;@zAFC+_E#_bn%+&(omG3r;FEmhwMs65Nmy$sN+Y_Vc@e{@#_HX?4m>C_x7l`vKJ`9@=ntEtwl_J&#%LP7Pnr4MDuiDI-xBpSV*6X)`t`ehGWP0wf;NL5j96ko{E4dHY{~RdfkLt?zWapgxjibWy zcg6qZ*?;^Ge^nCmBLQn#B-Cktrc3+!@CCF8q0b6_4*K)sPrJOwlJM)Lz>WO6sekCh zFY)`+sQl8Yf0)u=hTEQWfjWK$cr_Ki-k!-^KgiBZPn0 z#yq=YpP!Z4EoG)%_{U4{X9Dl>O;9JItr7e_4E7JX`7?L@FCQwauAKYzyZ$`h|F>t@ zZa?nDrF47m27A~6PB>j{a(&{fd+!pSpO-l@8!2tG=$F6itFM7v)8zec*Z!|}O#uT~ zXjO1TLMZr|G`!22JWk>>N!1Hk{G|3>eC$~7q5cm0Z^a$XgYo;k>m)Ev3ELzYO5Afa zgE=?64-VZ5p{O@FlP;IDkLmgEj~9Lk)LGfkfra3A8lI=U>xTsIn6>uo9ZRlfLdHny zMZMhgz}Qw5YS=RU^+(EC(vR%MO=tHh_?qXyU}TG-&qBKS1lYC*1JU@=_S zIar6h0|=o^S_J!^7xwA%?`)uvizN!mB_v^zo4?^PqI70F%ZpIOHU#f=@r4^nRir#j zzV`8kkZ}i<1K8L0M7k;kNcs*0y$nJ3rF-kXqODS(KJkx`^nc%p_HBS`b9V7GtB*i} z`(}w*bNg2NbcBx3=7<%RzOcF|zu!1ma0GzTlb7!-Slx#ER^V5gGCYMS*p(A+pc{)e z3j0osrEbQoRV_nW`kmmZN-ct;`e*SMNy&1Mr>#yu$DY%fnK*MW_h=+o$V& zWnH1j`V&ao*a0Onl9Z$OJ@5^WKUYD})Jp(bp*MPSo`a)T)OImB4^!$N*osPc3KPUy zT+R}oTKRgbc&(=VPaXW9ACh!|j7-1PJ+KB=Cl9UE`32ROMuvvj43~005ih+hc+?Bv zDXajjYjYlF!g+sw+}w?%l*11-VKeld%~)fGQRQXW3`@m6%i>8XKxNh`n+^2+`t&^| zeLR|9ym=bACpD_UH_+{i#rb4HRs%Az-)OFh=lt7*mIR{q(~} zoqkh}CqWRa69IF9%PSViQjOU|n!qza?ihld7L)`kV3IdLqh~n4#Aloe)Pvb|QOP(wTqA?OzHh}Du)YMXES0I4EOcA&t&Gb~0xiw$O3QyY z0G+LQ#+e%mcrudmZ3g!wb%6vJ492SXf+trei+22Vemt4~@emY+z$TLPkDNzQz68iX zA3DVM_6?RwaTOx7Ln|vlKN!$DU2gTvSTKcexTOfjw2u`y&23h#Tpd?moYA4*&)xV1 zXac#vB?JQ$EBPDC+{V?Efi;R{Uc6?l_;Ze?#5lZMbJU9dI%bp?YAeJ1a7$QiDjqQL z+>aNMl-ik_oEw139{<)NYIuV<*Olh#1eUZvjGMxxta`J+I?5d%?k@A@=dHm!42S4g z=?{^ecn;`x?$n2l{``L&#t;8gAb2pn?ig|fTywpx zqBPqbRwvB|oSsYDbSA+q!rU3Q3Tb;=C`%QyyI>t#a1Z#lTi0e8XBK+`Y}yY=bz*Nj ze(iT)-dmQ>*+QwUXf7#rDMhcEH77`>b)>7%yMkMOi9zq8``so(E|5kBOS0cD7E=@_ zxJD0SiqXXB>iTGAM`oZOi|GFgL-lv z@d>C-X@ir(BW_mrCR3-cZlvatv3iI^=-3R1-X2j2yvebxne)mrWCryiX-NMOY=T_|d9c<{^xm3(Ru90xy^Jve zz@gCuo05%CKJl7f(%4d^B%kxgFY>O@Gi+{ncBkY_{e8WR6u>g&n@uIpW$Q_|5G#nY#3+I%IX^ z`_agQB&KwrFDY)}&hCXFMBi5EhI_*9;Pe0%*(6;WUg?u}%wW`0_#Vhj7IafPT7}7R z=$d)`4K8w{7YZNh|iGaqbJRt6*2$#OI~Y4^_o>>muy% zw}Dv9QP**warhp*|Da!g9bnxkIXoC?bz$wj#uxxOxoTNeOUK%8i0~y?$pw?zS=q#7 zAbr+5>IKs_e0-&_TmVAK01lZh-2&QIQ!-2Zpxafr?Y^iL>r0_}sdCQ$L@-}TZcGxS z_ks$w>+nsPJ&h_@yY6&}d<-O{t*I zx<5u1xA|pyZCt?NL)XXP);`O&nTz%xUXY|apn+>Q3uXBu(t{HiGJV9Sq)h^|gfB}A zk=jy(#aLxb&^C(veDQnFWhRO4#eW@Ee)#~V#{xg~Igw6st@7ZZ1vJoR_A|^=dL|(~ z6YcgU#?#>%ug)wGwkE@Xml=6ZuA$Ei8}`wFuk}^9wZW;TFoghDvAZJ9mrzSb23`f`ftp%gzNW1#h4UhuDm8G7Chfym&!o*iShV&>H@4V>`Xbc z@Mfh4uCKgaWdk{g&34!8()X0g-DjlNeg@=kshz(p- zZ{>NU3`T*?sx$T7MtSw2%Is>!Cyf!_*jJ&`uO{>ZgQKo>ug>|7$rAHMcu-kz^;d|o#eQ>yKR*Px(UAbDXJtTO z?(AfU0-RgDwJ#kQK&^4xx@0=<4lenQXT-haYLYYbdeoK7!Y)p5qtz<5lsrgAj*fUG z=X0IUQ*rweR==5}cwzT}IIUL@)v=Vw-Lr|!F@gq(RMPlssfzIlxER_nsZF9Tx4S zrAxjgq&OpuaF7LNJAmQS=Lz4e1z>0AH544~qva_p04>2A`>qyP-KQ|A5yM2)E3Oj& z*lng-D7!1oJLrUSE2CHmaW}{q_hv&{sU-?P^IdKgTU(#&G|is_$N*00PPS5K48w4# zOGs>keIyg12!!e7APiC?Q9);)%aoi9I`2Z0J*$6 zOgu!@FV6ZKx9%=jjieDC_0k!i-fqPXcw)CRdRzPErRfF>t>^$Mr1z)uL){x-Q7@kN zS=mgJ`zFN=#4U)FlpUUhxQC`CaAqUU#aP+KpxJcI|_2<`ck4n`%WktZNX9)=hJi-&6b zO(@MuzDyJ4;czZxBviK{r>Wk{}VrJ1* z!JEa-o3Vz#d|ZzH-bZvZ-tWi_8DKC-+MHXWl)=ln)?yZXOjL_VVo5DAd{lu+RbEY8{9!-9{gft$Me*QWdPa!i8=h zW`bV0Oxq(j7Gq_5T(H3si5olQ5N%<_P@t2^yhFTm(ZD_X{mkIcv0uIiA?E~)+LuW^ z7U8lXBzBW`D|UBc5l%M{giW0eG^_h>n06D)aBvQ%sZsM=&n0)d0D=e0sS3g-BE=~M zLVB=1i{$yhS$1W$J42;Ieu`Gz3NFK?Lmb}w+j+g_NPe^`{$PEkOb}aDuMR|izNgeV zlK}$9I12}``fTOjan>n{gWlONb4t-Ni)I7#wuX91PLDWQHl_%*w+vE`|EZO`a}!ahTX zJqa7a)e6F+Cr^m4CyAk;rJ?M=GC5~5)|S?}G&9uRjBYq$e;0G;mY0Gpkf{y8Oqyu{ zwZe^@S-2o>(>uDp6uk4(=`hXJg?|iz;;mvtKJtI>f)fmv|3$(&deEn^g^$re$2%?f^5^nJb$-gBdZWhwfA9V}3 z^g`+##c+|ar0VLBJ_AALuTF}3NLNd08;pONG(X14M)CV@`_EIJ#v4iZ&c4dONo7AB zuKtZQNzqCcQv&k^l&4ZTMS~<=-`>XxhWh1D<_lQ$a&bY|9|D@Cq7xi=!rZmTV}uVx zf@^R8<TeyX>6|1?_2GEomu?T;`%Ob(xFACzk!KhLF=5`YMoN z(b>3*S%tj}J-shDZA&(CmI3+keW<>=5fFmvLsTb@he@wE(eHLPSwsv145Le+C^of> zNc3&ZQ1c)H2pS?KE$!!DEM9MY2&E_=+JCg0KF<`={G`#F}PL{)#BoD$lm0D&GE68mt4is&-Rx%AC{wetZqp*7|GLa z^kVb7kwxrMDL|oVi7X8IzLVK`02WV8OW5oUGv7q`)xLTf0PbpEyq4p6tBtUkQ>0&X z{Rm=s1B3fA@DYQ`hPf2aEHpWw*cIc>6!lxy4|%Db1~6*QBG|>QOzn7{MAHbZDj^-n z$s?2=S%V(-(?gDJdg-mkXiM5>I6#*CjDmMZW2;^(?b%FAti}Szk+EIWs+3W4lq+Yp z)f|NsA?LmKb*NUYH@~#wv6`fTEv$()+mU+pW@Sd`_=p`rZM?Glevy^|4N85H zIDKh(bFe~~=J1$$mPx`tFaCe}t(2qR0LO(sa}nBExs3tq(!iz>D80M&sc|P>c(}OH zd7IPtnnm*+@$A((6D39d1HzjffRo}_sb!DlhxQNEr%0P#wn*TYKaID*?$GF{J5 z81P2rTBdsAYB{sD?Y;sFnrZp~6nWb$Ib$K=SUo-0y#$ftf-O^_fT z(I$kn793pSZInNAT*z77@J^9?PmOgq3sZzi?sf~{#J1lbLbSkyCBcB7(=zd?jzZS zbiMK$wQNn;hiTO3={jgSS+v@7J}^O_#$Q+ICDH0da#7d>zKO-ea$ngUF%5!L z%N$~D$Zj4qCCsQt{oJyBuR2#c9R>dSMAlADDW<|RiGxF0MsR!!?&&n=dJTl0W{~PX6hJ%(bywHm2S?nlaX&*Dg zm4HMt(1LW_%y})$Ro7+iJXye=+0tQV=;Uiczkd;}o}`l2a|>fvDS(yk&CIEJG5}&J zYv{Sbg@tNVSM?c@E(MZ)uayvY`zWLE7{%Es!o5XnK zp-qZNgAVf;LI^8S%WdqdpHZJ)DQz|~wdKNifr`>mjs&MW9UE%gug)4;sTd!y&+p7Z ze>Cb*(>MW4oLPQPctq`!J|6;WMnQQhMd&ut=)4)6TYYobyv@!RanNXWDmOSgAw7~C zIE=y(V!p|!eAt8Dn=)c%15)mZ?xe;Cti5{F8mQ_*yv@EAXc76Ag(61Ch-bZGxx;XF z)f*b*H7z&F_gDB-{&M5pThI2J@y8bPJz>{~__dzTZB4t*&TsiPaaU0`&<$E6fP$9c z;H{S}vhYo&>R1!6hN&x2f|w;x4GE`Ncw9RZ3wdi+k(E&fi0A_+Lq)%2^mM`TbC${v^35dUb0BJQZT<-? z(lyr(#Gk?0JMxjWCIFfbrGS+xUtK`mMhd()QFofl(My59u#x^a5vB1#5Jq0QIj`%SQV)!DH1>~~ywWll&wiLFXRmsIAkT|hXq z7}rS_M!ca2PeXzZofhCEcb;~-pO)xi{CKdyA|P8$5%fiyCwtktyyJ)wz-vFwS6M7%I^sS{siALg z?`Bc+&2Cl?3rebqxnE*MHWP3gN6VJJm^?>+xjyQpk_I)U83ptS<1d3#J$*dBkku3W zS|uiv^L*RjGRIqe00D1W2^>f>A26Q0YQ0vM>$4h}3!dIM>G$TM0ma34H}+i)*kGf+ zNbgzO3ebhaDmDsF5`^yi(YtN0)K69?^CFJ(F)q}+FbD)h@q+mEF5)dzF9lm}uhE+T z9S%%3;ONVSRH6#*5WAG51Ma=s9SR~p%^twZe*f<53EOJ7waUg9+ee$XKp}n#% zaMw2BR8@TjzRVTdW^z#`p`uk)4gHf_bee{{N20fj*iRscyPgiZ908zhaKUS5V0OF= zQ=#NvNT+S*m$!T&TLyWXj?%GJR92z%>gO)%P_3^g2m$=Lp}Wb3s7&6`9K(wIwi;_F zrDT7h%G-_2I#jOZVo=rAJ{qk+Vxn%6-1D0JYOivAy!G62U~zPms4IwV?;7|{@3w#t z{)1jw(L5@5Fszu0Gq<41pm5s*Y4eU{nb6wZXCdsl?&N`!}dVunWpOyBS-{g$cAw?yKWwD{^}(CPlv6kQDfd+b`r{e=qGfMT zFEu-6EneB@%YDD<8;5`mL*6*?Yt}!i3M3nT{kJ4c?lkyTOk(f< zmfnAphrL=O>^N2F`Td1df4uhly=sksboSqRzD8xBb!S3vHpyneks-u2IZH>`qS}W{^ha$^q(L;`{l9z=&k$; z1^-l$bH75tKLgWWq2Lc8@UI-}&qnO~Updx|Nt!J{ZCTy^p)GK{+TfTkenobY`SY9a z6C-{I&7=W?a>ADpe9}$-YCxFxo_}t*OyhCqR9o>}$EW&d=VbzCdZ(2HAiV+wHa%hm zHp5rB5MK4y+9cMda)rL9h9Syh*im1q>OeUComXz6UVu{6Xd?MugZa(OcAI<`Bf>^6 zrB!yJSrll;GK_zteDP=W_xp#>A(f|WCCUd$m&m+mZ6e zk`(0BQKFF4L=~T+05FIg7*z@!HZD-i)#NBarC768Z_>JU-VtW<+1vzgJh|Z zLoD2gs$@dHb!8Ff#;fz{311@36?`tdqPJT8r? zr;hJ13!WNf$S|oL&3@vO^h6vW89Le~jlI7GxH@m83HEV6#wFTg0swtup<#4%;CVYR z{v6=B1m}hEPf9T{f^nRpAv{OoD=Iz<6)tESpJzcSqGK(htGYtCu!!?zoUVsJeLz|#?bs(ejiE4c&DV0TN{G}U79)7!263Xef@Gz-G zBLBFW_P}%6IIqTZqu~vqW;jDf`|wYd7^lQ_?O3Tk_NjD7wU+O z0$$L(hpXMeS=JR=<_C+<*4^K#*c3y}R*W(MoBKczA4X;1v#pk5AMda*KknP`iQI$LOF6}v_PZQ4bJ*dST?6HL%gQ@L>)W2~ zQK$iO4`jF$RjVi#PwlIUDNu=&Lv#Jp!T+8yHfo&He_|UW(Yp>C^`V%oBXHC_DaW6h z1$KAxEZ$n)t@Y9-$ zX@4GFH5mTLZ1&ih43iEdW1)ZDci|axnRQ|;Vs=Tq8Vs$n#0c8b77ci04mq6uJwdrK zd<={!z!Cs;NRNgd3(MJcj>?{>{osz=?C-BF$_oC87a{Dd|G7oA9_Gb zzrOrr9lvT4cH)@HC^OB78zJ0onud6cTc?QYi70;gVzn<(hdxP1`Rj48qpLmEQO}a= zK%BT3YLXZFN4;L){$5{+!x}a$L@JK0nN*@}o^6vL;wiSFdre{TJyd?~Sl1Ev>ZLMQ zD$p+zEqL5<0XFnqmH|%zjO6-wZ}#kQ6eOoaJ8+{{k3(X&|Kd34qu(WQz|9tUZ_{W5 zqH@3j1t64;Q3=U08(yj>4K>TXL81%1u0t5GLimoFZY!7h0|)6nc5Tl<%k{Mh;jG6y<#~>^#L)Oqx^ao#97PcQOLp6&a9QmcJ-14> zt&JD^u~mQzG1hE?aH*lSV*R0f09fnbbXq95qv;*eBeQmlQ0G!lB-AXU-`jTa2o2f! zw*$j;&9Jg^Y6)3im6;2tJt1L8%yV`iTLR>l5fJvQFO7}k?J}OTGtd_XJ_JD~`>hA# zVBY91T??2G9)C*F+M@3H@59oT=knTbQKTZ;ga;p2=UHKgW$h+EMDSO*m%ulbp2*tABM6AS3?QszqD|d*YvY2~yp(>i3 z?k#|nORHzdb62#6yJ$#)03#T$3eN`61qOh2A*D2NRbJD+mZmDF%t;>CD0rWp2iHe4 z9zTVtmgf!mX25hpYOCEzYByVCz6L>;I4_RhkHF= zkLQam2)}QLmE1f56hT*rQEGS2e~4@YxLM{_#8}l(=`LX@+v(PMowG`hvdSA52CR3a z%+_P)$wQt7e77rxsnpMJrUL#rxvwVYCl%;c4m+w%Pe;lIGP^EB=)l2V*t1C4<4t;7 zVqo#_R@)42kWrLO^XG(cW8^F8b4sEc>sfLsShpZZqLfD9X2XkZ{3+?Q ziCHx+@uhyY=67+A5a>;D2Q@%zgRmP+=LiZ{*OwGtih5z*{aN@b=|&yk4qu7Vnjdto zx7$$Dcmy5!w+@BhZ%L(VZ8Uzyc1RY_&2QYH2+ZWWfz>9!F;9C!eDRwAwUDp-u2cCr zZvv$^Cn2J3p(Zv|37oifQKx6Qo9|>upGN5wxAi%`4;rWLV!=G8y5&3W=Vz;T)=M%P zA?6$9rynrwzVIE)#gXr9~r7Q;PhY$7+LYSM3TOHGQvrDJ4=* zKVSQ+7h>wgdlWml{h9B9-L;FP6D{WQJcYVn*(X)FCKQ~urvX{xG3E$eA>RjX=$?}w zKwMN}3PgX!^Xn5b+;ng4i!EkiyG+q$4-#tc?am9a-Z>tFU4g#OuY#T zI;|ukB`kfuRsK2S6s!lcTSk$!hUG1a9kI$+E3}0^ z9vAF;iR;FV>5qffyjN)kMuf(>8FY%0c1hpHDx3(_N#BiF-x;OS`FzOF?aRjrX8Dy? z!|#!bnWEnV+ptE`k98+OJThwu21MewszuE{wa7_m?SdiyF9TxkVXlMfbWa$CSHYy_ zGs(BH_rb6@@+xdw)nWFr&jA~CKC_tq5>)rQHNH=nZ2`?FuLc+SY2rtNA%}V>7{4OV64|%!4b1k z<>P#?IN$jA5Yi0ETm@Dj-&oQ4Qwxn6qxPP4`lkO8x*^>-ROMm6>!;kN-bnl3hn}xr zGh4Z*&8K{c)1OS5p#n^$Sr?5FzyJXiAS~r#c*4k8cYlEIa!TncNC?tq$ucuf+xCt^ zu<$CdyrgGZ7!?4^I559_WbGkTvFCTd z4d+YO>X{s$MKwqvYF_2iG_Bp+9K@eWw0pz)Bo>jAG2hiDk~s*zxio@Xq5}WZYl%F3 z1>`b^W5e+^j`mu+vySSsVDrpN+gylPl~?5*rgS(y;Z*K<79#dthV{c(d((L42#FAA z>470l-Vr8xL{p97XrQWBy~SPG>Ix7)*ns91W5dZX*X5Z=-ioN#(pKO5TxJ*iwl3G> zzjVrR&a|nE!U263O&0D#gVzHMwq3*4HoKFy2TIr*+;9_lvsGiVM*^d6UgTVPu50;*W`{wd)G6&ek;8v@2p!ud$hibMk?E<^)s=o!sR-2MY4+k#{vsmdUaCQ%9g}7$hGZndRaT>J7~Xkc3F?&D^Xh+LQ1!aT^w@6 z#*YKmzI%skzBqc8$hn{9b3-6`hAB=R2ds?bMsu&zs$QEX9It2cR@FyShl;G)0-wjf z!eqiF>AA$Xvx$DUo83UQ^#TPxVu|Iu;A_v!QCxb zupkK%T!Op1H16*1?oOk9UXu0wd+)XOv(MV+;=lT?cpm7ctLvRrHRqUPj8bVq{LQ0J zC4h09&6N;5LV509qr*3d>2+S*e9bU$4B*+ zD34WL71i_c0Vw$dRx?a4t2!}D%?9tIImiP$J7l$_7h&xbG}hHTk@hZ|kCBTECmu*T z?GDVi&_I#xOHf|iyMgEI9m#Ho$!vuOHF|43k>prB16-G0Uf*AP?boLR9MYQr?Cr#8 zfRcy=)Z4KGlXGr&FEBuT>pz@BS#QJanxFF8<-2$F$Uag`n~||Tv9(oy^1UD)t~&S$ zb3bXgtSH#^>o81Rgjv;BJCy*0bEn4niI!7U7A_1M0u z!16M2B)abQ)Y5e}oJj>@tpOoL1eEeT34b98kIU(lB5o0qNybN=k2Bugoeiz@lESlt z=Iz%m(&V#QM}<$Ij=$g*5VFm=?>?_n*0520yh+n8ykGz&DkPMy7TiiIzd63IrCDcA zIH~H?rm?)}i}C_K;QTQ960&bMlzlYg1xN{x=h1zurrpbjS9>wvz%Espl#9BeJxr}L zo*{bMz5)X>?y+nIL;Y~+f!?wei-dUr$dH#-R3YNk)gGbaQlpzCUjmRww&JedUe&W- z9a*$JXNPB$SonbJG?=IC>b30swD!!^#xNzn82MW8J+L4@#sVCuHqNc&l&a7cYG6qi zsTf8_Q(s_UwKvZ;Ue1zK)4}dLEm;iB-_`4PZD#35d~~O+18xQn2=Q* z1f_SGeY!Txr>*WzDRjI6BmoEz@v>gyx3Kek!;q#3&8d(R@)Nb?FSr)*x8|VRL$|g` zb*pH$8S^vXBIq7xT9==~?sXq%hLB%>Z`y(wcSoe{@C^Y8u&(*(b&{97v^()93-LRA zTPNFenGj4`+W`suspAzATaS();7(uef**JxDyv5+)N~-4MC;X_G>3d8-4$WXLHzIv zA>Mm(er&F}cAm3A`JLOyBaXPRM>6moE0q5?danV#X;XaoicgHjg7p0N=khHLzc3zv z__L~yc_tAOBk#qtNngx?FRion19E={x|>mx?us0oStu-x^tjsjhSWT8be{Sw+bAfI zKe_9x3%M5{G3o!T&iI5~>5=7V`&4IFxF+^wb-^h+`J>lOt})`xPTg1-%-LTG&{+h9 zeulWD6z82xG#IQN{bO^?&j}W}`Y*AS8Qs>AAQ0#IBjCzap5!a=yfd>^M?oUv@d2Rk zMLx+|00#@&s4`e+{Lt2mS+tTTJ{nm?c z6?Ad;WWHdr>GralZ0u@<9)mBLOZ_#3Yy)nuYh$mV+_7b?c&)Z=-%5DWQtilYh*w!K z8hd>Fst(9%S8*kH_+R^a(bw;EUb@K`%4cO@L3 z_Jy=T?sy=NxKhjjU(paG6peYalWBG1+m731%H4k1Oj&$GicO~p43JGo=B~wCHMM?i zs9tmw!Dd)ai|AOYFs+oSxz5^agTYEnFj@`;`QnJ5;Af}dA>0an7N`Y~i8;}*yX+Ln z0YvP8ie)IT#itXZUyQMVbB}`B8wN|IM5h5zp{usID>4&$+}X6`)v948%h0KC;xhyM zM`6JO;7VX#9_1C;h|<<}#MkbS{#f~8mZYHY@?7vcKG38Tx1$&V3Oj4tpu99cdW@!H zT!r|9W+P*H$8L{i(Vr=OGIh;hX@1&mgBBPF4?m-)`^xErGWCPHdvr7sv#QZS6If43 z5QJ-0AsZ+N18`;W0*iy+!ZvG0K`$D;g+=L9Zb$`5ecxf`YjQjzMOS`T_ayls-wBFY zZW%ZlXs0!R(sse|5a+S#(MSr26j9lN8w$r^C99^BQoukhU##2v!hKkipK8g*Y&lU;YzcriK!{9g#r%(~eZ$K#Z1&rgg?ydEL1N*w`*Ay>RvRlD$ zp6}`e&7eDYB><)`>to!@%_OJAlH*pS?34t? zd=HwFteD4S{3|viYcreCuoa-wsJUvo9pgIrPzQeBuS?$$JJ})UV zqf^iQkh`YlvJs7#JpI~Ajca|FWb~kY3Eu|7K4Y*};&`gH&n}sXAMcIi-WBIP%?7x6 zc@slL57}en8pyU`<$w);UbGhCK1j{IdrW~TsD|?#@_jZ^+)u?GqXbyff-QOb-&kOf z%ov%0qFB6;>S4P`;f*T8^&U{2zZghhr?2Btuem$Le;4oq-fgQ3^Bf=UsIaz!u}@M+n-UVWN1PeK;%*lvNWI<-Ombr3hZp= zfy<|@T8_!3sE{2SLD~=>qIncP3ZCkkVIL%niXo#st}Sfl(M{vg&*gmM}2Avw&*Kuz=uJkt^xQY%z;hy;qKqtLJqn2gt9to zyB~|{{3@#?=PY>yoY-mW>;oYH+AiMh&bcYur+jQR6_~ zD{v2pX9@|YUeB5}^i^xymXU&Jl-G+=?k;{|lc_6+G?fMcK1sN-^E0lGbvghr@oIX` zfQm%D);r>azauF_8kQT5+W?GoE7`3nRny_MPOxDhn3hvktDv8;3lAn_E$C8FSv6Bf zwKUFOed^&$1h$UbVC`1+LeuH4nOMw%*h-vn)&oU2^(ubzUv~)skl8gl>HLoBWvm#?DRZ0AJ8a%8hH{H49=GtPIY*6Go+_?8nWMsh zDtbUE^9TbXX-o~0^05kz)|*+@Bil2HHvEyF*kP$=A+EW^#c6eY+zdDNBrAXb2YYp` zG9>yP3ODh`Ye(^Erxbz?CG}s|0z>aA>cprTA-EmXy48Tc_u0<5toCFJH9W)+GSs)d znA<3Nvq>eTPYj!|`D26I@nhK}-FHyVw!LE^Q_(y}Ey5w-iqkvcX1GqhvXdRKzR5nX za}OY#Q`NNlC&&Y!Os?zm9z*pb5l)FXPUl|Dcb(w)V#B_MqB_u#oY# zPs6UV;N>!~eLc@FQo|O-Wj1<6jdbq?6tN4I^$sZ6iSBxA>hwo*gPT(h=e&hLEu0qyNk;$cR4dkTOH_NlsV2ohX8^Led{GxH-*3!yT^Oji&C5!?>biK z{o|n@HgMvI7$T9{&wK!+P`mI0BhcztiA@g|pDQY9J%#aTIPJ|(Q?D+9>09kLD{j&< zQLQ4!I>aqSW7`5K7d4=o(DBG5{wh7zb|45$UF ziRNLQ&i7FPD4|z`NQ=G?r8P9jZT1c_U>heiRm5!=$?91Q#=l=*uhRx_-0>QZf|^d8 ze5`TM+Mw4~*=1+K&1xk58al21XDdg$VT>Qr&u2pcS+Li=51)8aZir2XhYBDAA0>yy zvmbuC0*GC;v6(lMj?bCjwE~#8vNbQ;BD|s^>fyOY2(82XHBb!qOfdkRN$=O>fDu5a z>eO}2Y7#S}1H>ddNp;w%A2IcJLx#5G_He-t*qBI-lU*Ed(p>><@bgFCp_;6r=#_|f z;i7QB`J$|hvtQbESE!@fADf`Pnv`%YSflNJBHXg3eY)3KuW&}_gno4Qm3)iZsM~|P z7R8`d<C;%*t?+Vy5w<>$m6(>sqQdPNb(O#;4dbF zDbcb1sX-FZBI231AESk-tan;^%MR+?&ELpcYp1QA$9XaB1^U%`jq!rqU#$QeYTrOt zQ04cfi_!|IL2AIB?9GoxHlqUkNQ3Qul(Nw9(nsXP*4ygj=l!;yPSI!P3mzl0Stng<6<`*UAZFppYvKpGxq_k67FaL9TEmow4mv)NCK^+UJD zrTBLI0JMg8aog`l)*u&hnH%7B&Q$-emkjt`u*J@HomK(mi&L$#VZEE=FdI!`p?IjY+$T4)WXE{w4YTnqG~rcOqp zwW((L-Jf868HiwV>Z@|bJka@Kb&PsSjHz16Di_?|=XrmN^}IC{!L~`%GRiE{-KcBm z5Zf9D#NN&XWXfMGs%E&53GO~|r8IWM9aJrl-O~|fSI^s?X`otqUS2yVF&%no#>jPH z6gzYtF#)O)L_l+6!Ervks9QB-BvG8(dB5PAPusSsZk{x^@k5&+E`e@C&AccQN9tqp zokM1P)0zTt>Epv@RsbXu$QwoXovZj={CCItvn}oG25SIRkE$hpxIHxV8CSUz{U_Z1 zQu!tB=&vj2ar^|Pk{po%>nSV2Ws_p906$H~8BUjmz7K$t0{5%C?ZKrT=T)9$^C9|> z>J-;99=fBkt=W?C?-E%Toj_Z)Q6)M_XUGaGd#r0=zfz92i-rYgYECKR~oODqzSNYKho*Y?d_Dby`ZeIVPA}`?;*ntcq4YJqKCR<)0Kky zXnb@2Wd$40?ZE{?vrV3yt)&Wjh-BL5q-!u397TsSEqOwLIMu zcDCR+=SPS|k2p9zjEn4tEOk-H(?(StYYm9)>Ga%P+0Cy1>)O+)II>}E7y=xI5*?M3 zL^b!Xwu*20BgkRn+Pj2`t9J8tIGZISz4jQnEtRU~ZRe~vUuXL}TiV=#Pga1*7*fLU zIEuNO3w>Ip%DG=c@@}cgW7ICrMiqIQAH4jo?Tj9|FVBxpS^M=PR%F>U9#p$^bLMV1 zIRv!k&@UINMQ%t-u|>Iw5!CoryK@ncwIqu`3$}fswE}4<-}kJpx5V_DL!Zol9Q$>L z)Ex#?z2gz)AlF{v&xFNH_3CpsyOHP#ny>6j?MFzANbWQ!JPbqyHLJlZsn4#4F0R@m z0mqU?UTVg*`x30=0i&_20WPa}q}BoLQecudjDWfFJudDm?gv^~306(2Y^ugbnPGdI z>tEXLj+h1_1=ilVO0!~Li?3LP-yLaWYJ?Ob2q3G3fjabD09TFH?sX>_%vbp3OE+Qi z=)w*_Z&t<&fHL=ns-)cbt{RZCX0-1{?Hp{&h?5f)9y_->o0yN~HF(PKho43D+?y+3 zs<7>c+uQ8oyboA}XP4Ea@YQ-idY;SgSnkk;=jP7FIdIh$iDZ4!rQz6O(fm1}RbHAm z>7nL!H3Xs7oK01PQHA4Sm^RbAd_ZXh3`$YS4GCqNWoVqM8bw6f*lIW)RC<)noHr^LKO57T=^}9B zNecJ&0w@iNBbTMEVcdrilOgP|SKFn^HOH+XW7psme8*|Z_A|m0O0IVSe9EXaSmn-^ zB`vPCb!6}?j^1GBb%1=J!q5-w(m!>YR{IAR>b{I@eeI$3{M6iLf@-N@EoeJ-pEIIw zyLF?-V2WHBV8MgBC95CM#}nCe$5k zTl?#c6R*Qjwd)<0SldB{>Th4~pY}!ea1L&86}{5?Ec1~M)fYhXe$aV6@b+pO{nk_ys;JOYp! znDgf8xaK9EY^ujihjn|Kn^P+3U`bnCtXju7GlkaolxxASPtSa<)5RKetp}vc{MZ-W znXlX^<{F)8nW;0iLI!SfcRem9l#s6D{MJfCHUROXB=hZUn0$2N22g4NT|G&oek1eD zCYUmdkp`;`zwoOc|H} zUF5v>CfMf;(Z;3Y(q@L$qll(^)df(&2VmnRYOk)X=%ZSDR`u2#m=)J@@dkX;o&s=h z8V39Aau~=9HWjbN$1b@N_qxTIkFZ_`7`y{yH>?)SOYUa?qo%2R6m(J07K=;<>#Cp} z#(9&2`t0aT0@=I}`!nvd%cHups^Nw(^uGQ1O0$uB!iQ0b9lA>M8RB`jA8y6{;DG0DY#JUD7`lg~$KtWIH zdUo$blG;LUcl9uj^Z>~m;h)XY1Z=#)^`3EtF;c$j^mkj= zy8YYiZ9vZ>#tqM{ROtXqo9Fzi`R)e;>t_Aou*a3j~DlpSsdI+#poO$)dz zve$ZZD2-gW6PJN~ImwP4Jv_*Yw^*ZaFX{n69g|dVBFV^+n zJkZ7}ao$(RkKS}V40Kinyp>)E?tzQZtYR=bsJJQYxdF4kc__@oZXjf~h8(w{m|!uQ zEy+vs;pElLcs=r6erVasexJDAWth^Ce->$4=JB|-0|m1Od^>U>fJX&~N{3No($pFR zFl?_pRrKuA*1wo&m1Nh#dOWPDsw*#){8ho!p0Hi_bM|AQNRDh4Y$`4+-h2PqB9aLw5)=sCwUAB_u3ywHA z5g$>d*cGS~^jhWlT|%$B5vMO!3$Twn0+7h>yaWRdbdL>2w5Iqw2bBWNdJUrx<{>U_ z4X1_YwN0zxZM1AlH0;_otAPK@bNMWHT)@5#*+l*Ff4l(HZ4^T{y~N7&-A}Hu0E35~ z9hf@o&-KkvXUf;l8a^ap=AHuwe!zm`5_L71R$$2MKf2HV$k3OI(F48B0rxbQ*zbwj zvDCGw3jkY;w4_Qn)M*<)*To3jZns?gfiFakP^8)LVe8fRJ?s_0HA!kP^rNWn&T7X+ zY_0qoV1q;80k|8(w-If2xL>z+sbGhUsnPrN}gL;&q$X#^C< z^tQr$gqUX3=o^iY$mIUA3wg{vKh8E0M=z7`hGlpJhl^@(YKiJ`6tvbm00 z$L?5qe`1stJDh7WmKB``Y}!HXPyj$S9wKmB8<9nY<)=>Hzy(N>A5&N*BG`^(r$d-G z52+!B52QqwQIXP#rQEVz>LFO`bw^dkkDz&N@)~|J;PLQ1fnD%dmGiI1w!i{Bvb(d4 zE8QXBaw_V(X9Ykx!woKH8e5DxRmMZl+Sa6b;?&syp4%Iv9{E4LxAswAS50Es00hq? z+YIgTux^V;8_J5aSk+%GE8Diqyuu%Zw5_qRoJzc``kbyWZA z>E_)@76G%uW@7$~nc#QRUS|`-8NhX@VU|0;;!J5f0hwgnRmrT~@4x=jTzkg}r0REt z={Eab1#CoK?TYtDJoc0Um!bdz{C{BpM1?lf^oY83j6495=h0_04*SQIs4Sx1k4F~J z)>W?+n0K_8`?B%l2s@tLYLVz_O6p%n`s+V_GKcD6ZDVi;O8|buOYm!*p-7;+JBd$Y zM&C;I%liBPE-)9cg^mLBS))*k>CQh{LS{)2_4!f5JJn4q{C7%MV{aPxYB5d{?Emsw z*4am}Ff^X+S0w$@-2Kb+{MwEF={JV7fW)`}1=R9i34g~w0BT{2@rpm^BmWv&e=sus zX9J=zdzA2cfS>w{1MmxacsX|Q~Ar0 zgw^72y!0PK=v4#eLY2_d_s_(_|M*{WLIDxvsFg62QIhe&1;n2WxfW8A-KO7eS< z0v3_W|}FUi!~}G9>}R={IEgcOwuQKB^L!i!#1D zSn0q29-xrJ1;VLPcI?Za!t6gjZotq25TaeIusi=(lkxxS(n?jez=^g?P!{o$8*F2D z{QCn3=`#_mlnKIsC^?45%^wH#Yuh3;o~N z_)oF144JNf0j3D1zstyoh=3N_ZoJaU5W|oWcxpmL9H?MIB^*4OAs$JlsNi}emnpg> zebiVz4CN;P{}gg|z-Z^@7;4GOZtC#_g_Ah=qYgo>C>>FfOdEWsIN~*n*Yv^hG_UZtf3G0QapR~PCx$r zIEG1@Ni}$J5{>?gZ@T&#Dw_ai^s+g`{$>Q<^_-X``69WSJr{1{LQ)cA%plvn$z)X% zVi@52PH1Pb4b}DKX`+@NC~D@!D1k*xPyBF0>cLY>Zx7At#$rL1@5N-`G2dUlQ9CJA zti~+t^E*J#Bd=HXCT!sEd}UyAn&$$W3%hjf**Oy3d0v{O^r0lnPevU^QJ{TG$rhuB z+086~zhO(pA0a%u>kIg_Hc&o-@=AobA%zcxbwG_J?7Z{ZQUi1!amWMj*d&B^_3npzvUFy)qn`=4$&H^A}Lv_Z%`EcbU zM6oY?r~Z<+4E$LG?}6s4P&@hdiM)7pYshz(8ez7t5_e8o$XO2AKA#SH0_@K}z}F)9 z9NV2Ay{)j6th(MOp)I|YV<_Yzc>mzzzPA<^`@t(JsMk=M?j||PFFx3Qi1zls*Oh@Z~HoI_^0; z{=j2f#ez~Ff*qM;M){O5-C9ClXnXidt?c`>(qQEG^(LBdlsT&O%Y4h;(jJ@kE*o9s zXG)sGVe%lZnY9BOSvY_6UrVn+7=HP=6zaeBb_~E^ou--$>Br2{edpFKVqhOqaj9KH6`@S<>ngJ z`qnoUl1;59h{ll+Gj3M;_t(6X%|j9-O)fR$l@wd}9|Xdi$IIP59W_$l6hKGQMN%PE z_S_dqbe`c^l&}i&ZQRW=)tD+h?Jc~exTdIB@D=E z+wNUMz0k6j1fF>yJ^VS=dnr__w~P}rp1$B2kmU>Gg{7J$ZVPsmfIuve>sHv1l0tuU1nl?>3yYY>kMFnb#=Y zX0jG1;&)RkPnNb%`CV@M4C|Hi5hE`~UQ@1|GZKur6Gk}t*UtprW8u#(>JjKXZcgxj zgqjTA8CKX;i_sRF$E}X%dH}ANFJ6?Aj@8px;~}i~o}AbO-UvEu*aW)L*N_2wRJ#N8 z*T6)XFVT4=uF;B|n7pc3q%!WSI3^eddK5Qm=zb&8*ir78Ehv~%U2nS#7L+;3X1sFl zdwIZW`k#5&e$RwAd-#(`>!mx!z-nIShda*N`m;WM;}@wi@np*zD`?z#NQ38;Mc01* zjj8X07SZTCH0@NOHtDteA`@__h1F2kGY_g!TQ0*h7VZK7{dqpQhAW z07b);Z5LA7!i!2e+YxXm4*?aA6n2rF(#8-K$TPfUrN>W2v(^rFil*1T7diAnrJGMB5T>NsNic~kKM>JNwbS5c57&Nt{lvT zHm5V#ERyQDy}Il-Oy{=Uj|x`IknxOm6kZjs+0m?}*6ZlNF2W-Of{jpDnIItN%Eyce zh#8F!PYfg*PibE%=n}1n&rywzcE$JSk{|m7BE8J{`eqLlM0*2_-u%WqbvB={SV<(C zfjZi#aP3oz{xwQ>YZ-r;iP>qn!p+2OhksMw!)eU#J-YepXR~R`P3J^gMyPD*l+03) zl$>n7f=hTji)HHNMUZl_mTZ7k)0i=5Qjtb36syJTa2ll1?aHKY(rf4VoBlZnf}hdc z%pJw3?@Faw#(_bp5s{}<_};Ur>4pCLtP;rQH2d2s6U?ieZ@xW!ebh`7NvHQ)DTNiE zPHYFNg+qpR7ML;<^R~W@t#!3jxObSURhrT!@j7YcLp6v;(+CO&zsgW!&5z8^&Sp*{ zKIe2khL=oYlRwK>DtyUms>0vORb;DD=}KFuTCU}9Hkb7lgIekR!JNdh`$DZ93awgM zVi-QRAPNDrNzwJ`Mw$=Inf%&^3reS=#LGO~&1c5#Jj8<|9uRJUH*cg7PQ+ss~OBxE{1t{tFg(HQ%2FQ`TK!1tnt zG>w(5yP#Qy>I{l}nXc>2t+xjSMq9ychH`WCLtqeBn4Vb+80B}WB2YlwOggXP&utqj zXjlemPJ&aMgyh`@?a*bw4CVhAi;pcWZ4`Vu3R8B13>pWGwMCu+xuPw>! z2?-&CTv8kM#tLGJn7JJfqt)4Ns&)H8YJ@=@WA_>XD`WJi{%Rz!*AByAfz`kW=qVx^UGci1}0?IkY=fj@w|G zLapLcTqyORle^4huD-hI%+EOa_L+-4cG+-3&oW#Tm}ux3cku92Iuial7RF6hsGki9!qVG6cC&w*>UgvJK2cDZ zp+S2>#wggT>Bbb;r0E(oxWt^mO)d9W!Z5qo%PY-t- z_d*ll$2UKZ-VA+gH!U`>Tu)}n{jgt&_vVNZ>g#6`=of_Cxxw<+*+kkY>!vB_r2$U} z1E$;J)%BpUy{qA#qB3TR(|o{3)5Tnu6oaY^co~>V6w+HW{o&P=R>AiA9zso6V8`XG zfJZE8RWEIK0l59k=+ToAI6RU@F{r|Ry9_p(TV(BX<(F1yZCd89fBX(32B^}IC&ikt z>YYXMDzA}ZR8Xx+6leSgZ?6b5=+&z#iI#=ZUip3CML>lfBa;MWL}p9Xmt26cc2_g| z#;ZKAaG@+me)>~N#*do{5XF05x=`1-Uc~o?SOw{}iHPL(rFVWTd9w_#k$sLw=D-N@ z+jB>5XbN`tGX<(==I}Mm^+MNWn6bXivw038uHfUebZeWk{q~+QKbs~>^#%j~=TEVg zAT+8>>h`(%Tb7u;-I)Fje@*HBv1(D*;CZlibBjv)e$b6ZiGlKl>Ojhz7?+a+xD`ed zwm3}NA~!dES?7Hvf)%>=ICXAUVe%I%&`SdelVj7eZ8;8oL)+PW3no3SF7}(_Vy)1S z?)ZF$ZwEh-M67r+bGe_fLKV4vcgnG))5cMxr)H&1rV@wNc#S(pD)utc%2ryOa9JXm zKuma$TWEnJ5$XtLtXyV{O4Zd%iWzvw`p_@%xU%qqF=%r}69YA&!5r;$i}N_Zx@*9P z7yU^i8V8Yd;TQ5LEYDHtiag*@=dbe|dAsaWK-E)q)AV_qYQk`ZB&g0cC06vAbOkj= zU?vycX%cI03=$I^@~v9GXuEY4?WO+jiYJfLy&^rmE6nTxFnne!z#<@!T5x+11@_a*Qz)3zk<_OiV#VD^qZTPmzT2xE z67hti5?Di+Ko5q^7)FA72T^H4czdX}S*yBfvi@VRQp zOT)LBxzt>5Ih4rtoX8Jq^jbFlD`mSw$Ad;ZL#e__2*XxPa{C5XTy=o3h&Ce3>98?< z1y7Xl<@d0Mrq=kcQYzmwf_gM>1$(BiC;F7_uP#aI?3e?2{1HFi&^)l+N3*tdyo?-r zCwU*^GH4!I_m+&^=`IHYLR*GSdgfSNV~mT>jprp@+?@i)f$2R(C^Otho^{6NOZXO( zpLCWPoDN}d>w3@M^j_#q_gLS87RR9RUklmg!uLcPcZB$wZ5d#~I)|UnZU?A~6^l=v zPTZ6fwG9K;+ofA^SC$^nR9z5OLbeh+6pDo2R$4`=fE-yJ%%?`PJ}vI^S2Vaj#~B-H z`w&@Qe+EIRgF^QRNDct~#ONXz25k|V&0LR>rE9S=$#f+0xmJJ5PFqaqTnadZ7oN}d zamU4bnTKW{=~5@m%?76fy@&~YjN6hswl!`Yfb|)mC0K}A1=v(9(*hBSj{Fw?u*v9_RLnWemME~rT{J=Bnntbydo*U zb+&7r(n=S6cd1`KY(jf$sAYY8&FVuFn0>ZMKUqdD9VeK=0v2u1scJL;)}xqG=4wZX z3@F7@#?ScGGPms7eO|ou6@f=3Eqv3G*HqB?fhmm5`OthV($0SEnc7ie$87XFRSMDJ zQA8{%Q4+7~I38YJ!Ae z23_9LPpG$75=QcDd0j$vZhf2+TNysk-0o7`A=n{V7U>@9QYJ3XvZ`3j+kX03!oBb1 zqs`v4#Unq%=o2b*n<%w`LUZ2BYpZZv;e$d`w&J5~*glh*pj2-8dSia0f6m3_vRo#d z%*<_=kCpntf{J?$XUoIrtI9j$FplD~MK=v*@#sl=6hsfq&Ex4z(GAJ#V8hJ#PzhOE z?YT24AM}@aRD*K|_Byw!ahX*Udp}Zfo=c#0PffCwLYV42Pl{rac(q-3IZ~S{Lpm5c zU535Hk#GqI|1F9YNC1VjHB?2k%!O`oGakg^ZtWFNr0uyF;rW{Rn?MF@nMq{VSYX<- z&|rnd(@Z1;sz%U|nm|uveav|tP7smJvKl-suX{Lw{};1lLM$n%d`7Xm)Dk= zC^%7nC0004GS<-s{jp@7#C(Cta-2%v`So{YwkJ%IOe9uy-rH7AiM0Fwq}hFes-lFc z|5Y-QRKJ4qmN4#8mYiuV^rQKu;U6)N16B34DyL&bjqbfpTj#)ev5bVjclS)1hnY_u zRln-@!cFrn6r%*GL>9^DD&Sg57dKoGQG?^bm(zTu%ChHAl`(xdRCDEEd`u~kO|#Ix zS&yKA7hdIo!U^Hz>ncnrKtzXRQsxr9-wvkdnw(F3ESFwn;Idnx!T*#=9r(_Po+-Xz zRUF)Qw>6Y9c72Am6BCHQI@BM>L}92Xf`LV=mKE%Ff86-^Ii3nv&(bdIRL~r!iXmNe zXlj!!HL0S(He_P)eyQS#z5J$Yk&NkeNgO75#Kyj&dq*kXjRumf5Vr*A(ATX@h#XLG zrl;u8Fx%HPfmAV?TDh{y@o@HC^kP0ye*S@BnjdxHVwb~yGXF%rVvObNlxWwUNV|M7qVdL#8 z9HNzY0@-uJf%q9+nj9T^%{rm$yK{YMB&Z@9fd)e18LR?xll|6;_TFthgS50TDM~rNJ7PtHI~xNWJZ#>>{*kz z9TynJ*HTe$ES3@TCNhvG!0mV8HZ_sEpf7^xqDJ;%Nvtz7I4>z?4`-b9jH_( z!z+=zKXVuC;aQl;Q!ug7atmaKYh)Jr;>r90qAU$K{MAvdsTGSpc--87R4v~~&6a-m>0uu9X?hQ%;|1ZYH15>B z=m~ubB*lgGptc=fjUEajRbuf(7D^9QGS+Lij{YH96@;Fyxkz_E#KSf6v{l{> z3B;H0=k1@Kh>X9PeDQ+)RXRLRBi4JYNvmv#R?0 z_lK4z@?tlgfWT^XT9?c5oJbQ59qn|TTEIUfCKbv*IB=<*ra0TQ*@ohqOxU~Sg9Q8a z;ip`l=Mk}}wu1FT7&ICmxiKGdogJDv?0apLvAvB8BUYSi!sK1FkV6d-q|2o8HdfI7LJWYG7Qo5CT%V z3jXu%oOc1CauyHM)#SWxn0c3)-=4&sv?D~;rIBE0RKTnzvuJB~LN zjex6rnBntkColC=2*yivdH2lQ?#ZQ-;hFYc-gs=%K&cMTQbo~A07pmaPhmt=tk;Jm zTA+t_pT_vrEqVc6fY`yTyKo##?Ns;Sl9vFr@_iwOU}1&E4ATcoHG)?v^I=e$8s_6oZJPxFhQp{(!2q4-pgonIIv*t*@{NZLr< z3roZlMX6%W-CXP~D~GaO>cQo$;K2A?SAh&o`n!!P!z+}H zqLoR}Z{x#^h;gsW;f4bwmmE~a$o*+cJthj0f~iou_eHs78P`qU2SbQ zAt~g2S5<{_{rTKjm^_b^j&7#d+M=p%VY9EG!fk%3d&(A`JQX()wOp25dn-M)UbmHEw@ z(dN3z`KpCf!=cWaI13H6JZtE_G4%55eWVur3ZhBwsZlr<>AY`1IG|i9C5BPt;(MF* z5$~Nmd1&eO>EMC-_`~rCNcJMWa>7&NBdOn7iD#iR%F=)wsjwFwnPiHDVef2kPkYB& zi^pTx3=S1Ah}KJbO&gdd;};Dr%Zq?@At)2wzBEl#(24m*>3c|8Fs?eRdErTDVO1^u zA0TGzyU$`Zi~$x=#Ni1Hs^6gZ#&d;_Vkm{xt1QFiXwt=H$P*cC@~Cd=CVn_7TCYU$ zd8lo;?SsXltE4^;J1ej!aQMm`voe80artN7nAi<3quE1Ro~7?%x=a-UA z>l8P);~|;5(LHM@kkIL@|2*~a_1y`fpTl|SyHa5^;5thc2Gdymemr zxyyN9;a{TIcVUP(p{X|U)JFHEwTURIrF6$K`p)t?>>?}XYRDe!p%;;Hr32wi6i?%Ps5TksPl5nfEm~Ir2{St2ZvcH%2Rz{Wtw5;8aGXg=Vu-lQx@*CCl>tJePkYuzTzKR&}!O zDXQ7lV0>;FXm3#+&DzkY?)-JmSv7Bt_7Z{G-e`%(=HKO|!75}8iP zLku@`4}EKAJgyfcsP1Pk4saNgEboTE_wIOyb(59D!&~+nyU!;E^n~#sQWnxuu`NeH9*(y65$SdHsq(2Xjtnfx#t#3f3#~Kb>U< zQ1W5|U=VCOdRfm;M5@Y7w)$05yYq?IF)QAcXaG+RtaZnaW#J{V|~myL%D%()!RjLK4xYTFO@a3)KT z7o&?Fz^zdNbpw&ySQF?5ou-P^vT~&galFrF$}|J4Xamz_bZBLNQt)~ad;m`U(I_=R z8hlRE#6||X0f^Zok3*tFG!5M#OxJGwWaurtBD@Iz@JEW(&z)h><&>#c_tUQ^W{D~k zE2(l%b|)}hIOVCP<7{+)lRWxK$pxY5UKr)eyG@h%Qt>u(;nmhaxLy#q2{`O?Y&^X- z!2o1`#!g6f(jGWSZKIp8_$m4mPZI2%b}j*HS{CbJ28?oubYsuL0?dIngpE z(6iwyQcsYB0V7R(gR3x|P`gRt5YcV>5}uaGX)BW}*P)moI^Se*^WseseeprrS@#rf z$#=)CL(RB#i&v-|ZsP@b_lp6-$RsRxbuUA~p2Z8Mt>LFeeU+)Gd~|` zdLV|O^?GPx4hf4+wj?$RHKI3#$v{-PhCY$cPKqLEDHXt~P{|_Rvv|XLw35bmwMbWN zg%zkul7a(J2uHuAcOm2*i$ODTnZ?k|+J!VSEtYNSha-xW zyvti?Ld`9Ae9{;yZ%b&ZSx}vp@--w6)y&P@WkkMNrxm#!_ESK^?v4nM_0Sq6RBjr?3?f<{W4=WJ3W{@l5e8sDxg`P&vQQ&-AqqiB*b@8 zg6Xt4B+xsN$#dJe?zRYXl@46j*YqJyC88_`a#LVWJUZ<<9UvW4$l$0v8V|Ky@0ZM$ zCdeKkfgO$R!(#3C+?|N-RdSYY2rs{TK9c#_mI0Uu=X|s$vI_CMWd$_V)f4d9#sn0G z>W$iO1zSVA7fE$5lB#ek5AWGG?gQ&~6lxVRC)RcqI@p)}_=F0v$!@8Gre%4t7MmGr z#C}ccOB$%sGe?S+)~z~k!P2s&&uLUTM_LOM!iZzGnpDj32sy)2@#bXJo(p;YMAPx2 zf|o*WuC9+@W)5Vp>3-ab0iTl?p1IOY7Li|Da2@GqheV87-$EbuLer7C=y46XR>TW} z(-8agE2^0E(p4DmWMl-x#k95$L9x_PDYAN1MEA)Yx@0Dj$t;s!x}km=rju#ILv>4r z7SucJy~w+=u}HVZbU`Qo`V(pG}uECm%QZwntLO?Y>hoG?BVz>)2$h4u*{l7Q+Zn8(UQAZ1b)NEg*wL z!7*Pc_x*ERuQWr!AnY20!9=mT8gp)={r3}C5mkjfrD&}7=hFzn1OZm{RfRbo^-7o% z$M`!7#!v1W9era!?i;2Fj+St+d0c}Uk#ZRf5PeJv(U0b`B1B>@%i}LePS%2@gALeP zMvcV9-coI1l6F|9sl{7INrxSQGCI|M`ov8aGHH_|Mh{A6h%aNlXd(&ole>rMIN#Q@4cf$w@gBxFLSa+)3P?@A8w0qpKIzWDDFa zgh&s@YrCl{y)L3)4`p}@W zB9{$y zfrhnttsk)3_|0RaAh#yPbHf?~K^5gn7u8x9i(WH}*X=4ONNB>4(dti}B5#hM=Hm2# zkgGM@ZEoR?8HBxQ5emQ+7ZF{n(?dlbLWeWOx*f`OSamoD-V(g%mQF(*`K zJ0AVT@#=6!%y5yGzI@w66h8Dd=lg@H0!8&qed?z$YPo(PY#!~w+&2?LdKHALgh%T= zJS;Ei9^kyl7U5Kjn=sQ4?2fBncqhPxAhAVh#NaOLC6TEw9 zsMR~V-6hshqE*i!W;MToakwe^F*}q*&L4`$9;)W4B~9Yo@I-t5FPEo%w;Ku@md|#79XGA zxI~BWk4h$oM8^pHo)>n?{wb8#9cMyn0RpvV9rVOQNfy#iwJ+0`M^e-n^EWXs94}Lw zW|dJz1u(s7RqMVtnNE1`dYy85EM%n{H8$c4yL=mBGH9*I>7quj-iT5Y??A zzJ>AnL=6kkau0)6{$#^Z-$?wkljgG;9WN{%hunRi5*V!m5X8Ml3MT@Kx~f}C@@lFd z24jDS=G`mtI_lm2%$RUkMfEOrW$=B7L#Z9nH2U8U{QYMQ8gob>{TTAq%BwKDu|l z7X!3{u@-$v@wo1{09;-;6>W(Z4Zz7a9stjP#bTXaZ7WKy8)nBP#$EHtb3ZtL_{80O z8KTp{q+RTQGMF=5iEMo;4he4mX>eL3$RL}d^1!|Jb)7M7WNoZyh!seB$T8TsYPLyZW;qwS_pm*w4uqBu&|Q*Vs&8#0|Q1AQa5FL zCUjrq(>P2Oe$rqu-v?f>iw3SWlWg$4&N#g>(`>Jb8|``(UiO(FjOs*p~5Cd z;uR*dsnSm(P#sGrC(FhcG@f-v6XI4&>Ee=OX*2oRy^l-6FuOmtLqQ5-<~#dWrSTdI z^#@amBh||o%!v7J--(r;c;Wq_X~0Q^R4P6#$z#&I67a@|b zwbFYrc|jHq$8#5@+q*sWh>o4n0T}Pki#vLpU&m&3pT+AP%$PuQtkEb4-1s36Yd%z`G?b~B2> zP@Vs9vnfJ`XQ+V9%5fY}Yo= zZ8hK#pdCnbc;98^pjjBkeS-8W08L&tG7j32%8} z^nN&rMpImlH}GVkW!aC)i|kjGBME|~9+3Gcio#ivizN&Ups-K?V>jL@YhZF(lB&d} zaFRoBRGH@=^s$9n#(`=SljB@)*h>G`9IG03PWQm3j~r!MN|gpryu zxUtjnC6#f*((_xydsv>iU;Mk%#S@Sc10d-vJ$bx=fh>C* zu@O0lfau<_yo;kut`-ORi|sWLVGZ1I^ecEg(cN;QG@kmjYxt?ve9>rZ<@w>9u!B7d z(u!f|iN4xJIlhFsViy-dUUh4m@GUibADt6}i4XVgTsbOMR_+k`x_Ed;`srSHnX)-D z!t?#HjD$izSIzIuM#B94N?sKdpRj+N!awAQV^Aow)4I8~3aja!y>UYEK%3@8=|GAU zZS*QB?|xEnUp#Wo82=D!JF|bDR{KZ>%aa!bd-Qo{bXx!lop{yAZ=2oZ6zn7o;`)&^ zats-2^XjM2o(xW_z^!$9m4jTnbOv$F+|B^Jw8Z9uczo9#ZZ&%t@7seXNJEPl=bkJO zKC6pl?KR9fk$z~Ho9T0&m*eg}b7e=ms+8f*l=8*ohIV88dqYcAD}LOQS4G<4Oir@E zz(BKJwUs)(a@Fem30@1S`^z1tg^c!~O`RxliL*}4Foh4`KQh}~V9%PiNTpQ3=hB$V z%#h=4ZMp+8Q7K=G_<7S$;d!A7HOV`VsyFrrP3?3Ot@M#*UDlmZc%g~sy)(3%ypf;r zQS|j(`~$uTaXb`E^#<@`4bK=GAB=OxL@IfyWsvi85Q{O_fZyFq zsN7bQ%2wa4N>!~~xv^93rxooUxVJP~%|ff-#`uV@ZC>bl`dBmPIO-b$5T##y4IF{U z@BCwC>~N=Me=KM6zaud=D89dbq0j6E+bH9;zWyRI8Zys{DTY2Kwq9+|l~4$Mb;DNY zJ7g-mo0FDAqJeI`K9d0C=+{~gvJhA`r*t8Nc|yE-_ZEe@M$%Arv5_{m9`BSkSeaiisZ@^}^_ zq6(kVOnHz93LN;x0pm-(tv`cWqow-(RjNZ+#oM^uPA@9}q@@8b)Ue=mJFFiL3#nJ= z)S3x_#^+l@>jiyZcBv=%icYIuwe-;kv1&>rRN_n-KN?a;4ux7lEY_A*r`-~9H!3ks z&ll|8bn`AY%oW0f{F}fmODH~-^n+Ea%IjGJR*6-5wOBQkhd4Txm4JL#mT!tir8!A% zyG2&0#fc_BJ(yDudISg>_3W>>T#!>ChPB<_Upi3rRCa}p!;QY3LC*eM^|&#c4T8gy zzT+c72Vcq`7#$Jh_kt_)`Nrf2X>GW%u`#1FN|&^4dhia+k%Iq4no_1M|C>3iq;I&~#(q2_bg+4}N-mYK z(BW}!lw~~Oe^VG5B^dZ5$waM~$wYD%2V99w?A~xD@*^#~j4f3paeTJ;xhK`#Y<=5^0O| zwEvku(s@;2`Wg$B=V?akbrJcJnV7I}$xy#V#*D40QoNZBK1_8GKqvTTGTH4b9gFpM z>?dC;cTJ#`e|CdcdC3V4pat!{(`tJ4xW>E2q7xbcM3Fkjh%qa_AHtJXQ)Rpc_c6W? z>6i=JD*zZG7K?=vXvKPhA4-PEQaopV#YaQUn<41gfu1bpw9cg_MFQDLXUu@2?xGWr zY7O=?fHEuNAv5xnTz5KiCtu5EuvJ!e;=lU>hH$F)O(Ky(?o4OB{0JsXkdLF2B*EPT zz@UMwQ{n6L8Ga&#vX;vHY5axvWQn{NOdRC{{Ekz-FO(R5-DWB*xSkn4yl62`7D+)Q z7N;Nd!RcjcFLC}k=tW@G!n2@7D)pgF_%$Z9Q#^j#s3n_7lm+wYGWzPkMAuARG{8oP zSPLHB#eBLo@M9OCMEGY)_OBOw86rA|2g5?Tbj6r?gIziJl(C!bqft+v+&A zqFYYapFZ`5vCJnbX8_mk#5|JtX?Ff(_LVvbm%b^P$xPqgfzxd1-;Er5u6GiCaZ z5Ucs)TW&YxUP%+kc0^;%?PIki>(Yop^Z;{*hF< z++YiecuIsu84Nrk2@xkV$2f*9m3F~TSljB+` zK#brEo62rOi<(?;yC0TfO3=%xFIIgM#8DK>P;=#C4AVUqh11qhI3kc=DJ{_ zvsA_{JT19gRYr#Nsz8;jym^M2>nTxkt1s}&7Wg7OGBg}En-FIpL#xvK%q{G;Oi4>J zxqO5KN;JE0fWafpVS0nuW5F+bv2N}^`5gSSZ!uJCP0-P zu~yszE)IRtG-w9*;m>$;HC4|nlGt5zkvK-W%sN-C28u zXhtcsCRQL;`IV@ygTvz{WsCpqy4W+$Z4*E~Hoq;b)O1mcvAsQI919Y8qD}KMR|B~> z4A;93L07_bb#FBX@tcUn>QJjx>TS3y9bJ8y1(hC!;*g}&rQMXsas?Q@^?=YRL0 zVEYTXk_W~48ZGl-B2b}5EAY*>8uwe(M&DUXzJef3-mH#u&x!%IN;%4;pdkxD`6Quj ziNz*s1>nf)Q(LXNz=*E>#jyLEVg=O3snY1?Aqsvd(}71tevKn_QhXdIo+^^8Uh(rA zJ!4ibqB-YlLWJE8$_Odg?;h+aix58lR)huQ*$vR_~3C3z0_${cjvQ( z3;VcOfH+&`^g;V7MRwCi7r`!9;E1LXzuTRRk<_qH$VMmZ)hI6HwUmH$gFq$mEdiT4 zv8q&9sJESz2tEy=?fxLtY^A<%2J(&^>ZZ83i>$EY~mHjeX z%>O+RT-);6$R@AabUl?vto;Phx$+S%aI3O<02966Y?XKI4@xF}?jInPYXJWyHobkt zVb_~nx-v4jGCxz&7sN7Ce1dMma=&n+$ijxZ3x~s{LVwuLJrncEB9YjHNn&V{Fd!AV zbUHm~>1-B$6;#7mgv9sfgJWY-F-j0Bp-QC+q3)`c_G4}Mv4cjbV=eNsZtfa!{1Joz z39Gs`_h%6+H+xMpzLNMgS4T9Dc9o@>aCZkylK5OYaS1-H)8QMhSiL1x?02GbxzF=y zn;ArS32q)9n$5>(LV8p!usyCEPL>w(xU)Dhv3v^YtUici024Qs3lV-nFLYlvodrn< zhp``jLJbR&M=mPy<_MrowkQar+@C zNv>9LB1->0H7_FZW6ti|Xv-Rt^6~sg1Q8yEr`&0H6hCT>)guj@crRk?YE3{ZF@oQf z%y!E0J^o+;M}&8=JI+yeGPudO>EdBw!#z&lwB z6c>rgGNZHW6T6-(NtYu_R%sWKPpx0rd{Gz4P?c-%gZ|)7cu+X9`yd>aMka9Z?q^!3 zI`Wn!Co_)UQ}Nblt>nA@qfH8_M;NuS_khxs)}z>p@1BpT5Q z7Nen9weZ*v*Y zYw)-|ExV{cpdz!qed+`fglIGh5ie;?O6?OYplZv7JH|8~Cqh`f72WW+gx0g>%uI1r z(3m};$@&a-3N^!vjPCrjeXpH0&$|EvHn~h1o0l~=0yy>Q!1^Jv=RR+c3L%~5zI!z7 z{h2-Uv+ep&w$t1AnoIdt5HgTR1wTx%&2>H+RDeh!bNjGX>*=yvg8OHLg@6jqw^^|b zo;r<@SII~$%e|-?CzwBFqdGHL-mUMs58wYqhMn3EGc-v}6?Z8DQs`M5Q}v zUE$%W^fZVB-Tnzy>x_L*?Th0$c3Tnq_=Tyj?m+;1?MI)f-mza4RLEPXe%yptzs zn}quSEg&+4%4Th47@#Y9P(%iJyX|7B5TnBk%yYtnW6Z{GG>gvVLxJdU*8HgR0sx3OM(sATgo@u&#qIzfX_}9N|+9hvlTNygUJH{m1$SE(bZA2 z_^@m?xg0*;2C{k&d*n_>pC|R zEBSOP4CHKX-dxgPd}=+M!N|NrVOq`r*KJsH(SrRUx=Z18h9}mXx8{yOQe(f4E;XWQ z`FYcu9I6@rmo~^-F~+_){74xhObm4m)&G<&hv$GkQfl<0Kxm6SPMyTr1+TN5m~Dy3 zXy3iD)lfUP6512?;&jYjnVPPwai?cxtA%towZ@zy4OXnb(Q3-tp>pS0=wp*NFNc+2 zAWqO86oSSH#;NcWSYMq%>%5Rch9=!g8}@9nQ)N@}M250DXdITWT$tro~X!eq` z(n{)^??x8ISvlKluF!J5xM27mw%1e19LVv!7TVuL_4%@#;+w<@AW!TU5N>j)?jxPw z$qP3n*4rGtNmLnkH_n1S(M{4<2m4oG{5RV(Mh;=2UXS`@rBfO9L;Qqy`WLiFzOI(K zV&(h5OA}?cj`xw|+V0cf7mf#Guf-YMUK013WWUDcC|L5Sq%|0!Zc7pD*uME>moeB5 ziUEfr)z^!YLsZFwUb|_J`VJ50J_;z-8rO-q{Y)VZmmEJyKS&scijLde+V!f1JRI^3 zfoHTNrFIY2w1va4Mz;4v2#x2`7N3g&5|nR5~avP z&lS0ofkYtzHaJ;7JW4ihLWD~@7gi8n-&xMzm`I!<)U2Y3SbiK`IoXm#9BCsR(M6fl zI!TveoM5KeGKYxJBR*=r?6S`7$e^xlK_a-;-H_ekz~;xds_XBUqN(E-G@DE!%da~d zK-ZnUKRB}g-0fe$^>Ke|8gTVHYc7D+rKsSUI#KR17*FcC6%lHm^@C@gxc~$W%8js#>e1 zXxNcJ>0*-TlDC0EF9-xCb7CRnTDznlaDQzf0a6(xyYY;WTpi6~ylfyvL zm2c{Ua`1H|W)}czC(%f*HtHua|Fmyw3Pn<-&Jzca_!QF=c7mdCjTB)Qr~Wy?D4$GK zs?_R?xi^qwpU?NOcl<7q_soQL_x42Z)RxmgV<&HQEjYcFq&}6YW@(M59qN|x*+N5N1aOTHu`Qrb5?Y!7O4tXBDsh^>i9=BE_GI8q_Y)Q zRi94Nmuv#4`9jzM1-fWMYm^732;8-3S9>8Ur_T~&D@eqB5Y(wP(#yIaTRdhnUFAfAjr6ICGSA1f ztsw$mRw-I%J|tPfnY0sipcZHQ>>|t8mKO7ZhSZ;^)lfX6OvDc#O z$Hmtdh>t~I+?_LX+-wH2ThEpy5`IjSWla_1Fi=qWjmxLvmse>v#X6s^2&SY!yb}jn zNSFoe1(+$4$@xB!DjTSyFPF^CQxa`uC37}~R#Zk?Wj3n3in;+g7}NWymaDUy%zCd! znx*y~-D|Bjt7W+EVwp@0pc$U7M8e=zew&llpU&jphcHmlj|?^^X(XG@(4}`vtKH6* z?}JfspYT~zC)SqHXuRwYhuc1+N(Tty>ryTaH@e=v=0Dz!>4Rf1ra_R7XEhL??G9T# z^1j>rdYlI0BLKi<_d9&%=ZY3f!v3aH0k=8oQv6L)cTq^hCsElf9x@wR#sdmcPJFgz z6e`k&&P&V>v_SRq=xcf}@isOmF3}&}_M@YjA}6&>K1JU~Z$o)!bbCIHQ3SRNW`i!P2>H z&F!P1mIa^q_YK-HW+#YBldSB<+;7p{mKKDxKlNi`fK?IyGEX$vfPV;k!AQ+Av{V{* z`-|0Xhwi)otbyk;(8rAo+3tBCNBY8|tv6X^RW8Ziw=^J=jPe%XM|*?_ZB4u<*&}S zzO)@$ep>hS4_3a$76?_dfq>ptZNr!7Sx4j-tfsJ##ve~5N)HR9f1^q+{|N&nShZT+ zLQ)6a=Gr@YoF4fybURDADPFj>P8H z8R@^hJze$i`)lIq^Vu*#XT&tM;WYsf_BJ4~+ zi>^b!O9(lEaC#kJkp1ZL7p-@AfA}OqEZ^H^_Z)*H#UweH` zDd;(sgX=Y0o0@;hPSsNfk#K!}Ut=TdCve{An?m9av&Vv`5y+ZW;3@ zgYoZ37$_F_%2%^V5{PL92PTf*=s3xBxKx=uHi$NsU3tN`Ig9MX%{_+9x#4)w?KT`g z@zklTj1tONJQly$5l=Rc-Y3D4iVu20H+0v1`E|}Hf&S(5Hw4AZuK?5Q4w9zoE}J?Q z{~Z=cHL~=EorVY@8-dMsKjqN+JP)8lcamAJHOuIFt?@}=vUI?V$1!t8rau*@>}ONS zz)=%m2Lp)bXG^?&(zp^S?ai*|Hgj3oHRaTdKOX)&1f*D@qNsUIEe3bX6~JjSYHWnC zn^kAl_7YySmMZC&@S|ROC$9{uKJ(A|3WG#1*2DmBAc98{%SxzFekJJH5mwd7{kxL& zPgi3cACTBFkqaZp#(TWNM}Q@&MJAJ-Zg&f3YVy3l__3GGJU4~mIO2)Xx9}z3Pjg~$ z%sQ*jen0@xX}$BEQmZ3W+lw5`G%^9B@!*#Y%?=GC;cgrbOOy`Pnv4ykaeVlRCxa+q z?dNeHzm+mp1g6N35>8mGR`e9L=}m?Q(2;cd+jJ(U0#nG$BZ&>ffdZwd+tTD6Z$FfD zI2|s`ktc}k-l>vTh`0>l+d0kWl~m_M5_n`|NfTa3eYk@xF> z!ow0tN^TU90M~F&w+>|9qvXY}5S&89dJLfH%W!&C1|#(~F0S_i4HRpsoNnw*l-w6_ za+MrImB@hb^W$~rptW-CURzVq8?78wTIu!GkLr;_8$qZB1ed#&%V9t6N}Gd*LdhD; zRf>Tk@E;pJqGl61rDCUt<3;gt#3bnlGPg>IagAqNabaJL|T0@ny~H% zvzh^lJfy`|&UP*ZqmGl}eQOQJ3!YpcAv7{9=A6%7yfAq@Ba*b+oTVd8DZFf>h8LDy zj*A9m3e*7P4~NrOAr;_+k=#E#nC8ZlTo%+CNm^v%`8=pAW)5^512vO}9&ZQsXC{)U z%R{~}aKU>nJ}LI>y+1JM=;QuCtf|)JcYoloaYl)b;8|z<96`~c>dRBLt~9dVU;!U* ziz@Kve);<_4w-Sfzl*_nK`vxD=EDCK7ByhOL_hCfD@IO&=Hd$ZMSB%P z+UZvP6F2Hf7ZQ~4mkG9S8WH6KL+78~s^Wh|br{bC6$qe8;uEg`#a^YWs?w-$S&fF3 znCzCYFC_JJ)a7A0;E0ii?Zo2ep1d!g7N?zK`_6DpWYdLZ6^a|RJEpCTX#TJm_(+iZ zv-(7YWtf2wW~lVXh+2_{`Iua57 z%)>PP%VLhk;Z= zAyz`5B78{rY~|fduAY~2L94Tpk`PekKggk%mx?goxjG-KbAfqijXx}L^q?&pNk@FU zgGS5Ck9OawX_G39H~aiU#HlF_f5T6WqEa>cEx58~2md)Lv@gEt5@XCM0LW^fR8{Rel+?eE?5gN8tMW{nJe5+n zL>S~YW%@Nwwn)M!D)Ys-R1THN1zWxJ6%U&LB=Du$L5eLW{WE~Sc^RBtQKwxSP0~oj z!D{3-o?%S*ugSw!YWT*?3A0W z+^x^>AT_^__;gQs01CfB0psVYc(gO1(5-n7`v7QKVIP0xdOEr7DA6xWn<=q#kfxql zf*PJkEcfP9E}tie*+R~PD7Yj7ocX06)>=rtXVRql>DOv5R%}(Z_Nap;Q|H|Bt@b=| z=7$!$r*n{*a|Rskk6f!A*K#A{>iF+SWRRL>bZbpUB*M|h3AI2RAs*kDp71{?uK<{r z=}wiBLZVggcB5ycK-v=Z(n8xkF*lsM`Q*wY>}b4`arAOY>Fy!UPmpI(Xm8llMFaFf z_?8qV#fHeOFUWXI*~?RxJg{x5Ovzg|FrDRD){4Pz*fleS;~AJckT*`E2-xt>l=0{t zK(&UBZ8LAv@5AgSruOuVOBbuHMHsH0qFd}S^@As6gT>-W^TxSNcLpWBEbw^ViM18~ z?5vgVWo3BY{&^)fupGVuNNGFxj?GPX9OC0fdw^+AF9oEs&!F26)cKPnk#%e5ne_DLW2MiWKbA8S0516?{--EWky`Rgl{ zsy@M@#CH|Huta&|aAzFOm&)`6f0W}13`fbwWad?RtVy&5=uA(tJEe$vle_-G1Sq5` zfuZ6s=x0*?xPc+!2d`#hclWLeNSu@reTKYjEJO%+2y8^8FA_Zk7hEl)Al<>IyT%Tm zvnrAA9d@f%Z4i*n)2|vr2UD718@+{gBnewF^oqt1N13j@!;SCNcPxAWCi+!x`yxMkKc8)d6I{c0vJOH^@v&otW5IYL+^zz(aI7j}C z?kGb%{{{vCLdBt%Rxpd@LdC;-!hsnx#!~%2d+W1qnLJ)eFFI}fFeHyOTm3s$0BEe3Pde6c3ALx>AC#>)hB-@a zF-PTiFl7WLj&VPg^CrS!U=Yv{IN0;$H7}4(z~gq-QY?Mv#w8kcoF=gG`~iM{-N!{E zCN$^tL!VAZOIwRsfkVlgJg<_~u4m0?V1?B_m-}k<%TgbZPOTc1!>eD=N1W2}SVU07tk`QaE(w#xR29WD zyL%=}g|K4NR4UPYiRYMK0t0@Fi08`Xg!sdHoqwOBMSCU?< zT1!UIJRuw2qK9vs8>b4+xH8>4l3QtGjmAX!}xqVYAR$D=|N*WXp+`PR?QDus()Nf4Ym= z*v$$ko2BKK>k6#YY)-~(Vvgr!+>;V?3p1P=pAWvx{_#_$ZojAK3@Ky>Kfzxq(g8_D zoE#e~lTl*>>&5?(51`Q>ELW||)#L>p*xAxmYBZ>5#qXA8c`eOw@$(yr5XG`sVVci3 zqDp5m2&P9|8I3s4)QPG}Q{UWF@=5c%E!xNDagyq($;ye79X0`RQ3WZTrU#n&Y9pi5 z=}Z*e&^w80$s`&wEs&n;3%SzZMVjwkn{hVlWJtZM%6)-6=VXRah+VGb1GiCD~fqGt+YY*<&#)JA>S$ErCQOG}D_}WF@oE;=p zEb&pNGv`U7OY!Y&RJUX~nfVxAZ=Of)6hFiE7Yi<-4vU&4{K5EXR-~OXeJF>5vJ244p zdk7qgHMZOTw{+qck)J>G08&kqWzalUoy&?W_eXa?b*@^gv&3m`WF*}?oj$qNkTEu4 z^AHm7yU3Lan&m7eWRPB!N6_UTWmYY}=r%4qt$i~*s0|3E{43#!0w%`}!QU`k@*EUa zhHsFEO)L|mL6XYY`WzdeEbbU-&6WQU=Na~*S-v6Rq}Bme<@7N|Hj^YrVyZ0Pb?)52 z0dK&%oUQ*mn3oe14Q%ynkx5_*7f%hlw{`e$E{E@3(D!sVLDY4*Fq7xNxb1?WX{Bn; zdK(-wR4sM(bbOKV^h-`JG9_#Nv_Rixlj!yj3TxkT-!b`BMdYsg*}7crT1BRWc_QFk zg>_6drQ^T8P(nsOg3JFHpyzZ>v zyAQ_#&3KS_m!v+IhkmnBQr$)U9KTwXc07ggD#6;e%+hhBu$E`6{N}**=&QdGLS{Ht*9~%iX`DKy zi`P^kDDII&F|z*d<=sQ!vZuT4UQLmHyX`QVGAS~?($-VdNRSCi$tBj}SMQF`q)HK* zp7*IN1Q548@7%|TphEaH5|@$xQLV}MnLa;waV0oklUKX7{tAGT zXEZD7zwU+qCgb8A-JDag9wrm8+-)pgZVydkTDulmUN@X~e4J@=#;3Kh0pt%qcIcL~Fk3HCi9lAD^&k?rcdUF4j84WM;30@H%neFc ziqZE96sRgcQ8)d>jdUjRMeaHP+M0pC*XXy{79i_|LtjmyQenz(Q0yA_cy~_pP$bRR z>bj9}c%G07)9b`gy%b#@8eot8OH<)8$;wO)l!ZAn-DeZ9=&)OqhL^GqKXz}yQK?g- zh7(4skwO|@@=@jw-NB#w;C%fF0cu3{q=RKkqA|AGMseT5T%!has!ZW28qexiOG5e= zf_-yfm~~Y@^%K!M3bzmT#(*2-bGT2{i{zmVckUM-mtWf@)_nWpS%IL-wGNUF>UL}s z?_lnlmit6`t<#{15Yf*RK^fq6<4Jf=7e$~Sn)#@c*A3fS=9Z}FmgxF@-7^izQ82fhIxl6#UeV{mMB z%nXS}|AogZPi_(0NNnlrrdIkKHB=1_1`0d>c&SbU!|y_Q0TQ)2i>cHVz zZEen8F~ebVI}cu-)sI()BJ8y$vq|*H5+PH?N<*7U<#UgSIyOIwVf@tI+oOwV3F}Z& z4YMVdw~H1^CjWrEyQ|_)l(%66@m)N-{i4-i8N8JHE>oh?A|p>BM@U(TAhZ(yfoAz5 zqiSx1fKmNNqoKsPBC1-9No8|!!Wo@BbMd%^In-i0uX}CjrKTN}%B;F{zTR2Q{^Pe- zi>$Mst%?#Jjv=Q*;}?*A5mlf>G2ihYx*2)X8k*S3hgrRvD7B9O1L+8Ttv#K^I}@>I{M%z zM1uA@Cy9c8WVFCEJW__VyZbB9Wbx+6SQwV@I*;0FT;94rY(=xPOwcv2>)Vfh*mSos zWL~S5U{4;MJRL}XSv24BJK|Xi#;lJOZt~udKsDRBfM5d3(UvGkm4=3RYAgFAF++yf z#-NaMD`4ZZfg*o(@{Z~vph&U4#U>B#LFa61$?#dr90%r6&N{hYvR8uG^}mYDB4|SL z5uo=uvY2jYqHwN6^*KsOD{Rf3=F1MzjJn<;KlYR5q1)^cso#<-m*GykJEGm>&NZ_aKeNU$>&oCIcSy2y zJU#v#Zjit&N&~RAr?C36u>jtZZ_~TCC7BZTMa=Zm%EL`aL#(;A(3{i=-jj`plWI5A z6q?*`Z>39rE)k7stnZL)sN-4)_j^?aRp9xk_{g?;zNQi$54XDG{UaOPiw*XLrk>xm z#_%v9#UhA^j2IlyjY6S#4^OT}_2E5|D22}}8Z9KHEJe#mhU%>3}{yO@6a_HDvvgZFI#sKJ@Yfguo1 zvoU0{4d=ji3R+nul1+e!-3G!9KoXkh;`%%n5Gz$Q9O6d2zLri9>_gd`0qQfL%1X>2 zc-V^b%;gmN+?;e{xB#412GHM;Pn#(vlAaO5rZVyp%*g)S%P_TAYZU1iQ4&Q!dbq*q zp+fcHiJV6$FS!x=8Gb=BjBMeI%GiFuI$&>yD9SDP1P7mMvt%Pi*6rQUxBCsqS82q)?g z@tAazq%5%W!^^(D30g7M`4p3TYk8iqh zu{$J}@!19BK=@-<)6k zSUR_rEi0W%^FU~sp*)AbHaMr*9{2wOhKL@Nf-!w(w!J=fg+rc!^H4XLjut&q*4%z%Z0zdSqj{umt(n z$Pw#b^ANG@aEN`I_Q2T~@(L*yMNy1k`;FzET&Myxx3=aNQ zzN;z(4E~oof>HuSUiZ7VT>^=OVrRLxNCE=TW(Btwr>VBVsjJc~SkCc_W4G-ShSu!2 zYYv?*#?D?(yCA$Btpl!7KQnhGCCS+zjX14N{uwlx7Io6R583Y}W)G5-*{k^9x2DO& zfjPmRa|{M&gYm$gyNm5Dx^BV3L_e)a-?J;%ubCB}U|B+cev?H(SS2Od=oFZGSca6j z25+GunW)!xTSdV&SKm^6?_6fC>sk@&U^DC>Q@xC<{-xdfI*ihy|6m0BpoT-EY>L

+suH@iQ>Z-f$AM`nQZF)`Bvkm}l}`u@nj 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

!|0$V6)PA-JMf3r95@*ZU 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 z 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

fda*cUz4p^zl>BZ<>y;PXW`hmyV|$_?r#A zZ_5#%J{HZoH%55R*R~N;% z_kXkD>1{z`=1VoP|JtR$z2G;82l$@DQ;x®t|Bo%P^Tf|* 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

v~Q6&(;ZwL10uZEA?qSYN%98amu4r*(J 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�C62a$e$nY;Ex%!Obt8ep9Zm> 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⪙dn<#E9p-=uewVKbbV@fQp#JVw%DcR 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

&ao&jnNg<2aZsi@1KL8QO>1f61 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
|u0o1R!*Jt6k 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 diff --git a/docs/osquery/osquery.asciidoc b/docs/osquery/osquery.asciidoc index e854904b6baf4..c4781affd744d 100644 --- a/docs/osquery/osquery.asciidoc +++ b/docs/osquery/osquery.asciidoc @@ -43,7 +43,7 @@ then view the results. and you'll get suggestions for agents by name, ID, platform, and policy. . Specify the query or pack to run: ** *Query*: Select a saved query or enter a new one in the text box. After you enter the query, you can expand the **Advanced** section to view or set <> included in the results from the live query. Mapping ECS fields is optional. -** *Pack*: Select from query packs that have been loaded and activated. After you select a pack, all of the queries in the pack are displayed. +** *Pack*: Select from available query packs. After you select a pack, all of the queries in the pack are displayed. + TIP: Refer to <> to learn about using and managing Elastic prebuilt packs. + @@ -173,13 +173,14 @@ For information about the prebuilt packs that are available, refer to < Date: Thu, 27 Oct 2022 12:34:28 -0500 Subject: [PATCH 23/28] [Enterprise Search] Attach ML Inference Pipeline - Pipeline re-use (#143979) * added create ml inference pipeline parameters interface * updated NLP_CONFIG_KEYS to use common constant as source to match server code * attach existing ml inference pipeline Added the ability to choose an existing ml inference pipeline and attach it to the index. This will re-use the existing pipeline instead of creating a new one. * testing ml inference logic * test parseMlInferenceParametersFromPipeline --- .../ml_inference_pipeline/index.test.ts | 43 +++ .../common/ml_inference_pipeline/index.ts | 30 +- .../common/types/pipelines.ts | 7 + .../attach_ml_inference_pipeline.test.ts | 47 +++ .../pipelines/attach_ml_inference_pipeline.ts | 36 ++ .../create_ml_inference_pipeline.test.ts | 0 .../create_ml_inference_pipeline.ts | 3 +- .../fetch_ml_inference_pipeline_processors.ts | 12 +- .../pipelines/fetch_ml_inference_pipelines.ts | 24 ++ .../add_ml_inference_pipeline_modal.tsx | 21 +- .../ml_inference/configure_pipeline.tsx | 183 ++++++++--- .../ml_inference/ml_inference_logic.test.ts | 308 +++++++++++++++++- .../ml_inference/ml_inference_logic.ts | 207 +++++++++++- .../ml_inference/pipeline_select_option.tsx | 96 ++++++ .../pipelines/ml_inference/types.ts | 1 + .../pipelines/ml_inference/utils.ts | 30 ++ .../search_index/pipelines/pipelines_logic.ts | 28 +- .../components/shared/ml_inference/utils.ts | 11 +- 18 files changed, 1009 insertions(+), 78 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.ts rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/{ml_models => pipelines}/create_ml_inference_pipeline.test.ts (100%) rename x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/{ml_models => pipelines}/create_ml_inference_pipeline.ts (89%) create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipelines.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx diff --git a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts index 538d8016a0a73..b2616ed7615ba 100644 --- a/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts +++ b/x-pack/plugins/enterprise_search/common/ml_inference_pipeline/index.test.ts @@ -17,6 +17,7 @@ import { getMlModelTypesForModelConfig, getSetProcessorForInferenceType, SUPPORTED_PYTORCH_TASKS as LOCAL_SUPPORTED_PYTORCH_TASKS, + parseMlInferenceParametersFromPipeline, } from '.'; const mockModel: MlTrainedModelConfig = { @@ -198,3 +199,45 @@ describe('generateMlInferencePipelineBody lib function', () => { ); }); }); + +describe('parseMlInferenceParametersFromPipeline', () => { + it('returns pipeline parameters from ingest pipeline', () => { + expect( + parseMlInferenceParametersFromPipeline('unit-test', { + processors: [ + { + inference: { + field_map: { + body: 'text_field', + }, + model_id: 'test-model', + target_field: 'ml.inference.test', + }, + }, + ], + }) + ).toEqual({ + destination_field: 'test', + model_id: 'test-model', + pipeline_name: 'unit-test', + source_field: 'body', + }); + }); + it('return null if pipeline missing inference processor', () => { + expect(parseMlInferenceParametersFromPipeline('unit-test', { processors: [] })).toBeNull(); + }); + it('return null if pipeline missing field_map', () => { + expect( + parseMlInferenceParametersFromPipeline('unit-test', { + processors: [ + { + inference: { + model_id: 'test-model', + target_field: 'test', + }, + }, + ], + }) + ).toBeNull(); + }); +}); 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 b5b4526d1723b..4e5b124f8dff0 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 @@ -5,9 +5,13 @@ * 2.0. */ -import { IngestSetProcessor, MlTrainedModelConfig } from '@elastic/elasticsearch/lib/api/types'; +import { + IngestPipeline, + IngestSetProcessor, + MlTrainedModelConfig, +} from '@elastic/elasticsearch/lib/api/types'; -import { MlInferencePipeline } from '../types/pipelines'; +import { MlInferencePipeline, CreateMlInferencePipelineParameters } from '../types/pipelines'; // Getting an error importing this from @kbn/ml-plugin/common/constants/data_frame_analytics' // So defining it locally for now with a test to make sure it matches. @@ -151,3 +155,25 @@ export const formatPipelineName = (rawName: string) => .trim() .replace(/\s+/g, '_') // Convert whitespaces to underscores .toLowerCase(); + +export const parseMlInferenceParametersFromPipeline = ( + name: string, + pipeline: IngestPipeline +): CreateMlInferencePipelineParameters | null => { + const processor = pipeline?.processors?.find((proc) => proc.inference !== undefined); + if (!processor || processor?.inference === undefined) { + return null; + } + const { inference: inferenceProcessor } = processor; + const sourceFields = Object.keys(inferenceProcessor.field_map ?? {}); + const sourceField = sourceFields.length === 1 ? sourceFields[0] : null; + if (!sourceField) { + return null; + } + return { + destination_field: inferenceProcessor.target_field.replace('ml.inference.', ''), + model_id: inferenceProcessor.model_id, + pipeline_name: name, + source_field: sourceField, + }; +}; diff --git a/x-pack/plugins/enterprise_search/common/types/pipelines.ts b/x-pack/plugins/enterprise_search/common/types/pipelines.ts index 9b53e98d584d7..38314f6d162de 100644 --- a/x-pack/plugins/enterprise_search/common/types/pipelines.ts +++ b/x-pack/plugins/enterprise_search/common/types/pipelines.ts @@ -64,3 +64,10 @@ export interface DeleteMlInferencePipelineResponse { deleted?: string; updated?: string; } + +export interface CreateMlInferencePipelineParameters { + destination_field?: string; + model_id: string; + pipeline_name: string; + source_field: string; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.test.ts new file mode 100644 index 0000000000000..4c88466ba32b7 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.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 { mockHttpValues } from '../../../__mocks__/kea_logic'; + +import { + attachMlInferencePipeline, + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse, +} from './attach_ml_inference_pipeline'; + +describe('AttachMlInferencePipelineApiLogic', () => { + const { http } = mockHttpValues; + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('createMlInferencePipeline', () => { + it('calls the api', async () => { + const response: Promise = Promise.resolve({ + addedToParentPipeline: true, + created: false, + id: 'unit-test', + }); + http.post.mockReturnValue(response); + + const args: AttachMlInferencePipelineApiLogicArgs = { + indexName: 'unit-test-index', + pipelineName: 'unit-test', + }; + const result = await attachMlInferencePipeline(args); + expect(http.post).toHaveBeenCalledWith( + '/internal/enterprise_search/indices/unit-test-index/ml_inference/pipeline_processors/attach', + { + body: '{"pipeline_name":"unit-test"}', + } + ); + expect(result).toEqual({ + addedToParentPipeline: true, + created: false, + id: args.pipelineName, + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.ts new file mode 100644 index 0000000000000..433c41a75ea0f --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/attach_ml_inference_pipeline.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 { AttachMlInferencePipelineResponse } from '../../../../../common/types/pipelines'; + +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export interface AttachMlInferencePipelineApiLogicArgs { + indexName: string; + pipelineName: string; +} + +export type { AttachMlInferencePipelineResponse }; + +export const attachMlInferencePipeline = async ( + args: AttachMlInferencePipelineApiLogicArgs +): Promise => { + const route = `/internal/enterprise_search/indices/${args.indexName}/ml_inference/pipeline_processors/attach`; + const params = { + pipeline_name: args.pipelineName, + }; + + return await HttpLogic.values.http.post(route, { + body: JSON.stringify(params), + }); +}; + +export const AttachMlInferencePipelineApiLogic = createApiLogic( + ['attach_ml_inference_pipeline_api_logic'], + attachMlInferencePipeline +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.test.ts similarity index 100% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.test.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.test.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts similarity index 89% rename from x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.ts rename to x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts index ee5e7dd1c4295..78f08c4bc0ee8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/ml_models/create_ml_inference_pipeline.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/create_ml_inference_pipeline.ts @@ -4,6 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { CreateMlInferencePipelineParameters } from '../../../../../common/types/pipelines'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; @@ -23,7 +24,7 @@ export const createMlInferencePipeline = async ( args: CreateMlInferencePipelineApiLogicArgs ): Promise => { const route = `/internal/enterprise_search/indices/${args.indexName}/ml_inference/pipeline_processors`; - const params = { + const params: CreateMlInferencePipelineParameters = { destination_field: args.destinationField, model_id: args.modelId, pipeline_name: args.pipelineName, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.ts index 85f481b513525..2d881a0463bb7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipeline_processors.ts @@ -9,10 +9,18 @@ import { InferencePipeline } from '../../../../../common/types/pipelines'; import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; import { HttpLogic } from '../../../shared/http'; -export const fetchMlInferencePipelineProcessors = async ({ indexName }: { indexName: string }) => { +export interface FetchMlInferencePipelineProcessorsApiLogicArgs { + indexName: string; +} + +export type FetchMlInferencePipelineProcessorsResponse = InferencePipeline[]; + +export const fetchMlInferencePipelineProcessors = async ({ + indexName, +}: FetchMlInferencePipelineProcessorsApiLogicArgs) => { const route = `/internal/enterprise_search/indices/${indexName}/ml_inference/pipeline_processors`; - return await HttpLogic.values.http.get(route); + return await HttpLogic.values.http.get(route); }; export const FetchMlInferencePipelineProcessorsApiLogic = createApiLogic( diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipelines.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipelines.ts new file mode 100644 index 0000000000000..d5df97d259fda --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/api/pipelines/fetch_ml_inference_pipelines.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { MlInferencePipeline } from '../../../../../common/types/pipelines'; +import { createApiLogic } from '../../../shared/api_logic/create_api_logic'; +import { HttpLogic } from '../../../shared/http'; + +export type FetchMlInferencePipelinesArgs = undefined; +export type FetchMlInferencePipelinesResponse = Record; + +export const fetchMlInferencePipelines = async () => { + const route = '/internal/enterprise_search/pipelines/ml_inference'; + + return await HttpLogic.values.http.get(route); +}; + +export const FetchMlInferencePipelinesApiLogic = createApiLogic( + ['fetch_ml_inference_pipelines_api_logic'], + fetchMlInferencePipelines +); diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx index edbf18f8b009c..cc0cc3eb8f954 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/add_ml_inference_pipeline_modal.tsx @@ -92,7 +92,7 @@ const AddProcessorContent: React.FC = ({ onClo ); } - if (supportedMLModels === undefined || supportedMLModels?.length === 0) { + if (supportedMLModels.length === 0) { return ; } return ( @@ -188,8 +188,10 @@ const ModalFooter: React.FC { const { addInferencePipelineModal: modal, isPipelineDataValid } = useValues(MLInferenceLogic); - const { createPipeline, setAddInferencePipelineStep } = useActions(MLInferenceLogic); + const { attachPipeline, createPipeline, setAddInferencePipelineStep } = + useActions(MLInferenceLogic); + const attachExistingPipeline = Boolean(modal.configuration.existingPipeline); let nextStep: AddInferencePipelineSteps | undefined; let previousStep: AddInferencePipelineSteps | undefined; switch (modal.step) { @@ -239,6 +241,21 @@ const ModalFooter: React.FC {CONTINUE_BUTTON_LABEL} + ) : attachExistingPipeline ? ( + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.transforms.addInferencePipelineModal.footer.attach', + { + defaultMessage: 'Attach', + } + )} + ) : ( ( { const { addInferencePipelineModal: { configuration }, formErrors, + existingInferencePipelines, supportedMLModels, sourceFields, } = useValues(MLInferenceLogic); - const { setInferencePipelineConfiguration } = useActions(MLInferenceLogic); + const { selectExistingPipeline, setInferencePipelineConfiguration } = + useActions(MLInferenceLogic); const { ingestionMethod } = useValues(IndexViewLogic); const { destinationField, modelID, pipelineName, sourceField } = configuration; - const models = supportedMLModels ?? []; const nameError = formErrors.pipelineName !== undefined && pipelineName.length > 0; const emptySourceFields = (sourceFields?.length ?? 0) === 0; @@ -76,12 +92,30 @@ export const ConfigurePipeline: React.FC = () => { ), value: MODEL_SELECT_PLACEHOLDER_VALUE, }, - ...models.map((model) => ({ + ...supportedMLModels.map((model) => ({ dropdownDisplay: , inputDisplay: model.model_id, value: model.model_id, })), ]; + const pipelineOptions: Array> = [ + { + disabled: true, + inputDisplay: i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder', + { defaultMessage: 'Select one' } + ), + value: PIPELINE_SELECT_PLACEHOLDER_VALUE, + }, + ...(existingInferencePipelines?.map((pipeline) => ({ + disabled: pipeline.disabled, + dropdownDisplay: , + inputDisplay: pipeline.pipelineName, + value: pipeline.pipelineName, + })) ?? []), + ]; + + const inputsDisabled = configuration.existingPipeline !== false; return ( <> @@ -106,45 +140,107 @@ export const ConfigurePipeline: React.FC = () => { - - + + + + setInferencePipelineConfiguration({ + ...EMPTY_PIPELINE_CONFIGURATION, + existingPipeline: e.target.value === 'true', + }) + } + /> + + + + {configuration.existingPipeline === true ? ( + + 0 ? pipelineName : PIPELINE_SELECT_PLACEHOLDER_VALUE + } + options={pipelineOptions} + onChange={(value) => selectExistingPipeline(value)} + /> + + ) : ( + + + setInferencePipelineConfiguration({ + ...configuration, + pipelineName: e.target.value, + }) + } + /> + )} - value={pipelineName} - onChange={(e) => - setInferencePipelineConfiguration({ - ...configuration, - pipelineName: e.target.value, - }) - } - /> - + + { data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-configureInferencePipeline-selectTrainedModel`} fullWidth hasDividers + disabled={inputsDisabled} itemLayoutAlign="top" onChange={(value) => setInferencePipelineConfiguration({ @@ -185,6 +282,7 @@ export const ConfigurePipeline: React.FC = () => { > { > diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts index c605009d7eb0d..4224c150af904 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/ml_inference_logic.test.ts @@ -7,20 +7,27 @@ import { LogicMounter } from '../../../../../__mocks__/kea_logic'; -import { HttpError, Status } from '../../../../../../../common/types/api'; +import { HttpResponse } from '@kbn/core/public'; + +import { ErrorResponse, HttpError, Status } from '../../../../../../../common/types/api'; +import { TrainedModelState } from '../../../../../../../common/types/pipelines'; 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 { AttachMlInferencePipelineApiLogic } from '../../../../api/pipelines/attach_ml_inference_pipeline'; +import { CreateMlInferencePipelineApiLogic } from '../../../../api/pipelines/create_ml_inference_pipeline'; +import { FetchMlInferencePipelineProcessorsApiLogic } from '../../../../api/pipelines/fetch_ml_inference_pipeline_processors'; +import { FetchMlInferencePipelinesApiLogic } from '../../../../api/pipelines/fetch_ml_inference_pipelines'; import { SimulateMlInterfacePipelineApiLogic } from '../../../../api/pipelines/simulate_ml_inference_pipeline_processors'; import { MLInferenceLogic, EMPTY_PIPELINE_CONFIGURATION, AddInferencePipelineSteps, + MLInferenceProcessorsValues, } from './ml_inference_logic'; -const DEFAULT_VALUES = { +const DEFAULT_VALUES: MLInferenceProcessorsValues = { addInferencePipelineModal: { configuration: { ...EMPTY_PIPELINE_CONFIGURATION, @@ -46,6 +53,7 @@ const DEFAULT_VALUES = { step: AddInferencePipelineSteps.Configuration, }, createErrors: [], + existingInferencePipelines: [], formErrors: { modelID: 'Field is required.', pipelineName: 'Field is required.', @@ -57,6 +65,8 @@ const DEFAULT_VALUES = { mappingData: undefined, mappingStatus: 0, mlInferencePipeline: undefined, + mlInferencePipelineProcessors: undefined, + mlInferencePipelinesData: undefined, mlModelsData: undefined, mlModelsStatus: 0, simulatePipelineData: undefined, @@ -64,7 +74,7 @@ const DEFAULT_VALUES = { simulatePipelineResult: undefined, simulatePipelineStatus: 0, sourceFields: undefined, - supportedMLModels: undefined, + supportedMLModels: [], }; describe('MlInferenceLogic', () => { @@ -77,13 +87,25 @@ describe('MlInferenceLogic', () => { const { mount: mountCreateMlInferencePipelineApiLogic } = new LogicMounter( CreateMlInferencePipelineApiLogic ); + const { mount: mountAttachMlInferencePipelineApiLogic } = new LogicMounter( + AttachMlInferencePipelineApiLogic + ); + const { mount: mountFetchMlInferencePipelineProcessorsApiLogic } = new LogicMounter( + FetchMlInferencePipelineProcessorsApiLogic + ); + const { mount: mountFetchMlInferencePipelinesApiLogic } = new LogicMounter( + FetchMlInferencePipelinesApiLogic + ); beforeEach(() => { jest.clearAllMocks(); mountMappingApiLogic(); mountMLModelsApiLogic(); + mountFetchMlInferencePipelineProcessorsApiLogic(); + mountFetchMlInferencePipelinesApiLogic(); mountSimulateMlInterfacePipelineApiLogic(); mountCreateMlInferencePipelineApiLogic(); + mountAttachMlInferencePipelineApiLogic(); mount(); }); @@ -110,6 +132,70 @@ describe('MlInferenceLogic', () => { }); }); }); + describe('attachApiError', () => { + it('updates create errors', () => { + MLInferenceLogic.actions.attachApiError({ + body: { + error: '', + message: 'this is an error', + statusCode: 500, + }, + } as HttpResponse); + + expect(MLInferenceLogic.values.createErrors).toEqual(['this is an error']); + }); + }); + describe('createApiError', () => { + it('updates create errors', () => { + MLInferenceLogic.actions.createApiError({ + body: { + error: '', + message: 'this is an error', + statusCode: 500, + }, + } as HttpResponse); + + expect(MLInferenceLogic.values.createErrors).toEqual(['this is an error']); + }); + }); + describe('makeAttachPipelineRequest', () => { + it('clears existing errors', () => { + MLInferenceLogic.actions.attachApiError({ + body: { + error: '', + message: 'this is an error', + statusCode: 500, + }, + } as HttpResponse); + + expect(MLInferenceLogic.values.createErrors).not.toHaveLength(0); + MLInferenceLogic.actions.makeAttachPipelineRequest({ + indexName: 'test', + pipelineName: 'unit-test', + }); + expect(MLInferenceLogic.values.createErrors).toHaveLength(0); + }); + }); + describe('makeCreatePipelineRequest', () => { + it('clears existing errors', () => { + MLInferenceLogic.actions.createApiError({ + body: { + error: '', + message: 'this is an error', + statusCode: 500, + }, + } as HttpResponse); + + expect(MLInferenceLogic.values.createErrors).not.toHaveLength(0); + MLInferenceLogic.actions.makeCreatePipelineRequest({ + indexName: 'test', + pipelineName: 'unit-test', + modelId: 'test-model', + sourceField: 'body', + }); + expect(MLInferenceLogic.values.createErrors).toHaveLength(0); + }); + }); }); describe('selectors', () => { @@ -162,6 +248,220 @@ describe('MlInferenceLogic', () => { expect(MLInferenceLogic.values.simulatePipelineResult).toEqual(simulateResponse); }); }); + describe('existingInferencePipelines', () => { + beforeEach(() => { + MappingsApiLogic.actions.apiSuccess({ + mappings: { + properties: { + body: { + type: 'text', + }, + }, + }, + }); + }); + it('returns empty list when there is not existing pipelines available', () => { + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([]); + }); + it('returns existing pipeline option', () => { + FetchMlInferencePipelinesApiLogic.actions.apiSuccess({ + 'unit-test': { + processors: [ + { + inference: { + field_map: { + body: 'text_field', + }, + model_id: 'test-model', + target_field: 'ml.inference.test-field', + }, + }, + ], + version: 1, + }, + }); + + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([ + { + destinationField: 'test-field', + disabled: false, + pipelineName: 'unit-test', + modelType: '', + modelId: 'test-model', + sourceField: 'body', + }, + ]); + }); + it('returns disabled pipeline option if missing source field', () => { + FetchMlInferencePipelinesApiLogic.actions.apiSuccess({ + 'unit-test': { + processors: [ + { + inference: { + field_map: { + body_content: 'text_field', + }, + model_id: 'test-model', + target_field: 'ml.inference.test-field', + }, + }, + ], + version: 1, + }, + }); + + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([ + { + destinationField: 'test-field', + disabled: true, + disabledReason: expect.any(String), + pipelineName: 'unit-test', + modelType: '', + modelId: 'test-model', + sourceField: 'body_content', + }, + ]); + }); + it('returns disabled pipeline option if model is redacted', () => { + FetchMlInferencePipelinesApiLogic.actions.apiSuccess({ + 'unit-test': { + processors: [ + { + inference: { + field_map: { + body: 'text_field', + }, + model_id: '', + target_field: 'ml.inference.test-field', + }, + }, + ], + version: 1, + }, + }); + + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([ + { + destinationField: 'test-field', + disabled: true, + disabledReason: expect.any(String), + pipelineName: 'unit-test', + modelType: '', + modelId: '', + sourceField: 'body', + }, + ]); + }); + it('returns disabled pipeline option if pipeline already attached', () => { + FetchMlInferencePipelineProcessorsApiLogic.actions.apiSuccess([ + { + modelId: 'test-model', + modelState: TrainedModelState.Started, + pipelineName: 'unit-test', + pipelineReferences: ['test@ml-inference'], + types: ['ner', 'pytorch'], + }, + ]); + FetchMlInferencePipelinesApiLogic.actions.apiSuccess({ + 'unit-test': { + processors: [ + { + inference: { + field_map: { + body: 'text_field', + }, + model_id: 'test-model', + target_field: 'ml.inference.test-field', + }, + }, + ], + version: 1, + }, + }); + + expect(MLInferenceLogic.values.existingInferencePipelines).toEqual([ + { + destinationField: 'test-field', + disabled: true, + disabledReason: expect.any(String), + pipelineName: 'unit-test', + modelType: '', + modelId: 'test-model', + sourceField: 'body', + }, + ]); + }); + }); + describe('mlInferencePipeline', () => { + it('returns undefined when configuration is invalid', () => { + MLInferenceLogic.actions.setInferencePipelineConfiguration({ + destinationField: '', + modelID: '', + pipelineName: 'unit-test', + sourceField: '', + }); + + expect(MLInferenceLogic.values.mlInferencePipeline).toBeUndefined(); + }); + it('generates inference pipeline', () => { + MLModelsApiLogic.actions.apiSuccess([ + { + inference_config: { + text_classification: { + classification_labels: ['one', 'two'], + tokenization: { + bert: {}, + }, + }, + }, + input: { + field_names: ['text_field'], + }, + model_id: 'test-model', + model_type: 'pytorch', + tags: [], + version: '1.0.0', + }, + ]); + MLInferenceLogic.actions.setInferencePipelineConfiguration({ + destinationField: '', + modelID: 'test-model', + pipelineName: 'unit-test', + sourceField: 'body', + }); + + expect(MLInferenceLogic.values.mlInferencePipeline).not.toBeUndefined(); + }); + it('returns undefined when existing pipeline not yet selected', () => { + MLInferenceLogic.actions.setInferencePipelineConfiguration({ + existingPipeline: true, + destinationField: '', + modelID: '', + pipelineName: '', + sourceField: '', + }); + expect(MLInferenceLogic.values.mlInferencePipeline).toBeUndefined(); + }); + it('return existing pipeline when selected', () => { + const existingPipeline = { + description: 'this is a test', + processors: [], + version: 1, + }; + FetchMlInferencePipelinesApiLogic.actions.apiSuccess({ + 'unit-test': existingPipeline, + }); + MLInferenceLogic.actions.setInferencePipelineConfiguration({ + existingPipeline: true, + destinationField: '', + modelID: '', + pipelineName: 'unit-test', + sourceField: '', + }); + expect(MLInferenceLogic.values.mlInferencePipeline).not.toBeUndefined(); + expect(MLInferenceLogic.values.mlInferencePipeline).toEqual(existingPipeline); + }); + }); }); describe('listeners', () => { 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 f4a968da1c2a1..fcdad4f66d141 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 @@ -15,6 +15,8 @@ import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_ import { formatPipelineName, generateMlInferencePipelineBody, + getMlModelTypesForModelConfig, + parseMlInferenceParametersFromPipeline, } from '../../../../../../../common/ml_inference_pipeline'; import { Status } from '../../../../../../../common/types/api'; import { MlInferencePipeline } from '../../../../../../../common/types/pipelines'; @@ -30,16 +32,30 @@ import { GetMappingsResponse, MappingsApiLogic, } from '../../../../api/mappings/mappings_logic'; -import { - CreateMlInferencePipelineApiLogic, - CreateMlInferencePipelineApiLogicArgs, - CreateMlInferencePipelineResponse, -} from '../../../../api/ml_models/create_ml_inference_pipeline'; import { GetMlModelsArgs, GetMlModelsResponse, MLModelsApiLogic, } from '../../../../api/ml_models/ml_models_logic'; +import { + AttachMlInferencePipelineApiLogic, + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse, +} from '../../../../api/pipelines/attach_ml_inference_pipeline'; +import { + CreateMlInferencePipelineApiLogic, + CreateMlInferencePipelineApiLogicArgs, + CreateMlInferencePipelineResponse, +} from '../../../../api/pipelines/create_ml_inference_pipeline'; +import { + FetchMlInferencePipelineProcessorsApiLogic, + FetchMlInferencePipelineProcessorsResponse, +} from '../../../../api/pipelines/fetch_ml_inference_pipeline_processors'; +import { + FetchMlInferencePipelinesApiLogic, + FetchMlInferencePipelinesArgs, + FetchMlInferencePipelinesResponse, +} from '../../../../api/pipelines/fetch_ml_inference_pipelines'; import { SimulateMlInterfacePipelineApiLogic, SimulateMlInterfacePipelineArgs, @@ -47,11 +63,20 @@ import { } from '../../../../api/pipelines/simulate_ml_inference_pipeline_processors'; import { isConnectorIndex } from '../../../../utils/indices'; -import { isSupportedMLModel, sortSourceFields } from '../../../shared/ml_inference/utils'; +import { + getMLType, + isSupportedMLModel, + sortSourceFields, +} from '../../../shared/ml_inference/utils'; import { AddInferencePipelineFormErrors, InferencePipelineConfiguration } from './types'; -import { validateInferencePipelineConfiguration } from './utils'; +import { + validateInferencePipelineConfiguration, + EXISTING_PIPELINE_DISABLED_MODEL_REDACTED, + EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELD, + EXISTING_PIPELINE_DISABLED_PIPELINE_EXISTS, +} from './utils'; export const EMPTY_PIPELINE_CONFIGURATION: InferencePipelineConfiguration = { destinationField: '', @@ -69,7 +94,26 @@ export enum AddInferencePipelineSteps { const API_REQUEST_COMPLETE_STATUSES = [Status.SUCCESS, Status.ERROR]; const DEFAULT_CONNECTOR_FIELDS = ['body', 'title', 'id', 'type', 'url']; +export interface MLInferencePipelineOption { + destinationField: string; + disabled: boolean; + disabledReason?: string; + modelId: string; + modelType: string; + pipelineName: string; + sourceField: string; +} + interface MLInferenceProcessorsActions { + attachApiError: Actions< + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse + >['apiError']; + attachApiSuccess: Actions< + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse + >['apiSuccess']; + attachPipeline: () => void; createApiError: Actions< CreateMlInferencePipelineApiLogicArgs, CreateMlInferencePipelineResponse @@ -79,18 +123,29 @@ interface MLInferenceProcessorsActions { CreateMlInferencePipelineResponse >['apiSuccess']; createPipeline: () => void; + makeAttachPipelineRequest: Actions< + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse + >['makeRequest']; makeCreatePipelineRequest: Actions< CreateMlInferencePipelineApiLogicArgs, CreateMlInferencePipelineResponse >['makeRequest']; makeMLModelsRequest: Actions['makeRequest']; makeMappingRequest: Actions['makeRequest']; + makeMlInferencePipelinesRequest: Actions< + FetchMlInferencePipelinesArgs, + FetchMlInferencePipelinesResponse + >['makeRequest']; makeSimulatePipelineRequest: Actions< SimulateMlInterfacePipelineArgs, SimulateMlInterfacePipelineResponse >['makeRequest']; mappingsApiError: Actions['apiError']; mlModelsApiError: Actions['apiError']; + selectExistingPipeline: (pipelineName: string) => { + pipelineName: string; + }; setAddInferencePipelineStep: (step: AddInferencePipelineSteps) => { step: AddInferencePipelineSteps; }; @@ -120,21 +175,24 @@ export interface AddInferencePipelineModal { step: AddInferencePipelineSteps; } -interface MLInferenceProcessorsValues { +export interface MLInferenceProcessorsValues { addInferencePipelineModal: AddInferencePipelineModal; createErrors: string[]; + existingInferencePipelines: MLInferencePipelineOption[]; formErrors: AddInferencePipelineFormErrors; - index: FetchIndexApiResponse; + index: FetchIndexApiResponse | undefined; isLoading: boolean; isPipelineDataValid: boolean; mappingData: typeof MappingsApiLogic.values.data; mappingStatus: Status; - mlInferencePipeline?: MlInferencePipeline; - mlModelsData: TrainedModelConfigResponse[]; + mlInferencePipeline: MlInferencePipeline | undefined; + mlInferencePipelineProcessors: FetchMlInferencePipelineProcessorsResponse | undefined; + mlInferencePipelinesData: FetchMlInferencePipelinesResponse | undefined; + mlModelsData: TrainedModelConfigResponse[] | undefined; mlModelsStatus: Status; simulatePipelineData: typeof SimulateMlInterfacePipelineApiLogic.values.data; simulatePipelineErrors: string[]; - simulatePipelineResult: IngestSimulateResponse; + simulatePipelineResult: IngestSimulateResponse | undefined; simulatePipelineStatus: Status; sourceFields: string[] | undefined; supportedMLModels: TrainedModelConfigResponse[]; @@ -144,8 +202,10 @@ export const MLInferenceLogic = kea< MakeLogicType >({ actions: { + attachPipeline: true, clearFormErrors: true, createPipeline: true, + selectExistingPipeline: (pipelineName: string) => ({ pipelineName }), setAddInferencePipelineStep: (step: AddInferencePipelineSteps) => ({ step }), setFormErrors: (inputErrors: AddInferencePipelineFormErrors) => ({ inputErrors }), setIndexName: (indexName: string) => ({ indexName }), @@ -160,6 +220,8 @@ export const MLInferenceLogic = kea< }, connect: { actions: [ + FetchMlInferencePipelinesApiLogic, + ['makeRequest as makeMlInferencePipelinesRequest'], MappingsApiLogic, ['makeRequest as makeMappingRequest', 'apiError as mappingsApiError'], MLModelsApiLogic, @@ -176,20 +238,43 @@ export const MLInferenceLogic = kea< 'apiSuccess as createApiSuccess', 'makeRequest as makeCreatePipelineRequest', ], + AttachMlInferencePipelineApiLogic, + [ + 'apiError as attachApiError', + 'apiSuccess as attachApiSuccess', + 'makeRequest as makeAttachPipelineRequest', + ], ], values: [ FetchIndexApiLogic, ['data as index'], + FetchMlInferencePipelinesApiLogic, + ['data as mlInferencePipelinesData'], MappingsApiLogic, ['data as mappingData', 'status as mappingStatus'], MLModelsApiLogic, ['data as mlModelsData', 'status as mlModelsStatus'], SimulateMlInterfacePipelineApiLogic, ['data as simulatePipelineData', 'status as simulatePipelineStatus'], + FetchMlInferencePipelineProcessorsApiLogic, + ['data as mlInferencePipelineProcessors'], ], }, events: {}, listeners: ({ values, actions }) => ({ + attachPipeline: () => { + const { + addInferencePipelineModal: { + configuration: { pipelineName }, + indexName, + }, + } = values; + + actions.makeAttachPipelineRequest({ + indexName, + pipelineName, + }); + }, createPipeline: () => { const { addInferencePipelineModal: { configuration, indexName }, @@ -206,7 +291,21 @@ export const MLInferenceLogic = kea< sourceField: configuration.sourceField, }); }, + selectExistingPipeline: ({ pipelineName }) => { + const pipeline = values.mlInferencePipelinesData?.[pipelineName]; + if (!pipeline) return; + const params = parseMlInferenceParametersFromPipeline(pipelineName, pipeline); + if (params === null) return; + actions.setInferencePipelineConfiguration({ + destinationField: params.destination_field ?? '', + existingPipeline: true, + modelID: params.model_id, + pipelineName, + sourceField: params.source_field, + }); + }, setIndexName: ({ indexName }) => { + actions.makeMlInferencePipelinesRequest(undefined); actions.makeMLModelsRequest(undefined); actions.makeMappingRequest({ indexName }); }, @@ -264,7 +363,9 @@ export const MLInferenceLogic = kea< createErrors: [ [], { + attachApiError: (_, error) => getErrorsFromHttpResponse(error), createApiError: (_, error) => getErrorsFromHttpResponse(error), + makeAttachPipelineRequest: () => [], makeCreatePipelineRequest: () => [], }, ], @@ -297,12 +398,24 @@ export const MLInferenceLogic = kea< selectors.isPipelineDataValid, selectors.addInferencePipelineModal, selectors.mlModelsData, + selectors.mlInferencePipelinesData, ], ( - isPipelineDataValid: boolean, - { configuration }: AddInferencePipelineModal, - models: MLInferenceProcessorsValues['mlModelsData'] + isPipelineDataValid: MLInferenceProcessorsValues['isPipelineDataValid'], + { configuration }: MLInferenceProcessorsValues['addInferencePipelineModal'], + models: MLInferenceProcessorsValues['mlModelsData'], + mlInferencePipelinesData: MLInferenceProcessorsValues['mlInferencePipelinesData'] ) => { + if (configuration.existingPipeline) { + if (configuration.pipelineName.length === 0) { + return undefined; + } + const pipeline = mlInferencePipelinesData?.[configuration.pipelineName]; + if (!pipeline) { + return undefined; + } + return pipeline as MlInferencePipeline; + } if (!isPipelineDataValid) return undefined; const model = models?.find((mlModel) => mlModel.model_id === configuration.modelID); if (!model) return undefined; @@ -350,7 +463,69 @@ export const MLInferenceLogic = kea< supportedMLModels: [ () => [selectors.mlModelsData], (mlModelsData: TrainedModelConfigResponse[] | undefined) => { - return mlModelsData?.filter(isSupportedMLModel); + return mlModelsData?.filter(isSupportedMLModel) ?? []; + }, + ], + existingInferencePipelines: [ + () => [ + selectors.mlInferencePipelinesData, + selectors.sourceFields, + selectors.supportedMLModels, + selectors.mlInferencePipelineProcessors, + ], + ( + mlInferencePipelinesData: MLInferenceProcessorsValues['mlInferencePipelinesData'], + sourceFields: MLInferenceProcessorsValues['sourceFields'], + supportedMLModels: MLInferenceProcessorsValues['supportedMLModels'], + mlInferencePipelineProcessors: MLInferenceProcessorsValues['mlInferencePipelineProcessors'] + ) => { + if (!mlInferencePipelinesData) { + return []; + } + const indexProcessorNames = + mlInferencePipelineProcessors?.map((processor) => processor.pipelineName) ?? []; + + const existingPipelines: MLInferencePipelineOption[] = Object.entries( + mlInferencePipelinesData + ) + .map(([pipelineName, pipeline]): MLInferencePipelineOption | undefined => { + if (!pipeline) return undefined; + const pipelineParams = parseMlInferenceParametersFromPipeline(pipelineName, pipeline); + if (!pipelineParams) return undefined; + const { + destination_field: destinationField, + model_id: modelId, + source_field: sourceField, + } = pipelineParams; + + let disabled: boolean = false; + let disabledReason: string | undefined; + if (!(sourceFields?.includes(sourceField) ?? false)) { + disabled = true; + disabledReason = EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELD; + } else if (indexProcessorNames.includes(pipelineName)) { + disabled = true; + disabledReason = EXISTING_PIPELINE_DISABLED_PIPELINE_EXISTS; + } else if (pipelineParams.model_id.length === 0) { + disabled = true; + disabledReason = EXISTING_PIPELINE_DISABLED_MODEL_REDACTED; + } + const mlModel = supportedMLModels.find((model) => model.model_id === modelId); + const modelType = mlModel ? getMLType(getMlModelTypesForModelConfig(mlModel)) : ''; + + return { + destinationField: destinationField ?? '', + disabled, + disabledReason, + modelId, + modelType, + pipelineName, + sourceField, + }; + }) + .filter((p): p is MLInferencePipelineOption => p !== undefined); + + return existingPipelines; }, ], }), diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx new file mode 100644 index 0000000000000..f782c827a9728 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/pipeline_select_option.tsx @@ -0,0 +1,96 @@ +/* + * Copyright 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, EuiIcon, EuiTextColor, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { MLInferencePipelineOption } from './ml_inference_logic'; +import { EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELD } from './utils'; + +export interface PipelineSelectOptionProps { + pipeline: MLInferencePipelineOption; +} + +const REDACTED_MODE_ID_DISPLAY = i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.redactedModel', + { + defaultMessage: 'Trained model not available in this space', + } +); + +export const PipelineSelectOption: React.FC = ({ pipeline }) => { + const modelIdDisplay = pipeline.modelId.length > 0 ? pipeline.modelId : REDACTED_MODE_ID_DISPLAY; + return ( + + {pipeline.disabled && ( + + + + + + + + {pipeline.disabledReason ?? EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELD} + + + + + )} + + +

{pipeline.pipelineName}

+ + + + + + {pipeline.disabled ? ( + modelIdDisplay + ) : ( + {modelIdDisplay} + )} + + {pipeline.modelType.length > 0 && ( + + + {pipeline.modelType} + + + )} + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.sourceField', + { defaultMessage: 'Source field' } + )} + + + {pipeline.sourceField} + + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.destinationField', + { defaultMessage: 'Destination field' } + )} + + + {pipeline.destinationField} + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.ts index 29ad5e9193fdb..9ad288c4b84f5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/types.ts @@ -7,6 +7,7 @@ export interface InferencePipelineConfiguration { destinationField: string; + existingPipeline?: boolean; modelID: string; pipelineName: string; sourceField: string; diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts index 8db23f5deb7d6..8ad94e5f92da4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/search_index/pipelines/ml_inference/utils.ts @@ -31,6 +31,12 @@ export const validateInferencePipelineConfiguration = ( config: InferencePipelineConfiguration ): AddInferencePipelineFormErrors => { const errors: AddInferencePipelineFormErrors = {}; + if (config.existingPipeline === true) { + if (config.pipelineName.length === 0) { + errors.pipelineName = FIELD_REQUIRED_ERROR; + } + return errors; + } if (config.pipelineName.trim().length === 0) { errors.pipelineName = FIELD_REQUIRED_ERROR; } else if (!isValidPipelineName(config.pipelineName)) { @@ -45,3 +51,27 @@ export const validateInferencePipelineConfiguration = ( return errors; }; + +export const EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELD = i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledSourceFieldDescription', + { + defaultMessage: + 'This pipeline cannot be selected because the source field does not exist on this index.', + } +); + +export const EXISTING_PIPELINE_DISABLED_PIPELINE_EXISTS = i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledPipelineExistsDescription', + { + defaultMessage: 'This pipeline cannot be selected because it is already attached.', + } +); + +// TODO: removed when we support attaching pipelines with unavailable models +export const EXISTING_PIPELINE_DISABLED_MODEL_REDACTED = i18n.translate( + 'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.disabledModelRedactedDescription', + { + defaultMessage: + 'This pipeline cannot be selected because it uses a trained model not available in this Kibana space.', + } +); 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 dca18863cde02..f4c9aad591c72 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 @@ -47,12 +47,21 @@ import { FetchIndexApiParams, FetchIndexApiResponse, } from '../../../api/index/fetch_index_api_logic'; -import { CreateMlInferencePipelineApiLogic } from '../../../api/ml_models/create_ml_inference_pipeline'; import { DeleteMlInferencePipelineApiLogic, DeleteMlInferencePipelineApiLogicArgs, DeleteMlInferencePipelineResponse, } from '../../../api/ml_models/delete_ml_inference_pipeline'; +import { + AttachMlInferencePipelineApiLogic, + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse, +} from '../../../api/pipelines/attach_ml_inference_pipeline'; +import { + CreateMlInferencePipelineApiLogic, + CreateMlInferencePipelineApiLogicArgs, + CreateMlInferencePipelineResponse, +} from '../../../api/pipelines/create_ml_inference_pipeline'; import { FetchMlInferencePipelineProcessorsApiLogic } from '../../../api/pipelines/fetch_ml_inference_pipeline_processors'; import { isApiIndex, isConnectorIndex, isCrawlerIndex } from '../../../utils/indices'; @@ -60,6 +69,10 @@ type PipelinesActions = Pick< Actions, 'apiError' | 'apiSuccess' | 'makeRequest' > & { + attachMlInferencePipelineSuccess: Actions< + AttachMlInferencePipelineApiLogicArgs, + AttachMlInferencePipelineResponse + >['apiSuccess']; closeAddMlInferencePipelineModal: () => void; closeModal: () => void; createCustomPipeline: Actions< @@ -74,6 +87,10 @@ type PipelinesActions = Pick< CreateCustomPipelineApiLogicArgs, CreateCustomPipelineApiLogicResponse >['apiSuccess']; + createMlInferencePipelineSuccess: Actions< + CreateMlInferencePipelineApiLogicArgs, + CreateMlInferencePipelineResponse + >['apiSuccess']; deleteMlPipeline: Actions< DeleteMlInferencePipelineApiLogicArgs, DeleteMlInferencePipelineResponse @@ -153,6 +170,8 @@ export const PipelinesLogic = kea { + // 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 }); + }, closeModal: () => actions.setPipelineState( isConnectorIndex(values.index) || isCrawlerIndex(values.index) @@ -287,6 +312,7 @@ export const PipelinesLogic = kea false, closeAddMlInferencePipelineModal: () => false, createMlInferencePipelineSuccess: () => false, openAddMlInferencePipelineModal: () => true, diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts index 0b2955cb7f30e..f24fe059cc5d0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_content/components/shared/ml_inference/utils.ts @@ -8,14 +8,9 @@ import { i18n } from '@kbn/i18n'; import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models'; -export const NLP_CONFIG_KEYS = [ - 'fill_mask', - 'ner', - 'text_classification', - 'text_embedding', - 'question_answering', - 'zero_shot_classification', -]; +import { SUPPORTED_PYTORCH_TASKS } from '../../../../../../common/ml_inference_pipeline'; + +export const NLP_CONFIG_KEYS: string[] = Object.values(SUPPORTED_PYTORCH_TASKS); export const RECOMMENDED_FIELDS = ['body', 'body_content', 'title']; export const NLP_DISPLAY_TITLES: Record = { From a5c8ebe00d2f257dc251e2073b353f9689e4bb53 Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Thu, 27 Oct 2022 12:03:29 -0600 Subject: [PATCH 24/28] [APM] Performance fix for 'cardinality' telemetry task (#144061) * Performance fix for 'cardinality' telemetry task * Make timeout/index required for telemetry searches * Fix tests Co-authored-by: Dario Gieselaar --- .../lib/apm_telemetry/collect_data_telemetry/index.ts | 6 +++++- .../lib/apm_telemetry/collect_data_telemetry/tasks.test.ts | 2 +- .../lib/apm_telemetry/collect_data_telemetry/tasks.ts | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts index 7a1c4da71b788..fcce003d89206 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts @@ -17,7 +17,11 @@ type ISavedObjectsClient = Pick; type TelemetryTaskExecutor = (params: { indices: ApmIndicesConfig; - search( + search< + TSearchRequest extends ESSearchRequest & { index: string | string[] } & { + body: { timeout: string }; + } + >( params: TSearchRequest ): Promise>; indicesStats( diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts index 253c8ed9e44d2..26c0caac5e2d3 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.test.ts @@ -434,7 +434,7 @@ describe('data telemetry collection tasks', () => { } }); - expect(await task?.executor({ search } as any)).toEqual({ + expect(await task?.executor({ search, indices } as any)).toEqual({ cardinality: { client: { geo: { country_iso_code: { rum: { '1d': 5 } } } }, transaction: { name: { all_agents: { '1d': 3 }, rum: { '1d': 1 } } }, diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts index d114b31d75e3c..37c7dad804a4d 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/tasks.ts @@ -148,6 +148,7 @@ export const tasks: TelemetryTask[] = [ await search({ index: indices.transaction, body: { + timeout, query: { bool: { filter: [ @@ -355,6 +356,7 @@ export const tasks: TelemetryTask[] = [ const response = await search({ index: [indices.transaction], body: { + timeout, query: { bool: { filter: [{ range: { '@timestamp': { gte: 'now-1d' } } }], @@ -1032,8 +1034,9 @@ export const tasks: TelemetryTask[] = [ }, { name: 'cardinality', - executor: async ({ search }) => { + executor: async ({ indices, search }) => { const allAgentsCardinalityResponse = await search({ + index: [indices.transaction], body: { size: 0, timeout, @@ -1058,6 +1061,7 @@ export const tasks: TelemetryTask[] = [ }); const rumAgentCardinalityResponse = await search({ + index: [indices.transaction], body: { size: 0, timeout, From 1831ebf0bb2fd140d8b44dfd3723b1d7af01cb64 Mon Sep 17 00:00:00 2001 From: Kyle Pollich Date: Thu, 27 Oct 2022 14:24:43 -0400 Subject: [PATCH 25/28] [Fleet] Update GH Projects automation (#144123) * [Fleet] Update GH Projects automation Update GH projects automation for issues labeled with `Team:Fleet` to be automatically added to the Ingest Dev project with the proper `Area` property set. * Update add-to-fleet-project.yml * Rename add-to-fleet-project.yml to add-fleet-issues-to-ingest-project.yml --- .../add-fleet-issues-to-ingest-project.yml | 51 +++++++++++++++++++ .github/workflows/add-to-fleet-project.yml | 36 ------------- 2 files changed, 51 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/add-fleet-issues-to-ingest-project.yml delete mode 100644 .github/workflows/add-to-fleet-project.yml diff --git a/.github/workflows/add-fleet-issues-to-ingest-project.yml b/.github/workflows/add-fleet-issues-to-ingest-project.yml new file mode 100644 index 0000000000000..117ec649cd8a1 --- /dev/null +++ b/.github/workflows/add-fleet-issues-to-ingest-project.yml @@ -0,0 +1,51 @@ +name: Add Fleet issue to Platform Ingest project + +on: + issues: + types: + - labeled + +env: + INGEST_PROJECT_ID: 'PVT_kwDOAGc3Zs4AEzn4' + FLEET_LABEL: 'Team:Fleet' + AREA_FIELD_ID: 'PVTSSF_lADOAGc3Zs4AEzn4zgEgZSo' + FLEET_UI_OPTION_ID: '411a7b86' + +jobs: + add_to_ingest_project: + runs-on: ubuntu-latest + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + if: ${{ github.event.label.name == env.FLEET_LABEL }} + with: + query: | + # Variables have to be snake cased because of https://github.com/octokit/graphql-action/issues/164 + mutation AddToIngestProject($project_id: ID!, $content_id: ID!) { + addProjectV2ItemById(input: { projectId: $project_id, contentId: $content_id }) { + item { + id + } + } + } + project_id: ${{ env.INGEST_PROJECT_ID }} + content_id: ${{ github.event.issue.node_id }} + env: + GITHUB_TOKEN: ${{ secrets.FLEET_TECH_KIBANA_USER_TOKEN }} + - uses: octokit/graphql-action@v2.x + id: set_fleet_ui_area + if: github.event.label.name == env.FLEET_LABEL + with: + query: | + mutation updateIngestArea($item_id: ID!, $project_id: ID!, $area_field_id: ID!, $area_id: String) { + updateProjectV2ItemFieldValue( + input: { itemId: $item_id, projectId: $project_id, fieldId: $area_field_id, value: { singleSelectOptionId: $area_id } }) { + clientMutationId + } + } + item_id: ${{ fromJSON(steps.add_to_project.outputs.data).addProjectV2ItemById.item.id }} + project_id: ${{ env.INGEST_PROJECT_ID }} + area_field_id: ${{ env.AREA_FIELD_ID }} + area_id: ${{ env.FLEET_UI_OPTION_ID }} + env: + GITHUB_TOKEN: ${{ secrets.FLEET_TECH_KIBANA_USER_TOKEN }} diff --git a/.github/workflows/add-to-fleet-project.yml b/.github/workflows/add-to-fleet-project.yml deleted file mode 100644 index e828a3a5b637e..0000000000000 --- a/.github/workflows/add-to-fleet-project.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Add to Fleet:Quality project -on: - issues: - types: - - labeled -jobs: - add_to_project: - runs-on: ubuntu-latest - if: | - contains(github.event.issue.labels.*.name, 'Team:Fleet') && ( - contains(github.event.issue.labels.*.name, 'technical debt') || - contains(github.event.issue.labels.*.name, 'bug') || - contains(github.event.issue.labels.*.name, 'performance') || - contains(github.event.issue.labels.*.name, 'failed-test') || - contains(github.event.issue.labels.*.name, 'chore') - ) - steps: - - uses: octokit/graphql-action@v2.x - id: add_to_project - with: - headers: '{"GraphQL-Features": "projects_next_graphql"}' - query: | - mutation add_to_project($projectid: ID!, $contentid: ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { - id - } - } - } - projectid: ${{ env.PROJECT_ID }} - contentid: ${{ github.event.issue.node_id }} - env: - # https://github.com/orgs/elastic/projects/763 - PROJECT_ID: "PN_kwDOAGc3Zs4AAsH6" - # Token with `write:org` access - GITHUB_TOKEN: ${{ secrets.FLEET_TECH_KIBANA_USER_TOKEN }} From 7a7b031fb6e70fdc3a33467f986862e7dc82757f Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:59:55 -0400 Subject: [PATCH 26/28] skip failing test suite (#143933) --- .../functional/apps/ml/anomaly_detection_jobs/custom_urls.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/custom_urls.ts b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/custom_urls.ts index 5661a30362641..e5f181ea8414c 100644 --- a/x-pack/test/functional/apps/ml/anomaly_detection_jobs/custom_urls.ts +++ b/x-pack/test/functional/apps/ml/anomaly_detection_jobs/custom_urls.ts @@ -62,7 +62,8 @@ export default function ({ getService }: FtrProviderContext) { const ml = getService('ml'); const browser = getService('browser'); - describe('custom urls', function () { + // Failing: See https://github.com/elastic/kibana/issues/143933 + describe.skip('custom urls', function () { this.tags(['ml']); let testDashboardId: string | null = null; From 4bd8693f9bf4f8089e64835d91be6093ddd54b61 Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 27 Oct 2022 21:33:50 +0200 Subject: [PATCH 27/28] [APM] Critical path for a single trace (#143735) * [APM] Critical path for a single trace * Add tech preview badge * Update synthtrace tests * Add new setting to mapping * Make sure timestamp.us is set for error events as well --- packages/kbn-apm-synthtrace/index.ts | 1 + .../src/lib/apm/apm_error.ts | 6 + .../src/lib/apm/base_span.ts | 6 + .../src/lib/apm/instance.ts | 45 ++- .../kbn-apm-synthtrace/src/lib/apm/service.ts | 21 +- .../src/lib/stream_processor.ts | 5 +- .../kbn-apm-synthtrace/src/lib/utils/dedot.ts | 1 + ...apm_events_to_elasticsearch_output.test.ts | 3 - .../test/scenarios/01_simple_trace.test.ts | 2 + .../01_simple_trace.test.ts.snap | 30 ++ .../server/collectors/management/schema.ts | 4 + .../server/collectors/management/types.ts | 1 + src/plugins/telemetry/schema/oss_plugins.json | 6 + .../critical_path/get_critical_path.test.ts | 274 ++++++++++++++++++ .../common/critical_path/get_critical_path.ts | 134 +++++++++ .../plugins/apm/common/critical_path/types.ts | 19 ++ .../index.tsx | 1 + ...dependency_operation_detail_trace_list.tsx | 1 + .../index.tsx | 9 + .../error_group_details/detail_view/index.tsx | 1 + .../app/service_map/popover/edge_contents.tsx | 1 + .../components/app/trace_explorer/index.tsx | 9 + .../components/app/trace_overview/index.tsx | 1 + .../distribution/index.tsx | 12 +- .../waterfall_with_summary/index.tsx | 6 + .../transaction_tabs.tsx | 12 + .../waterfall_container/index.tsx | 53 +++- .../waterfall/accordion_waterfall.tsx | 39 ++- .../waterfall_container/waterfall/index.tsx | 9 +- .../waterfall/waterfall_item.tsx | 47 ++- .../waterfall_container.stories.tsx | 11 + .../components/routing/home/dependencies.tsx | 2 + .../public/components/routing/home/index.tsx | 2 + .../routing/service_detail/index.tsx | 6 + ...e_critical_path_feature_enabled_setting.ts | 18 ++ x-pack/plugins/observability/common/index.ts | 1 + .../observability/common/ui_settings_keys.ts | 1 + .../observability/server/ui_settings.ts | 18 ++ 38 files changed, 779 insertions(+), 39 deletions(-) create mode 100644 x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts create mode 100644 x-pack/plugins/apm/common/critical_path/get_critical_path.ts create mode 100644 x-pack/plugins/apm/common/critical_path/types.ts create mode 100644 x-pack/plugins/apm/public/hooks/use_critical_path_feature_enabled_setting.ts diff --git a/packages/kbn-apm-synthtrace/index.ts b/packages/kbn-apm-synthtrace/index.ts index 170c5ed6206c1..1ff59bdd7d16a 100644 --- a/packages/kbn-apm-synthtrace/index.ts +++ b/packages/kbn-apm-synthtrace/index.ts @@ -8,6 +8,7 @@ export { timerange } from './src/lib/timerange'; export { apm } from './src/lib/apm'; +export { dedot } from './src/lib/utils/dedot'; export { stackMonitoring } from './src/lib/stack_monitoring'; export { observer } from './src/lib/agent_config'; export { cleanWriteTargets } from './src/lib/utils/clean_write_targets'; diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/apm_error.ts b/packages/kbn-apm-synthtrace/src/lib/apm/apm_error.ts index 334c0f296851d..216397f1e1b40 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/apm_error.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/apm_error.ts @@ -27,4 +27,10 @@ export class ApmError extends Serializable { ); return [data]; } + + timestamp(value: number) { + const ret = super.timestamp(value); + this.fields['timestamp.us'] = value * 1000; + return ret; + } } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/base_span.ts b/packages/kbn-apm-synthtrace/src/lib/apm/base_span.ts index 0cfe5940405a2..b74604c39c242 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/base_span.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/base_span.ts @@ -88,4 +88,10 @@ export class BaseSpan extends Serializable { }); return this; } + + override timestamp(timestamp: number) { + const ret = super.timestamp(timestamp); + this.fields['timestamp.us'] = timestamp * 1000; + return ret; + } } diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts b/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts index f69e54b3e300b..9a7fff73b64a7 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/instance.ts @@ -20,24 +20,49 @@ export type SpanParams = { } & ApmFields; export class Instance extends Entity { - transaction({ - transactionName, - transactionType = 'request', - }: { - transactionName: string; - transactionType?: string; - }) { + transaction( + ...options: + | [{ transactionName: string; transactionType?: string }] + | [string] + | [string, string] + ) { + let transactionName: string; + let transactionType: string | undefined; + if (options.length === 2) { + transactionName = options[0]; + transactionType = options[1]; + } else if (typeof options[0] === 'string') { + transactionName = options[0]; + } else { + transactionName = options[0].transactionName; + transactionType = options[0].transactionType; + } + return new Transaction({ ...this.fields, 'transaction.name': transactionName, - 'transaction.type': transactionType, + 'transaction.type': transactionType || 'request', }); } - span({ spanName, spanType, spanSubtype, ...apmFields }: SpanParams) { + span(...options: [string, string] | [string, string, string] | [SpanParams]) { + let spanName: string; + let spanType: string; + let spanSubtype: string; + let fields: ApmFields; + + if (options.length === 3 || options.length === 2) { + spanName = options[0]; + spanType = options[1]; + spanSubtype = options[2] || 'unknown'; + fields = {}; + } else { + ({ spanName, spanType, spanSubtype = 'unknown', ...fields } = options[0]); + } + return new Span({ ...this.fields, - ...apmFields, + ...fields, 'span.name': spanName, 'span.type': spanType, 'span.subtype': spanSubtype, diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/service.ts b/packages/kbn-apm-synthtrace/src/lib/apm/service.ts index 0939535a87135..1925c0cdcfd13 100644 --- a/packages/kbn-apm-synthtrace/src/lib/apm/service.ts +++ b/packages/kbn-apm-synthtrace/src/lib/apm/service.ts @@ -20,17 +20,18 @@ export class Service extends Entity { } } -export function service({ - name, - environment, - agentName, -}: { - name: string; - environment: string; - agentName: string; -}) { +export function service(name: string, environment: string, agentName: string): Service; + +export function service(options: { name: string; environment: string; agentName: string }): Service; + +export function service( + ...args: [{ name: string; environment: string; agentName: string }] | [string, string, string] +) { + const [serviceName, environment, agentName] = + args.length === 1 ? [args[0].name, args[0].environment, args[0].agentName] : args; + return new Service({ - 'service.name': name, + 'service.name': serviceName, 'service.environment': environment, 'agent.name': agentName, }); diff --git a/packages/kbn-apm-synthtrace/src/lib/stream_processor.ts b/packages/kbn-apm-synthtrace/src/lib/stream_processor.ts index 0d7d0ff5dfa51..84f0dbb0a62bf 100644 --- a/packages/kbn-apm-synthtrace/src/lib/stream_processor.ts +++ b/packages/kbn-apm-synthtrace/src/lib/stream_processor.ts @@ -187,10 +187,7 @@ export class StreamProcessor { document['service.node.name'] = document['service.node.name'] || document['container.id'] || document['host.name']; document['ecs.version'] = '1.4'; - // TODO this non standard field should not be enriched here - if (document['processor.event'] !== 'metric') { - document['timestamp.us'] = document['@timestamp']! * 1000; - } + return document; } diff --git a/packages/kbn-apm-synthtrace/src/lib/utils/dedot.ts b/packages/kbn-apm-synthtrace/src/lib/utils/dedot.ts index 4f38a7025f3b5..5d0f57fb5840b 100644 --- a/packages/kbn-apm-synthtrace/src/lib/utils/dedot.ts +++ b/packages/kbn-apm-synthtrace/src/lib/utils/dedot.ts @@ -13,4 +13,5 @@ export function dedot(source: Record, target: Record) const val = source[key as keyof typeof source]; set(target, key, val); } + return target; } diff --git a/packages/kbn-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts b/packages/kbn-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts index afafcc0c49665..edb20c4768ee5 100644 --- a/packages/kbn-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/apm_events_to_elasticsearch_output.test.ts @@ -59,9 +59,6 @@ describe('output apm events to elasticsearch', () => { "name": "instance-a", }, }, - "timestamp": Object { - "us": 1609455600000000, - }, } `); }); diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts b/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts index a278997ecdf73..a14ae076e8186 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/01_simple_trace.test.ts @@ -84,6 +84,7 @@ describe('simple trace', () => { 'service.environment': 'production', 'service.name': 'opbeans-java', 'service.node.name': 'instance-1', + 'timestamp.us': 1609459200000000, 'trace.id': '00000000000000000000000000000241', 'transaction.duration.us': 1000000, 'transaction.id': '0000000000000240', @@ -113,6 +114,7 @@ describe('simple trace', () => { 'span.name': 'GET apm-*/_search', 'span.subtype': 'elasticsearch', 'span.type': 'db', + 'timestamp.us': 1609459200050000, 'trace.id': '00000000000000000000000000000301', 'transaction.id': '0000000000000300', }); diff --git a/packages/kbn-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap b/packages/kbn-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap index 1a5fca39e9fd9..8b3306d2d3a4b 100644 --- a/packages/kbn-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap +++ b/packages/kbn-apm-synthtrace/src/test/scenarios/__snapshots__/01_simple_trace.test.ts.snap @@ -13,6 +13,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459200000000, "trace.id": "00000000000000000000000000000001", "transaction.duration.us": 1000000, "transaction.id": "0000000000000000", @@ -37,6 +38,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459200050000, "trace.id": "00000000000000000000000000000001", "transaction.id": "0000000000000000", }, @@ -51,6 +53,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459260000000, "trace.id": "00000000000000000000000000000005", "transaction.duration.us": 1000000, "transaction.id": "0000000000000004", @@ -75,6 +78,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459260050000, "trace.id": "00000000000000000000000000000005", "transaction.id": "0000000000000004", }, @@ -89,6 +93,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459320000000, "trace.id": "00000000000000000000000000000009", "transaction.duration.us": 1000000, "transaction.id": "0000000000000008", @@ -113,6 +118,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459320050000, "trace.id": "00000000000000000000000000000009", "transaction.id": "0000000000000008", }, @@ -127,6 +133,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459380000000, "trace.id": "00000000000000000000000000000013", "transaction.duration.us": 1000000, "transaction.id": "0000000000000012", @@ -151,6 +158,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459380050000, "trace.id": "00000000000000000000000000000013", "transaction.id": "0000000000000012", }, @@ -165,6 +173,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459440000000, "trace.id": "00000000000000000000000000000017", "transaction.duration.us": 1000000, "transaction.id": "0000000000000016", @@ -189,6 +198,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459440050000, "trace.id": "00000000000000000000000000000017", "transaction.id": "0000000000000016", }, @@ -203,6 +213,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459500000000, "trace.id": "00000000000000000000000000000021", "transaction.duration.us": 1000000, "transaction.id": "0000000000000020", @@ -227,6 +238,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459500050000, "trace.id": "00000000000000000000000000000021", "transaction.id": "0000000000000020", }, @@ -241,6 +253,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459560000000, "trace.id": "00000000000000000000000000000025", "transaction.duration.us": 1000000, "transaction.id": "0000000000000024", @@ -265,6 +278,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459560050000, "trace.id": "00000000000000000000000000000025", "transaction.id": "0000000000000024", }, @@ -279,6 +293,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459620000000, "trace.id": "00000000000000000000000000000029", "transaction.duration.us": 1000000, "transaction.id": "0000000000000028", @@ -303,6 +318,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459620050000, "trace.id": "00000000000000000000000000000029", "transaction.id": "0000000000000028", }, @@ -317,6 +333,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459680000000, "trace.id": "00000000000000000000000000000033", "transaction.duration.us": 1000000, "transaction.id": "0000000000000032", @@ -341,6 +358,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459680050000, "trace.id": "00000000000000000000000000000033", "transaction.id": "0000000000000032", }, @@ -355,6 +373,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459740000000, "trace.id": "00000000000000000000000000000037", "transaction.duration.us": 1000000, "transaction.id": "0000000000000036", @@ -379,6 +398,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459740050000, "trace.id": "00000000000000000000000000000037", "transaction.id": "0000000000000036", }, @@ -393,6 +413,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459800000000, "trace.id": "00000000000000000000000000000041", "transaction.duration.us": 1000000, "transaction.id": "0000000000000040", @@ -417,6 +438,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459800050000, "trace.id": "00000000000000000000000000000041", "transaction.id": "0000000000000040", }, @@ -431,6 +453,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459860000000, "trace.id": "00000000000000000000000000000045", "transaction.duration.us": 1000000, "transaction.id": "0000000000000044", @@ -455,6 +478,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459860050000, "trace.id": "00000000000000000000000000000045", "transaction.id": "0000000000000044", }, @@ -469,6 +493,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459920000000, "trace.id": "00000000000000000000000000000049", "transaction.duration.us": 1000000, "transaction.id": "0000000000000048", @@ -493,6 +518,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459920050000, "trace.id": "00000000000000000000000000000049", "transaction.id": "0000000000000048", }, @@ -507,6 +533,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609459980000000, "trace.id": "00000000000000000000000000000053", "transaction.duration.us": 1000000, "transaction.id": "0000000000000052", @@ -531,6 +558,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609459980050000, "trace.id": "00000000000000000000000000000053", "transaction.id": "0000000000000052", }, @@ -545,6 +573,7 @@ Array [ "service.environment": "production", "service.name": "opbeans-java", "service.node.name": "instance-1", + "timestamp.us": 1609460040000000, "trace.id": "00000000000000000000000000000057", "transaction.duration.us": 1000000, "transaction.id": "0000000000000056", @@ -569,6 +598,7 @@ Array [ "span.name": "GET apm-*/_search", "span.subtype": "elasticsearch", "span.type": "db", + "timestamp.us": 1609460040050000, "trace.id": "00000000000000000000000000000057", "transaction.id": "0000000000000056", }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 41df488839358..22b2a5de751f5 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -546,6 +546,10 @@ export const stackManagementSchema: MakeSchemaFrom = { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, }, + 'observability:apmEnableCriticalPath': { + type: 'boolean', + _meta: { description: 'Non-default value of setting.' }, + }, 'observability:enableInfrastructureHostsView': { type: 'boolean', _meta: { description: 'Non-default value of setting.' }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts index 2bd59dc69084f..6957323103545 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -147,6 +147,7 @@ export interface UsageStats { 'observability:apmServiceGroupMaxNumberOfServices': number; 'observability:apmServiceInventoryOptimizedSorting': boolean; 'observability:apmTraceExplorerTab': boolean; + 'observability:apmEnableCriticalPath': boolean; 'securitySolution:enableGroupedNav': boolean; 'securitySolution:showRelatedIntegrations': boolean; 'visualization:visualize:legacyGaugeChartsLibrary': boolean; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 1a97586dffa62..14db4bca74d4a 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -8864,6 +8864,12 @@ "description": "Non-default value of setting." } }, + "observability:apmEnableCriticalPath": { + "type": "boolean", + "_meta": { + "description": "Non-default value of setting." + } + }, "observability:enableInfrastructureHostsView": { "type": "boolean", "_meta": { diff --git a/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts b/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts new file mode 100644 index 0000000000000..38d1b0a3da1ca --- /dev/null +++ b/x-pack/plugins/apm/common/critical_path/get_critical_path.test.ts @@ -0,0 +1,274 @@ +/* + * Copyright 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 { apm, ApmFields, dedot } from '@kbn/apm-synthtrace'; +import { getWaterfall } from '../../public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; +import { Span } from '../../typings/es_schemas/ui/span'; +import { Transaction } from '../../typings/es_schemas/ui/transaction'; +import { getCriticalPath } from './get_critical_path'; + +describe('getCriticalPath', () => { + function getCriticalPathFromEvents(events: ApmFields[]) { + const waterfall = getWaterfall( + { + traceDocs: events.map( + (event) => dedot(event, {}) as Transaction | Span + ), + errorDocs: [], + exceedsMax: false, + linkedChildrenOfSpanCountBySpanId: {}, + }, + events[0]['transaction.id']! + ); + + return { + waterfall, + criticalPath: getCriticalPath(waterfall), + }; + } + it('adds the only active span to the critical path', () => { + const service = apm.service('a', 'development', 'java').instance('a'); + + const { + criticalPath: { segments }, + waterfall, + } = getCriticalPathFromEvents( + service + .transaction('/service-a') + .timestamp(1) + .duration(100) + .children( + service.span('foo', 'external', 'db').duration(100).timestamp(1) + ) + .serialize() + ); + + expect(segments).toEqual([ + { self: false, duration: 100000, item: waterfall.items[0], offset: 0 }, + { self: false, duration: 100000, item: waterfall.items[1], offset: 0 }, + { self: true, duration: 100000, item: waterfall.items[1], offset: 0 }, + ]); + }); + + it('adds the span that ended last', () => { + const service = apm.service('a', 'development', 'java').instance('a'); + + const { + criticalPath: { segments }, + waterfall, + } = getCriticalPathFromEvents( + service + .transaction('/service-a') + .timestamp(1) + .duration(100) + .children( + service.span('foo', 'external', 'db').duration(99).timestamp(1), + service.span('bar', 'external', 'db').duration(100).timestamp(1) + ) + .serialize() + ); + + const longerSpan = waterfall.items.find( + (item) => (item.doc as Span).span?.name === 'bar' + ); + + expect(segments).toEqual([ + { self: false, duration: 100000, item: waterfall.items[0], offset: 0 }, + { + self: false, + duration: 100000, + item: longerSpan, + offset: 0, + }, + { self: true, duration: 100000, item: longerSpan, offset: 0 }, + ]); + }); + + it('adds segment for uninstrumented gaps in the parent', () => { + const service = apm.service('a', 'development', 'java').instance('a'); + + const { + criticalPath: { segments }, + waterfall, + } = getCriticalPathFromEvents( + service + .transaction('/service-a') + .timestamp(1) + .duration(100) + .children( + service.span('foo', 'external', 'db').duration(50).timestamp(11) + ) + .serialize() + ); + + expect( + segments.map((segment) => ({ + self: segment.self, + duration: segment.duration, + id: segment.item.id, + offset: segment.offset, + })) + ).toEqual([ + { self: false, duration: 100000, id: waterfall.items[0].id, offset: 0 }, + { + self: true, + duration: 40000, + id: waterfall.items[0].id, + offset: 60000, + }, + { + self: false, + duration: 50000, + id: waterfall.items[1].id, + offset: 10000, + }, + { + self: true, + duration: 50000, + id: waterfall.items[1].id, + offset: 10000, + }, + { + self: true, + duration: 10000, + offset: 0, + id: waterfall.items[0].id, + }, + ]); + }); + + it('only considers a single child to be active at the same time', () => { + const service = apm.service('a', 'development', 'java').instance('a'); + + const { + criticalPath: { segments }, + waterfall, + } = getCriticalPathFromEvents( + service + .transaction('s1') + .timestamp(1) + .duration(100) + .children( + service.span('s2', 'external', 'db').duration(1).timestamp(1), + service.span('s3', 'external', 'db').duration(1).timestamp(2), + service.span('s4', 'external', 'db').duration(98).timestamp(3), + service + .span('s5', 'external', 'db') + .duration(98) + .timestamp(1) + .children( + service.span('s6', 'external', 'db').duration(30).timestamp(5), + service.span('s7', 'external', 'db').duration(30).timestamp(35) + ) + ) + .serialize() + ); + + const [_s1, s2, _s5, _s6, _s7, s3, s4] = waterfall.items; + + expect( + segments + .map((segment) => ({ + self: segment.self, + duration: segment.duration, + id: segment.item.id, + offset: segment.offset, + })) + .filter((segment) => segment.self) + .map((segment) => segment.id) + ).toEqual([s4.id, s3.id, s2.id]); + }); + + // https://www.uber.com/en-NL/blog/crisp-critical-path-analysis-for-microservice-architectures/ + it('correctly returns the critical path for the CRISP example', () => { + const service = apm.service('a', 'development', 'java').instance('a'); + + const { + criticalPath: { segments }, + waterfall, + } = getCriticalPathFromEvents( + service + .transaction('s1') + .timestamp(1) + .duration(100) + .children( + service.span('s2', 'external', 'db').duration(25).timestamp(6), + service + .span('s3', 'external', 'db') + .duration(50) + .timestamp(41) + .children( + service.span('s4', 'external', 'db').duration(20).timestamp(61), + service.span('s5', 'external', 'db').duration(30).timestamp(51) + ) + ) + .serialize() + ); + + const [s1, s2, s3, s5, _s4] = waterfall.items; + + expect( + segments + .map((segment) => ({ + self: segment.self, + duration: segment.duration, + id: segment.item.id, + offset: segment.offset, + })) + .filter((segment) => segment.self) + ).toEqual([ + // T9-T10 + { + self: true, + duration: 10000, + id: s1.id, + offset: 90000, + }, + // T8-T9 + { + self: true, + duration: 10000, + id: s3.id, + offset: 80000, + }, + // T5-T8 + { + self: true, + duration: s5.duration, + id: s5.id, + offset: s5.offset, + }, + // T4-T5 + { + self: true, + duration: 10000, + id: s3.id, + offset: 40000, + }, + // T3-T4 + { + self: true, + duration: 10000, + id: s1.id, + offset: 30000, + }, + // T2-T3 + { + self: true, + duration: 25000, + id: s2.id, + offset: 5000, + }, + // T1-T2 + { + duration: 5000, + id: s1.id, + offset: 0, + self: true, + }, + ]); + }); +}); diff --git a/x-pack/plugins/apm/common/critical_path/get_critical_path.ts b/x-pack/plugins/apm/common/critical_path/get_critical_path.ts new file mode 100644 index 0000000000000..c517548bf3d1f --- /dev/null +++ b/x-pack/plugins/apm/common/critical_path/get_critical_path.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 type { + IWaterfall, + IWaterfallSpanOrTransaction, +} from '../../public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; +import { CriticalPath, CriticalPathSegment } from './types'; + +export function getCriticalPath(waterfall: IWaterfall): CriticalPath { + const segments: CriticalPathSegment[] = []; + + function scan({ + item, + start, + end, + }: { + item: IWaterfallSpanOrTransaction; + start: number; + end: number; + }): void { + segments.push({ + self: false, + duration: end - start, + item, + offset: start, + }); + const directChildren = waterfall.childrenByParentId[item.id]; + + if (directChildren && directChildren.length > 0) { + // We iterate over all the item's direct children. The one that + // ends last is the first item in the array. + const orderedChildren = directChildren.concat().sort((a, b) => { + const endTimeA = a.offset + a.skew + a.duration; + const endTimeB = b.offset + b.skew + b.duration; + return endTimeB - endTimeA; + }); + + // For each point in time, determine what child is on the critical path. + // We start scanning at the end. Once we've decided what the child on the + // critical path is, scan its children, from the start time of that span + // until the end. The next scan time is the start time of the child that was + // on the critical path. + let scanTime = end; + + orderedChildren.forEach((child) => { + const normalizedChildStart = Math.max(child.offset + child.skew, start); + const childEnd = child.offset + child.skew + child.duration; + + // if a span ends before the current scan time, use the current + // scan time as when the child ended. We don't want to scan further + // than the scan time. This prevents overlap in the critical path. + const normalizedChildEnd = Math.min(childEnd, scanTime); + + const isOnCriticalPath = !( + // A span/tx is NOT on the critical path if: + // - The start time is equal to or greater than the current scan time. + // Otherwise, spans that started at the same time will all contribute to + // the critical path, but we only want one to contribute. + // - The span/tx ends before the start of the initial scan period. + // - The span ends _after_ the current scan time. + + ( + normalizedChildStart >= scanTime || + normalizedChildEnd < start || + childEnd > scanTime + ) + ); + + if (!isOnCriticalPath) { + return; + } + + if (normalizedChildEnd < scanTime - 1000) { + // This span is on the critical path, but it ended before the scan time. + // This means that there is a gap, so we add a segment to the critical path + // for the _parent_. There's a slight offset because we don't want really small + // segments that can be reasonably attributed to clock skew. + segments.push({ + item, + duration: scanTime - normalizedChildEnd, + offset: normalizedChildEnd, + self: true, + }); + } + + // scan this child for the period we're considering it to be on the critical path + scan({ + start: normalizedChildStart, + end: childEnd, + item: child, + }); + + // set the scan time to the start of the span, and scan the next child + scanTime = normalizedChildStart; + }); + + // there's an unattributed gap at the start, so add a segment for the parent as well + if (scanTime > start) { + segments.push({ + item, + offset: start, + duration: scanTime - start, + self: true, + }); + } + } else { + // for the entire scan period, add this item to the critical path + segments.push({ + item, + offset: start, + duration: end - start, + self: true, + }); + } + } + + if (waterfall.entryWaterfallTransaction) { + const start = + waterfall.entryWaterfallTransaction.skew + + waterfall.entryWaterfallTransaction.offset; + scan({ + item: waterfall.entryWaterfallTransaction, + start, + end: start + waterfall.entryWaterfallTransaction.duration, + }); + } + + return { segments }; +} diff --git a/x-pack/plugins/apm/common/critical_path/types.ts b/x-pack/plugins/apm/common/critical_path/types.ts new file mode 100644 index 0000000000000..56f3db04e866f --- /dev/null +++ b/x-pack/plugins/apm/common/critical_path/types.ts @@ -0,0 +1,19 @@ +/* + * Copyright 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 { IWaterfallSpanOrTransaction } from '../../public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_helpers/waterfall_helpers'; + +export interface CriticalPathSegment { + item: IWaterfallSpanOrTransaction; + offset: number; + duration: number; + self: boolean; +} + +export interface CriticalPath { + segments: CriticalPathSegment[]; +} diff --git a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx index 4cfb1a3ba9c06..792f3f0aece25 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_detail_operations/dependency_detail_operations_list/index.tsx @@ -41,6 +41,7 @@ function OperationLink({ spanName }: { spanName: string }) { {...query} spanName={spanName} detailTab={TransactionTab.timeline} + showCriticalPath={false} /> } /> diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx index 0cea6233edf95..4dde4af56ccf3 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/dependency_operation_detail_trace_list.tsx @@ -101,6 +101,7 @@ export function DependencyOperationDetailTraceList({ traceId, transactionId, transactionType, + showCriticalPath: false, }, }) : router.link('/link-to/trace/{traceId}', { diff --git a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx index 742f6e27b9be3..9acd060f5fe68 100644 --- a/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/dependency_operation_detail_view/index.tsx @@ -48,6 +48,7 @@ export function DependencyOperationDetailView() { detailTab, sortField = '@timestamp', sortDirection = 'desc', + showCriticalPath, }, } = useApmParams('/dependencies/operation'); @@ -199,6 +200,14 @@ export function DependencyOperationDetailView() { waterfallItemId={waterfallItemId} detailTab={detailTab} selectedSample={selectedSample || null} + showCriticalPath={showCriticalPath} + onShowCriticalPathChange={(nextShowCriticalPath) => { + push(history, { + query: { + showCriticalPath: nextShowCriticalPath ? 'true' : 'false', + }, + }); + }} /> diff --git a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx index 220f276f62152..4b41099240f54 100644 --- a/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx +++ b/x-pack/plugins/apm/public/components/app/error_group_details/detail_view/index.tsx @@ -99,6 +99,7 @@ export function DetailView({ errorGroup, urlParams, kuery }: Props) { const traceExplorerLink = router.link('/traces/explorer', { query: { ...query, + showCriticalPath: false, query: `${ERROR_GROUP_ID}:${groupId}`, type: TraceSearchType.kql, traceId: '', diff --git a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx index 4bcc8bb8ca53b..add6c48fef8e5 100644 --- a/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx +++ b/x-pack/plugins/apm/public/components/app/service_map/popover/edge_contents.tsx @@ -58,6 +58,7 @@ export function EdgeContents({ elementData }: ContentsProps) { traceId: '', transactionId: '', detailTab: TransactionTab.timeline, + showCriticalPath: false, }, }); diff --git a/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx index cf3306ad0d376..1b6c35adffc04 100644 --- a/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_explorer/index.tsx @@ -37,6 +37,7 @@ export function TraceExplorer() { transactionId, waterfallItemId, detailTab, + showCriticalPath, }, } = useApmParams('/traces/explorer'); @@ -158,6 +159,14 @@ export function TraceExplorer() { waterfallFetchResult.waterfall.entryWaterfallTransaction?.doc .service.name } + showCriticalPath={showCriticalPath} + onShowCriticalPathChange={(nextShowCriticalPath) => { + push(history, { + query: { + showCriticalPath: nextShowCriticalPath ? 'true' : 'false', + }, + }); + }} /> diff --git a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx index 1093a74f6bc2f..4c176527d49f6 100644 --- a/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/trace_overview/index.tsx @@ -44,6 +44,7 @@ export function TraceOverview({ children }: { children: React.ReactElement }) { traceId: '', transactionId: '', detailTab: TransactionTab.timeline, + showCriticalPath: false, }, }); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx index 0cb5fa49117c5..72bf5a048e9e7 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/distribution/index.tsx @@ -21,7 +21,7 @@ import { useApmParams } from '../../../../hooks/use_apm_params'; import { useTimeRange } from '../../../../hooks/use_time_range'; import { DurationDistributionChartWithScrubber } from '../../../shared/charts/duration_distribution_chart_with_scrubber'; import { HeightRetainer } from '../../../shared/height_retainer'; -import { fromQuery, toQuery } from '../../../shared/links/url_helpers'; +import { fromQuery, push, toQuery } from '../../../shared/links/url_helpers'; import { TransactionTab } from '../waterfall_with_summary/transaction_tabs'; import { useTransactionDistributionChartData } from './use_transaction_distribution_chart_data'; import { TraceSamplesFetchResult } from '../../../../hooks/use_transaction_trace_samples_fetcher'; @@ -43,7 +43,7 @@ export function TransactionDistribution({ const { traceId, transactionId } = urlParams; const { - query: { rangeFrom, rangeTo }, + query: { rangeFrom, rangeTo, showCriticalPath }, } = useApmParams('/services/{serviceName}/transactions/view'); const { start, end } = useTimeRange({ rangeFrom, rangeTo }); @@ -119,6 +119,14 @@ export function TransactionDistribution({ waterfallFetchResult={waterfallFetchResult} traceSamplesFetchStatus={traceSamplesFetchResult.status} traceSamples={traceSamplesFetchResult.data?.traceSamples} + showCriticalPath={showCriticalPath} + onShowCriticalPathChange={(nextShowCriticalPath) => { + push(history, { + query: { + showCriticalPath: nextShowCriticalPath ? 'true' : 'false', + }, + }); + }} />
diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx index 537ada31df0e5..4ef0bb54319a0 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/index.tsx @@ -34,6 +34,8 @@ interface Props { serviceName?: string; waterfallItemId?: string; detailTab?: TransactionTab; + showCriticalPath: boolean; + onShowCriticalPathChange: (showCriticalPath: boolean) => void; selectedSample?: TSample | null; } @@ -47,6 +49,8 @@ export function WaterfallWithSummary({ serviceName, waterfallItemId, detailTab, + showCriticalPath, + onShowCriticalPathChange, selectedSample, }: Props) { const [sampleActivePage, setSampleActivePage] = useState(0); @@ -171,6 +175,8 @@ export function WaterfallWithSummary({ onTabClick={onTabClick} waterfall={waterfallFetchResult.waterfall} isLoading={isLoading} + showCriticalPath={showCriticalPath} + onShowCriticalPathChange={onShowCriticalPathChange} /> ); diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx index e3fdaeea24846..85e8b36942936 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/transaction_tabs.tsx @@ -22,6 +22,8 @@ interface Props { serviceName?: string; waterfallItemId?: string; onTabClick: (tab: TransactionTab) => void; + showCriticalPath: boolean; + onShowCriticalPathChange: (showCriticalPath: boolean) => void; } export function TransactionTabs({ @@ -32,6 +34,8 @@ export function TransactionTabs({ waterfallItemId, serviceName, onTabClick, + showCriticalPath, + onShowCriticalPathChange, }: Props) { const tabs = [timelineTab, metadataTab, logsTab]; const currentTab = tabs.find(({ key }) => key === detailTab) ?? timelineTab; @@ -64,6 +68,8 @@ export function TransactionTabs({ serviceName={serviceName} waterfall={waterfall} transaction={transaction} + showCriticalPath={showCriticalPath} + onShowCriticalPathChange={onShowCriticalPathChange} /> )} @@ -104,16 +110,22 @@ function TimelineTabContent({ waterfall, waterfallItemId, serviceName, + showCriticalPath, + onShowCriticalPathChange, }: { waterfallItemId?: string; serviceName?: string; waterfall: IWaterfall; + showCriticalPath: boolean; + onShowCriticalPathChange: (showCriticalPath: boolean) => void; }) { return ( ); } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx index 2dd74aeae3eef..b9f149c32e491 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/index.tsx @@ -5,26 +5,36 @@ * 2.0. */ -import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSwitch } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { keyBy } from 'lodash'; +import React from 'react'; +import { useCriticalPathFeatureEnabledSetting } from '../../../../../hooks/use_critical_path_feature_enabled_setting'; +import { TechnicalPreviewBadge } from '../../../../shared/technical_preview_badge'; +import { Waterfall } from './waterfall'; import { IWaterfall, WaterfallLegendType, } from './waterfall/waterfall_helpers/waterfall_helpers'; -import { Waterfall } from './waterfall'; import { WaterfallLegends } from './waterfall_legends'; interface Props { waterfallItemId?: string; serviceName?: string; waterfall: IWaterfall; + showCriticalPath: boolean; + onShowCriticalPathChange: (showCriticalPath: boolean) => void; } export function WaterfallContainer({ serviceName, waterfallItemId, waterfall, + showCriticalPath, + onShowCriticalPathChange, }: Props) { + const isCriticalPathFeatureEnabled = useCriticalPathFeatureEnabledSetting(); + if (!waterfall) { return null; } @@ -74,9 +84,40 @@ export function WaterfallContainer({ }); return ( -
- - -
+ + {isCriticalPathFeatureEnabled ? ( + + + + {i18n.translate('xpack.apm.waterfall.showCriticalPath', { + defaultMessage: 'Show critical path', + })} + + + + + + } + checked={showCriticalPath} + onChange={(event) => { + onShowCriticalPathChange(event.target.checked); + }} + /> +
+ ) : null} + + + + + + + ); } diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx index c0932e041de1a..3b996bfb3cdd1 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/accordion_waterfall.tsx @@ -15,12 +15,16 @@ import { } from '@elastic/eui'; import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; +import { groupBy } from 'lodash'; +import { transparentize } from 'polished'; import { Margins } from '../../../../../shared/charts/timeline'; import { IWaterfall, IWaterfallSpanOrTransaction, } from './waterfall_helpers/waterfall_helpers'; import { WaterfallItem } from './waterfall_item'; +import { getCriticalPath } from '../../../../../../../common/critical_path/get_critical_path'; +import { useTheme } from '../../../../../../hooks/use_theme'; interface AccordionWaterfallProps { isOpen: boolean; @@ -32,6 +36,7 @@ interface AccordionWaterfallProps { waterfall: IWaterfall; timelineMargins: Margins; onClickWaterfallItem: (item: IWaterfallSpanOrTransaction) => void; + showCriticalPath: boolean; } const ACCORDION_HEIGHT = '48px'; @@ -85,8 +90,11 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { setMaxLevel, timelineMargins, onClickWaterfallItem, + showCriticalPath, } = props; + const theme = useTheme(); + const [isOpen, setIsOpen] = useState(props.isOpen); const [nextLevel] = useState(level + 1); @@ -94,7 +102,26 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { setMaxLevel(nextLevel); }, [nextLevel, setMaxLevel]); - const children = waterfall.childrenByParentId[item.id] || []; + let children = waterfall.childrenByParentId[item.id] || []; + + const criticalPath = showCriticalPath + ? getCriticalPath(waterfall) + : undefined; + + const criticalPathSegmentsById = groupBy( + criticalPath?.segments, + (segment) => segment.item.id + ); + + let displayedColor = item.color; + + if (showCriticalPath) { + children = children.filter( + (child) => criticalPathSegmentsById[child.id]?.length + ); + displayedColor = transparentize(0.5, item.color); + } + const errorCount = waterfall.getErrorCount(item.id); // To indent the items creating the parent/child tree @@ -131,7 +158,7 @@ export function AccordionWaterfall(props: AccordionWaterfallProps) { { onClickWaterfallItem(item); }} + segments={criticalPathSegmentsById[item.id] + ?.filter((segment) => segment.self) + .map((segment) => ({ + color: theme.eui.euiColorAccent, + left: + (segment.offset - item.offset - item.skew) / item.duration, + width: segment.duration / item.duration, + }))} /> diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/index.tsx index 04c3734eebaff..d117cb2d982c1 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/index.tsx @@ -52,8 +52,14 @@ const WaterfallItemsContainer = euiStyled.div` interface Props { waterfallItemId?: string; waterfall: IWaterfall; + showCriticalPath: boolean; } -export function Waterfall({ waterfall, waterfallItemId }: Props) { + +export function Waterfall({ + waterfall, + waterfallItemId, + showCriticalPath, +}: Props) { const history = useHistory(); const [isAccordionOpen, setIsAccordionOpen] = useState(true); const itemContainerHeight = 58; // TODO: This is a nasty way to calculate the height of the svg element. A better approach should be found @@ -119,6 +125,7 @@ export function Waterfall({ waterfall, waterfallItemId }: Props) { onClickWaterfallItem={(item: IWaterfallItem) => toggleFlyout({ history, item }) } + showCriticalPath={showCriticalPath} /> )} diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx index 0f03b430152f0..8057ee3a32b7d 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall/waterfall_item.tsx @@ -83,6 +83,31 @@ const ItemText = euiStyled.span` } `; +const CriticalPathItemBar = euiStyled.div` + box-sizing: border-box; + position: relative; + height: ${({ theme }) => theme.eui.euiSizeS}; + top : ${({ theme }) => theme.eui.euiSizeS}; + min-width: 2px; + background-color: transparent; + display: flex; + flex-direction: row; +`; + +const CriticalPathItemSegment = euiStyled.div<{ + left: number; + width: number; + color: string; +}>` + box-sizing: border-box; + position: absolute; + height: ${({ theme }) => theme.eui.euiSizeS}; + left: ${(props) => props.left * 100}%; + width: ${(props) => props.width * 100}%; + min-width: 2px; + background-color: ${(props) => props.color}; +`; + interface IWaterfallItemProps { timelineMargins: Margins; totalDuration?: number; @@ -92,6 +117,11 @@ interface IWaterfallItemProps { isSelected: boolean; errorCount: number; marginLeftLevel: number; + segments?: Array<{ + left: number; + width: number; + color: string; + }>; onClick: () => unknown; } @@ -194,6 +224,7 @@ export function WaterfallItem({ errorCount, marginLeftLevel, onClick, + segments, }: IWaterfallItemProps) { const [widthFactor, setWidthFactor] = useState(1); const waterfallItemRef: React.RefObject = useRef(null); @@ -217,7 +248,9 @@ export function WaterfallItem({ 100; const isCompositeSpan = item.docType === 'span' && item.doc.span.composite; + const itemBarStyle = getItemBarStyle(item, color, width, left); + const isServerlessColdstart = item.docType === 'transaction' && item.doc.faas?.coldstart; @@ -237,7 +270,19 @@ export function WaterfallItem({ style={itemBarStyle} color={isCompositeSpan ? 'transparent' : color} type={item.docType} - /> + > + {segments?.length ? ( + + {segments?.map((segment) => ( + + ))} + + ) : null} + diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx index a10518ab58e4c..0a08dcb166048 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/waterfall_with_summary/waterfall_container/waterfall_container.stories.tsx @@ -8,6 +8,7 @@ import { Meta, Story } from '@storybook/react'; import React, { ComponentProps } from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { noop } from 'lodash'; import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { WaterfallContainer } from '.'; import { getWaterfall } from './waterfall/waterfall_helpers/waterfall_helpers'; @@ -59,6 +60,8 @@ export const Example: Story = ({ serviceName={serviceName} waterfallItemId={waterfallItemId} waterfall={waterfall} + showCriticalPath={false} + onShowCriticalPathChange={noop} /> ); }; @@ -76,6 +79,8 @@ export const WithErrors: Story = ({ serviceName={serviceName} waterfallItemId={waterfallItemId} waterfall={waterfall} + showCriticalPath={false} + onShowCriticalPathChange={noop} /> ); }; @@ -93,6 +98,8 @@ export const ChildStartsBeforeParent: Story = ({ serviceName={serviceName} waterfallItemId={waterfallItemId} waterfall={waterfall} + showCriticalPath={false} + onShowCriticalPathChange={noop} /> ); }; @@ -110,6 +117,8 @@ export const InferredSpans: Story = ({ serviceName={serviceName} waterfallItemId={waterfallItemId} waterfall={waterfall} + showCriticalPath={false} + onShowCriticalPathChange={noop} /> ); }; @@ -127,6 +136,8 @@ export const ManyChildrenWithSameLength: Story = ({ serviceName={serviceName} waterfallItemId={waterfallItemId} waterfall={waterfall} + showCriticalPath={false} + onShowCriticalPathChange={noop} /> ); }; diff --git a/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx b/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx index 01109eedba483..7b0d93d7550e1 100644 --- a/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/dependencies.tsx @@ -79,6 +79,7 @@ export const dependencies = { t.literal(TransactionTab.metadata), t.literal(TransactionTab.logs), ]), + showCriticalPath: toBooleanRt, }), t.partial({ spanId: t.string, @@ -91,6 +92,7 @@ export const dependencies = { defaults: { query: { detailTab: TransactionTab.timeline, + showCriticalPath: '', }, }, element: , diff --git a/x-pack/plugins/apm/public/components/routing/home/index.tsx b/x-pack/plugins/apm/public/components/routing/home/index.tsx index 51a68488f9d81..36ead4f7b36c7 100644 --- a/x-pack/plugins/apm/public/components/routing/home/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/home/index.tsx @@ -213,6 +213,7 @@ export const home = { t.literal(TransactionTab.metadata), t.literal(TransactionTab.logs), ]), + showCriticalPath: toBooleanRt, }), }), defaults: { @@ -223,6 +224,7 @@ export const home = { traceId: '', transactionId: '', detailTab: TransactionTab.timeline, + showCriticalPath: '', }, }, }, diff --git a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx index fdd1aedfa0022..7cc2f7b113fe9 100644 --- a/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx +++ b/x-pack/plugins/apm/public/components/routing/service_detail/index.tsx @@ -180,6 +180,7 @@ export const serviceDetail = { t.type({ transactionName: t.string, comparisonEnabled: toBooleanRt, + showCriticalPath: toBooleanRt, }), t.partial({ traceId: t.string, @@ -188,6 +189,11 @@ export const serviceDetail = { offsetRt, ]), }), + defaults: { + query: { + showCriticalPath: '', + }, + }, }, '/services/{serviceName}/transactions': { element: , diff --git a/x-pack/plugins/apm/public/hooks/use_critical_path_feature_enabled_setting.ts b/x-pack/plugins/apm/public/hooks/use_critical_path_feature_enabled_setting.ts new file mode 100644 index 0000000000000..29c6d10ea2d69 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_critical_path_feature_enabled_setting.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { enableCriticalPath } from '@kbn/observability-plugin/common'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; + +export function useCriticalPathFeatureEnabledSetting() { + const { core } = useApmPluginContext(); + + const isCriticalPathFeatureEnabled = + core.uiSettings.get(enableCriticalPath); + + return isCriticalPathFeatureEnabled; +} diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts index 3c64645f9b1e8..123c0639f2d49 100644 --- a/x-pack/plugins/observability/common/index.ts +++ b/x-pack/plugins/observability/common/index.ts @@ -27,6 +27,7 @@ export { enableInfrastructureHostsView, enableServiceMetrics, enableAwsLambdaMetrics, + enableCriticalPath, } from './ui_settings_keys'; export { diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts index f41e492d25050..ab1684c2e5bfe 100644 --- a/x-pack/plugins/observability/common/ui_settings_keys.ts +++ b/x-pack/plugins/observability/common/ui_settings_keys.ts @@ -22,3 +22,4 @@ export const apmLabsButton = 'observability:apmLabsButton'; export const enableInfrastructureHostsView = 'observability:enableInfrastructureHostsView'; export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics'; export const enableServiceMetrics = 'observability:apmEnableServiceMetrics'; +export const enableCriticalPath = 'observability:apmEnableCriticalPath'; diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts index f272db404b7b8..e979bd6a7fb11 100644 --- a/x-pack/plugins/observability/server/ui_settings.ts +++ b/x-pack/plugins/observability/server/ui_settings.ts @@ -25,6 +25,7 @@ import { enableInfrastructureHostsView, enableServiceMetrics, enableAwsLambdaMetrics, + enableCriticalPath, } from '../common/ui_settings_keys'; const technicalPreviewLabel = i18n.translate( @@ -309,4 +310,21 @@ export const uiSettings: Record = { type: 'boolean', showInLabs: true, }, + [enableCriticalPath]: { + category: [observabilityFeatureId], + name: i18n.translate('xpack.observability.enableCriticalPath', { + defaultMessage: 'Critical path', + }), + description: i18n.translate('xpack.observability.enableCriticalPathDescription', { + defaultMessage: '{technicalPreviewLabel} Optionally display the critical path of a trace.', + values: { + technicalPreviewLabel: `[${technicalPreviewLabel}]`, + }, + }), + schema: schema.boolean(), + value: false, + requiresPageReload: true, + type: 'boolean', + showInLabs: true, + }, }; From fd1ad82e97d8672e6d004f60903a9c3159e74f5b Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Thu, 27 Oct 2022 14:05:58 -0600 Subject: [PATCH 28/28] [Security solution] Guided onboarding, alerts & cases (#143598) --- .../create/flyout/create_case_flyout.test.tsx | 20 ++ .../create/flyout/create_case_flyout.tsx | 7 +- .../use_cases_add_to_new_case_flyout.tsx | 7 +- .../common/experimental_features.ts | 5 + .../cypress/e2e/guided_onboarding/tour.cy.ts | 5 +- x-pack/plugins/security_solution/kibana.json | 1 + .../public/app/home/global_header/index.tsx | 10 +- .../public/app/home/index.tsx | 2 +- .../event_details/event_details.tsx | 41 +++- .../guided_onboarding/tour.test.tsx | 71 ------ .../components/guided_onboarding/tour.tsx | 189 --------------- .../guided_onboarding/tour_config.ts | 116 --------- .../guided_onboarding_tour/README.md | 140 +++++++++++ .../index.ts | 7 +- .../guided_onboarding_tour/tour.test.tsx | 100 ++++++++ .../guided_onboarding_tour/tour.tsx | 130 ++++++++++ .../guided_onboarding_tour/tour_config.ts | 139 +++++++++++ .../guided_onboarding_tour/tour_step.test.tsx | 225 ++++++++++++++++++ .../guided_onboarding_tour/tour_step.tsx | 116 +++++++++ .../index.test.tsx | 25 +- .../use_primary_navigation.tsx | 5 +- .../common/lib/kibana/kibana_react.mock.ts | 3 + .../use_add_to_case_actions.tsx | 30 ++- .../components/take_action_dropdown/index.tsx | 33 ++- .../render_cell_value.tsx | 71 +++--- .../timeline/body/actions/index.tsx | 54 ++++- .../timelines/containers/details/index.tsx | 4 +- .../plugins/security_solution/public/types.ts | 2 + .../translations/translations/fr-FR.json | 14 -- .../translations/translations/ja-JP.json | 14 -- .../translations/translations/zh-CN.json | 14 -- 31 files changed, 1048 insertions(+), 552 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.test.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.tsx delete mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour_config.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/README.md rename x-pack/plugins/security_solution/public/common/components/{guided_onboarding => guided_onboarding_tour}/index.ts (67%) create mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx diff --git a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.test.tsx b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.test.tsx index df2ba80901738..effa3d450af89 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.test.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.test.tsx @@ -44,4 +44,24 @@ describe('CreateCaseFlyout', () => { }); expect(onClose).toBeCalled(); }); + + it('renders headerContent when passed', async () => { + const headerContent =

; + const { getByTestId } = mockedContext.render( + + ); + + await act(async () => { + expect(getByTestId('testing123')).toBeTruthy(); + expect(getByTestId('create-case-flyout-header').children.length).toEqual(2); + }); + }); + + it('does not render headerContent when undefined', async () => { + const { getByTestId } = mockedContext.render(); + + await act(async () => { + expect(getByTestId('create-case-flyout-header').children.length).toEqual(1); + }); + }); }); diff --git a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx index 75a18f2e70209..8f5e420f6b79d 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/create_case_flyout.tsx @@ -25,6 +25,7 @@ export interface CreateCaseFlyoutProps { onClose?: () => void; onSuccess?: (theCase: Case) => Promise; attachments?: CaseAttachmentsWithoutOwner; + headerContent?: React.ReactNode; } const StyledFlyout = styled(EuiFlyout)` @@ -71,9 +72,10 @@ const FormWrapper = styled.div` `; export const CreateCaseFlyout = React.memo( - ({ afterCaseCreated, onClose, onSuccess, attachments }) => { + ({ afterCaseCreated, onClose, onSuccess, attachments, headerContent }) => { const handleCancel = onClose || function () {}; const handleOnSuccess = onSuccess || async function () {}; + return ( @@ -83,10 +85,11 @@ export const CreateCaseFlyout = React.memo( // maskProps is needed in order to apply the z-index to the parent overlay element, not to the flyout only maskProps={{ className: maskOverlayClassName }} > - +

{i18n.CREATE_CASE_TITLE}

+ {headerContent && headerContent} diff --git a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx index 86b03f46bf745..a92046e8c9928 100644 --- a/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx +++ b/x-pack/plugins/cases/public/components/create/flyout/use_cases_add_to_new_case_flyout.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import type React from 'react'; import { useCallback } from 'react'; import type { CaseAttachmentsWithoutOwner } from '../../../types'; import { useCasesToast } from '../../../common/use_cases_toast'; @@ -29,12 +30,16 @@ export const useCasesAddToNewCaseFlyout = (props: AddToNewCaseFlyoutProps = {}) }, [dispatch]); const openFlyout = useCallback( - ({ attachments }: { attachments?: CaseAttachmentsWithoutOwner } = {}) => { + ({ + attachments, + headerContent, + }: { attachments?: CaseAttachmentsWithoutOwner; headerContent?: React.ReactNode } = {}) => { dispatch({ type: CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT, payload: { ...props, attachments, + headerContent, onClose: () => { closeFlyout(); if (props.onClose) { diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts index aa581971e5f09..a92dde76777f5 100644 --- a/x-pack/plugins/security_solution/common/experimental_features.ts +++ b/x-pack/plugins/security_solution/common/experimental_features.ts @@ -64,6 +64,11 @@ export const allowedExperimentalValues = Object.freeze({ * Enables endpoint package level rbac */ endpointRbacEnabled: false, + + /** + * Enables the Guided Onboarding tour in security + */ + guidedOnboarding: false, }); type ExperimentalConfigKeys = Array; diff --git a/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts index 41e77e8aeac29..0339445bc8240 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/guided_onboarding/tour.cy.ts @@ -7,7 +7,6 @@ import { login, visit } from '../../tasks/login'; import { completeTour, goToNextStep, skipTour } from '../../tasks/guided_onboarding'; -import { SECURITY_TOUR_ACTIVE_KEY } from '../../../public/common/components/guided_onboarding'; import { OVERVIEW_URL } from '../../urls/navigation'; import { WELCOME_STEP, @@ -21,11 +20,11 @@ before(() => { login(); }); -describe('Guided onboarding tour', () => { +// need to redo these tests for new implementation +describe.skip('Guided onboarding tour', () => { describe('Tour is enabled', () => { beforeEach(() => { visit(OVERVIEW_URL); - window.localStorage.setItem(SECURITY_TOUR_ACTIVE_KEY, 'true'); }); it('can be completed', () => { diff --git a/x-pack/plugins/security_solution/kibana.json b/x-pack/plugins/security_solution/kibana.json index 4a6e3a105ee72..bddfc36c7d61d 100644 --- a/x-pack/plugins/security_solution/kibana.json +++ b/x-pack/plugins/security_solution/kibana.json @@ -19,6 +19,7 @@ "embeddable", "eventLog", "features", + "guidedOnboarding", "inspector", "kubernetesSecurity", "lens", diff --git a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx index 37efdce430317..c5d86011226c3 100644 --- a/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/global_header/index.tsx @@ -10,7 +10,7 @@ import { EuiHeaderSection, EuiHeaderSectionItem, } from '@elastic/eui'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; import { i18n } from '@kbn/i18n'; @@ -28,7 +28,6 @@ import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; import { getScopeFromPath, showSourcererByPath } from '../../../common/containers/sourcerer'; -import { useTourContext } from '../../../common/components/guided_onboarding'; const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', { defaultMessage: 'Add integrations', @@ -83,12 +82,6 @@ export const GlobalHeader = React.memo( }; }, [portalNode, setHeaderActionMenu, theme.theme$]); - const { isTourShown, endTour } = useTourContext(); - const closeOnboardingTourIfShown = useCallback(() => { - if (isTourShown) { - endTour(); - } - }, [isTourShown, endTour]); return ( @@ -105,7 +98,6 @@ export const GlobalHeader = React.memo( data-test-subj="add-data" href={href} iconType="indexOpen" - onClick={closeOnboardingTourIfShown} > {BUTTON_ADD_DATA} diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx index 36940cc055645..3711a990ef726 100644 --- a/x-pack/plugins/security_solution/public/app/home/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/index.tsx @@ -22,7 +22,7 @@ import { useUpgradeSecurityPackages } from '../../common/hooks/use_upgrade_secur import { GlobalHeader } from './global_header'; import { ConsoleManager } from '../../management/components/console/components/console_manager'; -import { TourContextProvider } from '../../common/components/guided_onboarding'; +import { TourContextProvider } from '../../common/components/guided_onboarding_tour'; import { useUrlState } from '../../common/hooks/use_url_state'; import { useUpdateBrowserTitle } from '../../common/hooks/use_update_browser_title'; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index a0ef3b8904e3f..edfe3e70e6ca0 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -7,19 +7,22 @@ import type { EuiTabbedContentTab } from '@elastic/eui'; import { - EuiHorizontalRule, - EuiTabbedContent, - EuiSpacer, - EuiLoadingContent, - EuiNotificationBadge, EuiFlexGroup, EuiFlexItem, + EuiHorizontalRule, + EuiLoadingContent, EuiLoadingSpinner, + EuiNotificationBadge, + EuiSpacer, + EuiTabbedContent, } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import { isEmpty } from 'lodash'; +import { GuidedOnboardingTourStep } from '../guided_onboarding_tour/tour_step'; +import { isDetectionsAlertsTable } from '../top_n/helpers'; +import { getTourAnchor, SecurityStepId } from '../guided_onboarding_tour/tour_config'; import type { AlertRawEventData } from './osquery_tab'; import { useOsqueryTab } from './osquery_tab'; import { EventFieldsBrowser } from './event_fields_browser'; @@ -179,6 +182,8 @@ const EventDetailsComponent: React.FC = ({ [detailsEcsData] ); + const isTourAnchor = useMemo(() => isDetectionsAlertsTable(scopeId), [scopeId]); + const showThreatSummary = useMemo(() => { const hasEnrichments = enrichmentCount > 0; const hasRiskInfoWithLicense = isLicenseValid && (hostRisk || userRisk); @@ -401,14 +406,26 @@ const EventDetailsComponent: React.FC = ({ [tabs, selectedTabId] ); + const tourAnchor = useMemo( + () => (isTourAnchor ? { 'tour-step': getTourAnchor(3, SecurityStepId.alertsCases) } : {}), + [isTourAnchor] + ); + return ( - + + + ); }; EventDetailsComponent.displayName = 'EventDetailsComponent'; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.test.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.test.tsx deleted file mode 100644 index e2cf3be0ae07d..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.test.tsx +++ /dev/null @@ -1,71 +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 { act, renderHook } from '@testing-library/react-hooks'; - -import { - SECURITY_TOUR_ACTIVE_KEY, - SECURITY_TOUR_STEP_KEY, - TourContextProvider, - useTourContext, -} from './tour'; - -describe('useTourContext', () => { - describe('localStorage', () => { - let localStorageTourActive: string | null; - let localStorageTourStep: string | null; - - beforeAll(() => { - localStorageTourActive = localStorage.getItem(SECURITY_TOUR_ACTIVE_KEY); - localStorage.removeItem(SECURITY_TOUR_ACTIVE_KEY); - localStorageTourStep = localStorage.getItem(SECURITY_TOUR_STEP_KEY); - localStorage.removeItem(SECURITY_TOUR_STEP_KEY); - }); - - afterAll(() => { - if (localStorageTourActive) { - localStorage.setItem(SECURITY_TOUR_ACTIVE_KEY, localStorageTourActive); - } - if (localStorageTourStep) { - localStorage.setItem(SECURITY_TOUR_STEP_KEY, localStorageTourStep); - } - }); - - test('tour is disabled', () => { - localStorage.setItem(SECURITY_TOUR_ACTIVE_KEY, JSON.stringify(false)); - const { result } = renderHook(() => useTourContext(), { - wrapper: TourContextProvider, - }); - expect(result.current.isTourShown).toBe(false); - }); - - test('tour is enabled', () => { - localStorage.setItem(SECURITY_TOUR_ACTIVE_KEY, JSON.stringify(true)); - const { result } = renderHook(() => useTourContext(), { - wrapper: TourContextProvider, - }); - expect(result.current.isTourShown).toBe(true); - }); - test('endTour callback', () => { - localStorage.setItem(SECURITY_TOUR_ACTIVE_KEY, JSON.stringify(true)); - let { result } = renderHook(() => useTourContext(), { - wrapper: TourContextProvider, - }); - expect(result.current.isTourShown).toBe(true); - act(() => { - result.current.endTour(); - }); - const localStorageValue = JSON.parse(localStorage.getItem(SECURITY_TOUR_ACTIVE_KEY)!); - expect(localStorageValue).toBe(false); - - ({ result } = renderHook(() => useTourContext(), { - wrapper: TourContextProvider, - })); - expect(result.current.isTourShown).toBe(false); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.tsx deleted file mode 100644 index 27288bb8a7145..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour.tsx +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ReactChild } from 'react'; -import React, { createContext, useContext, useState, useCallback } from 'react'; - -import type { EuiTourStepProps } from '@elastic/eui'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiTourStep, - EuiText, - EuiSpacer, - EuiImage, - useIsWithinBreakpoints, -} from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n-react'; - -import type { StepConfig } from './tour_config'; -import { tourConfig } from './tour_config'; - -export const SECURITY_TOUR_ACTIVE_KEY = 'guidedOnboarding.security.tourActive'; -export const SECURITY_TOUR_STEP_KEY = 'guidedOnboarding.security.tourStep'; -const getIsTourActiveFromLocalStorage = (): boolean => { - const localStorageValue = localStorage.getItem(SECURITY_TOUR_ACTIVE_KEY); - return localStorageValue ? JSON.parse(localStorageValue) : false; -}; -export const saveIsTourActiveToLocalStorage = (isTourActive: boolean): void => { - localStorage.setItem(SECURITY_TOUR_ACTIVE_KEY, JSON.stringify(isTourActive)); -}; - -export const getTourStepFromLocalStorage = (): number => { - return Number(localStorage.getItem(SECURITY_TOUR_STEP_KEY) ?? 1); -}; -const saveTourStepToLocalStorage = (step: number): void => { - localStorage.setItem(SECURITY_TOUR_STEP_KEY, JSON.stringify(step)); -}; - -const minWidth: EuiTourStepProps['minWidth'] = 360; -const maxWidth: EuiTourStepProps['maxWidth'] = 360; -const offset: EuiTourStepProps['offset'] = 20; -const repositionOnScroll: EuiTourStepProps['repositionOnScroll'] = true; - -const getSteps = (tourControls: { - activeStep: number; - incrementStep: () => void; - resetTour: () => void; -}) => { - const { activeStep, incrementStep, resetTour } = tourControls; - const footerAction = ( - - - resetTour()} - data-test-subj="onboarding--securityTourSkipButton" - > - - - - - incrementStep()} - color="success" - data-test-subj="onboarding--securityTourNextStepButton" - > - - - - - ); - const lastStepFooter = ( - resetTour()} - data-test-subj="onboarding--securityTourEndButton" - > - - - ); - return tourConfig.map((stepConfig: StepConfig) => { - const { content, imageConfig, dataTestSubj, ...rest } = stepConfig; - return ( - resetTour()} - panelProps={{ - 'data-test-subj': dataTestSubj, - }} - content={ - <> - -

{content}

-
- {imageConfig && ( - <> - - - - )} - - } - footerAction={activeStep === tourConfig.length ? lastStepFooter : footerAction} - /> - ); - }); -}; - -export interface TourContextValue { - isTourShown: boolean; - endTour: () => void; -} - -const TourContext = createContext({ - isTourShown: false, - endTour: () => {}, -} as TourContextValue); - -export const TourContextProvider = ({ children }: { children: ReactChild }) => { - const [isTourActive, _setIsTourActive] = useState(getIsTourActiveFromLocalStorage()); - const setIsTourActive = useCallback((value: boolean) => { - _setIsTourActive(value); - saveIsTourActiveToLocalStorage(value); - }, []); - - const [activeStep, _setActiveStep] = useState(getTourStepFromLocalStorage()); - - const incrementStep = useCallback(() => { - _setActiveStep((prevState) => { - const nextStep = (prevState >= tourConfig.length ? 0 : prevState) + 1; - saveTourStepToLocalStorage(nextStep); - return nextStep; - }); - }, []); - - const resetStep = useCallback(() => { - _setActiveStep(1); - saveTourStepToLocalStorage(1); - }, []); - - const resetTour = useCallback(() => { - setIsTourActive(false); - resetStep(); - }, [setIsTourActive, resetStep]); - - const isSmallScreen = useIsWithinBreakpoints(['xs', 's']); - const showTour = isTourActive && !isSmallScreen; - const context: TourContextValue = { isTourShown: showTour, endTour: resetTour }; - return ( - - <> - {children} - {showTour && <>{getSteps({ activeStep, incrementStep, resetTour })}} - - - ); -}; - -export const useTourContext = (): TourContextValue => { - const ctx = useContext(TourContext); - if (!ctx) { - throw new Error('useTourContext can only be called inside of TourContext!'); - } - return ctx; -}; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour_config.ts b/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour_config.ts deleted file mode 100644 index 5a0f6f30daadc..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/tour_config.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { EuiTourStepProps } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import alertsGif from '../../images/onboarding_tour_step_alerts.gif'; -import casesGif from '../../images/onboarding_tour_step_cases.gif'; - -export type StepConfig = Pick & { - anchor: string; - dataTestSubj: string; - imageConfig?: { - altText: string; - src: string; - }; -}; - -type TourConfig = StepConfig[]; - -export const tourConfig: TourConfig = [ - { - step: 1, - title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.overviewStep.tourTitle', { - defaultMessage: 'Welcome to Elastic Security', - }), - content: i18n.translate( - 'xpack.securitySolution.guided_onboarding.tour.overviewStep.tourContent', - { - defaultMessage: - 'Take a quick tour to explore a unified workflow for investigating suspicious activity.', - } - ), - anchor: `[id^="SolutionNav"]`, - anchorPosition: 'rightUp', - dataTestSubj: 'welcomeStep', - }, - { - step: 2, - title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.manageStep.tourTitle', { - defaultMessage: 'Protect your ecosystem', - }), - content: i18n.translate( - 'xpack.securitySolution.guided_onboarding.tour.manageStep.tourContent', - { - defaultMessage: - 'Decide what matters to you and your environment and create rules to detect and prevent malicious activity. ', - } - ), - anchor: `[data-test-subj="groupedNavItemLink-administration"]`, - anchorPosition: 'rightUp', - dataTestSubj: 'manageStep', - }, - { - step: 3, - title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.alertsStep.tourTitle', { - defaultMessage: 'Get notified when something changes', - }), - content: i18n.translate( - 'xpack.securitySolution.guided_onboarding.tour.alertsStep.tourContent', - { - defaultMessage: - "Know when a rule's conditions are met, so you can start your investigation right away. Set up notifications with third-party platforms like Slack, PagerDuty, and ServiceNow.", - } - ), - anchor: `[data-test-subj="groupedNavItemLink-alerts"]`, - anchorPosition: 'rightUp', - imageConfig: { - src: alertsGif, - altText: i18n.translate( - 'xpack.securitySolution.guided_onboarding.tour.alertsStep.imageAltText', - { - defaultMessage: 'Alerts demonstration', - } - ), - }, - dataTestSubj: 'alertsStep', - }, - { - step: 4, - title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.casesStep.tourTitle', { - defaultMessage: 'Create a case to track your investigation', - }), - content: i18n.translate('xpack.securitySolution.guided_onboarding.tour.casesStep.tourContent', { - defaultMessage: - 'Collect evidence, add more collaborators, and even push case details to third-party case management systems.', - }), - anchor: `[data-test-subj="groupedNavItemLink-cases"]`, - anchorPosition: 'rightUp', - imageConfig: { - src: casesGif, - altText: i18n.translate( - 'xpack.securitySolution.guided_onboarding.tour.casesStep.imageAltText', - { - defaultMessage: 'Cases demonstration', - } - ), - }, - dataTestSubj: 'casesStep', - }, - { - step: 5, - title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.dataStep.tourTitle', { - defaultMessage: `Start gathering your data!`, - }), - content: i18n.translate('xpack.securitySolution.guided_onboarding.tour.dataStep.tourContent', { - defaultMessage: `Collect data from your endpoints using the Elastic Agent and a variety of third-party integrations.`, - }), - anchor: `[data-test-subj="add-data"]`, - anchorPosition: 'rightUp', - dataTestSubj: 'dataStep', - }, -]; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/README.md b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/README.md new file mode 100644 index 0000000000000..eb30e20f1318e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/README.md @@ -0,0 +1,140 @@ +## Security Guided Onboarding Tour +This work required some creativity for reasons. Allow me to explain some weirdness + +The [`EuiTourStep`](https://elastic.github.io/eui/#/display/tour) component needs an **anchor** to attach on in the DOM. This can be defined in 2 ways: +``` +type EuiTourStepAnchorProps = ExclusiveUnion<{ + //Element to which the tour step popover attaches when open + children: ReactElement; + // Selector or reference to the element to which the tour step popover attaches when open + anchor?: never; +}, { + children?: never; + anchor: ElementTarget; +}>; +``` + +It was important that the `EuiTourStep` **anchor** is in the DOM when the tour step becomes active. Additionally, when the **anchor** leaves the DOM, we need `EuiTourStep` to leave the DOM as well. + +## How to use components (for OLM/D&R step engineers) + +- Define your steps in [`./tour_config.ts`](https://github.com/elastic/kibana/pull/143598/files#diff-2c0372fc996eadbff00dddb92101432bf38cc1613895cb9a208abd8eb2e12930R136) in the `securityTourConfig` const +- For each step, implement the `GuidedOnboardingTourStep` component at the location of the **anchor**. As stated in the previous section, there are two ways to define the **anchor**. I will explain examples of both methods: + +1. **Method 1 - as children.** Looking at step 1 of the `SecurityStepId.alertsCases` tour. In the `alertsCasesConfig` you can see the config for this step looks like: + + ``` + { + ...defaultConfig, + step: 1, + title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.ruleNameStep.tourTitle', { + defaultMessage: 'Test alert for practice', + }), + content: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.ruleNameStep.tourContent', + { + defaultMessage: + 'To help you practice triaging alerts, we enabled a rule to create your first alert.', + } + ), + anchorPosition: 'downCenter', + dataTestSubj: getTourAnchor(1, SecurityStepId.alertsCases), + } + ``` + + Notice that **no anchor prop is defined** in the step 1 config. + As you can see pictured below, the tour step anchor is the Rule name of the first alert. + + 1 + + The component for this anchor is `RenderCellValue` which returns `DefaultCellRenderer`. We wrap `DefaultCellRenderer` with `GuidedOnboardingTourStep`, passing `step={1} stepId={SecurityStepId.alertsCases}` to indicate the step. Since there are many other iterations of this component on the page, we also need to pass the `isTourAnchor` property to determine which of these components should be the anchor. In the code, this looks something like: + + ``` + export const RenderCellValue = (props) => { + const { columnId, rowIndex, scopeId } = props; + const isTourAnchor = useMemo( + () => + columnId === SIGNAL_RULE_NAME_FIELD_NAME && + isDetectionsAlertsTable(scopeId) && + rowIndex === 0, + [columnId, rowIndex, scopeId] + ); + + return ( + + + + ); + }; + ``` + +2. **Method 2 - as anchor props.** Looking at step 5 of the `SecurityStepId.alertsCases` tour. In the `alertsCasesConfig` you can see the config for this step looks like: + + ``` + { + ...defaultConfig, + step: 5, + title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.createCase.tourTitle', { + defaultMessage: `Add details`, + }), + content: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.createCase.tourContent', + { + defaultMessage: `In addition to the alert, you can add any relevant information you need to the case.`, + } + ), + anchor: `[data-test-subj="create-case-flyout"]`, + anchorPosition: 'leftUp', + dataTestSubj: getTourAnchor(5, SecurityStepId.alertsCases), + hideNextButton: true, + } + ``` + + Notice that the **anchor prop is defined** as `[data-test-subj="create-case-flyout"]` in the step 5 config. There is also a `hideNextButton` boolean utilized here. + As you can see pictured below, the tour step anchor is the create case flyout and the next button is hidden. + + 5 + + + Since cases is its own plugin and we are using a method to generate the flyout, we cannot wrap the flyout as children of the `GuidedOnboardingTourStep`. We do however need the `EuiTourStep` component to mount in the same location as the anchor. Therefore, I had to pass a new optional property to the case component called `headerContent` that simply accepts and renders ` React.ReactNode` at the top of the flyout. In the code, this looks something like: + + ``` + createCaseFlyout.open({ + attachments: caseAttachments, + ...(isTourShown(SecurityStepId.alertsCases) && activeStep === 4 + ? { + headerContent: ( + // isTourAnchor=true no matter what in order to + // force active guide step outside of security solution (cases) + + ), + } + : {}), + }); + ``` + +- The **`useTourContext`** is used within anchor components, returning the state of the security tour + ``` + export interface TourContextValue { + activeStep: number; + endTourStep: (stepId: SecurityStepId) => void; + incrementStep: (stepId: SecurityStepId, step?: number) => void; + isTourShown: (stepId: SecurityStepId) => boolean; + } + ``` + When the tour step does not have a next button, the anchor component will need to call `incrementStep` after an action is taken. For example, in `SecurityStepId.alertsCases` step 4, the user needs to click the "Add to case" button to advance the tour. + + 4 + + So we utilize the `useTourContext` to do the following check and increment the step in `handleAddToNewCaseClick`: + ``` + if (isTourShown(SecurityStepId.alertsCases) && activeStep === 4) { + incrementStep(SecurityStepId.alertsCases); + } + ``` + + In `SecurityStepId.alertsCases` step 5, the user needs to fill out the form and hit the "Create case" button in order to end the `alertsCases` portion the tour, so with the `afterCaseCreated` method we call `endTourStep(SecurityStepId.alertsCases)`. \ No newline at end of file diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/index.ts b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/index.ts similarity index 67% rename from x-pack/plugins/security_solution/public/common/components/guided_onboarding/index.ts rename to x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/index.ts index ed0dfa6c76339..2bb68dff4646f 100644 --- a/x-pack/plugins/security_solution/public/common/components/guided_onboarding/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/index.ts @@ -5,9 +5,4 @@ * 2.0. */ -export { - useTourContext, - TourContextProvider, - SECURITY_TOUR_ACTIVE_KEY, - SECURITY_TOUR_STEP_KEY, -} from './tour'; +export { useTourContext, TourContextProvider } from './tour'; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.test.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.test.tsx new file mode 100644 index 0000000000000..faea94a1c37ec --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.test.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 { act, renderHook } from '@testing-library/react-hooks'; +import { of } from 'rxjs'; +import { TourContextProvider, useTourContext } from './tour'; +import { SecurityStepId, securityTourConfig } from './tour_config'; +import { useKibana } from '../../lib/kibana'; + +jest.mock('../../lib/kibana'); +jest.mock('../../hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: () => true, +})); + +jest.mock('react-router-dom', () => { + const original = jest.requireActual('react-router-dom'); + + return { + ...original, + useLocation: jest.fn().mockReturnValue({ pathname: '/alerts' }), + }; +}); + +describe('useTourContext', () => { + const mockCompleteGuideStep = jest.fn(); + beforeEach(() => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + guidedOnboarding: { + guidedOnboardingApi: { + isGuideStepActive$: () => of(true), + completeGuideStep: mockCompleteGuideStep, + }, + }, + }, + }); + jest.clearAllMocks(); + }); + // @ts-ignore + const stepIds = Object.values(SecurityStepId); + describe.each(stepIds)('%s', (stepId) => { + it('if guidedOnboardingApi?.isGuideStepActive$ is false, isTourShown should be false', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + guidedOnboarding: { + guidedOnboardingApi: { + isGuideStepActive$: () => of(false), + }, + }, + }, + }); + const { result } = renderHook(() => useTourContext(), { + wrapper: TourContextProvider, + }); + expect(result.current.isTourShown(stepId)).toBe(false); + }); + it('if guidedOnboardingApi?.isGuideStepActive$ is true, isTourShown should be true', () => { + const { result } = renderHook(() => useTourContext(), { + wrapper: TourContextProvider, + }); + expect(result.current.isTourShown(stepId)).toBe(true); + }); + it('endTourStep calls completeGuideStep with correct stepId', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useTourContext(), { + wrapper: TourContextProvider, + }); + await waitForNextUpdate(); + result.current.endTourStep(stepId); + expect(mockCompleteGuideStep).toHaveBeenCalledWith('security', stepId); + }); + }); + it('activeStep is initially 1', () => { + const { result } = renderHook(() => useTourContext(), { + wrapper: TourContextProvider, + }); + expect(result.current.activeStep).toBe(1); + }); + it('increment step properly increments for each stepId, and if attempted to increment beyond length of tour config steps resets activeStep to 1', async () => { + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => useTourContext(), { + wrapper: TourContextProvider, + }); + await waitForNextUpdate(); + const stepCount = securityTourConfig[stepId].length; + for (let i = 0; i < stepCount - 1; i++) { + result.current.incrementStep(stepId); + } + const lastStep = stepCount ? stepCount : 1; + expect(result.current.activeStep).toBe(lastStep); + result.current.incrementStep(stepId); + expect(result.current.activeStep).toBe(1); + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.tsx new file mode 100644 index 0000000000000..43f6ca15b33cb --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour.tsx @@ -0,0 +1,130 @@ +/* + * Copyright 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 { ReactChild } from 'react'; +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; + +import useObservable from 'react-use/lib/useObservable'; +import { catchError, of, timeout } from 'rxjs'; +import { useLocation } from 'react-router-dom'; +import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features'; +import { isDetectionsPath } from '../../../helpers'; +import { useKibana } from '../../lib/kibana'; +import { securityTourConfig, SecurityStepId } from './tour_config'; + +export interface TourContextValue { + activeStep: number; + endTourStep: (stepId: SecurityStepId) => void; + incrementStep: (stepId: SecurityStepId, step?: number) => void; + isTourShown: (stepId: SecurityStepId) => boolean; +} + +const initialState: TourContextValue = { + activeStep: 0, + endTourStep: () => {}, + incrementStep: () => {}, + isTourShown: () => false, +}; + +const TourContext = createContext(initialState); + +export const RealTourContextProvider = ({ children }: { children: ReactChild }) => { + const { guidedOnboardingApi } = useKibana().services.guidedOnboarding; + + const isRulesTourActive = useObservable( + guidedOnboardingApi?.isGuideStepActive$('security', SecurityStepId.rules).pipe( + // if no result after 30s the observable will error, but the error handler will just emit false + timeout(30000), + catchError((error) => of(false)) + ) ?? of(false), + false + ); + const isAlertsCasesTourActive = useObservable( + guidedOnboardingApi?.isGuideStepActive$('security', SecurityStepId.alertsCases).pipe( + // if no result after 30s the observable will error, but the error handler will just emit false + timeout(30000), + catchError((error) => of(false)) + ) ?? of(false), + false + ); + + const tourStatus = useMemo( + () => ({ + [SecurityStepId.rules]: isRulesTourActive, + [SecurityStepId.alertsCases]: isAlertsCasesTourActive, + }), + [isRulesTourActive, isAlertsCasesTourActive] + ); + + const isTourShown = useCallback((stepId: SecurityStepId) => tourStatus[stepId], [tourStatus]); + const [activeStep, _setActiveStep] = useState(1); + + const incrementStep = useCallback((stepId: SecurityStepId) => { + _setActiveStep( + (prevState) => (prevState >= securityTourConfig[stepId].length ? 0 : prevState) + 1 + ); + }, []); + + // TODO: @Steph figure out if we're allowing user to skip tour or not, implement this if so + // const onSkipTour = useCallback((stepId: SecurityStepId) => { + // // active state means the user is on this step but has not yet begun. so when the user hits skip, + // // the tour will go back to this step until they "re-start it" + // // guidedOnboardingApi.idkSetStepTo(stepId, 'active') + // }, []); + + const [completeStep, setCompleteStep] = useState(null); + + useEffect(() => { + if (!completeStep || !guidedOnboardingApi) { + return; + } + let ignore = false; + const complete = async () => { + await guidedOnboardingApi.completeGuideStep('security', completeStep); + if (!ignore) { + setCompleteStep(null); + _setActiveStep(1); + } + }; + complete(); + return () => { + ignore = true; + }; + }, [completeStep, guidedOnboardingApi]); + + const endTourStep = useCallback((stepId: SecurityStepId) => { + setCompleteStep(stepId); + }, []); + + const context = { + activeStep, + endTourStep, + incrementStep, + isTourShown, + }; + + return {children}; +}; + +export const TourContextProvider = ({ children }: { children: ReactChild }) => { + const { pathname } = useLocation(); + const isTourEnabled = useIsExperimentalFeatureEnabled('guidedOnboarding'); + + if (isDetectionsPath(pathname) && isTourEnabled) { + return {children}; + } + + return {children}; +}; + +export const useTourContext = (): TourContextValue => { + const ctx = useContext(TourContext); + if (!ctx) { + throw new Error('useTourContext can only be called inside of TourContext!'); + } + return ctx; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts new file mode 100644 index 0000000000000..f7ed05be4c418 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_config.ts @@ -0,0 +1,139 @@ +/* + * Copyright 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 { EuiTourStepProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import type { ElementTarget } from '@elastic/eui/src/services/findElement'; + +export const enum SecurityStepId { + rules = 'rules', + alertsCases = 'alertsCases', +} + +export type StepConfig = Pick< + EuiTourStepProps, + 'step' | 'content' | 'anchorPosition' | 'title' | 'initialFocus' | 'anchor' +> & { + anchor?: ElementTarget; + dataTestSubj: string; + hideNextButton?: boolean; + imageConfig?: { + altText: string; + src: string; + }; +}; + +const defaultConfig = { + minWidth: 360, + maxWidth: 360, + offset: 10, + repositionOnScroll: true, +}; + +export const getTourAnchor = (step: number, stepId: SecurityStepId) => + `tourStepAnchor-${stepId}-${step}`; + +const alertsCasesConfig: StepConfig[] = [ + { + ...defaultConfig, + step: 1, + title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.ruleNameStep.tourTitle', { + defaultMessage: 'Test alert for practice', + }), + content: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.ruleNameStep.tourContent', + { + defaultMessage: + 'To help you practice triaging alerts, we enabled a rule to create your first alert.', + } + ), + anchorPosition: 'downCenter', + dataTestSubj: getTourAnchor(1, SecurityStepId.alertsCases), + initialFocus: `button[tour-step="nextButton"]`, + }, + { + ...defaultConfig, + step: 2, + title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.openFlyout.tourTitle', { + defaultMessage: 'Review the alert details', + }), + content: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.openFlyout.tourContent', + { + defaultMessage: + "Some information is provided at-a-glance in the table, but for full details, you'll want to open the alert.", + } + ), + anchorPosition: 'rightUp', + dataTestSubj: getTourAnchor(2, SecurityStepId.alertsCases), + hideNextButton: true, + }, + { + ...defaultConfig, + step: 3, + title: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.flyoutOverview.tourTitle', + { + defaultMessage: 'Explore alert details', + } + ), + content: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.flyoutOverview.tourContent', + { + defaultMessage: + 'Learn more about alerts by checking out all the information available on each tab.', + } + ), + // needs to use anchor to properly place tour step + anchor: `[tour-step="${getTourAnchor(3, SecurityStepId.alertsCases)}"] .euiTabs`, + anchorPosition: 'leftUp', + dataTestSubj: getTourAnchor(3, SecurityStepId.alertsCases), + }, + { + ...defaultConfig, + step: 4, + title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.addToCase.tourTitle', { + defaultMessage: 'Create a case', + }), + content: i18n.translate('xpack.securitySolution.guided_onboarding.tour.addToCase.tourContent', { + defaultMessage: 'From the Take action menu, add the alert to a new case.', + }), + anchorPosition: 'upRight', + dataTestSubj: getTourAnchor(4, SecurityStepId.alertsCases), + hideNextButton: true, + }, + { + ...defaultConfig, + step: 5, + title: i18n.translate('xpack.securitySolution.guided_onboarding.tour.createCase.tourTitle', { + defaultMessage: `Add details`, + }), + content: i18n.translate( + 'xpack.securitySolution.guided_onboarding.tour.createCase.tourContent', + { + defaultMessage: `In addition to the alert, you can add any relevant information you need to the case.`, + } + ), + anchor: `[data-test-subj="create-case-flyout"]`, + anchorPosition: 'leftUp', + dataTestSubj: getTourAnchor(5, SecurityStepId.alertsCases), + hideNextButton: true, + }, +]; + +interface SecurityTourConfig { + [SecurityStepId.rules]: StepConfig[]; + [SecurityStepId.alertsCases]: StepConfig[]; +} + +export const securityTourConfig: SecurityTourConfig = { + /** + * D&R team implement your tour config here + */ + [SecurityStepId.rules]: [], + [SecurityStepId.alertsCases]: alertsCasesConfig, +}; diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.test.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.test.tsx new file mode 100644 index 0000000000000..04f2cfd6a4311 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.test.tsx @@ -0,0 +1,225 @@ +/* + * Copyright 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 { GuidedOnboardingTourStep, SecurityTourStep } from './tour_step'; +import { SecurityStepId } from './tour_config'; +import { useTourContext } from './tour'; + +jest.mock('./tour'); +const mockTourStep = jest + .fn() + .mockImplementation(({ children }: { children: React.ReactNode }) => ( + {children} + )); +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + EuiTourStep: (props: any) => mockTourStep(props), + }; +}); +const defaultProps = { + isTourAnchor: true, + step: 1, + stepId: SecurityStepId.alertsCases, +}; + +const mockChildren =

{'random child element'}

; + +describe('GuidedOnboardingTourStep', () => { + beforeEach(() => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 1, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + jest.clearAllMocks(); + }); + it('renders as a tour step', () => { + const { getByTestId } = render( + {mockChildren} + ); + const tourStep = getByTestId('tourStepMock'); + const header = getByTestId('h1'); + expect(tourStep).toBeInTheDocument(); + expect(header).toBeInTheDocument(); + }); + it('isTourAnchor={false}, just render children', () => { + const { getByTestId, queryByTestId } = render( + + {mockChildren} + + ); + const tourStep = queryByTestId('tourStepMock'); + const header = getByTestId('h1'); + expect(tourStep).not.toBeInTheDocument(); + expect(header).toBeInTheDocument(); + }); +}); + +describe('SecurityTourStep', () => { + const { isTourAnchor: _, ...securityTourStepDefaultProps } = defaultProps; + beforeEach(() => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 1, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + jest.clearAllMocks(); + }); + + it('does not render if tour step does not exist', () => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 99, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + render( + + {mockChildren} + + ); + expect(mockTourStep).not.toHaveBeenCalled(); + }); + + it('does not render if tour step does not equal active step', () => { + render( + + {mockChildren} + + ); + expect(mockTourStep).not.toHaveBeenCalled(); + }); + + it('does not render if security tour step is not shown', () => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 1, + incrementStep: jest.fn(), + isTourShown: () => false, + }); + render({mockChildren}); + expect(mockTourStep).not.toHaveBeenCalled(); + }); + + it('renders tour step with correct number of steppers', () => { + render({mockChildren}); + const mockCall = { ...mockTourStep.mock.calls[0][0] }; + expect(mockCall.step).toEqual(1); + expect(mockCall.stepsTotal).toEqual(5); + }); + + it('forces the render for step 5 of the SecurityStepId.alertsCases tour step', () => { + render( + + {mockChildren} + + ); + const mockCall = { ...mockTourStep.mock.calls[0][0] }; + expect(mockCall.step).toEqual(5); + expect(mockCall.stepsTotal).toEqual(5); + }); + + it('does render next button if step hideNextButton=false ', () => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 3, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + render( + + {mockChildren} + + ); + const mockCall = { ...mockTourStep.mock.calls[0][0] }; + expect(mockCall.footerAction).toMatchInlineSnapshot(` + + + + `); + }); + + it('if a step has an anchor declared, the tour step should be a sibling of the mockChildren', () => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 3, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + const { container } = render( + + {mockChildren} + + ); + const selectParent = container.querySelector( + `[data-test-subj="tourStepMock"] [data-test-subj="h1"]` + ); + const selectSibling = container.querySelector( + `[data-test-subj="tourStepMock"]+[data-test-subj="h1"]` + ); + expect(selectSibling).toBeInTheDocument(); + expect(selectParent).not.toBeInTheDocument(); + }); + + it('if a step does not an anchor declared, the tour step should be the parent of the mockChildren', () => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 2, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + const { container } = render( + + {mockChildren} + + ); + const selectParent = container.querySelector( + `[data-test-subj="tourStepMock"] [data-test-subj="h1"]` + ); + const selectSibling = container.querySelector( + `[data-test-subj="tourStepMock"]+[data-test-subj="h1"]` + ); + expect(selectParent).toBeInTheDocument(); + expect(selectSibling).not.toBeInTheDocument(); + }); + + it('if a tour step does not have children and has anchor, only render tour step', () => { + const { getByTestId } = render(); + expect(getByTestId('tourStepMock')).toBeInTheDocument(); + }); + + it('if a tour step does not have children and does not have anchor, render nothing', () => { + const { queryByTestId } = render( + + ); + expect(queryByTestId('tourStepMock')).not.toBeInTheDocument(); + }); + + it('does not render next button if step hideNextButton=true ', () => { + (useTourContext as jest.Mock).mockReturnValue({ + activeStep: 4, + incrementStep: jest.fn(), + isTourShown: () => true, + }); + render( + + {mockChildren} + + ); + const mockCall = { ...mockTourStep.mock.calls[0][0] }; + expect(mockCall.footerAction).toMatchInlineSnapshot(``); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx new file mode 100644 index 0000000000000..ef07c5ce44a42 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/guided_onboarding_tour/tour_step.tsx @@ -0,0 +1,116 @@ +/* + * Copyright 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 type { EuiTourStepProps } from '@elastic/eui'; +import { EuiButton, EuiImage, EuiSpacer, EuiText, EuiTourStep } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +import { useTourContext } from './tour'; +import { securityTourConfig, SecurityStepId } from './tour_config'; +interface SecurityTourStep { + children?: React.ReactElement; + step: number; + stepId: SecurityStepId; +} + +export const SecurityTourStep = ({ children, step, stepId }: SecurityTourStep) => { + const { activeStep, incrementStep, isTourShown } = useTourContext(); + const tourStep = useMemo( + () => securityTourConfig[stepId].find((config) => config.step === step), + [step, stepId] + ); + const onClick = useCallback(() => incrementStep(stepId), [incrementStep, stepId]); + // step === 5 && stepId === SecurityStepId.alertsCases is in Cases app and out of context. + // If we mount this step, we know we need to render it + // we are also managing the context on the siem end in the background + const overrideContext = step === 5 && stepId === SecurityStepId.alertsCases; + if (tourStep == null || ((step !== activeStep || !isTourShown(stepId)) && !overrideContext)) { + return children ? children : null; + } + + const { anchor, content, imageConfig, dataTestSubj, hideNextButton = false, ...rest } = tourStep; + + const footerAction: EuiTourStepProps['footerAction'] = !hideNextButton ? ( + + + + ) : ( + <> + {/* Passing empty element instead of undefined. If undefined "Skip tour" button is shown, we do not want that*/} + + ); + + const commonProps = { + ...rest, + content: ( + <> + +

{content}

+
+ {imageConfig && ( + <> + + + + )} + + ), + footerAction, + // we would not have mounted this component if it was not open + isStepOpen: true, + // guided onboarding does not allow skipping tour through the steps + onFinish: () => null, + stepsTotal: securityTourConfig[stepId].length, + // TODO: re-add panelProps + // EUI has a bug https://github.com/elastic/eui/issues/6297 + // where any panelProps overwrite their panelProps, + // so we lose cool things like the EuiBeacon + // panelProps: { + // 'data-test-subj': dataTestSubj, + // } + }; + + // tour step either needs children or an anchor element + // see type EuiTourStepAnchorProps + return anchor != null ? ( + <> + + <>{children} + + ) : children != null ? ( + {children} + ) : null; +}; + +interface GuidedOnboardingTourStep extends SecurityTourStep { + // can be false if the anchor is an iterative element + // do not use this as an "is tour active" check, the SecurityTourStep checks that anyway + isTourAnchor?: boolean; +} + +// wraps tour anchor component +// and gives the tour step itself a place to mount once it is active +// mounts the tour step with a delay to ensure the anchor will render first +export const GuidedOnboardingTourStep = ({ + children, + // can be false if the anchor is an iterative element + // do not use this as an "is tour active" check, the SecurityTourStep checks that anyway + isTourAnchor = true, + ...props +}: GuidedOnboardingTourStep) => + isTourAnchor ? {children} : <>{children}; 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 5a99df01e5328..1d13d100b4d88 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 @@ -16,7 +16,7 @@ import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental 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 { useTourContext } from '../../guided_onboarding_tour'; import { useUserPrivileges } from '../../user_privileges'; import { noCasesPermissions, @@ -38,7 +38,7 @@ jest.mock('../../../hooks/use_selector'); 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('../../guided_onboarding_tour'); jest.mock('../../user_privileges'); const mockUseUserPrivileges = useUserPrivileges as jest.Mock; @@ -187,25 +187,4 @@ describe('useSecuritySolutionNavigation', () => { }); }); }); - - describe('Guided onboarding tour', () => { - it('nav can be collapsed if tour is not shown', () => { - const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( - () => useSecuritySolutionNavigation(), - { wrapper: TestProviders } - ); - - expect(result.current?.canBeCollapsed).toBe(true); - }); - it(`nav can't be collapsed if tour is shown`, () => { - (useTourContext as jest.Mock).mockReturnValue({ isTourShown: true }); - - const { result } = renderHook<{}, KibanaPageTemplateProps['solutionNav']>( - () => useSecuritySolutionNavigation(), - { wrapper: TestProviders } - ); - - expect(result.current?.canBeCollapsed).toBe(false); - }); - }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx index 9e83ae9339dcd..647193357b66b 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx @@ -13,7 +13,6 @@ import type { PrimaryNavigationProps } from './types'; import { usePrimaryNavigationItems } from './use_navigation_items'; import { useIsGroupedNavigationEnabled } from '../helpers'; import { SecuritySideNav } from '../security_side_nav'; -import { useTourContext } from '../../guided_onboarding'; const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', { defaultMessage: 'Security', @@ -31,8 +30,6 @@ export const usePrimaryNavigation = ({ const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab()); - const { isTourShown } = useTourContext(); - useEffect(() => { const currentTabSelected = mapLocationToTab(); @@ -49,7 +46,7 @@ export const usePrimaryNavigation = ({ }); return { - canBeCollapsed: !isTourShown, + canBeCollapsed: true, name: translatedNavTitle, icon: 'logoSecurity', ...(isGroupedNavigationEnabled diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 5d2fed9fc6241..efa9ce4831be7 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -44,6 +44,7 @@ import { noCasesPermissions } from '../../../cases_test_utils'; import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mocks'; import { mockApm } from '../apm/service.mock'; import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; +import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; const mockUiSettings: Record = { [DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' }, @@ -106,6 +107,7 @@ export const createStartServicesMock = ( cases.helpers.getUICapabilities.mockReturnValue(noCasesPermissions()); const triggersActionsUi = triggersActionsUiMock.createStart(); const cloudExperiments = cloudExperimentsMock.createStartMock(); + const guidedOnboarding = guidedOnboardingMock.createStart(); return { ...core, @@ -173,6 +175,7 @@ export const createStartServicesMock = ( }, triggersActionsUi, cloudExperiments, + guidedOnboarding, } as unknown as StartServices; }; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx index 538ae5b4ba211..70455fa342ab5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx @@ -9,6 +9,9 @@ import React, { useCallback, useMemo } from 'react'; import { EuiContextMenuItem } from '@elastic/eui'; import { CommentType } from '@kbn/cases-plugin/common'; import type { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; +import { GuidedOnboardingTourStep } from '../../../../common/components/guided_onboarding_tour/tour_step'; +import { SecurityStepId } from '../../../../common/components/guided_onboarding_tour/tour_config'; +import { useTourContext } from '../../../../common/components/guided_onboarding_tour'; import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/kibana'; import type { TimelineNonEcsData } from '../../../../../common/search_strategy'; import type { Ecs } from '../../../../../common/ecs'; @@ -53,9 +56,18 @@ export const useAddToCaseActions = ({ : []; }, [casesUi.helpers, ecsData, nonEcsData]); + const { activeStep, endTourStep, incrementStep, isTourShown } = useTourContext(); + + const afterCaseCreated = useCallback(async () => { + if (isTourShown(SecurityStepId.alertsCases)) { + endTourStep(SecurityStepId.alertsCases); + } + }, [endTourStep, isTourShown]); + const createCaseFlyout = casesUi.hooks.getUseCasesAddToNewCaseFlyout({ onClose: onMenuItemClick, onSuccess, + afterCaseCreated, }); const selectCaseModal = casesUi.hooks.getUseCasesAddToExistingCaseModal({ @@ -66,8 +78,22 @@ export const useAddToCaseActions = ({ const handleAddToNewCaseClick = useCallback(() => { // TODO rename this, this is really `closePopover()` onMenuItemClick(); - createCaseFlyout.open({ attachments: caseAttachments }); - }, [onMenuItemClick, createCaseFlyout, caseAttachments]); + createCaseFlyout.open({ + attachments: caseAttachments, + ...(isTourShown(SecurityStepId.alertsCases) && activeStep === 4 + ? { + headerContent: ( + // isTourAnchor=true no matter what in order to + // force active guide step outside of security solution (cases) + + ), + } + : {}), + }); + if (isTourShown(SecurityStepId.alertsCases) && activeStep === 4) { + incrementStep(SecurityStepId.alertsCases); + } + }, [onMenuItemClick, createCaseFlyout, caseAttachments, isTourShown, activeStep, incrementStep]); const handleAddToExistingCaseClick = useCallback(() => { // TODO rename this, this is really `closePopover()` diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index f4364443a6dfa..f9d5a5bc998c0 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -8,6 +8,8 @@ import React, { useCallback, useMemo, useState } from 'react'; import { EuiButton, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; +import { SecurityStepId } from '../../../common/components/guided_onboarding_tour/tour_config'; import { isActiveTimeline } from '../../../helpers'; import { TableId } from '../../../../common/types'; import { useResponderActionItem } from '../endpoint_responder'; @@ -252,19 +254,24 @@ export const TakeActionDropdown = React.memo( ] ); - const takeActionButton = useMemo(() => { - return ( - - {TAKE_ACTION} - - ); - }, [togglePopoverHandler]); + const takeActionButton = useMemo( + () => ( + + + {TAKE_ACTION} + + + ), + + [togglePopoverHandler] + ); + return items.length && !loadingEventDetails && ecsData ? ( = ({ - browserFields, - columnId, - data, - ecsData, - eventId, - globalFilters, - header, - isDetails, - isDraggable, - isExpandable, - isExpanded, - linkValues, - rowIndex, - colIndex, - rowRenderers, - setCellProps, - scopeId, - truncate, -}) => ( - -); +export const RenderCellValue: React.FC = ( + props +) => { + const { columnId, rowIndex, scopeId } = props; + const isTourAnchor = useMemo( + () => + columnId === SIGNAL_RULE_NAME_FIELD_NAME && + isDetectionsAlertsTable(scopeId) && + rowIndex === 0, + [columnId, rowIndex, scopeId] + ); + + return ( + + + + ); +}; export const useRenderCellValue = ({ setFlyoutAlert, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx index 2c1b5b06a284e..a0384d7707534 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/index.tsx @@ -12,6 +12,10 @@ import { noop } from 'lodash/fp'; import styled from 'styled-components'; import { DEFAULT_ACTION_BUTTON_WIDTH } from '@kbn/timelines-plugin/public'; +import { GuidedOnboardingTourStep } from '../../../../../common/components/guided_onboarding_tour/tour_step'; +import { isDetectionsAlertsTable } from '../../../../../common/components/top_n/helpers'; +import { useTourContext } from '../../../../../common/components/guided_onboarding_tour'; +import { SecurityStepId } from '../../../../../common/components/guided_onboarding_tour/tour_config'; import { getScopedActions, isTimelineScope } from '../../../../../helpers'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; import { eventHasNotes, getEventType, getPinOnClick } from '../helpers'; @@ -201,6 +205,24 @@ const ActionsComponent: React.FC = ({ scopedActions, ]); + const { isTourShown, incrementStep } = useTourContext(); + + const isTourAnchor = useMemo( + () => + isTourShown(SecurityStepId.alertsCases) && + eventType === 'signal' && + isDetectionsAlertsTable(timelineId) && + ariaRowindex === 1, + [isTourShown, ariaRowindex, eventType, timelineId] + ); + + const onExpandEvent = useCallback(() => { + if (isTourAnchor) { + incrementStep(SecurityStepId.alertsCases); + } + onEventDetailsPanelOpened(); + }, [incrementStep, isTourAnchor, onEventDetailsPanelOpened]); + return ( {showCheckboxes && !tGridEnabled && ( @@ -220,19 +242,25 @@ const ActionsComponent: React.FC = ({ )} -
- - - - - -
+ +
+ + + + + +
+
<> {timelineId !== TimelineId.active && ( Promise>(asyncNoop); const abortCtrl = useRef(new AbortController()); const searchSubscription$ = useRef(new Subscription()); - const [loading, setLoading] = useState(false); + + // loading = false initial state causes flashes of empty tables + const [loading, setLoading] = useState(true); const [timelineDetailsRequest, setTimelineDetailsRequest] = useState(null); const { addError, addWarning } = useAppToasts(); diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index a5f8e5897230d..70a5de2c00af6 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -41,6 +41,7 @@ import type { } from '@kbn/saved-objects-tagging-oss-plugin/public'; import type { ThreatIntelligencePluginStart } from '@kbn/threat-intelligence-plugin/public'; import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/common'; +import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -76,6 +77,7 @@ export interface StartPlugins { embeddable: EmbeddableStart; inspector: InspectorStart; fleet?: FleetStart; + guidedOnboarding: GuidedOnboardingPluginStart; kubernetesSecurity: KubernetesSecurityStart; lens: LensPublicStart; lists?: ListsPluginStart; diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 73cc46c87a0a8..283ffe2b84854 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -28234,21 +28234,7 @@ "xpack.securitySolution.getCurrentUser.unknownUser": "Inconnu", "xpack.securitySolution.globalHeader.buttonAddData": "Ajouter des intégrations", "xpack.securitySolution.goToDocumentationButton": "Afficher la documentation", - "xpack.securitySolution.guided_onboarding.endTour.buttonLabel": "Terminer la visite", "xpack.securitySolution.guided_onboarding.nextStep.buttonLabel": "Suivant", - "xpack.securitySolution.guided_onboarding.skipTour.buttonLabel": "Ignorer la visite", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.imageAltText": "Démonstration des alertes", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.tourContent": "Sachez quand les conditions d'une règle sont remplies, afin de pouvoir commencer votre investigation immédiatement. Configurez des notifications avec des plateformes tierces telles que Slack, PagerDuty et ServiceNow.", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.tourTitle": "Soyez informé en cas de modification", - "xpack.securitySolution.guided_onboarding.tour.casesStep.imageAltText": "Démonstration des cas", - "xpack.securitySolution.guided_onboarding.tour.casesStep.tourContent": "Recueillez des éléments probants, ajoutez des collaborateurs et transmettez même les détails de l'affaire à des systèmes tiers de gestion des cas.", - "xpack.securitySolution.guided_onboarding.tour.casesStep.tourTitle": "Créez un cas pour suivre votre investigation", - "xpack.securitySolution.guided_onboarding.tour.dataStep.tourContent": "Recueillez des données à partir de vos points de terminaison en utilisant l'agent Elastic et une variété d'intégrations tierces.", - "xpack.securitySolution.guided_onboarding.tour.dataStep.tourTitle": "Commencez à collecter vos données !", - "xpack.securitySolution.guided_onboarding.tour.manageStep.tourContent": "Décidez de ce qui est important pour vous et votre environnement, et créez des règles pour détecter et prévenir les activités malveillantes. ", - "xpack.securitySolution.guided_onboarding.tour.manageStep.tourTitle": "Protégez votre écosystème", - "xpack.securitySolution.guided_onboarding.tour.overviewStep.tourContent": "Faites une visite rapide pour découvrir un flux de travail unifié pour enquêter sur les activités suspectes.", - "xpack.securitySolution.guided_onboarding.tour.overviewStep.tourTitle": "Bienvenue dans Elastic Security", "xpack.securitySolution.handleInputAreaState.inputPlaceholderText": "Soumettre l’action de réponse", "xpack.securitySolution.header.editableTitle.cancel": "Annuler", "xpack.securitySolution.header.editableTitle.save": "Enregistrer", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0cc4a0683b366..671be94fced60 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -28209,21 +28209,7 @@ "xpack.securitySolution.getCurrentUser.unknownUser": "不明", "xpack.securitySolution.globalHeader.buttonAddData": "統合の追加", "xpack.securitySolution.goToDocumentationButton": "ドキュメンテーションを表示", - "xpack.securitySolution.guided_onboarding.endTour.buttonLabel": "ツアーを終了", "xpack.securitySolution.guided_onboarding.nextStep.buttonLabel": "次へ", - "xpack.securitySolution.guided_onboarding.skipTour.buttonLabel": "ツアーをスキップ", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.imageAltText": "アラートデモ", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.tourContent": "ルールの条件が満たされているときを把握し、調査をすぐに開始できるようにします。Slack、PagerDuty、ServiceNowなどのサードパーティプラットフォームで通知を設定します。", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.tourTitle": "変更が発生したときに通知", - "xpack.securitySolution.guided_onboarding.tour.casesStep.imageAltText": "ケースのデモ", - "xpack.securitySolution.guided_onboarding.tour.casesStep.tourContent": "エビデンスを収集し、その他のコラボレーターを追加し、さらにケース詳細をサードパーティケース管理システムにプッシュします。", - "xpack.securitySolution.guided_onboarding.tour.casesStep.tourTitle": "調査を追跡するには、ケースを作成します", - "xpack.securitySolution.guided_onboarding.tour.dataStep.tourContent": "Elasticエージェントとさまざまなサードパーティ統合を使用して、エンドポイントからデータを収集します。", - "xpack.securitySolution.guided_onboarding.tour.dataStep.tourTitle": "データの収集を開始してください。", - "xpack.securitySolution.guided_onboarding.tour.manageStep.tourContent": "重要な項目と環境を決定し、悪意のあるアクティビティを検出および防御するルールを作成します。", - "xpack.securitySolution.guided_onboarding.tour.manageStep.tourTitle": "エコシステムを保護", - "xpack.securitySolution.guided_onboarding.tour.overviewStep.tourContent": "不審なアクティビティの調査については、統合ワークフローを説明するクイックガイドを表示してください。", - "xpack.securitySolution.guided_onboarding.tour.overviewStep.tourTitle": "Elastic Securityへようこそ", "xpack.securitySolution.handleInputAreaState.inputPlaceholderText": "対応アクションを送信", "xpack.securitySolution.header.editableTitle.cancel": "キャンセル", "xpack.securitySolution.header.editableTitle.save": "保存", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d9d116ef0aeb6..a88ad972ae72e 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -28243,21 +28243,7 @@ "xpack.securitySolution.getCurrentUser.unknownUser": "未知", "xpack.securitySolution.globalHeader.buttonAddData": "添加集成", "xpack.securitySolution.goToDocumentationButton": "查看文档", - "xpack.securitySolution.guided_onboarding.endTour.buttonLabel": "结束教程", "xpack.securitySolution.guided_onboarding.nextStep.buttonLabel": "下一步", - "xpack.securitySolution.guided_onboarding.skipTour.buttonLabel": "跳过教程", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.imageAltText": "告警演示", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.tourContent": "知道何时满足规则条件,以便您立即开始调查。通过 Slack、PagerDuty 和 ServiceNow 等第三方平台设置通知。", - "xpack.securitySolution.guided_onboarding.tour.alertsStep.tourTitle": "发生更改时接收通知", - "xpack.securitySolution.guided_onboarding.tour.casesStep.imageAltText": "案例演示", - "xpack.securitySolution.guided_onboarding.tour.casesStep.tourContent": "收集证据,添加更多协作者,甚至将案例详情推送到第三方案例管理系统。", - "xpack.securitySolution.guided_onboarding.tour.casesStep.tourTitle": "创建案例以跟踪您的调查", - "xpack.securitySolution.guided_onboarding.tour.dataStep.tourContent": "使用 Elastic 代理和一系列第三方集成从您的终端收集数据。", - "xpack.securitySolution.guided_onboarding.tour.dataStep.tourTitle": "开始收集您的数据!", - "xpack.securitySolution.guided_onboarding.tour.manageStep.tourContent": "确定对您和您的环境至关重要的事项,并创建规则来检测并防止恶意活动。", - "xpack.securitySolution.guided_onboarding.tour.manageStep.tourTitle": "保护您的生态系统", - "xpack.securitySolution.guided_onboarding.tour.overviewStep.tourContent": "学习快速教程以浏览调查可疑活动的统一工作流。", - "xpack.securitySolution.guided_onboarding.tour.overviewStep.tourTitle": "欢迎使用 Elastic Security", "xpack.securitySolution.handleInputAreaState.inputPlaceholderText": "提交响应操作", "xpack.securitySolution.header.editableTitle.cancel": "取消", "xpack.securitySolution.header.editableTitle.save": "保存",