From 5a9fc214150e74c4d7a599c416d35e59dc8c247f Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Mon, 12 Apr 2021 11:22:07 +0200 Subject: [PATCH 01/79] [Discover] Unskip histogram hiding test (#95759) - improves the test to be no longer flaky --- test/functional/apps/discover/_discover_histogram.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/functional/apps/discover/_discover_histogram.ts b/test/functional/apps/discover/_discover_histogram.ts index 72deb74459ab9..e41422555f81d 100644 --- a/test/functional/apps/discover/_discover_histogram.ts +++ b/test/functional/apps/discover/_discover_histogram.ts @@ -21,9 +21,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; const testSubjects = getService('testSubjects'); const browser = getService('browser'); + const retry = getService('retry'); - // FLAKY: https://github.com/elastic/kibana/issues/94532 - describe.skip('discover histogram', function describeIndexTests() { + describe('discover histogram', function describeIndexTests() { before(async () => { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.load('long_window_logstash'); @@ -107,8 +107,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { canvasExists = await elasticChart.canvasExists(); expect(canvasExists).to.be(false); await testSubjects.click('discoverChartToggle'); - canvasExists = await elasticChart.canvasExists(); - expect(canvasExists).to.be(true); + await retry.waitFor(`Discover histogram to be displayed`, async () => { + canvasExists = await elasticChart.canvasExists(); + return canvasExists; + }); + await PageObjects.discover.saveSearch('persisted hidden histogram'); await PageObjects.header.waitUntilLoadingHasFinished(); From 9946125ab496f2843d84d1adfbc0c274128e9f55 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 12 Apr 2021 12:30:11 +0200 Subject: [PATCH 02/79] [Lens] Hide "Show more errors" once expanded (#96605) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../editor_frame/workspace_panel/workspace_panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 8a0b9922c736b..f9058b48dd1a8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -570,7 +570,7 @@ export const InnerVisualizationWrapper = ({ { setLocalState((prevState: WorkspaceState) => ({ From a05a66ccce5de2cd65ef28412080f93c56359cae Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 12 Apr 2021 12:49:47 +0100 Subject: [PATCH 03/79] skip flaky suite (#96691) --- .../components/flyout/add_timeline_button/index.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx index f8913148c625b..84406aed3619f 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_timeline_button/index.test.tsx @@ -35,7 +35,8 @@ jest.mock('../../../../common/components/inspect', () => ({ InspectButtonContainer: jest.fn(({ children }) =>
{children}
), })); -describe('AddTimelineButton', () => { +// FLAKY: https://github.com/elastic/kibana/issues/96691 +describe.skip('AddTimelineButton', () => { let wrapper: ReactWrapper; const props = { timelineId: TimelineId.active, From d2012c0ce3f55acabdf1d0f9f59ab22657d33d27 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 12 Apr 2021 14:25:15 +0200 Subject: [PATCH 04/79] [Lens] Make table and metric show on top Chart switcher (#96601) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../datatable_visualization/visualization.tsx | 1 + .../workspace_panel/chart_switch.tsx | 29 ++++++++++++------- .../metric_visualization/visualization.tsx | 1 + x-pack/plugins/lens/public/types.ts | 5 ++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 4094ecee74e1c..f8b56f4ff2f81 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -60,6 +60,7 @@ export const datatableVisualization: Visualization groupLabel: i18n.translate('xpack.lens.datatable.groupLabel', { defaultMessage: 'Tabular and single value', }), + sortPriority: 1, }, ], diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx index ef8c0798bb91e..5538dd26d0323 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/chart_switch.tsx @@ -219,12 +219,15 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { // reorganize visualizations in groups const grouped: Record< string, - Array< - VisualizationType & { - visualizationId: string; - selection: VisualizationSelection; - } - > + { + priority: number; + visualizations: Array< + VisualizationType & { + visualizationId: string; + selection: VisualizationSelection; + } + >; + } > = {}; // Will need it later on to quickly pick up the metadata from it const lookup: Record< @@ -240,13 +243,17 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { visualizationType.label.toLowerCase().includes(lowercasedSearchTerm) || visualizationType.fullLabel?.toLowerCase().includes(lowercasedSearchTerm); if (isSearchMatch) { - grouped[visualizationType.groupLabel] = grouped[visualizationType.groupLabel] || []; + grouped[visualizationType.groupLabel] = grouped[visualizationType.groupLabel] || { + priority: 0, + visualizations: [], + }; const visualizationEntry = { ...visualizationType, visualizationId, selection: getSelection(visualizationId, visualizationType.id), }; - grouped[visualizationType.groupLabel].push(visualizationEntry); + grouped[visualizationType.groupLabel].priority += visualizationType.sortPriority || 0; + grouped[visualizationType.groupLabel].visualizations.push(visualizationEntry); lookup[`${visualizationId}:${visualizationType.id}`] = visualizationEntry; } } @@ -254,9 +261,11 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) { return { visualizationTypes: Object.keys(grouped) - .sort() + .sort((groupA, groupB) => { + return grouped[groupB].priority - grouped[groupA].priority; + }) .flatMap((group): SelectableEntry[] => { - const visualizations = grouped[group]; + const { visualizations } = grouped[group]; if (visualizations.length === 0) { return []; } diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx index 34b9e4d2b2526..e0977be7535af 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx @@ -55,6 +55,7 @@ export const metricVisualization: Visualization = { groupLabel: i18n.translate('xpack.lens.metric.groupLabel', { defaultMessage: 'Tabular and single value', }), + sortPriority: 1, }, ], diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 3d34d22c5048a..94b4433a82551 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -550,6 +550,11 @@ export interface VisualizationType { * The group the visualization belongs to */ groupLabel: string; + /** + * The priority of the visualization in the list (global priority) + * Higher number means higher priority. When omitted defaults to 0 + */ + sortPriority?: number; } export interface Visualization { From 1de77ccb4e9c8be1e539da2d26edfa71747bcce3 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Mon, 12 Apr 2021 08:27:54 -0400 Subject: [PATCH 05/79] [Fleet] Create enrollment API keys as current user (#96464) --- .../routes/enrollment_api_key/handler.ts | 3 +- .../server/services/agent_policy_update.ts | 2 +- .../services/api_keys/enrollment_api_key.ts | 54 +++++++++++-------- .../apis/enrollment_api_keys/crud.ts | 49 ++++++++--------- 4 files changed, 57 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts index c85dc06c38286..0959a9a88704a 100644 --- a/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts +++ b/x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts @@ -67,10 +67,9 @@ export const postEnrollmentApiKeyHandler: RequestHandler< export const deleteEnrollmentApiKeyHandler: RequestHandler< TypeOf > = async (context, request, response) => { - const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; try { - await APIKeyService.deleteEnrollmentApiKey(soClient, esClient, request.params.keyId); + await APIKeyService.deleteEnrollmentApiKey(esClient, request.params.keyId); const body: DeleteEnrollmentAPIKeyResponse = { action: 'deleted' }; diff --git a/x-pack/plugins/fleet/server/services/agent_policy_update.ts b/x-pack/plugins/fleet/server/services/agent_policy_update.ts index dc566b2c435a6..3f5f717c94597 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy_update.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy_update.ts @@ -56,6 +56,6 @@ export async function agentPolicyUpdateEventHandler( if (action === 'deleted') { await unenrollForAgentPolicyId(soClient, esClient, agentPolicyId); - await deleteEnrollmentApiKeyForAgentPolicyId(soClient, esClient, agentPolicyId); + await deleteEnrollmentApiKeyForAgentPolicyId(esClient, agentPolicyId); } } diff --git a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts index 7059cc96159b9..b8a24a006a674 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/enrollment_api_key.ts @@ -17,7 +17,7 @@ import { ENROLLMENT_API_KEYS_INDEX } from '../../constants'; import { agentPolicyService } from '../agent_policy'; import { escapeSearchQueryPhrase } from '../saved_object'; -import { createAPIKey, invalidateAPIKeys } from './security'; +import { invalidateAPIKeys } from './security'; const uuidRegex = /^\([0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\)$/; @@ -77,14 +77,9 @@ export async function getEnrollmentAPIKey( /** * Invalidate an api key and mark it as inactive - * @param soClient * @param id */ -export async function deleteEnrollmentApiKey( - soClient: SavedObjectsClientContract, - esClient: ElasticsearchClient, - id: string -) { +export async function deleteEnrollmentApiKey(esClient: ElasticsearchClient, id: string) { const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id); await invalidateAPIKeys([enrollmentApiKey.api_key_id]); @@ -102,7 +97,6 @@ export async function deleteEnrollmentApiKey( } export async function deleteEnrollmentApiKeyForAgentPolicyId( - soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, agentPolicyId: string ) { @@ -120,7 +114,7 @@ export async function deleteEnrollmentApiKeyForAgentPolicyId( } for (const apiKey of items) { - await deleteEnrollmentApiKey(soClient, esClient, apiKey.id); + await deleteEnrollmentApiKey(esClient, apiKey.id); } } } @@ -182,19 +176,37 @@ export async function generateEnrollmentAPIKey( } const name = providedKeyName ? `${providedKeyName} (${id})` : id; - const key = await createAPIKey(soClient, name, { - // Useless role to avoid to have the privilege of the user that created the key - 'fleet-apikey-enroll': { - cluster: [], - applications: [ - { - application: '.fleet', - privileges: ['no-privileges'], - resources: ['*'], + + const { body: key } = await esClient.security + .createApiKey({ + body: { + name, + // @ts-expect-error Metadata in api keys + metadata: { + managed_by: 'fleet', + managed: true, + type: 'enroll', + policy_id: data.agentPolicyId, }, - ], - }, - }); + role_descriptors: { + // Useless role to avoid to have the privilege of the user that created the key + 'fleet-apikey-enroll': { + cluster: [], + index: [], + applications: [ + { + application: '.fleet', + privileges: ['no-privileges'], + resources: ['*'], + }, + ], + }, + }, + }, + }) + .catch((err) => { + throw new Error(`Impossible to create an api key: ${err.message}`); + }); if (!key) { throw new Error( diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts index 2569d9aef4b5b..d9946bb174f5d 100644 --- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts @@ -115,6 +115,28 @@ export default function (providerContext: FtrProviderContext) { expect(apiResponse.item).to.have.keys('id', 'api_key', 'api_key_id', 'name', 'policy_id'); }); + it('should create an ES ApiKey with metadata', async () => { + const { body: apiResponse } = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + }) + .expect(200); + + const { body: apiKeyRes } = await es.security.getApiKey({ + id: apiResponse.item.api_key_id, + }); + + // @ts-expect-error Metadata not yet in the client type + expect(apiKeyRes.api_keys[0].metadata).eql({ + policy_id: 'policy1', + managed_by: 'fleet', + managed: true, + type: 'enroll', + }); + }); + it('should create an ES ApiKey with limited privileges', async () => { const { body: apiResponse } = await supertest .post(`/api/fleet/enrollment-api-keys`) @@ -162,33 +184,6 @@ export default function (providerContext: FtrProviderContext) { }, }); }); - - describe('It should handle error when the Fleet user is invalid', () => { - before(async () => {}); - after(async () => { - await getService('supertest') - .post(`/api/fleet/agents/setup`) - .set('kbn-xsrf', 'xxx') - .send({ forceRecreate: true }); - }); - - it('should not allow to create an enrollment api key if the Fleet admin user is invalid', async () => { - await es.security.changePassword({ - username: 'fleet_enroll', - body: { - password: Buffer.from((Math.random() * 10000000).toString()).toString('base64'), - }, - }); - const res = await supertest - .post(`/api/fleet/enrollment-api-keys`) - .set('kbn-xsrf', 'xxx') - .send({ - policy_id: 'policy1', - }) - .expect(400); - expect(res.body.message).match(/Fleet Admin user is invalid/); - }); - }); }); }); } From 886d7e0140bfeb539aaa040056e31e2f218c4f06 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Mon, 12 Apr 2021 16:16:47 +0300 Subject: [PATCH 06/79] Stacked line charts incorrectly shows one term as 100% (#96203) * set "stacked" mode metric if the referenced axis is "percentage" * Fixed CI * Move logic inside chart_option component * Fixed CI * Update utils.ts * Update index.tsx * Update index.tsx Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../__snapshots__/chart_options.test.tsx.snap | 1 + .../metrics_axes/chart_options.test.tsx | 14 +++++++++++-- .../options/metrics_axes/chart_options.tsx | 20 +++++++++++++++++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap index 56f35ae021173..59a7cf966df91 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/__snapshots__/chart_options.test.tsx.snap @@ -54,6 +54,7 @@ exports[`ChartOptions component should init with the default set of props 1`] =
{ expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Normal); }); + + it('should set "stacked" mode and disabled control if the referenced axis is "percentage"', () => { + defaultProps.valueAxes[0].scale.mode = AxisMode.Percentage; + defaultProps.chart.mode = ChartMode.Normal; + const paramName = 'mode'; + const comp = mount(); + + expect(setParamByIndex).toBeCalledWith('seriesParams', 0, paramName, ChartMode.Stacked); + expect(comp.find({ paramName }).prop('disabled')).toBeTruthy(); + }); }); diff --git a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx index 6f0b4fc5c9d22..23452a87aae60 100644 --- a/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx +++ b/src/plugins/vis_type_xy/public/editor/components/options/metrics_axes/chart_options.tsx @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import React, { useMemo, useCallback } from 'react'; +import React, { useMemo, useCallback, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { SelectOption } from '../../../../../../vis_default_editor/public'; -import { SeriesParam, ValueAxis } from '../../../../types'; +import { SeriesParam, ValueAxis, ChartMode, AxisMode } from '../../../../types'; import { LineOptions } from './line_options'; import { SetParamByIndex, ChangeValueAxis } from '.'; import { ChartType } from '../../../../../common'; @@ -38,6 +38,7 @@ function ChartOptions({ changeValueAxis, setParamByIndex, }: ChartOptionsParams) { + const [disabledMode, setDisabledMode] = useState(false); const setChart: SetChart = useCallback( (paramName, value) => { setParamByIndex('seriesParams', index, paramName, value); @@ -68,6 +69,20 @@ function ChartOptions({ [valueAxes] ); + useEffect(() => { + const valueAxisToMetric = valueAxes.find((valueAxis) => valueAxis.id === chart.valueAxis); + if (valueAxisToMetric) { + if (valueAxisToMetric.scale.mode === AxisMode.Percentage) { + setDisabledMode(true); + if (chart.mode !== ChartMode.Stacked) { + setChart('mode', ChartMode.Stacked); + } + } else if (disabledMode) { + setDisabledMode(false); + } + } + }, [valueAxes, chart, disabledMode, setChart, setDisabledMode]); + return ( <> From c40121151fdf9ed17582e53902d214e6bed49ba6 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Mon, 12 Apr 2021 09:43:06 -0400 Subject: [PATCH 07/79] [Fleet] UI changes on hosted policy detail view (#96337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes several items from https://github.com/elastic/observability-design/issues/32 - Agent policy detail page - [x] Integrations tab: 1a) Show a lock icon with hover tooltip next to host policy name - [x] Integrations tab: 7a) hide the "Add integration" button - [x] Integrations tab: 7b) hide the "delete integration" action which appears in the [...] actions menu - [x] Settings tab: 5a) Do not show the “Delete policy” section for Hosted agent policies - [x] Settings tab: 5b) Disable the "name" and "description" inputs - Agents detail page - [x] 2b) remove the "actions" button in the page header (top right) ## Screenshots
Agent policy detail page - Integrations tab
  • 1a) Show a lock icon with hover tooltip next to host policy name
  • 7a) hide the "Add integration" button
  • 7b) hide the "delete integration" action which appears in the [...] actions menu

Non-hosted policy

Screen Shot 2021-04-08 at 1 30 24 PM

Hosted policy

Screen Shot 2021-04-08 at 1 29 26 PM
Agent policy detail page - Settings tab
  • 5a) Do not show the “Delete policy” section for Hosted agent policies
  • 5b) Disable the "name" and "description" inputs

non-hosted policy: items available

Screen Shot 2021-04-07 at 1 24 39 PM

Hosted policy: items hidden / disabled

Screen Shot 2021-04-07 at 1 24 23 PM
Agents detail page: 2b) remove the "actions" button in the page header (top right)

shown on non-hosted policy

Screen Shot 2021-04-08 at 9 55 06 AM

hidden on hosted policy

Screen Shot 2021-04-08 at 9 55 31 AM
### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/agent_policy_form.tsx | 3 +- .../package_policies_table.tsx | 112 +++++++++--------- .../agent_policy/details_page/index.tsx | 51 +++++--- .../agents/agent_details_page/index.tsx | 23 ++-- .../sections/agents/agent_list_page/index.tsx | 5 +- 5 files changed, 112 insertions(+), 82 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx index 238cba217da8e..a1ac30995f722 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/components/agent_policy_form.tsx @@ -144,6 +144,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({ isInvalid={Boolean(touchedFields[name] && validation[name])} > updateAgentPolicy({ [name]: e.target.value })} @@ -283,7 +284,7 @@ export const AgentPolicyForm: React.FunctionComponent = ({ }} /> - {isEditing && 'id' in agentPolicy ? ( + {isEditing && 'id' in agentPolicy && agentPolicy.is_managed !== true ? ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx index db88de0ba720b..9e23fc775a213 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/components/package_policies/package_policies_table.tsx @@ -167,42 +167,45 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ }), actions: [ { - render: (packagePolicy: InMemoryPackagePolicy) => ( - {}} - // key="packagePolicyView" - // > - // - // , - - - , - // FIXME: implement Copy package policy action - // {}} key="packagePolicyCopy"> - // - // , + render: (packagePolicy: InMemoryPackagePolicy) => { + const menuItems = [ + // FIXME: implement View package policy action + // {}} + // key="packagePolicyView" + // > + // + // , + + + , + // FIXME: implement Copy package policy action + // {}} key="packagePolicyCopy"> + // + // , + ]; + + if (!agentPolicy.is_managed) { + menuItems.push( {(deletePackagePoliciesPrompt) => { return ( @@ -220,10 +223,11 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ ); }} - , - ]} - /> - ), + + ); + } + return ; + }, }, ], }, @@ -244,19 +248,21 @@ export const PackagePoliciesTable: React.FunctionComponent = ({ }} {...rest} search={{ - toolsRight: [ - - - , - ], + toolsRight: agentPolicy.is_managed + ? [] + : [ + + + , + ], box: { incremental: true, schema: true, diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx index 350d6439c9d3d..3e6ca5944c380 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/details_page/index.tsx @@ -12,6 +12,8 @@ import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, + EuiIconTip, + EuiTitle, EuiText, EuiSpacer, EuiButtonEmpty, @@ -84,23 +86,42 @@ export const AgentPolicyDetailsPage: React.FunctionComponent = () => {
- -

- {isLoading ? ( - - ) : ( - (agentPolicy && agentPolicy.name) || ( - + ) : ( + + + +

+ {(agentPolicy && agentPolicy.name) || ( + + )} +

+
+
+ {agentPolicy?.is_managed && ( + + - ) + )} -

-
+ + )}
{agentPolicy && agentPolicy.description ? ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx index adeb56f489ea3..56b99f645f97c 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/index.tsx @@ -194,17 +194,18 @@ export const AgentDetailsPage: React.FunctionComponent = () => { ), }, { - content: ( - - ), + content: + isAgentPolicyLoading || agentPolicyData?.item?.is_managed ? undefined : ( + + ), }, ].map((item, index) => ( diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx index 8e9c549fe5609..d01d290e129b8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/index.tsx @@ -341,9 +341,10 @@ export const AgentListPage: React.FunctionComponent<{}> = () => { const isAgentSelectable = (agent: Agent) => { if (!agent.active) return false; + if (!agent.policy_id) return true; - const agentPolicy = agentPolicies.find((p) => p.id === agent.policy_id); - const isManaged = agent.policy_id && agentPolicy?.is_managed === true; + const agentPolicy = agentPoliciesIndexedById[agent.policy_id]; + const isManaged = agentPolicy?.is_managed === true; return !isManaged; }; From a2c47ef5f5890856c63e3ddfa769f859467c45d5 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 12 Apr 2021 15:53:53 +0200 Subject: [PATCH 08/79] [Exploratory View]Additional metrics for kpi over time (#96532) --- x-pack/plugins/lens/public/index.ts | 1 + .../public/indexpattern_datasource/types.ts | 1 + .../apm/service_latency_config.ts | 7 +- .../apm/service_throughput_config.ts | 9 +- .../configurations/constants/constants.ts | 2 + .../configurations/constants/url_constants.ts | 2 +- .../configurations/lens_attributes.test.ts | 85 +++++++--- .../configurations/lens_attributes.ts | 136 ++++++++++++---- .../logs/logs_frequency_config.ts | 2 +- .../metrics/cpu_usage_config.ts | 5 +- .../metrics/memory_usage_config.ts | 5 +- .../metrics/network_activity_config.ts | 5 +- .../configurations/rum/kpi_trends_config.ts | 33 ++-- .../rum/performance_dist_config.ts | 10 +- .../synthetics/field_formats.ts | 1 + .../synthetics/monitor_duration_config.ts | 7 +- .../synthetics/monitor_pings_config.ts | 2 +- .../exploratory_view/configurations/utils.ts | 11 +- .../exploratory_view/exploratory_view.tsx | 19 +-- .../hooks/use_default_index_pattern.tsx | 1 + .../hooks/use_init_exploratory_view.ts | 14 +- .../hooks/use_lens_attributes.ts | 13 +- .../hooks/use_url_storage.tsx | 6 +- .../columns/chart_types.test.tsx | 12 +- .../series_builder/columns/chart_types.tsx | 104 ++++++++++++ .../columns/data_types_col.test.tsx | 4 +- .../series_builder/columns/data_types_col.tsx | 12 +- .../columns/operation_type_select.test.tsx | 64 ++++++++ .../columns/operation_type_select.tsx | 82 ++++++++++ .../columns/report_definition_col.tsx | 22 ++- .../columns/report_types_col.test.tsx | 6 +- .../columns/report_types_col.tsx | 7 + .../series_builder/series_builder.tsx | 15 +- .../series_date_picker/index.tsx | 3 +- .../series_date_picker.test.tsx | 3 +- .../series_editor/columns/actions_col.tsx | 12 +- .../series_editor/columns/chart_types.tsx | 149 ------------------ .../columns/metric_selection.test.tsx | 112 ------------- .../columns/metric_selection.tsx | 86 ---------- .../shared/exploratory_view/types.ts | 15 +- .../utils/observability_index_patterns.ts | 12 +- 41 files changed, 585 insertions(+), 512 deletions(-) rename x-pack/plugins/observability/public/components/shared/exploratory_view/{series_editor => series_builder}/columns/chart_types.test.tsx (74%) create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx create mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx delete mode 100644 x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index cedb648215c0e..fcfed9b9f1fc5 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -33,6 +33,7 @@ export type { IndexPatternPersistedState, PersistedIndexPatternLayer, IndexPatternColumn, + FieldBasedIndexPatternColumn, OperationType, IncompleteColumn, FiltersIndexPatternColumn, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts index 79155184a5f6d..18f653c588ee8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/types.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/types.ts @@ -11,6 +11,7 @@ import { IndexPatternAggRestrictions } from '../../../../../src/plugins/data/pub import { DragDropIdentifier } from '../drag_drop/providers'; export { + FieldBasedIndexPatternColumn, IndexPatternColumn, OperationType, IncompleteColumn, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts index 3fcf98f712bef..7af3252584819 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_latency_config.ts @@ -8,7 +8,6 @@ import { ConfigProps, DataSeries } from '../../types'; import { FieldLabels } from '../constants'; import { buildPhraseFilter } from '../utils'; -import { OperationType } from '../../../../../../../lens/public'; export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { return { @@ -20,11 +19,11 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'average' as OperationType, + operationType: 'average', sourceField: 'transaction.duration.us', label: 'Latency', }, - hasMetricType: true, + hasOperationType: true, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', @@ -37,7 +36,7 @@ export function getServiceLatencyLensConfig({ seriesId, indexPattern }: ConfigPr 'client.geo.country_name', 'user_agent.device.name', ], - filters: [buildPhraseFilter('transaction.type', 'request', indexPattern)], + filters: buildPhraseFilter('transaction.type', 'request', indexPattern), labels: { ...FieldLabels }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts index c0f3d6dc9b010..7b1d472ac8bbf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/apm/service_throughput_config.ts @@ -8,7 +8,6 @@ import { ConfigProps, DataSeries } from '../../types'; import { FieldLabels } from '../constants/constants'; import { buildPhraseFilter } from '../utils'; -import { OperationType } from '../../../../../../../lens/public'; export function getServiceThroughputLensConfig({ seriesId, @@ -16,18 +15,18 @@ export function getServiceThroughputLensConfig({ }: ConfigProps): DataSeries { return { id: seriesId, - reportType: 'service-latency', + reportType: 'service-throughput', defaultSeriesType: 'line', seriesTypes: ['line', 'bar'], xAxisColumn: { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'average' as OperationType, + operationType: 'average', sourceField: 'transaction.duration.us', label: 'Throughput', }, - hasMetricType: true, + hasOperationType: true, defaultFilters: [ 'user_agent.name', 'user_agent.os.name', @@ -40,7 +39,7 @@ export function getServiceThroughputLensConfig({ 'client.geo.country_name', 'user_agent.device.name', ], - filters: [buildPhraseFilter('transaction.type', 'request', indexPattern)], + filters: buildPhraseFilter('transaction.type', 'request', indexPattern), labels: { ...FieldLabels }, reportDefinitions: [ { diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts index ed849c1eb47b3..14cd24c42e6a2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/constants.ts @@ -8,6 +8,8 @@ import { AppDataType, ReportViewTypeId } from '../../types'; import { CLS_FIELD, FCP_FIELD, FID_FIELD, LCP_FIELD, TBT_FIELD } from './elasticsearch_fieldnames'; +export const DEFAULT_TIME = { from: 'now-1h', to: 'now' }; + export const FieldLabels: Record = { 'user_agent.name': 'Browser family', 'user_agent.version': 'Browser version', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts index 5b99c19dbabb7..67d72a656744c 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/constants/url_constants.ts @@ -6,7 +6,7 @@ */ export enum URL_KEYS { - METRIC_TYPE = 'mt', + OPERATION_TYPE = 'op', REPORT_TYPE = 'rt', SERIES_TYPE = 'st', BREAK_DOWN = 'bd', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts index 139f3ab0d82ed..0de78c45041d4 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.test.ts @@ -42,14 +42,18 @@ describe('Lens Attribute', () => { it('should return expected field type', function () { expect(JSON.stringify(lnsAttr.getFieldMeta('transaction.type'))).toEqual( JSON.stringify({ - count: 0, - name: 'transaction.type', - type: 'string', - esTypes: ['keyword'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, + fieldMeta: { + count: 0, + name: 'transaction.type', + type: 'string', + esTypes: ['keyword'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + fieldName: 'transaction.type', + columnType: null, }) ); }); @@ -57,14 +61,18 @@ describe('Lens Attribute', () => { it('should return expected field type for custom field with default value', function () { expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual( JSON.stringify({ - count: 0, - name: 'transaction.duration.us', - type: 'number', - esTypes: ['long'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, + fieldMeta: { + count: 0, + name: 'transaction.duration.us', + type: 'number', + esTypes: ['long'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + fieldName: 'transaction.duration.us', + columnType: null, }) ); }); @@ -76,20 +84,45 @@ describe('Lens Attribute', () => { expect(JSON.stringify(lnsAttr.getFieldMeta('performance.metric'))).toEqual( JSON.stringify({ - count: 0, - name: LCP_FIELD, - type: 'number', - esTypes: ['scaled_float'], - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, + fieldMeta: { + count: 0, + name: LCP_FIELD, + type: 'number', + esTypes: ['scaled_float'], + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, + fieldName: LCP_FIELD, }) ); }); - it('should return expected number column', function () { - expect(lnsAttr.getNumberColumn('transaction.duration.us')).toEqual({ + it('should return expected number range column', function () { + expect(lnsAttr.getNumberRangeColumn('transaction.duration.us')).toEqual({ + dataType: 'number', + isBucketed: true, + label: 'Page load time (Seconds)', + operationType: 'range', + params: { + maxBars: 'auto', + ranges: [ + { + from: 0, + label: '', + to: 1000, + }, + ], + type: 'histogram', + }, + scale: 'interval', + sourceField: 'transaction.duration.us', + }); + }); + + it('should return expected number operation column', function () { + expect(lnsAttr.getNumberRangeColumn('transaction.duration.us')).toEqual({ dataType: 'number', isBucketed: true, label: 'Page load time (Seconds)', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts index 589a93d160068..12a5b19fb02fc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/lens_attributes.ts @@ -5,10 +5,14 @@ * 2.0. */ +import { i18n } from '@kbn/i18n'; +import { capitalize } from 'lodash'; import { CountIndexPatternColumn, DateHistogramIndexPatternColumn, - LastValueIndexPatternColumn, + AvgIndexPatternColumn, + MedianIndexPatternColumn, + PercentileIndexPatternColumn, OperationType, PersistedIndexPatternLayer, RangeIndexPatternColumn, @@ -17,6 +21,8 @@ import { XYState, XYCurveType, DataType, + OperationMetadata, + FieldBasedIndexPatternColumn, } from '../../../../../../lens/public'; import { buildPhraseFilter, @@ -30,6 +36,15 @@ function getLayerReferenceName(layerId: string) { return `indexpattern-datasource-layer-${layerId}`; } +function buildNumberColumn(sourceField: string) { + return { + sourceField, + dataType: 'number' as DataType, + isBucketed: false, + scale: 'ratio' as OperationMetadata['scale'], + }; +} + export class LensAttributes { indexPattern: IndexPattern; layers: Record; @@ -44,7 +59,7 @@ export class LensAttributes { reportViewConfig: DataSeries, seriesType?: SeriesType, filters?: UrlFilter[], - metricType?: OperationType, + operationType?: OperationType, reportDefinitions?: Record ) { this.indexPattern = indexPattern; @@ -52,8 +67,8 @@ export class LensAttributes { this.filters = filters ?? []; this.reportDefinitions = reportDefinitions ?? {}; - if (typeof reportViewConfig.yAxisColumn.operationType !== undefined && metricType) { - reportViewConfig.yAxisColumn.operationType = metricType; + if (typeof reportViewConfig.yAxisColumn.operationType !== undefined && operationType) { + reportViewConfig.yAxisColumn.operationType = operationType as FieldBasedIndexPatternColumn['operationType']; } this.seriesType = seriesType ?? reportViewConfig.defaultSeriesType; this.reportViewConfig = reportViewConfig; @@ -93,7 +108,7 @@ export class LensAttributes { this.visualization.layers[0].splitAccessor = undefined; } - getNumberColumn(sourceField: string): RangeIndexPatternColumn { + getNumberRangeColumn(sourceField: string): RangeIndexPatternColumn { return { sourceField, label: this.reportViewConfig.labels[sourceField], @@ -109,6 +124,38 @@ export class LensAttributes { }; } + getNumberOperationColumn( + sourceField: string, + operationType: 'average' | 'median' + ): AvgIndexPatternColumn | MedianIndexPatternColumn { + return { + ...buildNumberColumn(sourceField), + label: i18n.translate('xpack.observability.expView.columns.operation.label', { + defaultMessage: '{operationType} of {sourceField}', + values: { + sourceField: this.reportViewConfig.labels[sourceField], + operationType: capitalize(operationType), + }, + }), + operationType, + }; + } + + getPercentileNumberColumn( + sourceField: string, + percentileValue: string + ): PercentileIndexPatternColumn { + return { + ...buildNumberColumn(sourceField), + label: i18n.translate('xpack.observability.expView.columns.label', { + defaultMessage: '{percentileValue} percentile of {sourceField}', + values: { sourceField, percentileValue }, + }), + operationType: 'percentile', + params: { percentile: Number(percentileValue.split('th')[0]) }, + }; + } + getDateHistogramColumn(sourceField: string): DateHistogramIndexPatternColumn { return { sourceField, @@ -121,56 +168,89 @@ export class LensAttributes { }; } - getXAxis(): - | LastValueIndexPatternColumn - | DateHistogramIndexPatternColumn - | RangeIndexPatternColumn { + getXAxis() { const { xAxisColumn } = this.reportViewConfig; - const { type: fieldType, name: fieldName } = this.getFieldMeta(xAxisColumn.sourceField)!; + return this.getColumnBasedOnType(xAxisColumn.sourceField!); + } + + getColumnBasedOnType(sourceField: string, operationType?: OperationType) { + const { fieldMeta, columnType, fieldName } = this.getFieldMeta(sourceField); + const { type: fieldType } = fieldMeta ?? {}; + + if (fieldName === 'Records') { + return this.getRecordsColumn(); + } if (fieldType === 'date') { return this.getDateHistogramColumn(fieldName); } if (fieldType === 'number') { - return this.getNumberColumn(fieldName); + if (columnType === 'operation' || operationType) { + if (operationType === 'median' || operationType === 'average') { + return this.getNumberOperationColumn(fieldName, operationType); + } + if (operationType?.includes('th')) { + return this.getPercentileNumberColumn(sourceField, operationType); + } + } + return this.getNumberRangeColumn(fieldName); } // FIXME review my approach again return this.getDateHistogramColumn(fieldName); } - getFieldMeta(sourceField?: string) { - let xAxisField = sourceField; + getCustomFieldName(sourceField: string) { + let fieldName = sourceField; + let columnType = null; - if (xAxisField) { - const rdf = this.reportViewConfig.reportDefinitions ?? []; + const rdf = this.reportViewConfig.reportDefinitions ?? []; - const customField = rdf.find(({ field }) => field === xAxisField); + const customField = rdf.find(({ field }) => field === fieldName); - if (customField) { - if (this.reportDefinitions[xAxisField]) { - xAxisField = this.reportDefinitions[xAxisField]; - } else if (customField.defaultValue) { - xAxisField = customField.defaultValue; - } else if (customField.options?.[0].field) { - xAxisField = customField.options?.[0].field; - } + if (customField) { + if (this.reportDefinitions[fieldName]) { + fieldName = this.reportDefinitions[fieldName]; + if (customField?.options) + columnType = customField?.options?.find(({ field }) => field === fieldName)?.columnType; + } else if (customField.defaultValue) { + fieldName = customField.defaultValue; + } else if (customField.options?.[0].field) { + fieldName = customField.options?.[0].field; + columnType = customField.options?.[0].columnType; } - - return this.indexPattern.getFieldByName(xAxisField); } + + return { fieldName, columnType }; + } + + getFieldMeta(sourceField: string) { + const { fieldName, columnType } = this.getCustomFieldName(sourceField); + + const fieldMeta = this.indexPattern.getFieldByName(fieldName); + + return { fieldMeta, fieldName, columnType }; } getMainYAxis() { + const { sourceField, operationType, label } = this.reportViewConfig.yAxisColumn; + + if (sourceField === 'Records' || !sourceField) { + return this.getRecordsColumn(label); + } + + return this.getColumnBasedOnType(sourceField!, operationType); + } + + getRecordsColumn(label?: string): CountIndexPatternColumn { return { dataType: 'number', isBucketed: false, - label: 'Count of records', + label: label || 'Count of records', operationType: 'count', scale: 'ratio', sourceField: 'Records', - ...this.reportViewConfig.yAxisColumn, } as CountIndexPatternColumn; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts index 8a27d7ddd428b..9f8a336b59d34 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/logs/logs_frequency_config.ts @@ -24,7 +24,7 @@ export function getLogsFrequencyLensConfig({ seriesId }: Props): DataSeries { yAxisColumn: { operationType: 'count', }, - hasMetricType: false, + hasOperationType: false, defaultFilters: [], breakdowns: ['agent.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts index 6214975d8f1dd..d4b807de11f4e 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/cpu_usage_config.ts @@ -7,7 +7,6 @@ import { DataSeries } from '../../types'; import { FieldLabels } from '../constants'; -import { OperationType } from '../../../../../../../lens/public'; interface Props { seriesId: string; @@ -23,11 +22,11 @@ export function getCPUUsageLensConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'average' as OperationType, + operationType: 'average', sourceField: 'system.cpu.user.pct', label: 'CPU Usage %', }, - hasMetricType: true, + hasOperationType: true, defaultFilters: [], breakdowns: ['host.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts index 6f46c175f7882..38d1c425fc09a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/memory_usage_config.ts @@ -7,7 +7,6 @@ import { DataSeries } from '../../types'; import { FieldLabels } from '../constants'; -import { OperationType } from '../../../../../../../lens/public'; interface Props { seriesId: string; @@ -23,11 +22,11 @@ export function getMemoryUsageLensConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'average' as OperationType, + operationType: 'average', sourceField: 'system.memory.used.pct', label: 'Memory Usage %', }, - hasMetricType: true, + hasOperationType: true, defaultFilters: [], breakdowns: ['host.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts index 1bc9fed9c3f80..07a521225b38d 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/metrics/network_activity_config.ts @@ -7,7 +7,6 @@ import { DataSeries } from '../../types'; import { FieldLabels } from '../constants'; -import { OperationType } from '../../../../../../../lens/public'; interface Props { seriesId: string; @@ -23,10 +22,10 @@ export function getNetworkActivityLensConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'average' as OperationType, + operationType: 'average', sourceField: 'system.memory.used.pct', }, - hasMetricType: true, + hasOperationType: true, defaultFilters: [], breakdowns: ['host.hostname'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts index a1a3acd51f89c..cd38d912850cf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/kpi_trends_config.ts @@ -10,14 +10,21 @@ import { FieldLabels } from '../constants'; import { buildPhraseFilter } from '../utils'; import { CLIENT_GEO_COUNTRY_NAME, + CLS_FIELD, + FCP_FIELD, + FID_FIELD, + LCP_FIELD, PROCESSOR_EVENT, SERVICE_ENVIRONMENT, SERVICE_NAME, + TBT_FIELD, + TRANSACTION_DURATION, TRANSACTION_TYPE, USER_AGENT_DEVICE, USER_AGENT_NAME, USER_AGENT_OS, USER_AGENT_VERSION, + TRANSACTION_TIME_TO_FIRST_BYTE, } from '../constants/elasticsearch_fieldnames'; export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): DataSeries { @@ -30,10 +37,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'count', - label: 'Page views', + sourceField: 'business.kpi', + operationType: 'median', }, - hasMetricType: false, + hasOperationType: false, defaultFilters: [ USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, @@ -45,10 +52,10 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): ], breakdowns: [USER_AGENT_NAME, USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, USER_AGENT_DEVICE], filters: [ - buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern), - buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern), + ...buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern), + ...buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern), ], - labels: { ...FieldLabels, SERVICE_NAME: 'Web Application' }, + labels: { ...FieldLabels, [SERVICE_NAME]: 'Web Application' }, reportDefinitions: [ { field: SERVICE_NAME, @@ -58,14 +65,18 @@ export function getKPITrendsLensConfig({ seriesId, indexPattern }: ConfigProps): field: SERVICE_ENVIRONMENT, }, { - field: 'Business.KPI', + field: 'business.kpi', custom: true, defaultValue: 'Records', options: [ - { - field: 'Records', - label: 'Page views', - }, + { field: 'Records', label: 'Page views' }, + { label: 'Page load time', field: TRANSACTION_DURATION, columnType: 'operation' }, + { label: 'Backend time', field: TRANSACTION_TIME_TO_FIRST_BYTE, columnType: 'operation' }, + { label: 'First contentful paint', field: FCP_FIELD, columnType: 'operation' }, + { label: 'Total blocking time', field: TBT_FIELD, columnType: 'operation' }, + { label: 'Largest contentful paint', field: LCP_FIELD, columnType: 'operation' }, + { label: 'First input delay', field: FID_FIELD, columnType: 'operation' }, + { label: 'Cumulative layout shift', field: CLS_FIELD, columnType: 'operation' }, ], }, ], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts index 7005dea29d60d..4b6d5dd6e741b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/rum/performance_dist_config.ts @@ -19,6 +19,7 @@ import { SERVICE_NAME, TBT_FIELD, TRANSACTION_DURATION, + TRANSACTION_TIME_TO_FIRST_BYTE, TRANSACTION_TYPE, USER_AGENT_DEVICE, USER_AGENT_NAME, @@ -36,10 +37,10 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP sourceField: 'performance.metric', }, yAxisColumn: { - operationType: 'count', + sourceField: 'Records', label: 'Pages loaded', }, - hasMetricType: false, + hasOperationType: false, defaultFilters: [ USER_AGENT_OS, CLIENT_GEO_COUNTRY_NAME, @@ -64,6 +65,7 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP defaultValue: TRANSACTION_DURATION, options: [ { label: 'Page load time', field: TRANSACTION_DURATION }, + { label: 'Backend time', field: TRANSACTION_TIME_TO_FIRST_BYTE }, { label: 'First contentful paint', field: FCP_FIELD }, { label: 'Total blocking time', field: TBT_FIELD }, // FIXME, review if we need these descriptions @@ -74,8 +76,8 @@ export function getPerformanceDistLensConfig({ seriesId, indexPattern }: ConfigP }, ], filters: [ - buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern), - buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern), + ...buildPhraseFilter(TRANSACTION_TYPE, 'page-load', indexPattern), + ...buildPhraseFilter(PROCESSOR_EVENT, 'transaction', indexPattern), ], labels: { ...FieldLabels, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts index 4f036f0b9be65..8dad1839f0bcd 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/field_formats.ts @@ -16,6 +16,7 @@ export const syntheticsFieldFormats: FieldFormat[] = [ inputFormat: 'microseconds', outputFormat: 'asMilliseconds', outputPrecision: 0, + showSuffix: true, }, }, }, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts index f0ec3f0c31bef..efbc3d14441c2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_duration_config.ts @@ -6,8 +6,7 @@ */ import { DataSeries } from '../../types'; -import { FieldLabels } from '../constants/constants'; -import { OperationType } from '../../../../../../../lens/public'; +import { FieldLabels } from '../constants'; interface Props { seriesId: string; @@ -23,11 +22,11 @@ export function getMonitorDurationConfig({ seriesId }: Props): DataSeries { sourceField: '@timestamp', }, yAxisColumn: { - operationType: 'average' as OperationType, + operationType: 'average', sourceField: 'monitor.duration.us', label: 'Monitor duration (ms)', }, - hasMetricType: true, + hasOperationType: true, defaultFilters: ['monitor.type', 'observer.geo.name', 'tags'], breakdowns: [ 'observer.geo.name', diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts index 40c9f5750fb4d..68a36dcdcaf85 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/synthetics/monitor_pings_config.ts @@ -25,7 +25,7 @@ export function getMonitorPingsConfig({ seriesId }: Props): DataSeries { operationType: 'count', label: 'Monitor pings', }, - hasMetricType: false, + hasOperationType: false, defaultFilters: ['observer.geo.name'], breakdowns: ['monitor.status', 'observer.geo.name', 'monitor.type'], filters: [], diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts index c885673134786..c6b7b5d92d5f8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/configurations/utils.ts @@ -13,7 +13,7 @@ import { URL_KEYS } from './constants/url_constants'; export function convertToShortUrl(series: SeriesUrl) { const { - metric, + operationType, seriesType, reportType, breakdown, @@ -23,7 +23,7 @@ export function convertToShortUrl(series: SeriesUrl) { } = series; return { - [URL_KEYS.METRIC_TYPE]: metric, + [URL_KEYS.OPERATION_TYPE]: operationType, [URL_KEYS.REPORT_TYPE]: reportType, [URL_KEYS.SERIES_TYPE]: seriesType, [URL_KEYS.BREAK_DOWN]: breakdown, @@ -49,6 +49,9 @@ export function createExploratoryViewUrl(allSeries: AllSeries, baseHref = '') { } export function buildPhraseFilter(field: string, value: any, indexPattern: IIndexPattern) { - const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field)!; - return esFilters.buildPhraseFilter(fieldMeta, value, indexPattern); + const fieldMeta = indexPattern.fields.find((fieldT) => fieldT.name === field); + if (fieldMeta) { + return [esFilters.buildPhraseFilter(fieldMeta, value, indexPattern)]; + } + return []; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx index 0e7bc80e8659c..6bc069aafa5b8 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/exploratory_view.tsx @@ -6,8 +6,7 @@ */ import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; -import styled from 'styled-components'; -import { EuiLoadingSpinner, EuiPanel, EuiTitle } from '@elastic/eui'; +import { EuiPanel, EuiTitle } from '@elastic/eui'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { ObservabilityPublicPluginsStart } from '../../../plugin'; import { ExploratoryViewHeader } from './header/header'; @@ -15,7 +14,6 @@ import { SeriesEditor } from './series_editor/series_editor'; import { useUrlStorage } from './hooks/use_url_storage'; import { useLensAttributes } from './hooks/use_lens_attributes'; import { EmptyView } from './components/empty_view'; -import { useIndexPatternContext } from './hooks/use_default_index_pattern'; import { TypedLensByValueInput } from '../../../../../lens/public'; export function ExploratoryView() { @@ -27,15 +25,12 @@ export function ExploratoryView() { null ); - const { indexPattern } = useIndexPatternContext(); - const LensComponent = lens?.EmbeddableComponent; const { firstSeriesId: seriesId, firstSeries: series } = useUrlStorage(); const lensAttributesT = useLensAttributes({ seriesId, - indexPattern, }); useEffect(() => { @@ -48,11 +43,6 @@ export function ExploratoryView() { {lens ? ( <> - {!indexPattern && ( - - - - )} {lensAttributes && seriesId && series?.reportType && series?.time ? ( ); } - -const SpinnerWrap = styled.div` - height: 100vh; - display: flex; - justify-content: center; - align-items: center; -`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx index 7ead7d5e3cfad..c5a4d02492662 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_default_index_pattern.tsx @@ -39,6 +39,7 @@ export function IndexPatternContextProvider({ } = useKibana(); const loadIndexPattern = async (dataType: AppDataType) => { + setIndexPattern(undefined); const obsvIndexP = new ObservabilityIndexPatterns(data); const indPattern = await obsvIndexP.getIndexPattern(dataType); setIndexPattern(indPattern!); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts index 76fd64ef86736..de4343b290118 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_init_exploratory_view.ts @@ -27,15 +27,17 @@ export const useInitExploratoryView = (storage: IKbnUrlStateStorage) => { const firstSeries = allSeries[firstSeriesId]; + let dataType: DataType = firstSeries?.dataType ?? 'rum'; + + if (firstSeries?.rt) { + dataType = ReportToDataTypeMap[firstSeries?.rt]; + } + const { data: indexPattern, error } = useFetcher(() => { const obsvIndexP = new ObservabilityIndexPatterns(data); - let reportType: DataType = 'apm'; - if (firstSeries?.rt) { - reportType = ReportToDataTypeMap[firstSeries?.rt]; - } - return obsvIndexP.getIndexPattern(reportType); - }, [firstSeries?.rt, data]); + return obsvIndexP.getIndexPattern(dataType); + }, [dataType, data]); if (error) { throw error; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts index 274542380c137..555b21618c4b2 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_lens_attributes.ts @@ -11,12 +11,11 @@ import { LensAttributes } from '../configurations/lens_attributes'; import { useUrlStorage } from './use_url_storage'; import { getDefaultConfigs } from '../configurations/default_configs'; -import { IndexPattern } from '../../../../../../../../src/plugins/data/common'; import { DataSeries, SeriesUrl, UrlFilter } from '../types'; +import { useIndexPatternContext } from './use_default_index_pattern'; interface Props { seriesId: string; - indexPattern?: IndexPattern | null; } export const getFiltersFromDefs = ( @@ -39,12 +38,12 @@ export const getFiltersFromDefs = ( export const useLensAttributes = ({ seriesId, - indexPattern, }: Props): TypedLensByValueInput['attributes'] | null => { const { series } = useUrlStorage(seriesId); - const { breakdown, seriesType, metric: metricType, reportType, reportDefinitions = {} } = - series ?? {}; + const { breakdown, seriesType, operationType, reportType, reportDefinitions = {} } = series ?? {}; + + const { indexPattern } = useIndexPatternContext(); return useMemo(() => { if (!indexPattern || !reportType) { @@ -66,7 +65,7 @@ export const useLensAttributes = ({ dataViewConfig, seriesType, filters, - metricType, + operationType, reportDefinitions ); @@ -79,7 +78,7 @@ export const useLensAttributes = ({ indexPattern, breakdown, seriesType, - metricType, + operationType, reportType, reportDefinitions, seriesId, diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx index 6256b3b134f8c..a4fe15025245a 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/hooks/use_url_storage.tsx @@ -26,9 +26,9 @@ export function UrlStorageContextProvider({ } function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { - const { mt, st, rt, bd, ft, time, rdf, ...restSeries } = newValue; + const { op, st, rt, bd, ft, time, rdf, ...restSeries } = newValue; return { - metric: mt, + operationType: op, reportType: rt!, seriesType: st, breakdown: bd, @@ -40,7 +40,7 @@ function convertFromShortUrl(newValue: ShortUrlSeries): SeriesUrl { } interface ShortUrlSeries { - [URL_KEYS.METRIC_TYPE]?: OperationType; + [URL_KEYS.OPERATION_TYPE]?: OperationType; [URL_KEYS.REPORT_TYPE]?: ReportViewTypeId; [URL_KEYS.SERIES_TYPE]?: SeriesType; [URL_KEYS.BREAK_DOWN]?: string; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.test.tsx similarity index 74% rename from x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx rename to x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.test.tsx index f291d0de4dac0..bac935dbecbe7 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.test.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { fireEvent, screen, waitFor } from '@testing-library/react'; -import { SeriesChartTypes, XYChartTypes } from './chart_types'; import { mockUrlStorage, render } from '../../rtl_helpers'; +import { SeriesChartTypesSelect, XYChartTypesSelect } from './chart_types'; -describe.skip('SeriesChartTypes', function () { +describe.skip('SeriesChartTypesSelect', function () { it('should render properly', async function () { mockUrlStorage({}); - render(); + render(); await waitFor(() => { screen.getByText(/chart type/i); @@ -24,7 +24,7 @@ describe.skip('SeriesChartTypes', function () { it('should call set series on change', async function () { const { setSeries } = mockUrlStorage({}); - render(); + render(); await waitFor(() => { screen.getByText(/chart type/i); @@ -42,11 +42,11 @@ describe.skip('SeriesChartTypes', function () { expect(setSeries).toHaveBeenCalledTimes(3); }); - describe('XYChartTypes', function () { + describe('XYChartTypesSelect', function () { it('should render properly', async function () { mockUrlStorage({}); - render(); + render(); await waitFor(() => { screen.getByText(/chart type/i); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx new file mode 100644 index 0000000000000..029c39df13aad --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/chart_types.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSuperSelect } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { ObservabilityPublicPluginsStart } from '../../../../../plugin'; +import { useFetcher } from '../../../../..'; +import { useUrlStorage } from '../../hooks/use_url_storage'; +import { SeriesType } from '../../../../../../../lens/public'; + +export function SeriesChartTypesSelect({ + seriesId, + defaultChartType, +}: { + seriesId: string; + defaultChartType: SeriesType; +}) { + const { series, setSeries, allSeries } = useUrlStorage(seriesId); + + const seriesType = series?.seriesType ?? defaultChartType; + + const onChange = (value: SeriesType) => { + Object.keys(allSeries).forEach((seriesKey) => { + const seriesN = allSeries[seriesKey]; + + setSeries(seriesKey, { ...seriesN, seriesType: value }); + }); + }; + + return ( + + ); +} + +export interface XYChartTypesProps { + label?: string; + value: SeriesType; + includeChartTypes?: SeriesType[]; + excludeChartTypes?: SeriesType[]; + onChange: (value: SeriesType) => void; +} + +export function XYChartTypesSelect({ + onChange, + value, + includeChartTypes, + excludeChartTypes, +}: XYChartTypesProps) { + const { + services: { lens }, + } = useKibana(); + + const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]); + + let vizTypes = data ?? []; + + if ((excludeChartTypes ?? []).length > 0) { + vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id as SeriesType)); + } + + if ((includeChartTypes ?? []).length > 0) { + vizTypes = vizTypes.filter(({ id }) => includeChartTypes?.includes(id as SeriesType)); + } + + const options = (vizTypes ?? []).map(({ id, fullLabel, label, icon }) => { + const LabelWithIcon = ( + + + + + {fullLabel || label} + + ); + return { + value: id as SeriesType, + inputDisplay: LabelWithIcon, + dropdownDisplay: LabelWithIcon, + }; + }); + + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx index 039cdfc9b73f5..41b9f7d22ba00 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.test.tsx @@ -32,7 +32,7 @@ describe('DataTypesCol', function () { }); it('should set series on change on already selected', function () { - const { setSeries } = mockUrlStorage({ + const { removeSeries } = mockUrlStorage({ data: { [NEW_SERIES_KEY]: { dataType: 'synthetics', @@ -54,6 +54,6 @@ describe('DataTypesCol', function () { fireEvent.click(button); // undefined on click selected - expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: undefined }); + expect(removeSeries).toHaveBeenCalledWith('newSeriesKey'); }); }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx index b6464bbe3c6ed..d7e90d34a2596 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/data_types_col.tsx @@ -20,15 +20,19 @@ export const dataTypes: Array<{ id: AppDataType; label: string }> = [ ]; export function DataTypesCol() { - const { series, setSeries } = useUrlStorage(NEW_SERIES_KEY); + const { series, setSeries, removeSeries } = useUrlStorage(NEW_SERIES_KEY); - const { loadIndexPattern } = useIndexPatternContext(); + const { loadIndexPattern, indexPattern } = useIndexPatternContext(); const onDataTypeChange = (dataType?: AppDataType) => { if (dataType) { loadIndexPattern(dataType); } - setSeries(NEW_SERIES_KEY, { dataType } as any); + if (!dataType) { + removeSeries(NEW_SERIES_KEY); + } else { + setSeries(NEW_SERIES_KEY, { dataType } as any); + } }; const selectedDataType = series.dataType; @@ -43,6 +47,8 @@ export function DataTypesCol() { iconType="arrowRight" color={selectedDataType === dataTypeId ? 'primary' : 'text'} fill={selectedDataType === dataTypeId} + isDisabled={!indexPattern} + isLoading={!indexPattern && selectedDataType === dataTypeId} onClick={() => { onDataTypeChange(dataTypeId === selectedDataType ? undefined : dataTypeId); }} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx new file mode 100644 index 0000000000000..e05f91b4bb0bd --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.test.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { fireEvent, screen } from '@testing-library/react'; +import { mockUrlStorage, render } from '../../rtl_helpers'; +import { OperationTypeSelect } from './operation_type_select'; + +describe('OperationTypeSelect', function () { + it('should render properly', function () { + render(); + + screen.getByText('Select an option: , is selected'); + }); + + it('should display selected value', function () { + mockUrlStorage({ + data: { + 'performance-distribution': { + reportType: 'kpi', + operationType: 'median', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + screen.getByText('Median'); + }); + + it('should call set series on change', function () { + const { setSeries } = mockUrlStorage({ + data: { + 'series-id': { + reportType: 'kpi', + operationType: 'median', + time: { from: 'now-15m', to: 'now' }, + }, + }, + }); + + render(); + + fireEvent.click(screen.getByTestId('operationTypeSelect')); + + expect(setSeries).toHaveBeenCalledWith('series-id', { + operationType: 'median', + reportType: 'kpi', + time: { from: 'now-15m', to: 'now' }, + }); + + fireEvent.click(screen.getByText('95th Percentile')); + expect(setSeries).toHaveBeenCalledWith('series-id', { + operationType: '95th', + reportType: 'kpi', + time: { from: 'now-15m', to: 'now' }, + }); + }); +}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx new file mode 100644 index 0000000000000..46167af0b244a --- /dev/null +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/operation_type_select.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSuperSelect } from '@elastic/eui'; + +import { useUrlStorage } from '../../hooks/use_url_storage'; +import { OperationType } from '../../../../../../../lens/public'; + +export function OperationTypeSelect({ + seriesId, + defaultOperationType, +}: { + seriesId: string; + defaultOperationType?: OperationType; +}) { + const { series, setSeries } = useUrlStorage(seriesId); + + const operationType = series?.operationType; + + const onChange = (value: OperationType) => { + setSeries(seriesId, { ...series, operationType: value }); + }; + + useEffect(() => { + setSeries(seriesId, { ...series, operationType: operationType || defaultOperationType }); + }, [defaultOperationType, seriesId, operationType, setSeries, series]); + + const options = [ + { + value: 'average' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.average', { + defaultMessage: 'Average', + }), + }, + { + value: 'median' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.median', { + defaultMessage: 'Median', + }), + }, + { + value: '75th' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.75thPercentile', { + defaultMessage: '75th Percentile', + }), + }, + { + value: '90th' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.90thPercentile', { + defaultMessage: '90th Percentile', + }), + }, + { + value: '95th' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.95thPercentile', { + defaultMessage: '95th Percentile', + }), + }, + { + value: '99th' as OperationType, + inputDisplay: i18n.translate('xpack.observability.expView.operationType.99thPercentile', { + defaultMessage: '99th Percentile', + }), + }, + ]; + + return ( + + ); +} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx index b907efb57d5c2..a386b73a8f917 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_definition_col.tsx @@ -12,6 +12,8 @@ import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage'; import { CustomReportField } from '../custom_report_field'; import FieldValueSuggestions from '../../../field_value_suggestions'; import { DataSeries } from '../../types'; +import { SeriesChartTypesSelect } from './chart_types'; +import { OperationTypeSelect } from './operation_type_select'; export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSeries }) { const { indexPattern } = useIndexPatternContext(); @@ -20,7 +22,14 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe const { reportDefinitions: rtd = {} } = series; - const { reportDefinitions, labels, filters } = dataViewSeries; + const { + reportDefinitions, + labels, + filters, + defaultSeriesType, + hasOperationType, + yAxisColumn, + } = dataViewSeries; const onChange = (field: string, value?: string) => { if (!value) { @@ -91,6 +100,17 @@ export function ReportDefinitionCol({ dataViewSeries }: { dataViewSeries: DataSe )} ))} + + + + {hasOperationType && ( + + + + )} ); } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx index 567e2654130e8..f845bf9885af9 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.test.tsx @@ -10,6 +10,7 @@ import { fireEvent, screen } from '@testing-library/react'; import { mockUrlStorage, render } from '../../rtl_helpers'; import { ReportTypesCol, SELECTED_DATA_TYPE_FOR_REPORT } from './report_types_col'; import { ReportTypes } from '../series_builder'; +import { DEFAULT_TIME } from '../../configurations/constants'; describe('ReportTypesCol', function () { it('should render properly', function () { @@ -60,6 +61,9 @@ describe('ReportTypesCol', function () { fireEvent.click(button); // undefined on click selected - expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { dataType: 'synthetics' }); + expect(setSeries).toHaveBeenCalledWith('newSeriesKey', { + dataType: 'synthetics', + time: DEFAULT_TIME, + }); }); }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx index a473ddb570526..a8f98b98026b6 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/columns/report_types_col.tsx @@ -10,6 +10,8 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { ReportViewTypeId, SeriesUrl } from '../../types'; import { NEW_SERIES_KEY, useUrlStorage } from '../../hooks/use_url_storage'; +import { DEFAULT_TIME } from '../../configurations/constants'; +import { useIndexPatternContext } from '../../hooks/use_default_index_pattern'; interface Props { reportTypes: Array<{ id: ReportViewTypeId; label: string }>; @@ -21,6 +23,8 @@ export function ReportTypesCol({ reportTypes }: Props) { setSeries, } = useUrlStorage(NEW_SERIES_KEY); + const { indexPattern } = useIndexPatternContext(); + return reportTypes?.length > 0 ? ( {reportTypes.map(({ id: reportType, label }) => ( @@ -31,16 +35,19 @@ export function ReportTypesCol({ reportTypes }: Props) { iconType="arrowRight" color={selectedReportType === reportType ? 'primary' : 'text'} fill={selectedReportType === reportType} + isDisabled={!indexPattern} onClick={() => { if (reportType === selectedReportType) { setSeries(NEW_SERIES_KEY, { dataType: restSeries.dataType, + time: DEFAULT_TIME, } as SeriesUrl); } else { setSeries(NEW_SERIES_KEY, { ...restSeries, reportType, reportDefinitions: {}, + time: restSeries?.time ?? DEFAULT_TIME, }); } }} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx index 053f301529635..2280109fdacdf 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_builder/series_builder.tsx @@ -49,7 +49,14 @@ export const ReportTypes: Record { @@ -145,7 +154,7 @@ export function SeriesBuilder() { columns={columns} cellProps={{ style: { borderRight: '1px solid #d3dae6' } }} /> - + diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx index 922d33ffd39ac..960c2978287bc 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/index.tsx @@ -10,6 +10,7 @@ import React, { useEffect } from 'react'; import { useHasData } from '../../../../hooks/use_has_data'; import { useUrlStorage } from '../hooks/use_url_storage'; import { useQuickTimeRanges } from '../../../../hooks/use_quick_time_ranges'; +import { DEFAULT_TIME } from '../configurations/constants'; export interface TimePickerTime { from: string; @@ -38,7 +39,7 @@ export function SeriesDatePicker({ seriesId }: Props) { useEffect(() => { if (!series || !series.time) { - setSeries(seriesId, { ...series, time: { from: 'now-5h', to: 'now' } }); + setSeries(seriesId, { ...series, time: DEFAULT_TIME }); } }, [seriesId, series, setSeries]); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx index acc9ba9658a08..8fe1d5ed9f2ac 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_date_picker/series_date_picker.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { mockUrlStorage, mockUseHasData, render } from '../rtl_helpers'; import { fireEvent, waitFor } from '@testing-library/react'; import { SeriesDatePicker } from './index'; +import { DEFAULT_TIME } from '../configurations/constants'; describe('SeriesDatePicker', function () { it('should render properly', function () { @@ -40,7 +41,7 @@ describe('SeriesDatePicker', function () { expect(setSeries1).toHaveBeenCalledWith('uptime-pings-histogram', { breakdown: 'monitor.status', reportType: 'upp', - time: { from: 'now-5h', to: 'now' }, + time: DEFAULT_TIME, }); }); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx index c6209381a4da1..fe54262e13844 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/actions_col.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { DataSeries } from '../../types'; -import { SeriesChartTypes } from './chart_types'; -import { MetricSelection } from './metric_selection'; +import { OperationTypeSelect } from '../../series_builder/columns/operation_type_select'; +import { SeriesChartTypesSelect } from '../../series_builder/columns/chart_types'; interface Props { series: DataSeries; @@ -17,13 +17,13 @@ interface Props { export function ActionsCol({ series }: Props) { return ( - + - + - {series.hasMetricType && ( + {series.hasOperationType && ( - + )} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx deleted file mode 100644 index f83630cff414a..0000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/chart_types.tsx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; - -import { - EuiButton, - EuiButtonGroup, - EuiButtonIcon, - EuiLoadingSpinner, - EuiPopover, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; -import { useKibana } from '../../../../../../../../../src/plugins/kibana_react/public'; -import { ObservabilityPublicPluginsStart } from '../../../../../plugin'; -import { useFetcher } from '../../../../..'; -import { useUrlStorage } from '../../hooks/use_url_storage'; -import { SeriesType } from '../../../../../../../lens/public'; - -export function SeriesChartTypes({ - seriesId, - defaultChartType, -}: { - seriesId: string; - defaultChartType: SeriesType; -}) { - const { series, setSeries, allSeries } = useUrlStorage(seriesId); - - const seriesType = series?.seriesType ?? defaultChartType; - - const onChange = (value: SeriesType) => { - Object.keys(allSeries).forEach((seriesKey) => { - const seriesN = allSeries[seriesKey]; - - setSeries(seriesKey, { ...seriesN, seriesType: value }); - }); - }; - - return ( - - ); -} - -export interface XYChartTypesProps { - onChange: (value: SeriesType) => void; - value: SeriesType; - label?: string; - includeChartTypes?: string[]; - excludeChartTypes?: string[]; -} - -export function XYChartTypes({ - onChange, - value, - label, - includeChartTypes, - excludeChartTypes, -}: XYChartTypesProps) { - const [isOpen, setIsOpen] = useState(false); - - const { - services: { lens }, - } = useKibana(); - - const { data = [], loading } = useFetcher(() => lens.getXyVisTypes(), [lens]); - - let vizTypes = data ?? []; - - if ((excludeChartTypes ?? []).length > 0) { - vizTypes = vizTypes.filter(({ id }) => !excludeChartTypes?.includes(id)); - } - - if ((includeChartTypes ?? []).length > 0) { - vizTypes = vizTypes.filter(({ id }) => includeChartTypes?.includes(id)); - } - - return loading ? ( - - ) : ( - id === value)?.icon} - onClick={() => { - setIsOpen((prevState) => !prevState); - }} - > - {label} - - ) : ( - id === value)?.label} - iconType={vizTypes.find(({ id }) => id === value)?.icon!} - onClick={() => { - setIsOpen((prevState) => !prevState); - }} - /> - ) - } - closePopover={() => setIsOpen(false)} - > - ({ - id: t.id, - label: t.label, - title: t.label, - iconType: t.icon || 'empty', - 'data-test-subj': `lnsXY_seriesType-${t.id}`, - }))} - idSelected={value} - onChange={(valueN: string) => { - onChange(valueN as SeriesType); - }} - /> - - ); -} - -const ButtonGroup = styled(EuiButtonGroup)` - &&& { - .euiButtonGroupButton-isSelected { - background-color: #a5a9b1 !important; - } - } -`; diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx deleted file mode 100644 index ced04f0a59c8c..0000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.test.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import { fireEvent, screen } from '@testing-library/react'; -import { mockUrlStorage, render } from '../../rtl_helpers'; -import { MetricSelection } from './metric_selection'; - -describe('MetricSelection', function () { - it('should render properly', function () { - render(); - - screen.getByText('Average'); - }); - - it('should display selected value', function () { - mockUrlStorage({ - data: { - 'performance-distribution': { - reportType: 'kpi', - metric: 'median', - time: { from: 'now-15m', to: 'now' }, - }, - }, - }); - - render(); - - screen.getByText('Median'); - }); - - it('should be disabled on disabled state', function () { - render(); - - const btn = screen.getByRole('button'); - - expect(btn.classList).toContain('euiButton-isDisabled'); - }); - - it('should call set series on change', function () { - const { setSeries } = mockUrlStorage({ - data: { - 'performance-distribution': { - reportType: 'kpi', - metric: 'median', - time: { from: 'now-15m', to: 'now' }, - }, - }, - }); - - render(); - - fireEvent.click(screen.getByText('Median')); - - screen.getByText('Chart metric group'); - - fireEvent.click(screen.getByText('95th Percentile')); - - expect(setSeries).toHaveBeenNthCalledWith(1, 'performance-distribution', { - metric: '95th', - reportType: 'kpi', - time: { from: 'now-15m', to: 'now' }, - }); - // FIXME This is a bug in EUI EuiButtonGroup calls on change multiple times - // This should be one https://github.com/elastic/eui/issues/4629 - expect(setSeries).toHaveBeenCalledTimes(3); - }); - - it('should call set series on change for all series', function () { - const { setSeries } = mockUrlStorage({ - data: { - 'page-views': { - reportType: 'kpi', - metric: 'median', - time: { from: 'now-15m', to: 'now' }, - }, - 'performance-distribution': { - reportType: 'kpi', - metric: 'median', - time: { from: 'now-15m', to: 'now' }, - }, - }, - }); - - render(); - - fireEvent.click(screen.getByText('Median')); - - screen.getByText('Chart metric group'); - - fireEvent.click(screen.getByText('95th Percentile')); - - expect(setSeries).toHaveBeenNthCalledWith(1, 'page-views', { - metric: '95th', - reportType: 'kpi', - time: { from: 'now-15m', to: 'now' }, - }); - - expect(setSeries).toHaveBeenNthCalledWith(2, 'performance-distribution', { - metric: '95th', - reportType: 'kpi', - time: { from: 'now-15m', to: 'now' }, - }); - // FIXME This is a bug in EUI EuiButtonGroup calls on change multiple times - // This should be one https://github.com/elastic/eui/issues/4629 - expect(setSeries).toHaveBeenCalledTimes(6); - }); -}); diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx b/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx deleted file mode 100644 index fa4202d2c30ad..0000000000000 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/series_editor/columns/metric_selection.tsx +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiButton, EuiButtonGroup, EuiPopover } from '@elastic/eui'; -import { useUrlStorage } from '../../hooks/use_url_storage'; -import { OperationType } from '../../../../../../../lens/public'; - -const toggleButtons = [ - { - id: `average`, - label: i18n.translate('xpack.observability.expView.metricsSelect.average', { - defaultMessage: 'Average', - }), - }, - { - id: `median`, - label: i18n.translate('xpack.observability.expView.metricsSelect.median', { - defaultMessage: 'Median', - }), - }, - { - id: `95th`, - label: i18n.translate('xpack.observability.expView.metricsSelect.9thPercentile', { - defaultMessage: '95th Percentile', - }), - }, - { - id: `99th`, - label: i18n.translate('xpack.observability.expView.metricsSelect.99thPercentile', { - defaultMessage: '99th Percentile', - }), - }, -]; - -export function MetricSelection({ - seriesId, - isDisabled, -}: { - seriesId: string; - isDisabled: boolean; -}) { - const { series, setSeries, allSeries } = useUrlStorage(seriesId); - - const [isOpen, setIsOpen] = useState(false); - - const [toggleIdSelected, setToggleIdSelected] = useState(series?.metric ?? 'average'); - - const onChange = (optionId: OperationType) => { - setToggleIdSelected(optionId); - - Object.keys(allSeries).forEach((seriesKey) => { - const seriesN = allSeries[seriesKey]; - - setSeries(seriesKey, { ...seriesN, metric: optionId }); - }); - }; - const button = ( - setIsOpen((prevState) => !prevState)} - size="s" - color="text" - isDisabled={isDisabled} - > - {toggleButtons.find(({ id }) => id === toggleIdSelected)!.label} - - ); - - return ( - setIsOpen(false)}> - onChange(id as OperationType)} - /> - - ); -} diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts index d673fc4d6f6ee..141dcecd0ba5b 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/types.ts @@ -9,9 +9,9 @@ import { PaletteOutput } from 'src/plugins/charts/public'; import { LastValueIndexPatternColumn, DateHistogramIndexPatternColumn, + FieldBasedIndexPatternColumn, SeriesType, OperationType, - IndexPatternColumn, } from '../../../../../lens/public'; import { PersistableFilter } from '../../../../../lens/common'; @@ -41,14 +41,19 @@ export interface ReportDefinition { required?: boolean; custom?: boolean; defaultValue?: string; - options?: Array<{ field: string; label: string; description?: string }>; + options?: Array<{ + field: string; + label: string; + description?: string; + columnType?: 'range' | 'operation'; + }>; } export interface DataSeries { reportType: ReportViewType; id: string; xAxisColumn: Partial | Partial; - yAxisColumn: Partial; + yAxisColumn: Partial; breakdowns: string[]; defaultSeriesType: SeriesType; @@ -57,7 +62,7 @@ export interface DataSeries { filters?: PersistableFilter[]; reportDefinitions: ReportDefinition[]; labels: Record; - hasMetricType: boolean; + hasOperationType: boolean; palette?: PaletteOutput; } @@ -70,7 +75,7 @@ export interface SeriesUrl { filters?: UrlFilter[]; seriesType?: SeriesType; reportType: ReportViewTypeId; - metric?: OperationType; + operationType?: OperationType; dataType?: AppDataType; reportDefinitions?: Record; } diff --git a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts index e0a2941b24d3c..527ef48364d22 100644 --- a/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts +++ b/x-pack/plugins/observability/public/components/shared/exploratory_view/utils/observability_index_patterns.ts @@ -47,12 +47,16 @@ const appToPatternMap: Record = { }; export function isParamsSame(param1: IFieldFormat['_params'], param2: FieldFormatParams) { - return ( + const isSame = param1?.inputFormat === param2?.inputFormat && param1?.outputFormat === param2?.outputFormat && - param1?.showSuffix === param2?.showSuffix && - param2?.outputPrecision === param1?.outputPrecision - ); + param1?.showSuffix === param2?.showSuffix; + + if (param2.outputPrecision !== undefined) { + return param2?.outputPrecision === param1?.outputPrecision && isSame; + } + + return isSame; } export class ObservabilityIndexPatterns { From 98f40a216a7188b97568f2363af1f757b3bfe97e Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Mon, 12 Apr 2021 16:56:28 +0300 Subject: [PATCH 09/79] [TSVB] Visualize runtime fields (#95772) * [TSVB] Visualize runtime fields * fix CI * Update visualization_error.tsx * Update build_request_body.ts * fix group by for table view * fix issue on switching the index pattern mode Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../common/calculate_label.test.ts | 23 +++++++ .../common/calculate_label.ts | 12 ++-- .../vis_type_timeseries/common/constants.ts | 1 + .../common/fields_utils.test.ts | 13 +--- .../common/fields_utils.ts | 60 +++++++++++++--- .../common/index_patterns_utils.test.ts | 12 ++-- .../common/index_patterns_utils.ts | 18 +++-- .../vis_type_timeseries/common/types.ts | 4 +- .../components/aggs/field_select.tsx | 69 ++++++++++++++----- .../components/aggs/filter_ratio.js | 20 +++--- .../components/aggs/metric_select.js | 4 +- .../application/components/aggs/percentile.js | 20 +++--- .../aggs/percentile_rank/percentile_rank.tsx | 20 +++--- .../components/aggs/positive_rate.js | 23 +++---- .../application/components/aggs/std_agg.js | 32 +++------ .../components/aggs/std_deviation.js | 20 +++--- .../application/components/aggs/top_hit.js | 38 ++++------ .../components/annotations_editor.js | 21 +++--- .../application/components/index_pattern.js | 24 +++---- .../index_pattern_select.tsx | 7 +- .../components/panel_config/table.tsx | 21 +++--- .../splits/__snapshots__/terms.test.js.snap | 56 +++++++-------- .../application/components/splits/terms.js | 22 +++--- .../components/vis_types/table/config.js | 18 ++--- .../public/timeseries_vis_renderer.tsx | 3 +- .../lib/cached_index_pattern_fetcher.test.ts | 23 +------ .../search_strategies/lib/fields_fetcher.ts | 15 ++-- .../annotations/build_request_body.ts | 16 +---- .../annotations/get_request_params.ts | 9 ++- ....js => get_interval_and_timefield.test.ts} | 19 +++-- ...field.js => get_interval_and_timefield.ts} | 23 ++++--- .../server/lib/vis_data/get_table_data.ts | 15 ++-- .../server/lib/vis_data/helpers/get_splits.js | 5 +- .../annotations/date_histogram.js | 6 +- .../request_processors/annotations/query.js | 19 +++-- .../annotations/top_hits.js | 5 +- .../series/date_histogram.js | 7 +- .../series/filter_ratios.js | 6 +- .../series/filter_ratios.test.js | 2 +- .../series/metric_buckets.js | 4 +- .../series/positive_rate.js | 4 +- .../request_processors/series/query.js | 10 +-- .../request_processors/series/query.test.js | 21 +++--- .../series/sibling_buckets.js | 4 +- .../series/split_by_filter.js | 4 +- .../series/split_by_filter.test.js | 11 ++- .../series/split_by_filters.js | 9 ++- .../series/split_by_filters.test.js | 11 ++- .../series/split_by_terms.js | 5 +- .../series/split_by_terms.test.js | 20 ++++-- .../table/date_histogram.js | 8 ++- .../request_processors/table/filter_ratios.js | 6 +- .../table/metric_buckets.js | 4 +- .../request_processors/table/positive_rate.js | 4 +- .../request_processors/table/query.js | 8 +-- .../table/sibling_buckets.js | 4 +- .../table/split_by_everything.js | 4 +- .../table/split_by_terms.js | 4 +- .../response_processors/series/series_agg.js | 10 ++- .../response_processors/table/series_agg.js | 10 ++- .../lib/vis_data/series/build_request_body.ts | 2 +- .../lib/vis_data/series/get_request_params.ts | 3 +- .../components/visualization_container.tsx | 11 ++- .../public/components/visualization_error.tsx | 42 +++++++++++ .../test/functional/apps/rollup_job/tsvb.js | 1 + 65 files changed, 532 insertions(+), 423 deletions(-) rename src/plugins/vis_type_timeseries/server/lib/vis_data/{get_interval_and_timefield.test.js => get_interval_and_timefield.test.ts} (68%) rename src/plugins/vis_type_timeseries/server/lib/vis_data/{get_interval_and_timefield.js => get_interval_and_timefield.ts} (57%) create mode 100644 src/plugins/visualizations/public/components/visualization_error.tsx diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.test.ts b/src/plugins/vis_type_timeseries/common/calculate_label.test.ts index d5277623a136d..eab9665436c01 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.test.ts +++ b/src/plugins/vis_type_timeseries/common/calculate_label.test.ts @@ -8,6 +8,7 @@ import { calculateLabel } from './calculate_label'; import type { MetricsItemsSchema } from './types'; +import { SanitizedFieldType } from './types'; describe('calculateLabel(metric, metrics)', () => { test('returns the metric.alias if set', () => { @@ -82,4 +83,26 @@ describe('calculateLabel(metric, metrics)', () => { expect(label).toEqual('Derivative of Outbound Traffic'); }); + + test('should throw an error if field not found', () => { + const metric = ({ id: 2, type: 'max', field: 3 } as unknown) as MetricsItemsSchema; + const metrics = ([ + { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, + metric, + ] as unknown) as MetricsItemsSchema[]; + const fields: SanitizedFieldType[] = [{ name: '2', label: '2', type: 'field' }]; + + expect(() => calculateLabel(metric, metrics, fields)).toThrowError('Field "3" not found'); + }); + + test('should not throw an error if field not found (isThrowErrorOnFieldNotFound is false)', () => { + const metric = ({ id: 2, type: 'max', field: 3 } as unknown) as MetricsItemsSchema; + const metrics = ([ + { id: 1, type: 'max', field: 'network.out.bytes', alias: 'Outbound Traffic' }, + metric, + ] as unknown) as MetricsItemsSchema[]; + const fields: SanitizedFieldType[] = [{ name: '2', label: '2', type: 'field' }]; + + expect(calculateLabel(metric, metrics, fields, false)).toBe('Max of 3'); + }); }); diff --git a/src/plugins/vis_type_timeseries/common/calculate_label.ts b/src/plugins/vis_type_timeseries/common/calculate_label.ts index 73b5d3f652644..bd1482e14f4f4 100644 --- a/src/plugins/vis_type_timeseries/common/calculate_label.ts +++ b/src/plugins/vis_type_timeseries/common/calculate_label.ts @@ -10,6 +10,7 @@ import { includes, startsWith } from 'lodash'; import { i18n } from '@kbn/i18n'; import { lookup } from './agg_lookup'; import { MetricsItemsSchema, SanitizedFieldType } from './types'; +import { extractFieldLabel } from './fields_utils'; const paths = [ 'cumulative_sum', @@ -26,14 +27,11 @@ const paths = [ 'positive_only', ]; -export const extractFieldLabel = (fields: SanitizedFieldType[], name: string) => { - return fields.find((f) => f.name === name)?.label ?? name; -}; - export const calculateLabel = ( metric: MetricsItemsSchema, metrics: MetricsItemsSchema[] = [], - fields: SanitizedFieldType[] = [] + fields: SanitizedFieldType[] = [], + isThrowErrorOnFieldNotFound: boolean = true ): string => { if (!metric) { return i18n.translate('visTypeTimeseries.calculateLabel.unknownLabel', { @@ -71,7 +69,7 @@ export const calculateLabel = ( if (metric.type === 'positive_rate') { return i18n.translate('visTypeTimeseries.calculateLabel.positiveRateLabel', { defaultMessage: 'Counter Rate of {field}', - values: { field: extractFieldLabel(fields, metric.field!) }, + values: { field: extractFieldLabel(fields, metric.field!, isThrowErrorOnFieldNotFound) }, }); } if (metric.type === 'static') { @@ -115,7 +113,7 @@ export const calculateLabel = ( defaultMessage: '{lookupMetricType} of {metricField}', values: { lookupMetricType: lookup[metric.type], - metricField: extractFieldLabel(fields, metric.field!), + metricField: extractFieldLabel(fields, metric.field!, isThrowErrorOnFieldNotFound), }, }); }; diff --git a/src/plugins/vis_type_timeseries/common/constants.ts b/src/plugins/vis_type_timeseries/common/constants.ts index 66617c8518985..1debfaf951e99 100644 --- a/src/plugins/vis_type_timeseries/common/constants.ts +++ b/src/plugins/vis_type_timeseries/common/constants.ts @@ -13,3 +13,4 @@ export const ROUTES = { VIS_DATA: '/api/metrics/vis/data', FIELDS: '/api/metrics/fields', }; +export const USE_KIBANA_INDEXES_KEY = 'use_kibana_indexes'; diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts index d1036aab2dc3e..9550697e22851 100644 --- a/src/plugins/vis_type_timeseries/common/fields_utils.test.ts +++ b/src/plugins/vis_type_timeseries/common/fields_utils.test.ts @@ -7,7 +7,7 @@ */ import { toSanitizedFieldType } from './fields_utils'; -import type { FieldSpec, RuntimeField } from '../../data/common'; +import type { FieldSpec } from '../../data/common'; describe('fields_utils', () => { describe('toSanitizedFieldType', () => { @@ -34,17 +34,6 @@ describe('fields_utils', () => { `); }); - test('should filter runtime fields', async () => { - const fields: FieldSpec[] = [ - { - ...mockedField, - runtimeField: {} as RuntimeField, - }, - ]; - - expect(toSanitizedFieldType(fields)).toMatchInlineSnapshot(`Array []`); - }); - test('should filter non-aggregatable fields', async () => { const fields: FieldSpec[] = [ { diff --git a/src/plugins/vis_type_timeseries/common/fields_utils.ts b/src/plugins/vis_type_timeseries/common/fields_utils.ts index 04499d5320ab8..6a83dd323b3fd 100644 --- a/src/plugins/vis_type_timeseries/common/fields_utils.ts +++ b/src/plugins/vis_type_timeseries/common/fields_utils.ts @@ -6,17 +6,60 @@ * Side Public License, v 1. */ +import { i18n } from '@kbn/i18n'; import { FieldSpec } from '../../data/common'; import { isNestedField } from '../../data/common'; -import { SanitizedFieldType } from './types'; +import { FetchedIndexPattern, SanitizedFieldType } from './types'; -export const toSanitizedFieldType = (fields: FieldSpec[]) => { - return fields - .filter( - (field) => - // Make sure to only include mapped fields, e.g. no index pattern runtime fields - !field.runtimeField && field.aggregatable && !isNestedField(field) - ) +export class FieldNotFoundError extends Error { + constructor(name: string) { + super( + i18n.translate('visTypeTimeseries.fields.fieldNotFound', { + defaultMessage: `Field "{field}" not found`, + values: { field: name }, + }) + ); + } + + public get name() { + return this.constructor.name; + } + + public get body() { + return this.message; + } +} + +export const extractFieldLabel = ( + fields: SanitizedFieldType[], + name: string, + isThrowErrorOnFieldNotFound: boolean = true +) => { + if (fields.length && name) { + const field = fields.find((f) => f.name === name); + + if (field) { + return field.label || field.name; + } + if (isThrowErrorOnFieldNotFound) { + throw new FieldNotFoundError(name); + } + } + return name; +}; + +export function validateField(name: string, index: FetchedIndexPattern) { + if (name && index.indexPattern) { + const field = index.indexPattern.fields.find((f) => f.name === name); + if (!field) { + throw new FieldNotFoundError(name); + } + } +} + +export const toSanitizedFieldType = (fields: FieldSpec[]) => + fields + .filter((field) => field.aggregatable && !isNestedField(field)) .map( (field) => ({ @@ -25,4 +68,3 @@ export const toSanitizedFieldType = (fields: FieldSpec[]) => { type: field.type, } as SanitizedFieldType) ); -}; diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts index 0428e6e80ae78..1111a9c525243 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.test.ts @@ -81,7 +81,7 @@ describe('fetchIndexPattern', () => { }); describe('text-based index', () => { - test('should return the Kibana index if it exists', async () => { + test('should return the Kibana index if it exists (fetchKibabaIndexForStringIndexes is true)', async () => { mockedIndices = [ { id: 'indexId', @@ -89,7 +89,9 @@ describe('fetchIndexPattern', () => { }, ] as IndexPattern[]; - const value = await fetchIndexPattern('indexTitle', indexPatternsService); + const value = await fetchIndexPattern('indexTitle', indexPatternsService, { + fetchKibabaIndexForStringIndexes: true, + }); expect(value).toMatchInlineSnapshot(` Object { @@ -102,8 +104,10 @@ describe('fetchIndexPattern', () => { `); }); - test('should return only indexPatternString if Kibana index does not exist', async () => { - const value = await fetchIndexPattern('indexTitle', indexPatternsService); + test('should return only indexPatternString if Kibana index does not exist (fetchKibabaIndexForStringIndexes is true)', async () => { + const value = await fetchIndexPattern('indexTitle', indexPatternsService, { + fetchKibabaIndexForStringIndexes: true, + }); expect(value).toMatchInlineSnapshot(` Object { diff --git a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts index af9f0750b2604..5dacad338e7a8 100644 --- a/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts +++ b/src/plugins/vis_type_timeseries/common/index_patterns_utils.ts @@ -52,7 +52,12 @@ export const extractIndexPatternValues = ( export const fetchIndexPattern = async ( indexPatternValue: IndexPatternValue | undefined, - indexPatternsService: Pick + indexPatternsService: Pick, + options: { + fetchKibabaIndexForStringIndexes: boolean; + } = { + fetchKibabaIndexForStringIndexes: false, + } ): Promise => { let indexPattern: FetchedIndexPattern['indexPattern']; let indexPatternString: string = ''; @@ -61,13 +66,16 @@ export const fetchIndexPattern = async ( indexPattern = await indexPatternsService.getDefault(); } else { if (isStringTypeIndexPattern(indexPatternValue)) { - indexPattern = (await indexPatternsService.find(indexPatternValue)).find( - (index) => index.title === indexPatternValue - ); - + if (options.fetchKibabaIndexForStringIndexes) { + indexPattern = (await indexPatternsService.find(indexPatternValue)).find( + (index) => index.title === indexPatternValue + ); + } if (!indexPattern) { indexPatternString = indexPatternValue; } + + indexPatternString = indexPatternValue; } else if (indexPatternValue.id) { indexPattern = await indexPatternsService.get(indexPatternValue.id); } diff --git a/src/plugins/vis_type_timeseries/common/types.ts b/src/plugins/vis_type_timeseries/common/types.ts index 74e247b7af06d..240b3e68cf65d 100644 --- a/src/plugins/vis_type_timeseries/common/types.ts +++ b/src/plugins/vis_type_timeseries/common/types.ts @@ -46,6 +46,7 @@ interface TableData { export type SeriesData = { type: Exclude; uiRestrictions: TimeseriesUIRestrictions; + error?: string; } & { [key: string]: PanelSeries; }; @@ -56,7 +57,7 @@ interface PanelSeries { }; id: string; series: PanelData[]; - error?: unknown; + error?: string; } export interface PanelData { @@ -66,6 +67,7 @@ export interface PanelData { seriesId: string; splitByLabel: string; isSplitByTerms: boolean; + error?: string; } export const isVisTableData = (data: TimeseriesVisData): data is TableData => diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx index 82989cc15d6c9..7d42eb3f40ac5 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/field_select.tsx @@ -5,19 +5,26 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - -import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiComboBox, EuiComboBoxProps, EuiComboBoxOptionOption } from '@elastic/eui'; -import { METRIC_TYPES } from '../../../../common/metric_types'; +import React, { ReactNode, useContext } from 'react'; +import { + EuiComboBox, + EuiComboBoxProps, + EuiComboBoxOptionOption, + EuiFormRow, + htmlIdGenerator, +} from '@elastic/eui'; import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; import type { SanitizedFieldType, IndexPatternValue } from '../../../../common/types'; import type { TimeseriesUIRestrictions } from '../../../../common/ui_restrictions'; // @ts-ignore import { isFieldEnabled } from '../../lib/check_ui_restrictions'; +import { PanelModelContext } from '../../contexts/panel_model_context'; +import { USE_KIBANA_INDEXES_KEY } from '../../../../common/constants'; interface FieldSelectProps { + label: string | ReactNode; type: string; fields: Record; indexPattern: IndexPatternValue; @@ -45,6 +52,7 @@ const sortByLabel = (a: EuiComboBoxOptionOption, b: EuiComboBoxOptionOpt }; export function FieldSelect({ + label, type, fields, indexPattern = '', @@ -56,11 +64,10 @@ export function FieldSelect({ uiRestrictions, 'data-test-subj': dataTestSubj = 'metricsIndexPatternFieldsSelect', }: FieldSelectProps) { - if (type === METRIC_TYPES.COUNT) { - return null; - } + const panelModel = useContext(PanelModelContext); + const htmlId = htmlIdGenerator(); - const selectedOptions: Array> = []; + let selectedOptions: Array> = []; let newPlaceholder = placeholder; const fieldsSelector = getIndexPatternKey(indexPattern); @@ -112,19 +119,43 @@ export function FieldSelect({ } }); - if (value && !selectedOptions.length) { - onChange([]); + let isInvalid; + + if (Boolean(panelModel?.[USE_KIBANA_INDEXES_KEY])) { + isInvalid = Boolean(value && fields[fieldsSelector] && !selectedOptions.length); + + if (value && !selectedOptions.length) { + selectedOptions = [{ label: value!, id: 'INVALID_FIELD' }]; + } + } else { + if (value && !selectedOptions.length) { + onChange([]); + } } return ( - + + + ); } diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js index 90353f9af8e35..c380b0e09e7d3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/filter_ratio.js @@ -153,24 +153,20 @@ export const FilterRatioAgg = (props) => { {model.metric_agg !== 'count' ? ( - } - > - - + fields={fields} + type={model.metric_agg} + restrict={getSupportedFieldsByMetricType(model.metric_agg)} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> ) : null} diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js index 964017cf886ec..7ce432a3bf676 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/metric_select.js @@ -70,7 +70,7 @@ export function MetricSelect(props) { const percentileOptions = siblings .filter((row) => /^percentile/.test(row.type)) .reduce((acc, row) => { - const label = calculateLabel(row, calculatedMetrics, fields); + const label = calculateLabel(row, calculatedMetrics, fields, false); switch (row.type) { case METRIC_TYPES.PERCENTILE_RANK: @@ -100,7 +100,7 @@ export function MetricSelect(props) { }, []); const options = siblings.filter(filterRows(includeSiblings)).map((row) => { - const label = calculateLabel(row, calculatedMetrics, fields); + const label = calculateLabel(row, calculatedMetrics, fields, false); return { value: row.id, label }; }); const allOptions = [...options, ...additionalOptions, ...percentileOptions]; diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js index 77b2e2f020307..45bb5387c5cd3 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/percentile.js @@ -78,24 +78,20 @@ export function PercentileAgg(props) { /> - } - > - - + fields={fields} + type={model.type} + restrict={RESTRICT_FIELDS} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> { /> - } - > - - + fields={fields} + type={model.type} + restrict={RESTRICT_FIELDS} + indexPattern={indexPattern} + value={model.field ?? ''} + onChange={handleSelectChange('field')} + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js index 4b1528ca27081..09d9f2f1a62f2 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/positive_rate.js @@ -99,27 +99,22 @@ export const PositiveRateAgg = (props) => { /> - } + fields={props.fields} + type={model.type} + restrict={[KBN_FIELD_TYPES.NUMBER]} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + uiRestrictions={props.uiRestrictions} fullWidth - > - - + /> - } + fields={fields} + type={model.type} + restrict={restrictFields} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + uiRestrictions={uiRestrictions} fullWidth - > - - + /> ) : null} diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js index 749a97fa79f28..d4caa8a94652f 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/std_deviation.js @@ -107,24 +107,20 @@ const StandardDeviationAggUi = (props) => { /> - } - > - - + fields={fields} + type={model.type} + restrict={RESTRICT_FIELDS} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> { /> - } - > - - + fields={fields} + type={model.type} + restrict={aggWithOptionsRestrictFields} + indexPattern={indexPattern} + value={model.field} + onChange={handleSelectChange('field')} + /> @@ -223,23 +219,19 @@ const TopHitAggUi = (props) => { - } - > - - + restrict={ORDER_DATE_RESTRICT_FIELDS} + value={model.order_by} + onChange={handleSelectChange('order_by')} + indexPattern={indexPattern} + fields={fields} + /> - } + restrict={RESTRICT_FIELDS} + value={model.time_field} + onChange={this.handleChange(model, 'time_field')} + indexPattern={model.index_pattern} + fields={this.props.fields} fullWidth - > - - + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js index 5a991238d10f8..e7a34c6e6596d 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js +++ b/src/plugins/vis_type_timeseries/public/application/components/index_pattern.js @@ -77,8 +77,8 @@ export const IndexPattern = ({ const intervalName = `${prefix}interval`; const maxBarsName = `${prefix}max_bars`; const dropBucketName = `${prefix}drop_last_bucket`; - const defaultIndex = useContext(DefaultIndexPatternContext); const updateControlValidity = useContext(FormValidationContext); + const defaultIndex = useContext(DefaultIndexPatternContext); const uiRestrictions = get(useContext(VisDataContext), 'uiRestrictions'); const maxBarsUiSettings = config.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); @@ -192,22 +192,18 @@ export const IndexPattern = ({ /> - - - + restrict={RESTRICT_FIELDS} + value={model[timeFieldName]} + disabled={disabled} + onChange={handleSelectChange(timeFieldName)} + indexPattern={model[indexPatternName]} + fields={fields} + placeholder={!model[indexPatternName] ? defaultIndex?.timeFieldName : undefined} + /> - } - > - - + fields={this.props.fields} + value={model.pivot_id} + indexPattern={model.index_pattern} + onChange={this.handlePivotChange} + uiRestrictions={this.context.uiRestrictions} + type={BUCKET_TYPES.TERMS} + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap index 09cd6d550fd96..562c463f6c83c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/__snapshots__/terms.test.js.snap @@ -26,13 +26,25 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js - } - labelType="label" - > - - + onChange={[Function]} + type="terms" + value="OriginCityName" + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js index ab5342e925bd7..7db6a75e2392c 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js +++ b/src/plugins/vis_type_timeseries/public/application/components/splits/terms.js @@ -110,8 +110,7 @@ export const SplitByTermsUI = ({ - } - > - - + data-test-subj="groupByField" + indexPattern={indexPattern} + onChange={handleSelectChange('terms_field')} + value={model.terms_field} + fields={fields} + uiRestrictions={uiRestrictions} + type={'terms'} + /> diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js index 0ba8d3e855365..1940ac8b2e9b9 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_types/table/config.js @@ -186,20 +186,16 @@ export class TableSeriesConfig extends Component { - } - > - - + fields={this.props.fields} + indexPattern={this.props.panel.index_pattern} + value={model.aggregate_by} + onChange={handleSelectChange('aggregate_by')} + fullWidth + /> { }); describe('text-based index', () => { - test('should return the Kibana index if it exists', async () => { - mockedIndices = [ - { - id: 'indexId', - title: 'indexTitle', - }, - ] as IndexPattern[]; - - const value = await cachedIndexPatternFetcher('indexTitle'); - - expect(value).toMatchInlineSnapshot(` - Object { - "indexPattern": Object { - "id": "indexId", - "title": "indexTitle", - }, - "indexPatternString": "indexTitle", - } - `); - }); - - test('should return only indexPatternString if Kibana index does not exist', async () => { + test('should return only indexPatternString', async () => { const value = await cachedIndexPatternFetcher('indexTitle'); expect(value).toMatchInlineSnapshot(` diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts index 9003eb7fc2ced..4b13e62430c47 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/fields_fetcher.ts @@ -6,10 +6,13 @@ * Side Public License, v 1. */ +import { getIndexPatternKey } from '../../../../common/index_patterns_utils'; + import type { VisTypeTimeseriesVisDataRequest } from '../../../types'; import type { AbstractSearchStrategy, DefaultSearchCapabilities } from '../index'; import type { IndexPatternsService } from '../../../../../data/common'; import type { CachedIndexPatternFetcher } from './cached_index_pattern_fetcher'; +import type { IndexPatternValue } from '../../../../common/types'; export interface FieldsFetcherServices { indexPatternsService: IndexPatternsService; @@ -29,11 +32,13 @@ export const createFieldsFetcher = ( ) => { const fieldsCacheMap = new Map(); - return async (index: string) => { - if (fieldsCacheMap.has(index)) { - return fieldsCacheMap.get(index); + return async (indexPatternValue: IndexPatternValue) => { + const key = getIndexPatternKey(indexPatternValue); + + if (fieldsCacheMap.has(key)) { + return fieldsCacheMap.get(key); } - const fetchedIndex = await cachedIndexPatternFetcher(index); + const fetchedIndex = await cachedIndexPatternFetcher(indexPatternValue); const fields = await searchStrategy.getFieldsForWildcard( fetchedIndex, @@ -41,7 +46,7 @@ export const createFieldsFetcher = ( capabilities ); - fieldsCacheMap.set(index, fields); + fieldsCacheMap.set(key, fields); return fields; }; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts index 5a84598bb5ed2..1350e56b68f59 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/build_request_body.ts @@ -7,8 +7,8 @@ */ import { IUiSettingsClient } from 'kibana/server'; -import { EsQueryConfig, IndexPattern } from 'src/plugins/data/server'; -import { AnnotationItemsSchema, PanelSchema } from '../../../../common/types'; +import { EsQueryConfig } from 'src/plugins/data/server'; +import { AnnotationItemsSchema, FetchedIndexPattern, PanelSchema } from '../../../../common/types'; import { VisTypeTimeseriesVisDataRequest } from '../../../types'; import { DefaultSearchCapabilities } from '../../search_strategies'; import { buildProcessorFunction } from '../build_processor_function'; @@ -17,16 +17,6 @@ import { processors } from '../request_processors/annotations'; /** * Builds annotation request body - * - * @param {...args}: [ - * req: {Object} - a request object, - * panel: {Object} - a panel object, - * annotation: {Object} - an annotation object, - * esQueryConfig: {Object} - es query config object, - * indexPatternObject: {Object} - an index pattern object, - * capabilities: {Object} - a search capabilities object - * ] - * @returns {Object} doc - processed body */ export async function buildAnnotationRequest( ...args: [ @@ -34,7 +24,7 @@ export async function buildAnnotationRequest( PanelSchema, AnnotationItemsSchema, EsQueryConfig, - IndexPattern | null | undefined, + FetchedIndexPattern, DefaultSearchCapabilities, IUiSettingsClient ] diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts index 32086fbf4f5b4..40f1b4f2cc051 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/annotations/get_request_params.ts @@ -33,24 +33,23 @@ export async function getAnnotationRequestParams( cachedIndexPatternFetcher, }: AnnotationServices ) { - const { indexPattern, indexPatternString } = await cachedIndexPatternFetcher( - annotation.index_pattern - ); + const annotationIndex = await cachedIndexPatternFetcher(annotation.index_pattern); const request = await buildAnnotationRequest( req, panel, annotation, esQueryConfig, - indexPattern, + annotationIndex, capabilities, uiSettings ); return { - index: indexPatternString, + index: annotationIndex.indexPatternString, body: { ...request, + runtime_mappings: annotationIndex.indexPattern?.getComputedFields().runtimeFields ?? {}, timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined, }, }; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts similarity index 68% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts index ceb867e4e6d1e..7c0a0f5deb601 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.test.ts @@ -7,25 +7,30 @@ */ import { getIntervalAndTimefield } from './get_interval_and_timefield'; +import { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../common/types'; describe('getIntervalAndTimefield(panel, series)', () => { + const index: FetchedIndexPattern = {} as FetchedIndexPattern; + test('returns the panel interval and timefield', () => { - const panel = { time_field: '@timestamp', interval: 'auto' }; - const series = {}; - expect(getIntervalAndTimefield(panel, series)).toEqual({ + const panel = { time_field: '@timestamp', interval: 'auto' } as PanelSchema; + const series = {} as SeriesItemsSchema; + + expect(getIntervalAndTimefield(panel, series, index)).toEqual({ timeField: '@timestamp', interval: 'auto', }); }); test('returns the series interval and timefield', () => { - const panel = { time_field: '@timestamp', interval: 'auto' }; - const series = { + const panel = { time_field: '@timestamp', interval: 'auto' } as PanelSchema; + const series = ({ override_index_pattern: true, series_interval: '1m', series_time_field: 'time', - }; - expect(getIntervalAndTimefield(panel, series)).toEqual({ + } as unknown) as SeriesItemsSchema; + + expect(getIntervalAndTimefield(panel, series, index)).toEqual({ timeField: 'time', interval: '1m', }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts similarity index 57% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts index ebab984ff25aa..e3d0cec1a6939 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_interval_and_timefield.ts @@ -7,28 +7,31 @@ */ import { AUTO_INTERVAL } from '../../../common/constants'; +import { FetchedIndexPattern, PanelSchema, SeriesItemsSchema } from '../../../common/types'; +import { validateField } from '../../../common/fields_utils'; -const DEFAULT_TIME_FIELD = '@timestamp'; - -export function getIntervalAndTimefield(panel, series = {}, indexPattern) { - const getDefaultTimeField = () => indexPattern?.timeFieldName ?? DEFAULT_TIME_FIELD; - +export function getIntervalAndTimefield( + panel: PanelSchema, + series: SeriesItemsSchema, + index: FetchedIndexPattern +) { const timeField = - (series.override_index_pattern && series.series_time_field) || - panel.time_field || - getDefaultTimeField(); + (series.override_index_pattern ? series.series_time_field : panel.time_field) || + index.indexPattern?.timeFieldName; + + validateField(timeField!, index); let interval = panel.interval; let maxBars = panel.max_bars; if (series.override_index_pattern) { - interval = series.series_interval; + interval = series.series_interval || AUTO_INTERVAL; maxBars = series.series_max_bars; } return { + maxBars, timeField, interval: interval || AUTO_INTERVAL, - maxBars, }; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts index 0cc1188086b7b..b50fdb6b8226d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_table_data.ts @@ -18,7 +18,7 @@ import { handleErrorResponse } from './handle_error_response'; import { processBucket } from './table/process_bucket'; import { createFieldsFetcher } from '../search_strategies/lib/fields_fetcher'; -import { extractFieldLabel } from '../../../common/calculate_label'; +import { extractFieldLabel } from '../../../common/fields_utils'; import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRequestServices, @@ -58,8 +58,8 @@ export async function getTableData( }); const calculatePivotLabel = async () => { - if (panel.pivot_id && panelIndex.indexPattern?.title) { - const fields = await extractFields(panelIndex.indexPattern.title); + if (panel.pivot_id && panelIndex.indexPattern?.id) { + const fields = await extractFields({ id: panelIndex.indexPattern.id }); return extractFieldLabel(fields, panel.pivot_id); } @@ -68,7 +68,6 @@ export async function getTableData( const meta = { type: panel.type, - pivot_label: panel.pivot_label || (await calculatePivotLabel()), uiRestrictions: capabilities.uiRestrictions, }; @@ -77,14 +76,17 @@ export async function getTableData( req, panel, services.esQueryConfig, - panelIndex.indexPattern, + panelIndex, capabilities, services.uiSettings ); const [resp] = await searchStrategy.search(requestContext, req, [ { - body, + body: { + ...body, + runtime_mappings: panelIndex.indexPattern?.getComputedFields().runtimeFields ?? {}, + }, index: panelIndex.indexPatternString, }, ]); @@ -101,6 +103,7 @@ export async function getTableData( return { ...meta, + pivot_label: panel.pivot_label || (await calculatePivotLabel()), series, }; } catch (err) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js index 268c26115233e..27e7c5c908b9a 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_splits.js @@ -23,9 +23,8 @@ export async function getSplits(resp, panel, series, meta, extractFields) { const color = new Color(series.color); const metric = getLastMetric(series); const buckets = _.get(resp, `aggregations.${series.id}.buckets`); - - const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : []; - const splitByLabel = calculateLabel(metric, series.metrics, fieldsForMetaIndex); + const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : []; + const splitByLabel = calculateLabel(metric, series.metrics, fieldsForSeries); if (buckets) { if (Array.isArray(buckets)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js index 22a475a9997a7..f3ee416be81a8 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js @@ -10,6 +10,7 @@ import { overwrite } from '../../helpers'; import { getBucketSize } from '../../helpers/get_bucket_size'; import { getTimerange } from '../../helpers/get_timerange'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; +import { validateField } from '../../../../../common/fields_utils'; const { dateHistogramInterval } = search.aggs; @@ -18,13 +19,16 @@ export function dateHistogram( panel, annotation, esQueryConfig, - indexPattern, + annotationIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); const timeField = annotation.time_field; + + validateField(timeField, annotationIndex); + const { bucketSize, intervalString } = getBucketSize( req, 'auto', diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js index e7270371a3fdc..46a3c369e548d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js @@ -9,26 +9,30 @@ import { getBucketSize } from '../../helpers/get_bucket_size'; import { getTimerange } from '../../helpers/get_timerange'; import { esQuery, UI_SETTINGS } from '../../../../../../data/server'; +import { validateField } from '../../../../../common/fields_utils'; export function query( req, panel, annotation, esQueryConfig, - indexPattern, + annotationIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const timeField = annotation.time_field; + const timeField = (annotation.time_field || annotationIndex.indexPattern?.timeField) ?? ''; + + validateField(timeField, annotationIndex); + const { bucketSize } = getBucketSize(req, 'auto', capabilities, barTargetUiSettings); const { from, to } = getTimerange(req); doc.size = 0; const queries = !annotation.ignore_global_filters ? req.body.query : []; const filters = !annotation.ignore_global_filters ? req.body.filters : []; - doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(annotationIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { range: { [timeField]: { @@ -42,13 +46,18 @@ export function query( if (annotation.query_string) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [annotation.query_string], [], esQueryConfig) + esQuery.buildEsQuery( + annotationIndex.indexPattern, + [annotation.query_string], + [], + esQueryConfig + ) ); } if (!annotation.ignore_panel_filters && panel.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig) + esQuery.buildEsQuery(annotationIndex.indexPattern, [panel.filter], [], esQueryConfig) ); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js index 2e759cb6b8b74..1b4434c4867c8 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js @@ -7,12 +7,15 @@ */ import { overwrite } from '../../helpers'; +import { validateField } from '../../../../../common/fields_utils'; -export function topHits(req, panel, annotation) { +export function topHits(req, panel, annotation, esQueryConfig, annotationIndex) { return (next) => (doc) => { const fields = (annotation.fields && annotation.fields.split(/[,\s]+/)) || []; const timeField = annotation.time_field; + validateField(timeField, annotationIndex); + overwrite(doc, `aggs.${annotation.id}.aggs.hits.top_hits`, { sort: [ { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js index a9b4f99fdb693..41ed472c31936 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.js @@ -12,6 +12,7 @@ import { offsetTime } from '../../offset_time'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { isLastValueTimerangeMode } from '../../helpers/get_timerange_mode'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; + const { dateHistogramInterval } = search.aggs; export function dateHistogram( @@ -19,7 +20,7 @@ export function dateHistogram( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { @@ -27,7 +28,7 @@ export function dateHistogram( const maxBarsUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS); const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, indexPattern); + const { timeField, interval, maxBars } = getIntervalAndTimefield(panel, series, seriesIndex); const { bucketSize, intervalString } = getBucketSize( req, interval, @@ -64,9 +65,9 @@ export function dateHistogram( overwrite(doc, `aggs.${series.id}.meta`, { timeField, intervalString, - index: indexPattern?.title, bucketSize, seriesId: series.id, + index: seriesIndex.indexPattern?.id, }); return next(doc); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js index 4639af9db83b8..d45943f6f21ac 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.js @@ -12,19 +12,19 @@ import { esQuery } from '../../../../../../data/server'; const filter = (metric) => metric.type === 'filter_ratio'; -export function ratios(req, panel, series, esQueryConfig, indexPattern) { +export function ratios(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.metrics.some(filter)) { series.metrics.filter(filter).forEach((metric) => { overwrite( doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-numerator.filter`, - esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.numerator, [], esQueryConfig) ); overwrite( doc, `aggs.${series.id}.aggs.timeseries.aggs.${metric.id}-denominator.filter`, - esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.denominator, [], esQueryConfig) ); let numeratorPath = `${metric.id}-numerator>_count`; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js index 345488ec01d5e..a93827ba82cd6 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/filter_ratios.test.js @@ -8,7 +8,7 @@ import { ratios } from './filter_ratios'; -describe('ratios(req, panel, series, esQueryConfig, indexPatternObject)', () => { +describe('ratios(req, panel, series, esQueryConfig, seriesIndex)', () => { let panel; let series; let req; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js index 86b691f6496c9..29a11bf163e0b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/metric_buckets.js @@ -17,14 +17,14 @@ export function metricBuckets( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, indexPattern); + const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); series.metrics diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js index ce61374c0b124..208321a98737e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/positive_rate.js @@ -56,14 +56,14 @@ export function positiveRate( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, indexPattern); + const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); if (series.metrics.some(filter)) { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js index d0e92c9157cb5..a5f4e17289e06 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.js @@ -10,16 +10,16 @@ import { offsetTime } from '../../offset_time'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, series, esQueryConfig, indexPattern) { +export function query(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, series, indexPattern); + const { timeField } = getIntervalAndTimefield(panel, series, seriesIndex); const { from, to } = offsetTime(req, series.offset_time); doc.size = 0; const ignoreGlobalFilter = panel.ignore_global_filter || series.ignore_global_filter; const queries = !ignoreGlobalFilter ? req.body.query : []; const filters = !ignoreGlobalFilter ? req.body.filters : []; - doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { range: { @@ -34,13 +34,13 @@ export function query(req, panel, series, esQueryConfig, indexPattern) { if (panel.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [panel.filter], [], esQueryConfig) ); } if (series.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [series.filter], [], esQueryConfig) ); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js index 2772aed822517..b3e88dbf1c6b9 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/query.test.js @@ -8,15 +8,17 @@ import { query } from './query'; -describe('query(req, panel, series)', () => { +describe('query', () => { let panel; let series; let req; + let seriesIndex; const config = { allowLeadingWildcards: true, queryStringOptions: { analyze_wildcard: true }, }; + beforeEach(() => { req = { body: { @@ -32,17 +34,18 @@ describe('query(req, panel, series)', () => { interval: '10s', }; series = { id: 'test' }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - query(req, panel, series, config)(next)({}); + query(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns doc with query for timerange', () => { const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -69,7 +72,7 @@ describe('query(req, panel, series)', () => { test('returns doc with query for timerange (offset by 1h)', () => { series.offset_time = '1h'; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -108,7 +111,7 @@ describe('query(req, panel, series)', () => { }, ]; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -147,7 +150,7 @@ describe('query(req, panel, series)', () => { test('returns doc with series filter', () => { series.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -201,7 +204,7 @@ describe('query(req, panel, series)', () => { ]; panel.filter = { query: 'host:web-server', language: 'lucene' }; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -269,7 +272,7 @@ describe('query(req, panel, series)', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; panel.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { @@ -325,7 +328,7 @@ describe('query(req, panel, series)', () => { panel.filter = { query: 'host:web-server', language: 'lucene' }; series.ignore_global_filter = true; const next = (doc) => doc; - const doc = query(req, panel, series, config)(next)({}); + const doc = query(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ size: 0, query: { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js index 401344d48f865..dbeb3b1393bd5 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/sibling_buckets.js @@ -17,13 +17,13 @@ export function siblingBuckets( panel, series, esQueryConfig, - indexPattern, + seriesIndex, capabilities, uiSettings ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, series, indexPattern); + const { interval } = getIntervalAndTimefield(panel, series, seriesIndex); const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings); series.metrics diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js index 25d62d4f7fe07..01e1b9f8d1dce 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.js @@ -9,7 +9,7 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) { +export function splitByFilter(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.split_mode !== 'filter') { return next(doc); @@ -18,7 +18,7 @@ export function splitByFilter(req, panel, series, esQueryConfig, indexPattern) { overwrite( doc, `aggs.${series.id}.filter`, - esQuery.buildEsQuery(indexPattern, [series.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [series.filter], [], esQueryConfig) ); return next(doc); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js index ad6e84dbc7842..9722833837167 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filter.test.js @@ -12,8 +12,12 @@ describe('splitByFilter(req, panel, series)', () => { let panel; let series; let req; + let config; + let seriesIndex; + beforeEach(() => { panel = {}; + config = {}; series = { id: 'test', split_mode: 'filter', @@ -27,17 +31,18 @@ describe('splitByFilter(req, panel, series)', () => { }, }, }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - splitByFilter(req, panel, series)(next)({}); + splitByFilter(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns a valid filter with a query_string', () => { const next = (doc) => doc; - const doc = splitByFilter(req, panel, series)(next)({}); + const doc = splitByFilter(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -63,7 +68,7 @@ describe('splitByFilter(req, panel, series)', () => { test('calls next and does not add a filter', () => { series.split_mode = 'terms'; const next = jest.fn((doc) => doc); - const doc = splitByFilter(req, panel, series)(next)({}); + const doc = splitByFilter(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); expect(doc).toEqual({}); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js index 237ed16e5a8b6..77b9ccc5880fe 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.js @@ -9,11 +9,16 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByFilters(req, panel, series, esQueryConfig, indexPattern) { +export function splitByFilters(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.split_mode === 'filters' && series.split_filters) { series.split_filters.forEach((filter) => { - const builtEsQuery = esQuery.buildEsQuery(indexPattern, [filter.filter], [], esQueryConfig); + const builtEsQuery = esQuery.buildEsQuery( + seriesIndex.indexPattern, + [filter.filter], + [], + esQueryConfig + ); overwrite(doc, `aggs.${series.id}.filters.filters.${filter.id}`, builtEsQuery); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js index fdcdfe45d2fd2..2a44bf2538a4b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_filters.test.js @@ -12,7 +12,11 @@ describe('splitByFilters(req, panel, series)', () => { let panel; let series; let req; + let config; + let seriesIndex; + beforeEach(() => { + config = {}; panel = { time_field: 'timestamp', }; @@ -43,17 +47,18 @@ describe('splitByFilters(req, panel, series)', () => { }, }, }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - splitByFilters(req, panel, series)(next)({}); + splitByFilters(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns a valid terms agg', () => { const next = (doc) => doc; - const doc = splitByFilters(req, panel, series)(next)({}); + const doc = splitByFilters(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -97,7 +102,7 @@ describe('splitByFilters(req, panel, series)', () => { test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; const next = jest.fn((doc) => doc); - const doc = splitByFilters(req, panel, series)(next)({}); + const doc = splitByFilters(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); expect(doc).toEqual({}); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js index 8f72bd2d12951..9c2bdbe03f886 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.js @@ -10,13 +10,16 @@ import { overwrite } from '../../helpers'; import { basicAggs } from '../../../../../common/basic_aggs'; import { getBucketsPath } from '../../helpers/get_buckets_path'; import { bucketTransform } from '../../helpers/bucket_transform'; +import { validateField } from '../../../../../common/fields_utils'; -export function splitByTerms(req, panel, series) { +export function splitByTerms(req, panel, series, esQueryConfig, seriesIndex) { return (next) => (doc) => { if (series.split_mode === 'terms' && series.terms_field) { const termsField = series.terms_field; const orderByTerms = series.terms_order_by; + validateField(termsField, seriesIndex); + const direction = series.terms_direction || 'desc'; const metric = series.metrics.find((item) => item.id === orderByTerms); overwrite(doc, `aggs.${series.id}.terms.field`, termsField); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js index 37d188c00eee3..984eb385ca4a6 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/split_by_terms.test.js @@ -8,11 +8,18 @@ import { splitByTerms } from './split_by_terms'; -describe('splitByTerms(req, panel, series)', () => { +describe('splitByTerms', () => { let panel; let series; let req; + let config; + let seriesIndex; + beforeEach(() => { + config = { + allowLeadingWildcards: true, + queryStringOptions: { analyze_wildcard: true }, + }; panel = { time_field: 'timestamp', }; @@ -31,17 +38,18 @@ describe('splitByTerms(req, panel, series)', () => { }, }, }; + seriesIndex = {}; }); test('calls next when finished', () => { const next = jest.fn(); - splitByTerms(req, panel, series)(next)({}); + splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); }); test('returns a valid terms agg', () => { const next = (doc) => doc; - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -61,7 +69,7 @@ describe('splitByTerms(req, panel, series)', () => { const next = (doc) => doc; series.terms_order_by = '_key'; series.terms_direction = 'asc'; - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -80,7 +88,7 @@ describe('splitByTerms(req, panel, series)', () => { test('returns a valid terms agg with custom sort', () => { series.terms_order_by = 'avgmetric'; const next = (doc) => doc; - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(doc).toEqual({ aggs: { test: { @@ -106,7 +114,7 @@ describe('splitByTerms(req, panel, series)', () => { test('calls next and does not add a terms agg', () => { series.split_mode = 'everything'; const next = jest.fn((doc) => doc); - const doc = splitByTerms(req, panel, series)(next)({}); + const doc = splitByTerms(req, panel, series, config, seriesIndex)(next)({}); expect(next.mock.calls.length).toEqual(1); expect(doc).toEqual({}); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js index aff1bd5041be5..4840e625383ca 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/date_histogram.js @@ -13,15 +13,17 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { getTimerange } from '../../helpers/get_timerange'; import { calculateAggRoot } from './calculate_agg_root'; import { search, UI_SETTINGS } from '../../../../../../../plugins/data/server'; + const { dateHistogramInterval } = search.aggs; -export function dateHistogram(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function dateHistogram(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { timeField, interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { timeField, interval } = getIntervalAndTimefield(panel, {}, seriesIndex); + const meta = { timeField, - index: indexPattern?.title, + index: seriesIndex.indexPattern?.id, }; const getDateHistogramForLastBucketMode = () => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js index abb5971908771..e15330334639f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/filter_ratios.js @@ -13,7 +13,7 @@ import { calculateAggRoot } from './calculate_agg_root'; const filter = (metric) => metric.type === 'filter_ratio'; -export function ratios(req, panel, esQueryConfig, indexPattern) { +export function ratios(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { panel.series.forEach((column) => { const aggRoot = calculateAggRoot(doc, column); @@ -22,12 +22,12 @@ export function ratios(req, panel, esQueryConfig, indexPattern) { overwrite( doc, `${aggRoot}.timeseries.aggs.${metric.id}-numerator.filter`, - esQuery.buildEsQuery(indexPattern, metric.numerator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.numerator, [], esQueryConfig) ); overwrite( doc, `${aggRoot}.timeseries.aggs.${metric.id}-denominator.filter`, - esQuery.buildEsQuery(indexPattern, metric.denominator, [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, metric.denominator, [], esQueryConfig) ); let numeratorPath = `${metric.id}-numerator>_count`; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js index 5ce508bd9b279..421f9d2d75f0c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/metric_buckets.js @@ -13,10 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { calculateAggRoot } from './calculate_agg_root'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function metricBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function metricBuckets(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js index 176721e7b563a..3390362b56115 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/positive_rate.js @@ -12,10 +12,10 @@ import { calculateAggRoot } from './calculate_agg_root'; import { createPositiveRate, filter } from '../series/positive_rate'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function positiveRate(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function positiveRate(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); const { intervalString } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js index 76df07b76e80e..66783e0cdfaef 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/query.js @@ -10,16 +10,16 @@ import { getTimerange } from '../../helpers/get_timerange'; import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { esQuery } from '../../../../../../data/server'; -export function query(req, panel, esQueryConfig, indexPattern) { +export function query(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { - const { timeField } = getIntervalAndTimefield(panel, {}, indexPattern); + const { timeField } = getIntervalAndTimefield(panel, {}, seriesIndex); const { from, to } = getTimerange(req); doc.size = 0; const queries = !panel.ignore_global_filter ? req.body.query : []; const filters = !panel.ignore_global_filter ? req.body.filters : []; - doc.query = esQuery.buildEsQuery(indexPattern, queries, filters, esQueryConfig); + doc.query = esQuery.buildEsQuery(seriesIndex.indexPattern, queries, filters, esQueryConfig); const timerange = { range: { @@ -33,7 +33,7 @@ export function query(req, panel, esQueryConfig, indexPattern) { doc.query.bool.must.push(timerange); if (panel.filter) { doc.query.bool.must.push( - esQuery.buildEsQuery(indexPattern, [panel.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [panel.filter], [], esQueryConfig) ); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js index 5539f16df41e0..9b4b0f244fc2c 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/sibling_buckets.js @@ -13,10 +13,10 @@ import { getIntervalAndTimefield } from '../../get_interval_and_timefield'; import { calculateAggRoot } from './calculate_agg_root'; import { UI_SETTINGS } from '../../../../../../data/common'; -export function siblingBuckets(req, panel, esQueryConfig, indexPattern, capabilities, uiSettings) { +export function siblingBuckets(req, panel, esQueryConfig, seriesIndex, capabilities, uiSettings) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const { interval } = getIntervalAndTimefield(panel, {}, indexPattern); + const { interval } = getIntervalAndTimefield(panel, {}, seriesIndex); const { bucketSize } = getBucketSize(req, interval, capabilities, barTargetUiSettings); panel.series.forEach((column) => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js index 595d49ebbd836..cda022294507f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_everything.js @@ -9,7 +9,7 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByEverything(req, panel, esQueryConfig, indexPattern) { +export function splitByEverything(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { panel.series .filter((c) => !(c.aggregate_by && c.aggregate_function)) @@ -18,7 +18,7 @@ export function splitByEverything(req, panel, esQueryConfig, indexPattern) { overwrite( doc, `aggs.pivot.aggs.${column.id}.filter`, - esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [column.filter], [], esQueryConfig) ); } else { overwrite(doc, `aggs.pivot.aggs.${column.id}.filter.match_all`, {}); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js index b4e07455be0fb..b3afc334ac2dd 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/table/split_by_terms.js @@ -9,7 +9,7 @@ import { overwrite } from '../../helpers'; import { esQuery } from '../../../../../../data/server'; -export function splitByTerms(req, panel, esQueryConfig, indexPattern) { +export function splitByTerms(req, panel, esQueryConfig, seriesIndex) { return (next) => (doc) => { panel.series .filter((c) => c.aggregate_by && c.aggregate_function) @@ -21,7 +21,7 @@ export function splitByTerms(req, panel, esQueryConfig, indexPattern) { overwrite( doc, `aggs.pivot.aggs.${column.id}.column_filter.filter`, - esQuery.buildEsQuery(indexPattern, [column.filter], [], esQueryConfig) + esQuery.buildEsQuery(seriesIndex.indexPattern, [column.filter], [], esQueryConfig) ); } }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js index ba0271ba286a1..a803439c7581f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/series_agg.js @@ -5,9 +5,8 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import { last, first } from 'lodash'; import { SeriesAgg } from './_series_agg'; -import _ from 'lodash'; import { getDefaultDecoration } from '../../helpers/get_default_decoration'; import { calculateLabel } from '../../../../../common/calculate_label'; @@ -33,15 +32,14 @@ export function seriesAgg(resp, panel, series, meta, extractFields) { return (fn && fn(acc)) || acc; }, targetSeries); - const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : []; + const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : []; results.push({ id: `${series.id}`, label: - series.label || - calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex), + series.label || calculateLabel(last(series.metrics), series.metrics, fieldsForSeries), color: series.color, - data: _.first(data), + data: first(data), ...decoration, }); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js index 9af05afd41182..ae4968e007b1d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/table/series_agg.js @@ -7,7 +7,7 @@ */ import { SeriesAgg } from './_series_agg'; -import _ from 'lodash'; +import { last, first } from 'lodash'; import { calculateLabel } from '../../../../../common/calculate_label'; export function seriesAgg(resp, panel, series, meta, extractFields) { @@ -25,15 +25,13 @@ export function seriesAgg(resp, panel, series, meta, extractFields) { }); const fn = SeriesAgg[series.aggregate_function]; const data = fn(targetSeries); - - const fieldsForMetaIndex = meta.index ? await extractFields(meta.index) : []; + const fieldsForSeries = meta.index ? await extractFields({ id: meta.index }) : []; results.push({ id: `${series.id}`, label: - series.label || - calculateLabel(_.last(series.metrics), series.metrics, fieldsForMetaIndex), - data: _.first(data), + series.label || calculateLabel(last(series.metrics), series.metrics, fieldsForSeries), + data: first(data), }); } return next(results); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts index bab3abe13bcb0..bc046cbdcf8aa 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/build_request_body.ts @@ -18,7 +18,7 @@ import { processors } from '../request_processors/series/index'; * panel: {Object} - a panel object, * series: {Object} - an series object, * esQueryConfig: {Object} - es query config object, - * indexPatternObject: {Object} - an index pattern object, + * seriesIndex: {Object} - an index pattern object, * capabilities: {Object} - a search capabilities object * ] * @returns {Object} doc - processed body diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts index 1f2735da8fb06..827df30dacf6d 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/series/get_request_params.ts @@ -39,7 +39,7 @@ export async function getSeriesRequestParams( panel, series, esQueryConfig, - seriesIndex.indexPattern, + seriesIndex, capabilities, uiSettings ); @@ -48,6 +48,7 @@ export async function getSeriesRequestParams( index: seriesIndex.indexPatternString, body: { ...request, + runtime_mappings: seriesIndex.indexPattern?.getComputedFields().runtimeFields ?? {}, timeout: esShardTimeout > 0 ? `${esShardTimeout}ms` : undefined, }, }; diff --git a/src/plugins/visualizations/public/components/visualization_container.tsx b/src/plugins/visualizations/public/components/visualization_container.tsx index 3081c39530d75..063715b6438eb 100644 --- a/src/plugins/visualizations/public/components/visualization_container.tsx +++ b/src/plugins/visualizations/public/components/visualization_container.tsx @@ -10,6 +10,7 @@ import React, { ReactNode, Suspense } from 'react'; import { EuiLoadingChart } from '@elastic/eui'; import classNames from 'classnames'; import { VisualizationNoResults } from './visualization_noresults'; +import { VisualizationError } from './visualization_error'; import { IInterpreterRenderHandlers } from '../../../expressions/common'; interface VisualizationContainerProps { @@ -18,6 +19,7 @@ interface VisualizationContainerProps { children: ReactNode; handlers: IInterpreterRenderHandlers; showNoResult?: boolean; + error?: string; } export const VisualizationContainer = ({ @@ -26,6 +28,7 @@ export const VisualizationContainer = ({ children, handlers, showNoResult = false, + error, }: VisualizationContainerProps) => { const classes = classNames('visualization', className); @@ -38,7 +41,13 @@ export const VisualizationContainer = ({ return (
- {showNoResult ? handlers.done()} /> : children} + {error ? ( + handlers.done()} error={error} /> + ) : showNoResult ? ( + handlers.done()} /> + ) : ( + children + )}
); diff --git a/src/plugins/visualizations/public/components/visualization_error.tsx b/src/plugins/visualizations/public/components/visualization_error.tsx new file mode 100644 index 0000000000000..81600a4e3601c --- /dev/null +++ b/src/plugins/visualizations/public/components/visualization_error.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EuiEmptyPrompt } from '@elastic/eui'; +import React from 'react'; + +interface VisualizationNoResultsProps { + onInit?: () => void; + error: string; +} + +export class VisualizationError extends React.Component { + public render() { + return ( + {this.props.error}

} + /> + ); + } + + public componentDidMount() { + this.afterRender(); + } + + public componentDidUpdate() { + this.afterRender(); + } + + private afterRender() { + if (this.props.onInit) { + this.props.onInit(); + } + } +} diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index d0c7c86d6d5c3..891805acb3256 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -83,6 +83,7 @@ export default function ({ getService, getPageObjects }) { ); await PageObjects.visualBuilder.clickPanelOptions('metric'); await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName, false); + await PageObjects.visualBuilder.selectIndexPatternTimeField('@timestamp'); await PageObjects.visualBuilder.setMetricsDataTimerangeMode('Last value'); await PageObjects.visualBuilder.setIntervalValue('1d'); await PageObjects.visualBuilder.setDropLastBucket(false); From 2d0b32a40afc2e095b035d27cfc95c5e5f6c74b2 Mon Sep 17 00:00:00 2001 From: Maja Grubic Date: Mon, 12 Apr 2021 15:25:50 +0100 Subject: [PATCH 10/79] [Discover] Integration of Runtime Fields editor - edit operation (#95498) * [Discover] Updating a functional test * [Discover] Support for edit operation * Fix unit tests * Fix typescript * Fixing failing functional test * Fixing wrongly commented line * Uncomment accidentally commented line * Reintroducing accidnetally removed unit test * Trigger data refetch onSave * Remove refreshAppState variable * Bundling observers together * Clean state before refetch * Update formatting in data grid * [Discover] Updating a functional test * Adding a functional test * Fixing package.json * Reset fieldCount after data fetch * [Discover] Updating a functional test * Don't allow editing of unmapped fields * Fix issues with mobile display * Allow editing if it's a runtime field * [Discover] Updating a functional test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/discover/kibana.json | 3 +- .../public/application/angular/discover.js | 7 +++ .../application/angular/discover_legacy.html | 1 + .../application/angular/discover_state.ts | 2 +- .../angular/helpers/row_formatter.test.ts | 5 +- .../angular/helpers/row_formatter.ts | 8 +++- .../components/create_discover_directive.ts | 1 + .../application/components/discover.tsx | 8 ++++ .../discover_grid/get_render_cell_value.tsx | 10 +++- .../components/sidebar/discover_field.tsx | 39 +++++++++++++-- .../sidebar/discover_sidebar.test.tsx | 7 +++ .../components/sidebar/discover_sidebar.tsx | 41 ++++++++++++++++ .../discover_sidebar_responsive.test.tsx | 1 + .../sidebar/discover_sidebar_responsive.tsx | 35 +++++++++++++- .../public/application/components/types.ts | 2 + src/plugins/discover/public/build_services.ts | 3 ++ src/plugins/discover/public/plugin.tsx | 2 + src/plugins/discover/tsconfig.json | 3 +- .../apps/discover/_data_grid_context.ts | 2 +- .../apps/discover/_runtime_fields_editor.ts | 47 +++++++++++++++++++ test/functional/apps/discover/index.ts | 1 + test/functional/page_objects/discover_page.ts | 8 ++++ test/functional/services/field_editor.ts | 6 +++ 23 files changed, 227 insertions(+), 15 deletions(-) create mode 100644 test/functional/apps/discover/_runtime_fields_editor.ts diff --git a/src/plugins/discover/kibana.json b/src/plugins/discover/kibana.json index 7db03f726e6f5..6ea22001f5d80 100644 --- a/src/plugins/discover/kibana.json +++ b/src/plugins/discover/kibana.json @@ -12,7 +12,8 @@ "urlForwarding", "navigation", "uiActions", - "savedObjects" + "savedObjects", + "indexPatternFieldEditor" ], "optionalPlugins": ["home", "share", "usageCollection"], "requiredBundles": ["kibanaUtils", "home", "kibanaReact"] diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 45382af098644..35a89eb45f35e 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -458,6 +458,13 @@ function discoverController($route, $scope) { $scope.fetchStatus = fetchStatuses.COMPLETE; } + $scope.refreshAppState = async () => { + $scope.hits = []; + $scope.rows = []; + $scope.fieldCounts = {}; + await refetch$.next(); + }; + function getRequestResponder({ searchSessionId = null } = { searchSessionId: null }) { inspectorAdapters.requests.reset(); const title = i18n.translate('discover.inspectorRequestDataTitle', { diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index f14800f81d08e..fadaffde5c5c3 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -16,6 +16,7 @@ top-nav-menu="topNavMenu" use-new-fields-api="useNewFieldsApi" unmapped-fields-config="unmappedFieldsConfig" + refresh-app-state="refreshAppState" > diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts index e7d5ed469525f..9ebeff69d7542 100644 --- a/src/plugins/discover/public/application/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -177,7 +177,7 @@ export function getState({ }, uiSettings ); - // todo filter source depending on fields fetchinbg flag (if no columns remain and source fetching is enabled, use default columns) + // todo filter source depending on fields fetching flag (if no columns remain and source fetching is enabled, use default columns) let previousAppState: AppState; const appStateContainer = createStateContainer(initialAppState); diff --git a/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts b/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts index 050959dff98a4..4c6b9002ce867 100644 --- a/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts +++ b/src/plugins/discover/public/application/angular/helpers/row_formatter.test.ts @@ -90,6 +90,7 @@ describe('Row formatter', () => { }, { 'object.value': [5, 10], + getByName: jest.fn(), }, indexPattern ).trim() @@ -107,7 +108,7 @@ describe('Row formatter', () => { }); const formatted = formatTopLevelObject( { fields: { 'a.zzz': [100], 'a.ccc': [50] } }, - { 'a.zzz': [100], 'a.ccc': [50] }, + { 'a.zzz': [100], 'a.ccc': [50], getByName: jest.fn() }, indexPattern ).trim(); expect(formatted.indexOf('
a.ccc:
')).toBeLessThan(formatted.indexOf('
a.zzz:
')); @@ -134,6 +135,7 @@ describe('Row formatter', () => { { 'object.value': [5, 10], 'object.keys': ['a', 'b'], + getByName: jest.fn(), }, indexPattern ).trim() @@ -154,6 +156,7 @@ describe('Row formatter', () => { }, { 'object.value': [5, 10], + getByName: jest.fn(), }, indexPattern ).trim() diff --git a/src/plugins/discover/public/application/angular/helpers/row_formatter.ts b/src/plugins/discover/public/application/angular/helpers/row_formatter.ts index a226cefb53960..02902b0634797 100644 --- a/src/plugins/discover/public/application/angular/helpers/row_formatter.ts +++ b/src/plugins/discover/public/application/angular/helpers/row_formatter.ts @@ -28,11 +28,13 @@ export const formatRow = (hit: Record, indexPattern: IndexPattern) const highlights = hit?.highlight ?? {}; // Keys are sorted in the hits object const formatted = indexPattern.formatHit(hit); + const fields = indexPattern.fields; const highlightPairs: Array<[string, unknown]> = []; const sourcePairs: Array<[string, unknown]> = []; Object.entries(formatted).forEach(([key, val]) => { + const displayKey = fields.getByName ? fields.getByName(key)?.displayName : undefined; const pairs = highlights[key] ? highlightPairs : sourcePairs; - pairs.push([key, val]); + pairs.push([displayKey ? displayKey : key, val]); }); return doTemplate({ defPairs: [...highlightPairs, ...sourcePairs] }); }; @@ -48,9 +50,11 @@ export const formatTopLevelObject = ( const sorted = Object.entries(fields).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)); sorted.forEach(([key, values]) => { const field = indexPattern.getFieldByName(key); + const displayKey = fields.getByName ? fields.getByName(key)?.displayName : undefined; const formatter = field ? indexPattern.getFormatterForField(field) : { convert: (v: string, ...rest: unknown[]) => String(v) }; + if (!values.map) return; const formatted = values .map((val: unknown) => formatter.convert(val, 'html', { @@ -61,7 +65,7 @@ export const formatTopLevelObject = ( ) .join(', '); const pairs = highlights[key] ? highlightPairs : sourcePairs; - pairs.push([key, formatted]); + pairs.push([displayKey ? displayKey : key, formatted]); }); return doTemplate({ defPairs: [...highlightPairs, ...sourcePairs] }); }; diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts index 5abf87fdfbc08..cc88ef03c5d03 100644 --- a/src/plugins/discover/public/application/components/create_discover_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_directive.ts @@ -28,5 +28,6 @@ export function createDiscoverDirective(reactDirective: any) { ['updateQuery', { watchDepth: 'reference' }], ['updateSavedQueryId', { watchDepth: 'reference' }], ['unmappedFieldsConfig', { watchDepth: 'value' }], + ['refreshAppState', { watchDepth: 'reference' }], ]); } diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 9615a1c10ea8e..6b71bd892b520 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -68,6 +68,7 @@ export function Discover({ searchSource, state, unmappedFieldsConfig, + refreshAppState, }: DiscoverProps) { const [expandedDoc, setExpandedDoc] = useState(undefined); const scrollableDesktop = useRef(null); @@ -203,6 +204,12 @@ export function Discover({ [opts, state] ); + const onEditRuntimeField = () => { + if (refreshAppState) { + refreshAppState(); + } + }; + const columns = useMemo(() => { if (!state.columns) { return []; @@ -245,6 +252,7 @@ export function Discover({ trackUiMetric={trackUiMetric} unmappedFieldsConfig={unmappedFieldsConfig} useNewFieldsApi={useNewFieldsApi} + onEditRuntimeField={onEditRuntimeField} />
diff --git a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx index dce0a82934c25..03203a79d9dd0 100644 --- a/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/get_render_cell_value.tsx @@ -77,6 +77,9 @@ export const getRenderCellValueFn = ( const sourcePairs: Array<[string, string]> = []; Object.entries(innerColumns).forEach(([key, values]) => { const subField = indexPattern.getFieldByName(key); + const displayKey = indexPattern.fields.getByName + ? indexPattern.fields.getByName(key)?.displayName + : undefined; const formatter = subField ? indexPattern.getFormatterForField(subField) : { convert: (v: string, ...rest: unknown[]) => String(v) }; @@ -90,7 +93,7 @@ export const getRenderCellValueFn = ( ) .join(', '); const pairs = highlights[key] ? highlightPairs : sourcePairs; - pairs.push([key, formatted]); + pairs.push([displayKey ? displayKey : key, formatted]); }); return ( @@ -130,7 +133,10 @@ export const getRenderCellValueFn = ( Object.entries(formatted).forEach(([key, val]) => { const pairs = highlights[key] ? highlightPairs : sourcePairs; - pairs.push([key, val as string]); + const displayKey = indexPattern.fields.getByName + ? indexPattern.fields.getByName(key)?.displayName + : undefined; + pairs.push([displayKey ? displayKey : key, val as string]); }); return ( diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx index b0d71c774f445..a630ddda40f30 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field.tsx @@ -16,6 +16,8 @@ import { EuiToolTip, EuiTitle, EuiIcon, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UiCounterMetricType } from '@kbn/analytics'; @@ -69,6 +71,8 @@ export interface DiscoverFieldProps { trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; multiFields?: Array<{ field: IndexPatternField; isSelected: boolean }>; + + onEditField?: (fieldName: string) => void; } export function DiscoverField({ @@ -82,6 +86,7 @@ export function DiscoverField({ selected, trackUiMetric, multiFields, + onEditField, }: DiscoverFieldProps) { const addLabelAria = i18n.translate('discover.fieldChooser.discoverField.addButtonAriaLabel', { defaultMessage: 'Add {field} to table', @@ -250,7 +255,6 @@ export function DiscoverField({ }; const fieldInfoIcon = getFieldInfoIcon(); - const shouldRenderMultiFields = !!multiFields; const renderMultiFields = () => { if (!multiFields) { @@ -282,6 +286,35 @@ export function DiscoverField({ ); }; + const isRuntimeField = Boolean(indexPattern.getFieldByName(field.name)?.runtimeField); + const isUnknownField = field.type === 'unknown' || field.type === 'unknown_selected'; + const canEditField = onEditField && (!isUnknownField || isRuntimeField); + const displayNameGrow = canEditField ? 9 : 10; + const popoverTitle = ( + + + {field.displayName} + {canEditField && ( + + { + if (onEditField) { + togglePopover(); + onEditField(field.name); + } + }} + iconType="pencil" + data-test-subj={`discoverFieldListPanelEdit-${field.name}`} + aria-label={i18n.translate('discover.fieldChooser.discoverField.editFieldLabel', { + defaultMessage: 'Edit index pattern field', + })} + /> + + )} + + + ); + return ( - - {field.displayName} - + {popoverTitle}
{i18n.translate('discover.fieldChooser.discoverField.fieldTopValuesLabel', { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx index 947972ce1cfc5..0b3f55b5630cc 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.test.tsx @@ -48,6 +48,12 @@ const mockServices = ({ } }, }, + indexPatternFieldEditor: { + openEditor: jest.fn(), + userPermissions: { + editIndexPattern: jest.fn(), + }, + }, } as unknown) as DiscoverServices; jest.mock('../../../kibana_services', () => ({ @@ -102,6 +108,7 @@ function getCompProps(): DiscoverSidebarProps { fieldFilter: getDefaultFieldFilter(), setFieldFilter: jest.fn(), setAppState: jest.fn(), + onEditRuntimeField: jest.fn(), }; } diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 1be42e1cd6b17..a3bf2e150d088 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -49,6 +49,17 @@ export interface DiscoverSidebarProps extends DiscoverSidebarResponsiveProps { * Change current state of fieldFilter */ setFieldFilter: (next: FieldFilterState) => void; + + /** + * Callback to close the flyout sidebar rendered in a flyout, close flyout + */ + closeFlyout?: () => void; + + /** + * Pass the reference to field editor component to the parent, so it can be properly unmounted + * @param ref reference to the field editor component + */ + setFieldEditorRef?: (ref: () => void | undefined) => void; } export function DiscoverSidebar({ @@ -72,8 +83,14 @@ export function DiscoverSidebar({ useNewFieldsApi = false, useFlyout = false, unmappedFieldsConfig, + onEditRuntimeField, + setFieldEditorRef, + closeFlyout, }: DiscoverSidebarProps) { const [fields, setFields] = useState(null); + const { indexPatternFieldEditor } = services; + const indexPatternFieldEditPermission = indexPatternFieldEditor?.userPermissions.editIndexPattern(); + const canEditIndexPatternField = !!indexPatternFieldEditPermission && useNewFieldsApi; const [scrollContainer, setScrollContainer] = useState(null); const [fieldsToRender, setFieldsToRender] = useState(FIELDS_PER_PAGE); const [fieldsPerPage, setFieldsPerPage] = useState(FIELDS_PER_PAGE); @@ -220,6 +237,27 @@ export function DiscoverSidebar({ return null; } + const editField = (fieldName: string) => { + if (!canEditIndexPatternField) { + return; + } + const ref = indexPatternFieldEditor.openEditor({ + ctx: { + indexPattern: selectedIndexPattern, + }, + fieldName, + onSave: async () => { + onEditRuntimeField(); + }, + }); + if (setFieldEditorRef) { + setFieldEditorRef(ref); + } + if (closeFlyout) { + closeFlyout(); + } + }; + if (useFlyout) { return (
); @@ -388,6 +427,7 @@ export function DiscoverSidebar({ getDetails={getDetailsByField} trackUiMetric={trackUiMetric} multiFields={multiFields?.get(field.name)} + onEditField={canEditIndexPatternField ? editField : undefined} /> ); @@ -414,6 +454,7 @@ export function DiscoverSidebar({ getDetails={getDetailsByField} trackUiMetric={trackUiMetric} multiFields={multiFields?.get(field.name)} + onEditField={canEditIndexPatternField ? editField : undefined} /> ); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx index 79e8caabd4930..caec61cc501b9 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx @@ -102,6 +102,7 @@ function getCompProps(): DiscoverSidebarResponsiveProps { setAppState: jest.fn(), state: {}, trackUiMetric: jest.fn(), + onEditRuntimeField: jest.fn(), }; } diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx index 0808ef47c0dc1..6a16399f0e2e1 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import { sortBy } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -121,6 +121,8 @@ export interface DiscoverSidebarResponsiveProps { */ showUnmappedFields: boolean; }; + + onEditRuntimeField: () => void; } /** @@ -132,15 +134,42 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) const [fieldFilter, setFieldFilter] = useState(getDefaultFieldFilter()); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const closeFieldEditor = useRef<() => void | undefined>(); + + useEffect(() => { + const cleanup = () => { + if (closeFieldEditor?.current) { + closeFieldEditor?.current(); + } + }; + return () => { + // Make sure to close the editor when unmounting + cleanup(); + }; + }, []); + if (!props.selectedIndexPattern) { return null; } + const setFieldEditorRef = (ref: () => void | undefined) => { + closeFieldEditor.current = ref; + }; + + const closeFlyout = () => { + setIsFlyoutVisible(false); + }; + return ( <> {props.isClosed ? null : ( - + )} @@ -215,6 +244,8 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) fieldFilter={fieldFilter} setFieldFilter={setFieldFilter} alwaysShowActionButtons={true} + setFieldEditorRef={setFieldEditorRef} + closeFlyout={closeFlyout} /> diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts index 23a3cc9a9bc74..93620bc1d6bca 100644 --- a/src/plugins/discover/public/application/components/types.ts +++ b/src/plugins/discover/public/application/components/types.ts @@ -167,4 +167,6 @@ export interface DiscoverProps { */ showUnmappedFields: boolean; }; + + refreshAppState?: () => void; } diff --git a/src/plugins/discover/public/build_services.ts b/src/plugins/discover/public/build_services.ts index 252265692d203..cf95d5a85b9f2 100644 --- a/src/plugins/discover/public/build_services.ts +++ b/src/plugins/discover/public/build_services.ts @@ -34,6 +34,7 @@ import { getHistory } from './kibana_services'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; import { UrlForwardingStart } from '../../url_forwarding/public'; import { NavigationPublicPluginStart } from '../../navigation/public'; +import { IndexPatternFieldEditorStart } from '../../index_pattern_field_editor/public'; export interface DiscoverServices { addBasePath: (path: string) => string; @@ -59,6 +60,7 @@ export interface DiscoverServices { getEmbeddableInjector: any; uiSettings: IUiSettingsClient; trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; + indexPatternFieldEditor: IndexPatternFieldEditorStart; } export async function buildServices( @@ -100,5 +102,6 @@ export async function buildServices( toastNotifications: core.notifications.toasts, uiSettings: core.uiSettings, trackUiMetric: usageCollection?.reportUiCounter.bind(usageCollection, 'discover'), + indexPatternFieldEditor: plugins.indexPatternFieldEditor, }; } diff --git a/src/plugins/discover/public/plugin.tsx b/src/plugins/discover/public/plugin.tsx index 0e0836e3d9573..692704c92356e 100644 --- a/src/plugins/discover/public/plugin.tsx +++ b/src/plugins/discover/public/plugin.tsx @@ -62,6 +62,7 @@ import { import { SearchEmbeddableFactory } from './application/embeddable'; import { UsageCollectionSetup } from '../../usage_collection/public'; import { replaceUrlHashQuery } from '../../kibana_utils/public/'; +import { IndexPatternFieldEditorStart } from '../../../plugins/index_pattern_field_editor/public'; declare module '../../share/public' { export interface UrlGeneratorStateMapping { @@ -133,6 +134,7 @@ export interface DiscoverStartPlugins { inspector: InspectorPublicPluginStart; savedObjects: SavedObjectsStart; usageCollection?: UsageCollectionSetup; + indexPatternFieldEditor: IndexPatternFieldEditorStart; } const innerAngularName = 'app/discover'; diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index ec98199c3423e..c0179ad3c8d20 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -23,6 +23,7 @@ { "path": "../usage_collection/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, { "path": "../kibana_react/tsconfig.json" }, - { "path": "../kibana_legacy/tsconfig.json" } + { "path": "../kibana_legacy/tsconfig.json" }, + { "path": "../index_pattern_field_editor/tsconfig.json"} ] } diff --git a/test/functional/apps/discover/_data_grid_context.ts b/test/functional/apps/discover/_data_grid_context.ts index 326fba9e6c087..bc259c71b47b4 100644 --- a/test/functional/apps/discover/_data_grid_context.ts +++ b/test/functional/apps/discover/_data_grid_context.ts @@ -110,7 +110,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await alert?.accept(); expect(await browser.getCurrentUrl()).to.contain('#/context'); await PageObjects.header.waitUntilLoadingHasFinished(); - expect(await docTable.getRowsText()).to.have.length(6); + expect(await docTable.getBodyRows()).to.have.length(6); }); }); } diff --git a/test/functional/apps/discover/_runtime_fields_editor.ts b/test/functional/apps/discover/_runtime_fields_editor.ts new file mode 100644 index 0000000000000..729ad08db81aa --- /dev/null +++ b/test/functional/apps/discover/_runtime_fields_editor.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 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 expect from '@kbn/expect'; +import { FtrProviderContext } from './ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const fieldEditor = getService('fieldEditor'); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); + const defaultSettings = { + defaultIndex: 'logstash-*', + 'discover:searchFieldsFromSource': false, + }; + describe('discover integration with runtime fields editor', function describeIndexTests() { + before(async function () { + await esArchiver.load('discover'); + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace(defaultSettings); + log.debug('discover'); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + }); + + after(async () => { + await kibanaServer.uiSettings.replace({ 'discover:searchFieldsFromSource': true }); + }); + + it('allows adding custom label to existing fields', async function () { + await PageObjects.discover.clickFieldListItemAdd('bytes'); + await PageObjects.discover.editField('bytes'); + await fieldEditor.enableCustomLabel(); + await fieldEditor.setCustomLabel('megabytes'); + await fieldEditor.save(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.discover.getDocHeader()).to.have.string('megabytes'); + expect((await PageObjects.discover.getAllFieldNames()).includes('megabytes')).to.be(true); + }); + }); +} diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index e526cdaccbd4c..db76cd1c20c38 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -47,6 +47,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_data_grid_doc_navigation')); loadTestFile(require.resolve('./_data_grid_doc_table')); loadTestFile(require.resolve('./_indexpattern_with_unmapped_fields')); + loadTestFile(require.resolve('./_runtime_fields_editor')); loadTestFile(require.resolve('./_huge_fields')); }); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 32288239f9848..b4042e7072d7f 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -255,6 +255,14 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider .map((field) => $(field).text()); } + public async editField(field: string) { + await retry.try(async () => { + await testSubjects.click(`field-${field}`); + await testSubjects.click(`discoverFieldListPanelEdit-${field}`); + await find.byClassName('indexPatternFieldEditor__form'); + }); + } + public async hasNoResults() { return await testSubjects.exists('discoverNoResults'); } diff --git a/test/functional/services/field_editor.ts b/test/functional/services/field_editor.ts index 7d6dad4f7858e..342e2afec28d3 100644 --- a/test/functional/services/field_editor.ts +++ b/test/functional/services/field_editor.ts @@ -16,6 +16,12 @@ export function FieldEditorProvider({ getService }: FtrProviderContext) { public async setName(name: string) { await testSubjects.setValue('nameField > input', name); } + public async enableCustomLabel() { + await testSubjects.setEuiSwitch('customLabelRow > toggle', 'check'); + } + public async setCustomLabel(name: string) { + await testSubjects.setValue('customLabelRow > input', name); + } public async enableValue() { await testSubjects.setEuiSwitch('valueRow > toggle', 'check'); } From 2ab94f05e1e8846c77a41cfc36eaa722b207f80e Mon Sep 17 00:00:00 2001 From: Matthew Kime Date: Mon, 12 Apr 2021 09:27:44 -0500 Subject: [PATCH 11/79] Index pattern management - fix refresh of index pattern list after delete (#92619) * refresh id and title list * add functional test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../index_pattern_management/public/components/utils.ts | 2 +- .../apps/management/_create_index_pattern_wizard.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/plugins/index_pattern_management/public/components/utils.ts b/src/plugins/index_pattern_management/public/components/utils.ts index 5701a1e375204..68e78199798b4 100644 --- a/src/plugins/index_pattern_management/public/components/utils.ts +++ b/src/plugins/index_pattern_management/public/components/utils.ts @@ -14,7 +14,7 @@ export async function getIndexPatterns( indexPatternManagementStart: IndexPatternManagementStart, indexPatternsService: IndexPatternsContract ) { - const existingIndexPatterns = await indexPatternsService.getIdsWithTitle(); + const existingIndexPatterns = await indexPatternsService.getIdsWithTitle(true); const indexPatternsListItems = await Promise.all( existingIndexPatterns.map(async ({ id, title }) => { const isDefault = defaultIndex === id; diff --git a/test/functional/apps/management/_create_index_pattern_wizard.js b/test/functional/apps/management/_create_index_pattern_wizard.js index 8db11052d5ed0..306d251629396 100644 --- a/test/functional/apps/management/_create_index_pattern_wizard.js +++ b/test/functional/apps/management/_create_index_pattern_wizard.js @@ -12,7 +12,7 @@ export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); const es = getService('legacyEs'); - const PageObjects = getPageObjects(['settings', 'common']); + const PageObjects = getPageObjects(['settings', 'common', 'header']); const security = getService('security'); describe('"Create Index Pattern" wizard', function () { @@ -60,6 +60,12 @@ export default function ({ getService, getPageObjects }) { await PageObjects.settings.createIndexPattern('alias1', false); }); + it('can delete an index pattern', async () => { + await PageObjects.settings.removeIndexPattern(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await testSubjects.exists('indexPatternTable'); + }); + after(async () => { await es.transport.request({ path: '/_aliases', From 1f9700ec65b0ee2574f2828bfd24f79def46abb9 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 12 Apr 2021 16:44:48 +0200 Subject: [PATCH 12/79] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20enable=20drilldown?= =?UTF-8?q?=20actions=20in=20"edit"=20mode=20(#96023)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 enable drilldown actions in "edit" mode * style: 💄 remove unused import Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/embeddable_enhanced/public/plugin.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts index 96224644a457f..4b27b31ad3e0e 100644 --- a/x-pack/plugins/embeddable_enhanced/public/plugin.ts +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -18,7 +18,6 @@ import { defaultEmbeddableFactoryProvider, EmbeddableContext, PANEL_NOTIFICATION_TRIGGER, - ViewMode, } from '../../../../src/plugins/embeddable/public'; import { EnhancedEmbeddable } from './types'; import { @@ -119,7 +118,6 @@ export class EmbeddableEnhancedPlugin const dynamicActions = new DynamicActionManager({ isCompatible: async (context: unknown) => { if (!this.isEmbeddableContext(context)) return false; - if (context.embeddable.getInput().viewMode !== ViewMode.VIEW) return false; return context.embeddable.runtimeId === embeddable.runtimeId; }, storage, From 60d8fab88d08cef5ded3f380bcf0b000b121eae2 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 12 Apr 2021 16:49:49 +0200 Subject: [PATCH 13/79] Document more "xpack.data_enhanced.search.sessions.*" settings (#96542) --- .../search-sessions-settings.asciidoc | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/settings/search-sessions-settings.asciidoc b/docs/settings/search-sessions-settings.asciidoc index cf64d08e4806c..abd6a8f12b568 100644 --- a/docs/settings/search-sessions-settings.asciidoc +++ b/docs/settings/search-sessions-settings.asciidoc @@ -11,15 +11,33 @@ Configure the search session settings in your `kibana.yml` configuration file. [cols="2*<"] |=== a| `xpack.data_enhanced.` -`search.sessions.enabled` +`search.sessions.enabled` {ess-icon} | Set to `true` (default) to enable search sessions. a| `xpack.data_enhanced.` -`search.sessions.trackingInterval` -| The frequency for updating the state of a search session. The default is 10s. +`search.sessions.trackingInterval` {ess-icon} +| The frequency for updating the state of a search session. The default is `10s`. a| `xpack.data_enhanced.` -`search.sessions.defaultExpiration` +`search.sessions.pageSize` {ess-icon} +| How many search sessions {kib} processes at once while monitoring +session progress. The default is `100`. + +a| `xpack.data_enhanced.` +`search.sessions.notTouchedTimeout` {ess-icon} +| How long {kib} stores search results from unsaved sessions, +after the last search in the session completes. The default is `5m`. + +a| `xpack.data_enhanced.` +`search.sessions.notTouchedInProgressTimeout` {ess-icon} +| How long a search session can run after a user navigates away without saving a session. The default is `1m`. + +a| `xpack.data_enhanced.` +`search.sessions.maxUpdateRetries` {ess-icon} +| How many retries {kib} can perform while attempting to save a search session. The default is `3`. + +a| `xpack.data_enhanced.` +`search.sessions.defaultExpiration` {ess-icon} | How long search session results are stored before they are deleted. -Extending a search session resets the expiration by the same value. The default is 7d. +Extending a search session resets the expiration by the same value. The default is `7d`. |=== From 9bbf1faf4e38673d153070c047860ac616ac8ec1 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 12 Apr 2021 16:56:24 +0200 Subject: [PATCH 14/79] [Lens] Rename table dimensions (#96602) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../lens/public/datatable_visualization/visualization.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index f8b56f4ff2f81..9bd482c73bff5 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -183,7 +183,7 @@ export const datatableVisualization: Visualization { groupId: 'rows', groupLabel: i18n.translate('xpack.lens.datatable.breakdownRows', { - defaultMessage: 'Split rows', + defaultMessage: 'Rows', }), groupTooltip: i18n.translate('xpack.lens.datatable.breakdownRows.description', { defaultMessage: @@ -210,7 +210,7 @@ export const datatableVisualization: Visualization { groupId: 'columns', groupLabel: i18n.translate('xpack.lens.datatable.breakdownColumns', { - defaultMessage: 'Split columns', + defaultMessage: 'Columns', }), groupTooltip: i18n.translate('xpack.lens.datatable.breakdownColumns.description', { defaultMessage: From 3cf599502269b87defac42e8f6bc36a75bac0c03 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Mon, 12 Apr 2021 16:56:44 +0200 Subject: [PATCH 15/79] [Lens] Fix transferable logic to handle newer operations on datasource change (#96617) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../operations/layer_helpers.test.ts | 32 +++++++++++++++++++ .../operations/layer_helpers.ts | 14 ++++++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index 62cce21ead636..34e2eb2c90122 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -2089,6 +2089,38 @@ describe('state_helpers', () => { }); }); + it('should remove operations indirectly referencing unavailable fields', () => { + const layer: IndexPatternLayer = { + columnOrder: ['col1', 'col2'], + columns: { + col1: { + label: '', + dataType: 'number', + operationType: 'moving_average', + isBucketed: false, + scale: 'ratio', + references: ['col2'], + timeScale: undefined, + filter: undefined, + params: { + window: 7, + }, + }, + col2: { + dataType: 'number', + isBucketed: false, + label: '', + operationType: 'average', + sourceField: 'xxx', + }, + }, + indexPatternId: 'original', + }; + const updatedLayer = updateLayerIndexPattern(layer, newIndexPattern); + expect(updatedLayer.columnOrder).toEqual([]); + expect(updatedLayer.columns).toEqual({}); + }); + it('should remove operations referencing fields with insufficient capabilities', () => { const layer: IndexPatternLayer = { columnOrder: ['col1', 'col2'], diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index 7853b7da7956e..1661e5de8248e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -929,9 +929,17 @@ export function updateLayerIndexPattern( layer: IndexPatternLayer, newIndexPattern: IndexPattern ): IndexPatternLayer { - const keptColumns: IndexPatternLayer['columns'] = _.pickBy(layer.columns, (column) => - isColumnTransferable(column, newIndexPattern) - ); + const keptColumns: IndexPatternLayer['columns'] = _.pickBy(layer.columns, (column) => { + if ('references' in column) { + return ( + isColumnTransferable(column, newIndexPattern) && + column.references.every((columnId) => + isColumnTransferable(layer.columns[columnId], newIndexPattern) + ) + ); + } + return isColumnTransferable(column, newIndexPattern); + }); const newColumns: IndexPatternLayer['columns'] = _.mapValues(keptColumns, (column) => { const operationDefinition = operationDefinitionMap[column.operationType]; return operationDefinition.transfer From d338f1c3de637bece7f684d696702e8447d4f2eb Mon Sep 17 00:00:00 2001 From: John Schulz Date: Mon, 12 Apr 2021 11:01:38 -0400 Subject: [PATCH 16/79] Allow integrations of hosted policies to be updated (#96705) ## Summary Remove the restriction against updating integrations on hosted policies. I described the current behavior and asked if it should change in [1]. Based on the responses in [2] & [3] and looking back at prior discussion around hosted policies, I don't think updates should be restricted. Adding or removing integrations is still blocked for hosted policies. Updated API tests to confirm behavior. [1] https://github.com/elastic/kibana/issues/76843#issuecomment-816096760 [2] https://github.com/elastic/kibana/issues/76843#issuecomment-816153871 [3] https://github.com/elastic/kibana/issues/76843#issuecomment-816538672 ## Screenshots
Current behavior

Error about updating integrations of a managed policy

Screen Shot 2021-04-08 at 3 23 37 PM
via flow A Screen Shot 2021-04-08 at 3 01 32 PM Screen Shot 2021-04-08 at 3 13 24 PM
via flow B Screen Shot 2021-04-08 at 3 19 52 PM Screen Shot 2021-04-08 at 3 20 06 PM
This PR

Successful updates using either form

Screen Shot 2021-04-09 at 1 21 02 PM Screen Shot 2021-04-09 at 1 05 10 PM
### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../fleet/server/services/package_policy.ts | 20 ++++++++----------- .../apis/package_policy/update.ts | 16 ++++++--------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 418a10225edad..210c9128b1ec7 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -316,18 +316,14 @@ class PackagePolicyService { const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id); if (!parentAgentPolicy) { throw new Error('Agent policy not found'); - } else { - if (parentAgentPolicy.is_managed) { - throw new IngestManagerError(`Cannot update integrations of managed policy ${id}`); - } - if ( - (parentAgentPolicy.package_policies as PackagePolicy[]).find( - (siblingPackagePolicy) => - siblingPackagePolicy.id !== id && siblingPackagePolicy.name === packagePolicy.name - ) - ) { - throw new Error('There is already a package with the same name on this agent policy'); - } + } + if ( + (parentAgentPolicy.package_policies as PackagePolicy[]).find( + (siblingPackagePolicy) => + siblingPackagePolicy.id !== id && siblingPackagePolicy.name === packagePolicy.name + ) + ) { + throw new Error('There is already a package with the same name on this agent policy'); } let inputs = restOfPackagePolicy.inputs.map((input) => diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts index 3e652d47ac425..6e6a475cd4824 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/update.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/update.ts @@ -4,7 +4,6 @@ * 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 '../../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry } from '../../helpers'; @@ -115,15 +114,15 @@ export default function (providerContext: FtrProviderContext) { await getService('esArchiver').unload('empty_kibana'); }); - it('should fail on managed agent policies', async function () { - const { body } = await supertest + it('should work with valid values on "regular" policies', async function () { + await supertest .put(`/api/fleet/package_policies/${packagePolicyId}`) .set('kbn-xsrf', 'xxxx') .send({ name: 'filetest-1', description: '', namespace: 'updated_namespace', - policy_id: managedAgentPolicyId, + policy_id: agentPolicyId, enabled: true, output_id: '', inputs: [], @@ -132,13 +131,10 @@ export default function (providerContext: FtrProviderContext) { title: 'For File Tests', version: '0.1.0', }, - }) - .expect(400); - - expect(body.message).to.contain('Cannot update integrations of managed policy'); + }); }); - it('should work with valid values', async function () { + it('should work with valid values on hosted policies', async function () { await supertest .put(`/api/fleet/package_policies/${packagePolicyId}`) .set('kbn-xsrf', 'xxxx') @@ -146,7 +142,7 @@ export default function (providerContext: FtrProviderContext) { name: 'filetest-1', description: '', namespace: 'updated_namespace', - policy_id: agentPolicyId, + policy_id: managedAgentPolicyId, enabled: true, output_id: '', inputs: [], From 7448238444b9e36ae15286aa2897f055f30d42a7 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 12 Apr 2021 17:55:50 +0200 Subject: [PATCH 17/79] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20improve=20UI?= =?UTF-8?q?=20actions=20plugin=20readme=20(#96030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: ✏️ improve UI actions plugin readme * docs: improve trigger description * docs: remove unnecessary comma --- src/plugins/ui_actions/README.asciidoc | 73 +++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/plugins/ui_actions/README.asciidoc b/src/plugins/ui_actions/README.asciidoc index 577aa2eae354b..27b3eae3a52a7 100644 --- a/src/plugins/ui_actions/README.asciidoc +++ b/src/plugins/ui_actions/README.asciidoc @@ -1,14 +1,71 @@ [[uiactions-plugin]] == UI Actions -An API for: - -- creating custom functionality (`actions`) -- creating custom user interaction events (`triggers`) -- attaching and detaching `actions` to `triggers`. -- emitting `trigger` events -- executing `actions` attached to a given `trigger`. -- exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. +UI Actions plugins provides API to manage *triggers* and *actions*. + +*Trigger* is an abstract description of user's intent to perform an action +(like user clicking on a value inside chart). It allows us to do runtime +binding between code from different plugins. For, example one such +trigger is when somebody applies filters on dashboard; another one is when +somebody opens a Dashboard panel context menu. + +*Actions* are pieces of code that execute in response to a trigger. For example, +to the dashboard filtering trigger multiple actions can be attached. Once a user +filters on the dashboard all possible actions are displayed to the user in a +popup menu and the user has to chose one. + +In general this plugin provides: + +- Creating custom functionality (actions). +- Creating custom user interaction events (triggers). +- Attaching and detaching actions to triggers. +- Emitting trigger events. +- Executing actions attached to a given trigger. +- Exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. + +=== Basic usage + +To get started, first you need to know a trigger you will attach your actions to. +You can either pick an existing one, or register your own one: + +[source,typescript jsx] +---- +plugins.uiActions.registerTrigger({ + id: 'MY_APP_PIE_CHART_CLICK', + title: 'Pie chart click', + description: 'When user clicks on a pie chart slice.', +}); +---- + +Now, when user clicks on a pie slice you need to "trigger" your trigger and +provide some context data: + +[source,typescript jsx] +---- +plugins.uiActions.getTrigger('MY_APP_PIE_CHART_CLICK').exec({ + /* Custom context data. */ +}); +---- + +Finally, your code or developers from other plugins can register UI actions that +listen for the above trigger and execute some code when the trigger is triggered. + +[source,typescript jsx] +---- +plugins.uiActions.registerAction({ + id: 'DO_SOMETHING', + isCompatible: async (context) => true, + execute: async (context) => { + // Do something. + }, +}); +plugins.uiActions.attachAction('MY_APP_PIE_CHART_CLICK', 'DO_SOMETHING'); +---- + +Now your `DO_SOMETHING` action will automatically execute when `MY_APP_PIE_CHART_CLICK` +trigger is triggered; or, if more than one compatible action is attached to +that trigger, user will be presented with a context menu popup to select one +action to execute. === Examples From b33022f680db69400b37b359bf8b82e8ed21877a Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Mon, 12 Apr 2021 11:58:19 -0400 Subject: [PATCH 18/79] [Security Solution][Artifacts] Artifact creation for Endpoint Event Filtering (#96499) * generate endpoint event filters artifacts * Add ExperimentalFeature object to the initialization params of ManifestManager * create event filters artifacts if feature flag is on * change artifact migration to be less chatty in the logs (also: don't reference Fleet) --- .../exception_lists/exception_list_client.ts | 13 +++++ .../endpoint/endpoint_app_context_services.ts | 14 +++++ .../server/endpoint/lib/artifacts/common.ts | 3 + .../server/endpoint/lib/artifacts/lists.ts | 41 +++++++++++--- .../migrate_artifacts_to_fleet.test.ts | 6 +- .../artifacts/migrate_artifacts_to_fleet.ts | 10 ++-- .../server/endpoint/mocks.ts | 1 + .../manifest_manager/manifest_manager.mock.ts | 2 + .../manifest_manager/manifest_manager.ts | 55 ++++++++++++++++--- .../security_solution/server/plugin.ts | 26 ++++----- 10 files changed, 135 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts index 4b371b6dcb930..84b6de1672cd6 100644 --- a/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts +++ b/x-pack/plugins/lists/server/services/exception_lists/exception_list_client.ts @@ -52,6 +52,7 @@ import { } from './find_exception_list_items'; import { createEndpointList } from './create_endpoint_list'; import { createEndpointTrustedAppsList } from './create_endpoint_trusted_apps_list'; +import { createEndpointEventFiltersList } from './create_endoint_event_filters_list'; export class ExceptionListClient { private readonly user: string; @@ -108,6 +109,18 @@ export class ExceptionListClient { }); }; + /** + * Create the Endpoint Event Filters Agnostic list if it does not yet exist (`null` is returned if it does exist) + */ + public createEndpointEventFiltersList = async (): Promise => { + const { savedObjectsClient, user } = this; + return createEndpointEventFiltersList({ + savedObjectsClient, + user, + version: 1, + }); + }; + /** * This is the same as "createListItem" except it applies specifically to the agnostic endpoint list and will * auto-call the "createEndpointList" for you so that you have the best chance of the agnostic endpoint diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index f4a5d6add4f41..103e3ae80831a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -37,6 +37,10 @@ import { metadataTransformPrefix } from '../../common/endpoint/constants'; import { AppClientFactory } from '../client'; import { ConfigType } from '../config'; import { LicenseService } from '../../common/license/license'; +import { + ExperimentalFeatures, + parseExperimentalConfigValue, +} from '../../common/experimental_features'; export interface MetadataService { queryStrategy( @@ -107,6 +111,9 @@ export class EndpointAppContextService { private agentPolicyService: AgentPolicyServiceInterface | undefined; private savedObjectsStart: SavedObjectsServiceStart | undefined; private metadataService: MetadataService | undefined; + private config: ConfigType | undefined; + + private experimentalFeatures: ExperimentalFeatures | undefined; public start(dependencies: EndpointAppContextServiceStartContract) { this.agentService = dependencies.agentService; @@ -115,6 +122,9 @@ export class EndpointAppContextService { this.manifestManager = dependencies.manifestManager; this.savedObjectsStart = dependencies.savedObjectsStart; this.metadataService = createMetadataService(dependencies.packageService!); + this.config = dependencies.config; + + this.experimentalFeatures = parseExperimentalConfigValue(this.config.enableExperimental); if (this.manifestManager && dependencies.registerIngestCallback) { dependencies.registerIngestCallback( @@ -140,6 +150,10 @@ export class EndpointAppContextService { public stop() {} + public getExperimentalFeatures(): Readonly | undefined { + return this.experimentalFeatures; + } + public getAgentService(): AgentService | undefined { return this.agentService; } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts index 65bd6ffd15f5f..7cfcf11379dd8 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/common.ts @@ -22,6 +22,9 @@ export const ArtifactConstants = { SUPPORTED_OPERATING_SYSTEMS: ['macos', 'windows'], SUPPORTED_TRUSTED_APPS_OPERATING_SYSTEMS: ['macos', 'windows', 'linux'], GLOBAL_TRUSTED_APPS_NAME: 'endpoint-trustlist', + + SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS: ['macos', 'windows', 'linux'], + GLOBAL_EVENT_FILTERS_NAME: 'endpoint-eventfilterlist', }; export const ManifestConstants = { diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts index 322bb2ca47a45..1c3c92c50afd3 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/lists.ts @@ -14,20 +14,21 @@ import { Entry, EntryNested } from '../../../../../lists/common/schemas/types'; import { ExceptionListClient } from '../../../../../lists/server'; import { ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../common/shared_imports'; import { + internalArtifactCompleteSchema, + InternalArtifactCompleteSchema, InternalArtifactSchema, TranslatedEntry, - WrappedTranslatedExceptionList, - wrappedTranslatedExceptionList, - TranslatedEntryNestedEntry, - translatedEntryNestedEntry, translatedEntry as translatedEntryType, + translatedEntryMatchAnyMatcher, TranslatedEntryMatcher, translatedEntryMatchMatcher, - translatedEntryMatchAnyMatcher, + TranslatedEntryNestedEntry, + translatedEntryNestedEntry, TranslatedExceptionListItem, - internalArtifactCompleteSchema, - InternalArtifactCompleteSchema, + WrappedTranslatedExceptionList, + wrappedTranslatedExceptionList, } from '../../schemas'; +import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '../../../../../lists/common/constants'; export async function buildArtifact( exceptions: WrappedTranslatedExceptionList, @@ -77,7 +78,10 @@ export async function getFilteredEndpointExceptionList( eClient: ExceptionListClient, schemaVersion: string, filter: string, - listId: typeof ENDPOINT_LIST_ID | typeof ENDPOINT_TRUSTED_APPS_LIST_ID + listId: + | typeof ENDPOINT_LIST_ID + | typeof ENDPOINT_TRUSTED_APPS_LIST_ID + | typeof ENDPOINT_EVENT_FILTERS_LIST_ID ): Promise { const exceptions: WrappedTranslatedExceptionList = { entries: [] }; let page = 1; @@ -142,6 +146,27 @@ export async function getEndpointTrustedAppsList( ); } +export async function getEndpointEventFiltersList( + eClient: ExceptionListClient, + schemaVersion: string, + os: string, + policyId?: string +): Promise { + const osFilter = `exception-list-agnostic.attributes.os_types:\"${os}\"`; + const policyFilter = `(exception-list-agnostic.attributes.tags:\"policy:all\"${ + policyId ? ` or exception-list-agnostic.attributes.tags:\"policy:${policyId}\"` : '' + })`; + + await eClient.createEndpointEventFiltersList(); + + return getFilteredEndpointExceptionList( + eClient, + schemaVersion, + `${osFilter} and ${policyFilter}`, + ENDPOINT_EVENT_FILTERS_LIST_ID + ); +} + /** * Translates Exception list items to Exceptions the endpoint can understand * @param exceptions diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts index d0ad6e4734baf..cf1f178a80e78 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.test.ts @@ -66,8 +66,8 @@ describe('When migrating artifacts to fleet', () => { it('should do nothing if `fleetServerEnabled` flag is false', async () => { await migrateArtifactsToFleet(soClient, artifactClient, logger, false); - expect(logger.info).toHaveBeenCalledWith( - 'Skipping Artifacts migration to fleet. [fleetServerEnabled] flag is off' + expect(logger.debug).toHaveBeenCalledWith( + 'Skipping Artifacts migration. [fleetServerEnabled] flag is off' ); expect(soClient.find).not.toHaveBeenCalled(); }); @@ -94,7 +94,7 @@ describe('When migrating artifacts to fleet', () => { const error = new Error('test: delete failed'); soClient.delete.mockRejectedValue(error); await expect(migrateArtifactsToFleet(soClient, artifactClient, logger, true)).rejects.toThrow( - 'Artifact SO migration to fleet failed' + 'Artifact SO migration failed' ); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts index bcbcb7f63e3ca..ba3c15cecf217 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/artifacts/migrate_artifacts_to_fleet.ts @@ -27,7 +27,7 @@ export const migrateArtifactsToFleet = async ( isFleetServerEnabled: boolean ): Promise => { if (!isFleetServerEnabled) { - logger.info('Skipping Artifacts migration to fleet. [fleetServerEnabled] flag is off'); + logger.debug('Skipping Artifacts migration. [fleetServerEnabled] flag is off'); return; } @@ -49,14 +49,16 @@ export const migrateArtifactsToFleet = async ( if (totalArtifactsMigrated === -1) { totalArtifactsMigrated = total; if (total > 0) { - logger.info(`Migrating artifacts from SavedObject to Fleet`); + logger.info(`Migrating artifacts from SavedObject`); } } // If nothing else to process, then exit out if (total === 0) { hasMore = false; - logger.info(`Total Artifacts migrated to Fleet: ${totalArtifactsMigrated}`); + if (totalArtifactsMigrated > 0) { + logger.info(`Total Artifacts migrated: ${totalArtifactsMigrated}`); + } return; } @@ -78,7 +80,7 @@ export const migrateArtifactsToFleet = async ( } } } catch (e) { - const error = new ArtifactMigrationError('Artifact SO migration to fleet failed', e); + const error = new ArtifactMigrationError('Artifact SO migration failed', e); logger.error(error); throw error; } diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index c82d2b6524773..d1911a39166dc 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -56,6 +56,7 @@ export const createMockEndpointAppContextService = ( return ({ start: jest.fn(), stop: jest.fn(), + getExperimentalFeatures: jest.fn(), getAgentService: jest.fn(), getAgentPolicyService: jest.fn(), getManifestManager: jest.fn().mockReturnValue(mockManifestManager ?? jest.fn()), diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index ececb425af657..6f41fe3578496 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -22,6 +22,7 @@ import { } from '../../../lib/artifacts/mocks'; import { createEndpointArtifactClientMock, getManifestClientMock } from '../mocks'; import { ManifestManager, ManifestManagerContext } from './manifest_manager'; +import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({ data, @@ -85,6 +86,7 @@ export const buildManifestManagerContextMock = ( ...fullOpts, artifactClient: createEndpointArtifactClientMock(), logger: loggingSystemMock.create().get() as jest.Mocked, + experimentalFeatures: parseExperimentalConfigValue([]), }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 9ed17686fd2bc..b3d8b63687d31 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -22,6 +22,7 @@ import { ArtifactConstants, buildArtifact, getArtifactId, + getEndpointEventFiltersList, getEndpointExceptionList, getEndpointTrustedAppsList, isCompressed, @@ -34,6 +35,7 @@ import { } from '../../../schemas/artifacts'; import { EndpointArtifactClientInterface } from '../artifact_client'; import { ManifestClient } from '../manifest_client'; +import { ExperimentalFeatures } from '../../../../../common/experimental_features'; interface ArtifactsBuildResult { defaultArtifacts: InternalArtifactCompleteSchema[]; @@ -81,6 +83,7 @@ export interface ManifestManagerContext { packagePolicyService: PackagePolicyServiceInterface; logger: Logger; cache: LRU; + experimentalFeatures: ExperimentalFeatures; } const getArtifactIds = (manifest: ManifestSchema) => @@ -99,11 +102,9 @@ export class ManifestManager { protected logger: Logger; protected cache: LRU; protected schemaVersion: ManifestSchemaVersion; + protected experimentalFeatures: ExperimentalFeatures; - constructor( - context: ManifestManagerContext, - private readonly isFleetServerEnabled: boolean = false - ) { + constructor(context: ManifestManagerContext) { this.artifactClient = context.artifactClient; this.exceptionListClient = context.exceptionListClient; this.packagePolicyService = context.packagePolicyService; @@ -111,6 +112,7 @@ export class ManifestManager { this.logger = context.logger; this.cache = context.cache; this.schemaVersion = 'v1'; + this.experimentalFeatures = context.experimentalFeatures; } /** @@ -198,6 +200,41 @@ export class ManifestManager { return { defaultArtifacts, policySpecificArtifacts }; } + /** + * Builds an array of endpoint event filters (one per supported OS) based on the current state of the + * Event Filters list + * @protected + */ + protected async buildEventFiltersArtifacts(): Promise { + const defaultArtifacts: InternalArtifactCompleteSchema[] = []; + const policySpecificArtifacts: Record = {}; + + for (const os of ArtifactConstants.SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS) { + defaultArtifacts.push(await this.buildEventFiltersForOs(os)); + } + + await iterateAllListItems( + (page) => this.listEndpointPolicyIds(page), + async (policyId) => { + for (const os of ArtifactConstants.SUPPORTED_EVENT_FILTERS_OPERATING_SYSTEMS) { + policySpecificArtifacts[policyId] = policySpecificArtifacts[policyId] || []; + policySpecificArtifacts[policyId].push(await this.buildEventFiltersForOs(os, policyId)); + } + } + ); + + return { defaultArtifacts, policySpecificArtifacts }; + } + + protected async buildEventFiltersForOs(os: string, policyId?: string) { + return buildArtifact( + await getEndpointEventFiltersList(this.exceptionListClient, this.schemaVersion, os, policyId), + this.schemaVersion, + os, + ArtifactConstants.GLOBAL_EVENT_FILTERS_NAME + ); + } + /** * Writes new artifact SO. * @@ -286,7 +323,7 @@ export class ManifestManager { semanticVersion: manifestSo.attributes.semanticVersion, soVersion: manifestSo.version, }, - this.isFleetServerEnabled + this.experimentalFeatures.fleetServerEnabled ); for (const entry of manifestSo.attributes.artifacts) { @@ -327,12 +364,16 @@ export class ManifestManager { public async buildNewManifest( baselineManifest: Manifest = ManifestManager.createDefaultManifest( this.schemaVersion, - this.isFleetServerEnabled + this.experimentalFeatures.fleetServerEnabled ) ): Promise { const results = await Promise.all([ this.buildExceptionListArtifacts(), this.buildTrustedAppsArtifacts(), + // If Endpoint Event Filtering feature is ON, then add in the exceptions for them + ...(this.experimentalFeatures.eventFilteringEnabled + ? [this.buildEventFiltersArtifacts()] + : []), ]); const manifest = new Manifest( @@ -341,7 +382,7 @@ export class ManifestManager { semanticVersion: baselineManifest.getSemanticVersion(), soVersion: baselineManifest.getSavedObjectVersion(), }, - this.isFleetServerEnabled + this.experimentalFeatures.fleetServerEnabled ); for (const result of results) { diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 04f98e53ea9a3..8dab308affad8 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -349,24 +349,22 @@ export class Plugin implements IPlugin { @@ -376,7 +374,7 @@ export class Plugin implements IPlugin { - logger.info('Fleet setup complete - Starting ManifestTask'); + logger.info('Dependent plugin setup complete - Starting ManifestTask'); if (this.manifestTask) { this.manifestTask.start({ From f544d8d458ef1612b5da1950b0e00c4d88ca4225 Mon Sep 17 00:00:00 2001 From: Rudolf Meijering Date: Mon, 12 Apr 2021 18:19:42 +0200 Subject: [PATCH 19/79] Migrations v2 ignore fleet agent events (#96690) * migrationsv2: ignore fleet agent events and tsvb telemetry * migrationsv1: ignore tsvb-validation-telemetry * Skip fleet test that depends on fleet-agent-events * Fix typescript errors Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../migrations/core/elastic_index.test.ts | 15 +++- .../migrations/core/elastic_index.ts | 23 +++--- .../migrations/kibana/kibana_migrator.test.ts | 6 +- .../migrationsv2/actions/index.test.ts | 3 +- .../migrationsv2/actions/index.ts | 17 ++++- .../integration_tests/actions.test.ts | 75 +++++++++++++++---- .../migrations_state_action_machine.test.ts | 28 +++++++ .../saved_objects/migrationsv2/model.test.ts | 8 ++ .../saved_objects/migrationsv2/model.ts | 6 ++ .../server/saved_objects/migrationsv2/next.ts | 12 ++- .../saved_objects/migrationsv2/types.ts | 5 ++ .../apis/agents_setup.ts | 2 +- 12 files changed, 164 insertions(+), 36 deletions(-) diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts index 5cb2a88c4733f..2fc78fc619cab 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts @@ -414,11 +414,18 @@ describe('ElasticIndex', () => { size: 100, query: { bool: { - must_not: { - term: { - type: 'fleet-agent-events', + must_not: [ + { + term: { + type: 'fleet-agent-events', + }, }, - }, + { + term: { + type: 'tsvb-validation-telemetry', + }, + }, + ], }, }, }, diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index a5f3cb36e736b..462425ff6e3e0 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -70,16 +70,19 @@ export function reader( let scrollId: string | undefined; // When migrating from the outdated index we use a read query which excludes - // saved objects which are no longer used. These saved objects will still be - // kept in the outdated index for backup purposes, but won't be availble in - // the upgraded index. - const excludeUnusedTypes = { + // saved object types which are no longer used. These saved objects will + // still be kept in the outdated index for backup purposes, but won't be + // availble in the upgraded index. + const EXCLUDE_UNUSED_TYPES = [ + 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 + 'tsvb-validation-telemetry', // https://github.com/elastic/kibana/issues/95617 + ]; + + const excludeUnusedTypesQuery = { bool: { - must_not: { - term: { - type: 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 - }, - }, + must_not: EXCLUDE_UNUSED_TYPES.map((type) => ({ + term: { type }, + })), }, }; @@ -92,7 +95,7 @@ export function reader( : client.search>({ body: { size: batchSize, - query: excludeUnusedTypes, + query: excludeUnusedTypesQuery, }, index, scroll, diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index 40d18c3b5063a..221e78e3e12e2 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -321,7 +321,7 @@ describe('KibanaMigrator', () => { options.client.tasks.get.mockReturnValue( elasticsearchClientMock.createSuccessTransportRequestPromise({ completed: true, - error: { type: 'elatsicsearch_exception', reason: 'task failed with an error' }, + error: { type: 'elasticsearch_exception', reason: 'task failed with an error' }, failures: [], task: { description: 'task description' } as any, }) @@ -331,11 +331,11 @@ describe('KibanaMigrator', () => { migrator.prepareMigrations(); await expect(migrator.runMigrations()).rejects.toMatchInlineSnapshot(` [Error: Unable to complete saved object migrations for the [.my-index] index. Error: Reindex failed with the following error: - {"_tag":"Some","value":{"type":"elatsicsearch_exception","reason":"task failed with an error"}}] + {"_tag":"Some","value":{"type":"elasticsearch_exception","reason":"task failed with an error"}}] `); expect(loggingSystemMock.collect(options.logger).error[0][0]).toMatchInlineSnapshot(` [Error: Reindex failed with the following error: - {"_tag":"Some","value":{"type":"elatsicsearch_exception","reason":"task failed with an error"}}] + {"_tag":"Some","value":{"type":"elasticsearch_exception","reason":"task failed with an error"}}] `); }); }); diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts index 14ca73e7fcca0..bee17f42d7bdb 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.test.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.test.ts @@ -85,7 +85,8 @@ describe('actions', () => { 'my_source_index', 'my_target_index', Option.none, - false + false, + Option.none ); try { await task(); diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index 8ac683a29d657..d759c0c9be20e 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -14,6 +14,7 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors'; import { pipe } from 'fp-ts/lib/pipeable'; import { flow } from 'fp-ts/lib/function'; +import { QueryContainer } from '@elastic/eui/src/components/search_bar/query/ast_to_es_query_dsl'; import { ElasticsearchClient } from '../../../elasticsearch'; import { IndexMapping } from '../../mappings'; import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '../../serialization'; @@ -436,7 +437,12 @@ export const reindex = ( sourceIndex: string, targetIndex: string, reindexScript: Option.Option, - requireAlias: boolean + requireAlias: boolean, + /* When reindexing we use a source query to exclude saved objects types which + * are no longer used. These saved objects will still be kept in the outdated + * index for backup purposes, but won't be availble in the upgraded index. + */ + unusedTypesToExclude: Option.Option ): TaskEither.TaskEither => () => { return client .reindex({ @@ -450,6 +456,15 @@ export const reindex = ( index: sourceIndex, // Set reindex batch size size: BATCH_SIZE, + // Exclude saved object types + query: Option.fold( + () => undefined, + (types) => ({ + bool: { + must_not: types.map((type) => ({ term: { type } })), + }, + }) + )(unusedTypesToExclude), }, dest: { index: targetIndex, diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index aa9a5ea92ac11..3ed3ace416990 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -66,7 +66,8 @@ describe('migration actions', () => { { _source: { title: 'doc 1' } }, { _source: { title: 'doc 2' } }, { _source: { title: 'doc 3' } }, - { _source: { title: 'saved object 4' } }, + { _source: { title: 'saved object 4', type: 'another_unused_type' } }, + { _source: { title: 'f-agent-event 5', type: 'f_agent_event' } }, ] as unknown) as SavedObjectsRawDoc[]; await bulkOverwriteTransformedDocuments(client, 'existing_index_with_docs', sourceDocs)(); @@ -343,7 +344,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target', Option.none, - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -364,6 +366,37 @@ describe('migration actions', () => { "doc 2", "doc 3", "saved object 4", + "f-agent-event 5", + ] + `); + }); + it('resolves right and excludes all unusedTypesToExclude documents', async () => { + const res = (await reindex( + client, + 'existing_index_with_docs', + 'reindex_target_excluded_docs', + Option.none, + false, + Option.some(['f_agent_event', 'another_unused_type']) + )()) as Either.Right; + const task = waitForReindexTask(client, res.right.taskId, '10s'); + await expect(task()).resolves.toMatchInlineSnapshot(` + Object { + "_tag": "Right", + "right": "reindex_succeeded", + } + `); + + const results = ((await searchForOutdatedDocuments(client, { + batchSize: 1000, + targetIndex: 'reindex_target_excluded_docs', + outdatedDocumentsQuery: undefined, + })()) as Either.Right).right.outdatedDocuments; + expect(results.map((doc) => doc._source.title)).toMatchInlineSnapshot(` + Array [ + "doc 1", + "doc 2", + "doc 3", ] `); }); @@ -374,7 +407,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_2', Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -394,6 +428,7 @@ describe('migration actions', () => { "doc 2_updated", "doc 3_updated", "saved object 4_updated", + "f-agent-event 5_updated", ] `); }); @@ -405,7 +440,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_3', Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - false + false, + Option.none )()) as Either.Right; let task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -421,7 +457,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_3', Option.none, - false + false, + Option.none )()) as Either.Right; task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -443,6 +480,7 @@ describe('migration actions', () => { "doc 2_updated", "doc 3_updated", "saved object 4_updated", + "f-agent-event 5_updated", ] `); }); @@ -469,7 +507,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_4', Option.some(`ctx._source.title = ctx._source.title + '_updated'`), - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -491,6 +530,7 @@ describe('migration actions', () => { "doc 2", "doc 3_updated", "saved object 4_updated", + "f-agent-event 5_updated", ] `); }); @@ -517,7 +557,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_5', Option.none, - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, reindexTaskId, '10s'); @@ -551,7 +592,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_6', Option.none, - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, reindexTaskId, '10s'); @@ -571,7 +613,8 @@ describe('migration actions', () => { 'no_such_index', 'reindex_target', Option.none, - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` @@ -591,7 +634,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'existing_index_with_write_block', Option.none, - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); @@ -612,7 +656,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'existing_index_with_write_block', Option.none, - true + true, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); @@ -633,7 +678,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target', Option.none, - false + false, + Option.none )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '0s'); @@ -659,7 +705,8 @@ describe('migration actions', () => { 'existing_index_with_docs', 'reindex_target_7', Option.none, - false + false, + Option.none )()) as Either.Right; await waitForReindexTask(client, res.right.taskId, '10s')(); @@ -714,7 +761,7 @@ describe('migration actions', () => { targetIndex: 'existing_index_with_docs', outdatedDocumentsQuery: undefined, })()) as Either.Right).right.outdatedDocuments; - expect(resultsWithoutQuery.length).toBe(4); + expect(resultsWithoutQuery.length).toBe(5); }); it('resolves with _id, _source, _seq_no and _primary_term', async () => { expect.assertions(1); diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index d4ce7b74baa5f..2c2cd0032abfd 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -249,6 +249,13 @@ describe('migrationsStateActionMachine', () => { }, }, }, + "unusedTypesToExclude": Object { + "_tag": "Some", + "value": Array [ + "fleet-agent-events", + "tsvb-validation-telemetry", + ], + }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", }, @@ -310,6 +317,13 @@ describe('migrationsStateActionMachine', () => { }, }, }, + "unusedTypesToExclude": Object { + "_tag": "Some", + "value": Array [ + "fleet-agent-events", + "tsvb-validation-telemetry", + ], + }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", }, @@ -456,6 +470,13 @@ describe('migrationsStateActionMachine', () => { }, }, }, + "unusedTypesToExclude": Object { + "_tag": "Some", + "value": Array [ + "fleet-agent-events", + "tsvb-validation-telemetry", + ], + }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", }, @@ -512,6 +533,13 @@ describe('migrationsStateActionMachine', () => { }, }, }, + "unusedTypesToExclude": Object { + "_tag": "Some", + "value": Array [ + "fleet-agent-events", + "tsvb-validation-telemetry", + ], + }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", }, diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index f9bf3418c0ab6..4fd9b7cbb3df4 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -69,6 +69,7 @@ describe('migrations v2 model', () => { versionAlias: '.kibana_7.11.0', versionIndex: '.kibana_7.11.0_001', tempIndex: '.kibana_7.11.0_reindex_temp', + unusedTypesToExclude: Option.some(['unused-fleet-agent-events']), }; describe('exponential retry delays for retryable_es_client_error', () => { @@ -1242,6 +1243,13 @@ describe('migrations v2 model', () => { }, }, }, + "unusedTypesToExclude": Object { + "_tag": "Some", + "value": Array [ + "fleet-agent-events", + "tsvb-validation-telemetry", + ], + }, "versionAlias": ".kibana_task_manager_8.1.0", "versionIndex": ".kibana_task_manager_8.1.0_001", } diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index e62bd108faea0..2353452a6a51b 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -768,6 +768,11 @@ export const createInitialState = ({ }, }; + const unusedTypesToExclude = Option.some([ + 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 + 'tsvb-validation-telemetry', // https://github.com/elastic/kibana/issues/95617 + ]); + const initialState: InitState = { controlState: 'INIT', indexPrefix, @@ -786,6 +791,7 @@ export const createInitialState = ({ retryAttempts: migrationsConfig.retryAttempts, batchSize: migrationsConfig.batchSize, logs: [], + unusedTypesToExclude, }; return initialState; }; diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 5c159f4f24e22..67b2004a4b31a 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -61,7 +61,14 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra CREATE_REINDEX_TEMP: (state: CreateReindexTempState) => Actions.createIndex(client, state.tempIndex, state.tempIndexMappings), REINDEX_SOURCE_TO_TEMP: (state: ReindexSourceToTempState) => - Actions.reindex(client, state.sourceIndex.value, state.tempIndex, Option.none, false), + Actions.reindex( + client, + state.sourceIndex.value, + state.tempIndex, + Option.none, + false, + state.unusedTypesToExclude + ), SET_TEMP_WRITE_BLOCK: (state: SetTempWriteBlock) => Actions.setWriteBlock(client, state.tempIndex), REINDEX_SOURCE_TO_TEMP_WAIT_FOR_TASK: (state: ReindexSourceToTempWaitForTaskState) => @@ -104,7 +111,8 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra state.legacyIndex, state.sourceIndex.value, state.preMigrationScript, - false + false, + state.unusedTypesToExclude ), LEGACY_REINDEX_WAIT_FOR_TASK: (state: LegacyReindexWaitForTaskState) => Actions.waitForReindexTask(client, state.legacyReindexTaskId, '60s'), diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts index 8d6fe3f030eb3..cc4aa18171843 100644 --- a/src/core/server/saved_objects/migrationsv2/types.ts +++ b/src/core/server/saved_objects/migrationsv2/types.ts @@ -89,6 +89,11 @@ export interface BaseState extends ControlState { * prevents lost deletes e.g. `.kibana_7.11.0_reindex`. */ readonly tempIndex: string; + /* When reindexing we use a source query to exclude saved objects types which + * are no longer used. These saved objects will still be kept in the outdated + * index for backup purposes, but won't be availble in the upgraded index. + */ + readonly unusedTypesToExclude: Option.Option; } export type InitState = BaseState & { diff --git a/x-pack/test/fleet_api_integration/apis/agents_setup.ts b/x-pack/test/fleet_api_integration/apis/agents_setup.ts index 91d6ca0119d1d..700a06750d2f4 100644 --- a/x-pack/test/fleet_api_integration/apis/agents_setup.ts +++ b/x-pack/test/fleet_api_integration/apis/agents_setup.ts @@ -101,7 +101,7 @@ export default function (providerContext: FtrProviderContext) { ); }); - it('should create or update the fleet_enroll user if called multiple times with forceRecreate flag', async () => { + it.skip('should create or update the fleet_enroll user if called multiple times with forceRecreate flag', async () => { await supertest.post(`/api/fleet/agents/setup`).set('kbn-xsrf', 'xxxx').expect(200); const { body: userResponseFirstTime } = await es.security.getUser({ From b645fec8b82be0ccfa6fc16378482333a2977afa Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Mon, 12 Apr 2021 12:25:03 -0400 Subject: [PATCH 20/79] [Dashboard] Move all dashboard extract/inject into persistable state (#96095) * Move all dashboard inject/extract to be part of embeddable persistable state * Fixes typescript errors * Remove comments * Fixes test * API Doc changes * Fix integration tests * Fix functional testS * Fix unit tests * Update Dashboard plugin API to get dashboard embeddable renderer * Fix Types Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- ...ugins-embeddable-server.embeddablestart.md | 11 + ...kibana-plugin-plugins-embeddable-server.md | 6 + .../public/app.tsx | 4 +- .../public/by_value/embeddable.tsx | 4 +- .../public/plugin.tsx | 3 +- src/plugins/dashboard/common/bwc/types.ts | 1 + ...hboard_container_persistable_state.test.ts | 158 +++++ .../dashboard_container_persistable_state.ts | 125 ++++ .../embeddable_saved_object_converters.ts | 2 + src/plugins/dashboard/common/index.ts | 1 + .../common/saved_dashboard_references.test.ts | 132 ++++- .../common/saved_dashboard_references.ts | 195 ++++--- src/plugins/dashboard/common/types.ts | 15 +- .../dashboard_container_factory.tsx | 14 +- src/plugins/dashboard/public/plugin.tsx | 35 +- .../dashboard_container_embeddable_factory.ts | 24 + src/plugins/dashboard/server/plugin.ts | 20 +- .../dashboard_migrations.test.ts | 544 ++++++++++-------- src/plugins/embeddable/server/index.ts | 4 +- src/plugins/embeddable/server/server.api.md | 5 + .../apis/saved_objects/export.ts | 6 +- 21 files changed, 964 insertions(+), 345 deletions(-) create mode 100644 docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablestart.md create mode 100644 src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.test.ts create mode 100644 src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts create mode 100644 src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts diff --git a/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablestart.md b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablestart.md new file mode 100644 index 0000000000000..c69850006e146 --- /dev/null +++ b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.embeddablestart.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-server](./kibana-plugin-plugins-embeddable-server.md) > [EmbeddableStart](./kibana-plugin-plugins-embeddable-server.embeddablestart.md) + +## EmbeddableStart type + +Signature: + +```typescript +export declare type EmbeddableStart = PersistableStateService; +``` diff --git a/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.md b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.md index 19ee57d677250..5b3083e039847 100644 --- a/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.md +++ b/docs/development/plugins/embeddable/server/kibana-plugin-plugins-embeddable-server.md @@ -18,3 +18,9 @@ | --- | --- | | [plugin](./kibana-plugin-plugins-embeddable-server.plugin.md) | | +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [EmbeddableStart](./kibana-plugin-plugins-embeddable-server.embeddablestart.md) | | + diff --git a/examples/dashboard_embeddable_examples/public/app.tsx b/examples/dashboard_embeddable_examples/public/app.tsx index 0e21e4421e742..8a6b5a90a22a8 100644 --- a/examples/dashboard_embeddable_examples/public/app.tsx +++ b/examples/dashboard_embeddable_examples/public/app.tsx @@ -55,7 +55,9 @@ const Nav = withRouter(({ history, pages }: NavProps) => { interface Props { basename: string; - DashboardContainerByValueRenderer: DashboardStart['DashboardContainerByValueRenderer']; + DashboardContainerByValueRenderer: ReturnType< + DashboardStart['getDashboardContainerByValueRenderer'] + >; } const DashboardEmbeddableExplorerApp = ({ basename, DashboardContainerByValueRenderer }: Props) => { diff --git a/examples/dashboard_embeddable_examples/public/by_value/embeddable.tsx b/examples/dashboard_embeddable_examples/public/by_value/embeddable.tsx index cba87d466176e..29297341c3016 100644 --- a/examples/dashboard_embeddable_examples/public/by_value/embeddable.tsx +++ b/examples/dashboard_embeddable_examples/public/by_value/embeddable.tsx @@ -96,7 +96,9 @@ const initialInput: DashboardContainerInput = { export const DashboardEmbeddableByValue = ({ DashboardContainerByValueRenderer, }: { - DashboardContainerByValueRenderer: DashboardStart['DashboardContainerByValueRenderer']; + DashboardContainerByValueRenderer: ReturnType< + DashboardStart['getDashboardContainerByValueRenderer'] + >; }) => { const [input, setInput] = useState(initialInput); diff --git a/examples/dashboard_embeddable_examples/public/plugin.tsx b/examples/dashboard_embeddable_examples/public/plugin.tsx index e57c12daaef23..57678f5a2a517 100644 --- a/examples/dashboard_embeddable_examples/public/plugin.tsx +++ b/examples/dashboard_embeddable_examples/public/plugin.tsx @@ -33,8 +33,7 @@ export class DashboardEmbeddableExamples implements Plugin { + it('should inject the extracted saved object panel', () => { + const inject = createInject(persistableStateService); + const references = [extractedSavedObjectPanelRef]; + + const injected = inject( + dashboardWithExtractedPanel, + references + ) as DashboardContainerStateWithType; + + expect(injected).toEqual(unextractedDashboardState); + }); + + it('should extract the saved object panel', () => { + const extract = createExtract(persistableStateService); + const { state: extractedState, references: extractedReferences } = extract( + unextractedDashboardState + ); + + expect(extractedState).toEqual(dashboardWithExtractedPanel); + expect(extractedReferences[0]).toEqual(extractedSavedObjectPanelRef); + }); +}); + +const dashboardWithExtractedByValuePanel: DashboardContainerStateWithType = { + id: 'id', + type: 'dashboard', + panels: { + panel_1: { + type: 'panel_type', + gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' }, + explicitInput: { + id: 'panel_1', + extracted_reference: 'ref', + }, + }, + }, +}; + +const extractedByValueRef = { + id: 'id', + name: 'panel_1:ref', + type: 'panel_type', +}; + +const unextractedDashboardByValueState: DashboardContainerStateWithType = { + id: 'id', + type: 'dashboard', + panels: { + panel_1: { + type: 'panel_type', + gridData: { w: 0, h: 0, x: 0, y: 0, i: '0' }, + explicitInput: { + id: 'panel_1', + value: 'id', + }, + }, + }, +}; + +describe('inject/extract by value panels', () => { + it('should inject the extracted references', () => { + const inject = createInject(persistableStateService); + + persistableStateService.inject.mockImplementationOnce((state, references) => { + const ref = references.find((r) => r.name === 'ref'); + if (!ref) { + return state; + } + + if (('extracted_reference' in state) as any) { + (state as any).value = ref.id; + delete (state as any).extracted_reference; + } + + return state; + }); + + const injectedState = inject(dashboardWithExtractedByValuePanel, [extractedByValueRef]); + + expect(injectedState).toEqual(unextractedDashboardByValueState); + }); + + it('should extract references using persistable state', () => { + const extract = createExtract(persistableStateService); + + persistableStateService.extract.mockImplementationOnce((state) => { + if ((state as any).value === 'id') { + delete (state as any).value; + (state as any).extracted_reference = 'ref'; + + return { + state, + references: [{ id: extractedByValueRef.id, name: 'ref', type: extractedByValueRef.type }], + }; + } + + return { state, references: [] }; + }); + + const { state: extractedState, references: extractedReferences } = extract( + unextractedDashboardByValueState + ); + + expect(extractedState).toEqual(dashboardWithExtractedByValuePanel); + expect(extractedReferences).toEqual([extractedByValueRef]); + }); +}); diff --git a/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts b/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts new file mode 100644 index 0000000000000..6104fcfdbe949 --- /dev/null +++ b/src/plugins/dashboard/common/embeddable/dashboard_container_persistable_state.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { + EmbeddableInput, + EmbeddablePersistableStateService, + EmbeddableStateWithType, +} from '../../../embeddable/common'; +import { SavedObjectReference } from '../../../../core/types'; +import { DashboardContainerStateWithType, DashboardPanelState } from '../types'; + +const getPanelStatePrefix = (state: DashboardPanelState) => `${state.explicitInput.id}:`; + +export const createInject = ( + persistableStateService: EmbeddablePersistableStateService +): EmbeddablePersistableStateService['inject'] => { + return (state: EmbeddableStateWithType, references: SavedObjectReference[]) => { + const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType; + + if ('panels' in workingState) { + workingState.panels = { ...workingState.panels }; + + for (const [key, panel] of Object.entries(workingState.panels)) { + workingState.panels[key] = { ...panel }; + // Find the references for this panel + const prefix = getPanelStatePrefix(panel); + + const filteredReferences = references + .filter((reference) => reference.name.indexOf(prefix) === 0) + .map((reference) => ({ ...reference, name: reference.name.replace(prefix, '') })); + + const panelReferences = filteredReferences.length === 0 ? references : filteredReferences; + + // Inject dashboard references back in + if (panel.panelRefName !== undefined) { + const matchingReference = panelReferences.find( + (reference) => reference.name === panel.panelRefName + ); + + if (!matchingReference) { + throw new Error(`Could not find reference "${panel.panelRefName}"`); + } + + if (matchingReference !== undefined) { + workingState.panels[key] = { + ...panel, + type: matchingReference.type, + explicitInput: { + ...workingState.panels[key].explicitInput, + savedObjectId: matchingReference.id, + }, + }; + + delete workingState.panels[key].panelRefName; + } + } + + const { type, ...injectedState } = persistableStateService.inject( + { ...workingState.panels[key].explicitInput, type: workingState.panels[key].type }, + panelReferences + ); + + workingState.panels[key].explicitInput = injectedState as EmbeddableInput; + } + } + + return workingState as EmbeddableStateWithType; + }; +}; + +export const createExtract = ( + persistableStateService: EmbeddablePersistableStateService +): EmbeddablePersistableStateService['extract'] => { + return (state: EmbeddableStateWithType) => { + const workingState = { ...state } as EmbeddableStateWithType | DashboardContainerStateWithType; + + const references: SavedObjectReference[] = []; + + if ('panels' in workingState) { + workingState.panels = { ...workingState.panels }; + + // Run every panel through the state service to get the nested references + for (const [key, panel] of Object.entries(workingState.panels)) { + const prefix = getPanelStatePrefix(panel); + + // If the panel is a saved object, then we will make the reference for that saved object and change the explicit input + if (panel.explicitInput.savedObjectId) { + panel.panelRefName = `panel_${key}`; + + references.push({ + name: `${prefix}panel_${key}`, + type: panel.type, + id: panel.explicitInput.savedObjectId as string, + }); + + delete panel.explicitInput.savedObjectId; + delete panel.explicitInput.type; + } + + const { state: panelState, references: panelReferences } = persistableStateService.extract({ + ...panel.explicitInput, + type: panel.type, + }); + + // We're going to prefix the names of the references so that we don't end up with dupes (from visualizations for instance) + const prefixedReferences = panelReferences.map((reference) => ({ + ...reference, + name: `${prefix}${reference.name}`, + })); + + references.push(...prefixedReferences); + + const { type, ...restOfState } = panelState; + workingState.panels[key].explicitInput = restOfState as EmbeddableInput; + } + } + + return { state: workingState as EmbeddableStateWithType, references }; + }; +}; diff --git a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts b/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts index 96725d4405112..a06f248eb8125 100644 --- a/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts +++ b/src/plugins/dashboard/common/embeddable/embeddable_saved_object_converters.ts @@ -16,6 +16,7 @@ export function convertSavedDashboardPanelToPanelState( return { type: savedDashboardPanel.type, gridData: savedDashboardPanel.gridData, + panelRefName: savedDashboardPanel.panelRefName, explicitInput: { id: savedDashboardPanel.panelIndex, ...(savedDashboardPanel.id !== undefined && { savedObjectId: savedDashboardPanel.id }), @@ -38,5 +39,6 @@ export function convertPanelStateToSavedDashboardPanel( embeddableConfig: omit(panelState.explicitInput, ['id', 'savedObjectId', 'title']), ...(panelState.explicitInput.title !== undefined && { title: panelState.explicitInput.title }), ...(savedObjectId !== undefined && { id: savedObjectId }), + ...(panelState.panelRefName !== undefined && { panelRefName: panelState.panelRefName }), }; } diff --git a/src/plugins/dashboard/common/index.ts b/src/plugins/dashboard/common/index.ts index a1d5487eeb244..017b7d804c872 100644 --- a/src/plugins/dashboard/common/index.ts +++ b/src/plugins/dashboard/common/index.ts @@ -14,6 +14,7 @@ export { DashboardDocPre700, } from './bwc/types'; export { + DashboardContainerStateWithType, SavedDashboardPanelTo60, SavedDashboardPanel610, SavedDashboardPanel620, diff --git a/src/plugins/dashboard/common/saved_dashboard_references.test.ts b/src/plugins/dashboard/common/saved_dashboard_references.test.ts index 584d7e5e63a92..9ab0e7b644496 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.test.ts +++ b/src/plugins/dashboard/common/saved_dashboard_references.test.ts @@ -12,14 +12,34 @@ import { InjectDeps, ExtractDeps, } from './saved_dashboard_references'; + +import { createExtract, createInject } from './embeddable/dashboard_container_persistable_state'; import { createEmbeddablePersistableStateServiceMock } from '../../embeddable/common/mocks'; const embeddablePersistableStateServiceMock = createEmbeddablePersistableStateServiceMock(); +const dashboardInject = createInject(embeddablePersistableStateServiceMock); +const dashboardExtract = createExtract(embeddablePersistableStateServiceMock); + +embeddablePersistableStateServiceMock.extract.mockImplementation((state) => { + if (state.type === 'dashboard') { + return dashboardExtract(state); + } + + return { state, references: [] }; +}); + +embeddablePersistableStateServiceMock.inject.mockImplementation((state, references) => { + if (state.type === 'dashboard') { + return dashboardInject(state, references); + } + + return state; +}); const deps: InjectDeps & ExtractDeps = { embeddablePersistableStateService: embeddablePersistableStateServiceMock, }; -describe('extractReferences', () => { +describe('legacy extract references', () => { test('extracts references from panelsJSON', () => { const doc = { id: '1', @@ -30,13 +50,13 @@ describe('extractReferences', () => { type: 'visualization', id: '1', title: 'Title 1', - version: '7.9.1', + version: '7.0.0', }, { type: 'visualization', id: '2', title: 'Title 2', - version: '7.9.1', + version: '7.0.0', }, ]), }, @@ -48,7 +68,7 @@ describe('extractReferences', () => { Object { "attributes": Object { "foo": true, - "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_0\\"},{\\"version\\":\\"7.9.1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_1\\"}]", + "panelsJSON": "[{\\"title\\":\\"Title 1\\",\\"version\\":\\"7.0.0\\",\\"panelRefName\\":\\"panel_0\\"},{\\"title\\":\\"Title 2\\",\\"version\\":\\"7.0.0\\",\\"panelRefName\\":\\"panel_1\\"}]", }, "references": Array [ Object { @@ -75,7 +95,7 @@ describe('extractReferences', () => { { id: '1', title: 'Title 1', - version: '7.9.1', + version: '7.0.0', }, ]), }, @@ -186,6 +206,102 @@ describe('extractReferences', () => { }); }); +describe('extractReferences', () => { + test('extracts references from panelsJSON', () => { + const doc = { + id: '1', + attributes: { + foo: true, + panelsJSON: JSON.stringify([ + { + panelIndex: 'panel-1', + type: 'visualization', + id: '1', + title: 'Title 1', + version: '7.9.1', + }, + { + panelIndex: 'panel-2', + type: 'visualization', + id: '2', + title: 'Title 2', + version: '7.9.1', + }, + ]), + }, + references: [], + }; + const updatedDoc = extractReferences(doc, deps); + + expect(updatedDoc).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "foo": true, + "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_panel-1\\"},{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"panelIndex\\":\\"panel-2\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_panel-2\\"}]", + }, + "references": Array [ + Object { + "id": "1", + "name": "panel-1:panel_panel-1", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel-2:panel_panel-2", + "type": "visualization", + }, + ], + } + `); + }); + + test('fails when "type" attribute is missing from a panel', () => { + const doc = { + id: '1', + attributes: { + foo: true, + panelsJSON: JSON.stringify([ + { + id: '1', + title: 'Title 1', + version: '7.9.1', + }, + ]), + }, + references: [], + }; + expect(() => extractReferences(doc, deps)).toThrowErrorMatchingInlineSnapshot( + `"\\"type\\" attribute is missing from panel \\"0\\""` + ); + }); + + test('passes when "id" attribute is missing from a panel', () => { + const doc = { + id: '1', + attributes: { + foo: true, + panelsJSON: JSON.stringify([ + { + type: 'visualization', + title: 'Title 1', + version: '7.9.1', + }, + ]), + }, + references: [], + }; + expect(extractReferences(doc, deps)).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "foo": true, + "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]", + }, + "references": Array [], + } + `); + }); +}); + describe('injectReferences', () => { test('returns injected attributes', () => { const attributes = { @@ -195,10 +311,12 @@ describe('injectReferences', () => { { panelRefName: 'panel_0', title: 'Title 1', + version: '7.9.0', }, { panelRefName: 'panel_1', title: 'Title 2', + version: '7.9.0', }, ]), }; @@ -219,7 +337,7 @@ describe('injectReferences', () => { expect(newAttributes).toMatchInlineSnapshot(` Object { "id": "1", - "panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\"}]", + "panelsJSON": "[{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"7.9.0\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"id\\":\\"2\\"}]", "title": "test", } `); @@ -280,7 +398,7 @@ describe('injectReferences', () => { expect(newAttributes).toMatchInlineSnapshot(` Object { "id": "1", - "panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]", + "panelsJSON": "[{\\"version\\":\\"\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"id\\":\\"1\\"},{\\"version\\":\\"\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\"}]", "title": "test", } `); diff --git a/src/plugins/dashboard/common/saved_dashboard_references.ts b/src/plugins/dashboard/common/saved_dashboard_references.ts index f1fea99057f83..16ab470ce7d6f 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.ts +++ b/src/plugins/dashboard/common/saved_dashboard_references.ts @@ -8,22 +8,71 @@ import semverSatisfies from 'semver/functions/satisfies'; import { SavedObjectAttributes, SavedObjectReference } from '../../../core/types'; -import { - extractPanelsReferences, - injectPanelsReferences, -} from './embeddable/embeddable_references'; -import { SavedDashboardPanel730ToLatest } from './types'; +import { DashboardContainerStateWithType, DashboardPanelState } from './types'; import { EmbeddablePersistableStateService } from '../../embeddable/common/types'; - +import { + convertPanelStateToSavedDashboardPanel, + convertSavedDashboardPanelToPanelState, +} from './embeddable/embeddable_saved_object_converters'; +import { SavedDashboardPanel } from './types'; export interface ExtractDeps { embeddablePersistableStateService: EmbeddablePersistableStateService; } - export interface SavedObjectAttributesAndReferences { attributes: SavedObjectAttributes; references: SavedObjectReference[]; } +const isPre730Panel = (panel: Record): boolean => { + return 'version' in panel ? semverSatisfies(panel.version, '<7.3') : true; +}; + +function dashboardAttributesToState( + attributes: SavedObjectAttributes +): { + state: DashboardContainerStateWithType; + panels: SavedDashboardPanel[]; +} { + let inputPanels = [] as SavedDashboardPanel[]; + if (typeof attributes.panelsJSON === 'string') { + inputPanels = JSON.parse(attributes.panelsJSON) as SavedDashboardPanel[]; + } + + return { + panels: inputPanels, + state: { + id: attributes.id as string, + type: 'dashboard', + panels: inputPanels.reduce>((current, panel, index) => { + const panelIndex = panel.panelIndex || `${index}`; + current[panelIndex] = convertSavedDashboardPanelToPanelState(panel); + return current; + }, {}), + }, + }; +} + +function panelStatesToPanels( + panelStates: DashboardContainerStateWithType['panels'], + originalPanels: SavedDashboardPanel[] +): SavedDashboardPanel[] { + return Object.entries(panelStates).map(([id, panelState]) => { + // Find matching original panel to get the version + let originalPanel = originalPanels.find((p) => p.panelIndex === id); + + if (!originalPanel) { + // Maybe original panel doesn't have a panel index and it's just straight up based on it's index + const numericId = parseInt(id, 10); + originalPanel = isNaN(numericId) ? originalPanel : originalPanels[numericId]; + } + + return convertPanelStateToSavedDashboardPanel( + panelState, + originalPanel?.version ? originalPanel.version : '' + ); + }); +} + export function extractReferences( { attributes, references = [] }: SavedObjectAttributesAndReferences, deps: ExtractDeps @@ -31,64 +80,33 @@ export function extractReferences( if (typeof attributes.panelsJSON !== 'string') { return { attributes, references }; } - const panelReferences: SavedObjectReference[] = []; - let panels: Array> = JSON.parse(String(attributes.panelsJSON)); - const isPre730Panel = (panel: Record): boolean => { - return 'version' in panel ? semverSatisfies(panel.version, '<7.3') : true; - }; + const { panels, state } = dashboardAttributesToState(attributes); - const hasPre730Panel = panels.some(isPre730Panel); - - /** - * `extractPanelsReferences` only knows how to reliably handle "latest" panels - * It is possible that `extractReferences` is run on older dashboard SO with older panels, - * for example, when importing a saved object using saved object UI `extractReferences` is called BEFORE any server side migrations are run. - * - * In this case we skip running `extractPanelsReferences` on such object. - * We also know that there is nothing to extract - * (First possible entity to be extracted by this mechanism is a dashboard drilldown since 7.11) - */ - if (!hasPre730Panel) { - const extractedReferencesResult = extractPanelsReferences( - // it is ~safe~ to cast to `SavedDashboardPanel730ToLatest` because above we've checked that there are only >=7.3 panels - (panels as unknown) as SavedDashboardPanel730ToLatest[], - deps - ); + if (((panels as unknown) as Array>).some(isPre730Panel)) { + return pre730ExtractReferences({ attributes, references }, deps); + } - panels = (extractedReferencesResult.map((res) => res.panel) as unknown) as Array< - Record - >; - extractedReferencesResult.forEach((res) => { - panelReferences.push(...res.references); - }); + const missingTypeIndex = panels.findIndex((panel) => panel.type === undefined); + if (missingTypeIndex >= 0) { + throw new Error(`"type" attribute is missing from panel "${missingTypeIndex}"`); } - // TODO: This extraction should be done by EmbeddablePersistableStateService - // https://github.com/elastic/kibana/issues/82830 - panels.forEach((panel, i) => { - if (!panel.type) { - throw new Error(`"type" attribute is missing from panel "${i}"`); - } - if (!panel.id) { - // Embeddables are not required to be backed off a saved object. - return; - } - panel.panelRefName = `panel_${i}`; - panelReferences.push({ - name: `panel_${i}`, - type: panel.type, - id: panel.id, - }); - delete panel.type; - delete panel.id; - }); + const { + state: extractedState, + references: extractedReferences, + } = deps.embeddablePersistableStateService.extract(state); + + const extractedPanels = panelStatesToPanels( + (extractedState as DashboardContainerStateWithType).panels, + panels + ); return { - references: [...references, ...panelReferences], + references: [...references, ...extractedReferences], attributes: { ...attributes, - panelsJSON: JSON.stringify(panels), + panelsJSON: JSON.stringify(extractedPanels), }, }; } @@ -107,33 +125,60 @@ export function injectReferences( if (typeof attributes.panelsJSON !== 'string') { return attributes; } - let panels = JSON.parse(attributes.panelsJSON); + const parsedPanels = JSON.parse(attributes.panelsJSON); // Same here, prevent failing saved object import if ever panels aren't an array. - if (!Array.isArray(panels)) { + if (!Array.isArray(parsedPanels)) { return attributes; } - // TODO: This injection should be done by EmbeddablePersistableStateService - // https://github.com/elastic/kibana/issues/82830 - panels.forEach((panel) => { - if (!panel.panelRefName) { - return; + const { panels, state } = dashboardAttributesToState(attributes); + + const injectedState = deps.embeddablePersistableStateService.inject(state, references); + const injectedPanels = panelStatesToPanels( + (injectedState as DashboardContainerStateWithType).panels, + panels + ); + + return { + ...attributes, + panelsJSON: JSON.stringify(injectedPanels), + }; +} + +function pre730ExtractReferences( + { attributes, references = [] }: SavedObjectAttributesAndReferences, + deps: ExtractDeps +): SavedObjectAttributesAndReferences { + if (typeof attributes.panelsJSON !== 'string') { + return { attributes, references }; + } + const panelReferences: SavedObjectReference[] = []; + const panels: Array> = JSON.parse(String(attributes.panelsJSON)); + + panels.forEach((panel, i) => { + if (!panel.type) { + throw new Error(`"type" attribute is missing from panel "${i}"`); } - const reference = references.find((ref) => ref.name === panel.panelRefName); - if (!reference) { - // Throw an error since "panelRefName" means the reference exists within - // "references" and in this scenario we have bad data. - throw new Error(`Could not find reference "${panel.panelRefName}"`); + if (!panel.id) { + // Embeddables are not required to be backed off a saved object. + return; } - panel.id = reference.id; - panel.type = reference.type; - delete panel.panelRefName; - }); - panels = injectPanelsReferences(panels, references, deps); + panel.panelRefName = `panel_${i}`; + panelReferences.push({ + name: `panel_${i}`, + type: panel.type, + id: panel.id, + }); + delete panel.type; + delete panel.id; + }); return { - ...attributes, - panelsJSON: JSON.stringify(panels), + references: [...references, ...panelReferences], + attributes: { + ...attributes, + panelsJSON: JSON.stringify(panels), + }, }; } diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts index c8ef3c81662c7..9a6d185ef2ac1 100644 --- a/src/plugins/dashboard/common/types.ts +++ b/src/plugins/dashboard/common/types.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -import { EmbeddableInput, PanelState } from '../../../../src/plugins/embeddable/common/types'; +import { + EmbeddableInput, + EmbeddableStateWithType, + PanelState, +} from '../../../../src/plugins/embeddable/common/types'; import { SavedObjectEmbeddableInput } from '../../../../src/plugins/embeddable/common/lib/saved_object_embeddable'; import { RawSavedDashboardPanelTo60, @@ -25,6 +29,7 @@ export interface DashboardPanelState< TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput > extends PanelState { readonly gridData: GridData; + panelRefName?: string; } /** @@ -80,3 +85,11 @@ export type SavedDashboardPanel730ToLatest = Pick< readonly id?: string; readonly type: string; }; + +// Making this interface because so much of the Container type from embeddable is tied up in public +// Once that is all available from common, we should be able to move the dashboard_container type to our common as well +export interface DashboardContainerStateWithType extends EmbeddableStateWithType { + panels: { + [panelId: string]: DashboardPanelState; + }; +} diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 6501f92689d17..9b93f0bbd0711 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -7,6 +7,7 @@ */ import { i18n } from '@kbn/i18n'; +import { EmbeddablePersistableStateService } from 'src/plugins/embeddable/common'; import { Container, ErrorEmbeddable, @@ -20,6 +21,10 @@ import { DashboardContainerServices, } from './dashboard_container'; import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; +import { + createExtract, + createInject, +} from '../../../common/embeddable/dashboard_container_persistable_state'; export type DashboardContainerFactory = EmbeddableFactory< DashboardContainerInput, @@ -32,7 +37,10 @@ export class DashboardContainerFactoryDefinition public readonly isContainerType = true; public readonly type = DASHBOARD_CONTAINER_TYPE; - constructor(private readonly getStartServices: () => Promise) {} + constructor( + private readonly getStartServices: () => Promise, + private readonly persistableStateService: EmbeddablePersistableStateService + ) {} public isEditable = async () => { // Currently unused for dashboards @@ -62,4 +70,8 @@ export class DashboardContainerFactoryDefinition const services = await this.getStartServices(); return new DashboardContainer(initialInput, services, parent); }; + + public inject = createInject(this.persistableStateService); + + public extract = createExtract(this.persistableStateService); } diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index 5bf730996ab4f..e2f52a47455b3 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -121,9 +121,11 @@ export type DashboardSetup = void; export interface DashboardStart { getSavedDashboardLoader: () => SavedObjectLoader; + getDashboardContainerByValueRenderer: () => ReturnType< + typeof createDashboardContainerByValueRenderer + >; dashboardUrlGenerator?: DashboardUrlGenerator; dashboardFeatureFlagConfig: DashboardFeatureFlagConfig; - DashboardContainerByValueRenderer: ReturnType; } export class DashboardPlugin @@ -260,8 +262,16 @@ export class DashboardPlugin }, }); - const dashboardContainerFactory = new DashboardContainerFactoryDefinition(getStartServices); - embeddable.registerEmbeddableFactory(dashboardContainerFactory.type, dashboardContainerFactory); + getStartServices().then((coreStart) => { + const dashboardContainerFactory = new DashboardContainerFactoryDefinition( + getStartServices, + coreStart.embeddable + ); + embeddable.registerEmbeddableFactory( + dashboardContainerFactory.type, + dashboardContainerFactory + ); + }); const placeholderFactory = new PlaceholderEmbeddableFactory(); embeddable.registerEmbeddableFactory(placeholderFactory.type, placeholderFactory); @@ -403,17 +413,24 @@ export class DashboardPlugin savedObjects: plugins.savedObjects, embeddableStart: plugins.embeddable, }); - const dashboardContainerFactory = plugins.embeddable.getEmbeddableFactory( - DASHBOARD_CONTAINER_TYPE - )! as DashboardContainerFactory; return { getSavedDashboardLoader: () => savedDashboardLoader, + getDashboardContainerByValueRenderer: () => { + const dashboardContainerFactory = plugins.embeddable.getEmbeddableFactory( + DASHBOARD_CONTAINER_TYPE + ); + + if (!dashboardContainerFactory) { + throw new Error(`${DASHBOARD_CONTAINER_TYPE} Embeddable Factory not found`); + } + + return createDashboardContainerByValueRenderer({ + factory: dashboardContainerFactory as DashboardContainerFactory, + }); + }, dashboardUrlGenerator: this.dashboardUrlGenerator, dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!, - DashboardContainerByValueRenderer: createDashboardContainerByValueRenderer({ - factory: dashboardContainerFactory, - }), }; } diff --git a/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts b/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts new file mode 100644 index 0000000000000..995731341739a --- /dev/null +++ b/src/plugins/dashboard/server/embeddable/dashboard_container_embeddable_factory.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { EmbeddablePersistableStateService } from 'src/plugins/embeddable/common'; +import { EmbeddableRegistryDefinition } from '../../../embeddable/server'; +import { + createExtract, + createInject, +} from '../../common/embeddable/dashboard_container_persistable_state'; + +export const dashboardPersistableStateServiceFactory = ( + persistableStateService: EmbeddablePersistableStateService +): EmbeddableRegistryDefinition => { + return { + id: 'dashboard', + extract: createExtract(persistableStateService), + inject: createInject(persistableStateService), + }; +}; diff --git a/src/plugins/dashboard/server/plugin.ts b/src/plugins/dashboard/server/plugin.ts index 020ecfeaa9239..3aeaf31c190bd 100644 --- a/src/plugins/dashboard/server/plugin.ts +++ b/src/plugins/dashboard/server/plugin.ts @@ -18,24 +18,29 @@ import { createDashboardSavedObjectType } from './saved_objects'; import { capabilitiesProvider } from './capabilities_provider'; import { DashboardPluginSetup, DashboardPluginStart } from './types'; -import { EmbeddableSetup } from '../../embeddable/server'; +import { EmbeddableSetup, EmbeddableStart } from '../../embeddable/server'; import { UsageCollectionSetup } from '../../usage_collection/server'; import { registerDashboardUsageCollector } from './usage/register_collector'; +import { dashboardPersistableStateServiceFactory } from './embeddable/dashboard_container_embeddable_factory'; interface SetupDeps { embeddable: EmbeddableSetup; usageCollection: UsageCollectionSetup; } +interface StartDeps { + embeddable: EmbeddableStart; +} + export class DashboardPlugin - implements Plugin { + implements Plugin { private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); } - public setup(core: CoreSetup, plugins: SetupDeps) { + public setup(core: CoreSetup, plugins: SetupDeps) { this.logger.debug('dashboard: Setup'); core.savedObjects.registerType( @@ -48,6 +53,15 @@ export class DashboardPlugin core.capabilities.registerProvider(capabilitiesProvider); registerDashboardUsageCollector(plugins.usageCollection, plugins.embeddable); + + (async () => { + const [, startPlugins] = await core.getStartServices(); + + plugins.embeddable.registerEmbeddableFactory( + dashboardPersistableStateServiceFactory(startPlugins.embeddable) + ); + })(); + return {}; } diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts index e2949847bc926..9671a8d847c0a 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts @@ -6,13 +6,39 @@ * Side Public License, v 1. */ -import { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { SavedObjectReference, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { savedObjectsServiceMock } from '../../../../core/server/mocks'; import { createEmbeddableSetupMock } from '../../../embeddable/server/mocks'; import { createDashboardSavedObjectTypeMigrations } from './dashboard_migrations'; import { DashboardDoc730ToLatest } from '../../common'; +import { + createExtract, + createInject, +} from '../../common/embeddable/dashboard_container_persistable_state'; +import { EmbeddableStateWithType } from 'src/plugins/embeddable/common'; const embeddableSetupMock = createEmbeddableSetupMock(); +const extract = createExtract(embeddableSetupMock); +const inject = createInject(embeddableSetupMock); +const extractImplementation = (state: EmbeddableStateWithType) => { + if (state.type === 'dashboard') { + return extract(state); + } + return { state, references: [] }; +}; +const injectImplementation = ( + state: EmbeddableStateWithType, + references: SavedObjectReference[] +) => { + if (state.type === 'dashboard') { + return inject(state, references); + } + + return state; +}; +embeddableSetupMock.extract.mockImplementation(extractImplementation); +embeddableSetupMock.inject.mockImplementation(injectImplementation); + const migrations = createDashboardSavedObjectTypeMigrations({ embeddable: embeddableSetupMock, }); @@ -25,10 +51,10 @@ describe('dashboard', () => { test('skips error on empty object', () => { expect(migration({} as SavedObjectUnsanitizedDoc, contextMock)).toMatchInlineSnapshot(` -Object { - "references": Array [], -} -`); + Object { + "references": Array [], + } + `); }); test('skips errors when searchSourceJSON is null', () => { @@ -45,29 +71,29 @@ Object { }; const migratedDoc = migration(doc, contextMock); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('skips errors when searchSourceJSON is undefined', () => { @@ -84,29 +110,29 @@ Object { }; const migratedDoc = migration(doc, contextMock); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('skips error when searchSourceJSON is not a string', () => { @@ -122,29 +148,29 @@ Object { }, }; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('skips error when searchSourceJSON is invalid json', () => { @@ -160,29 +186,29 @@ Object { }, }; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('skips error when "index" and "filter" is missing from searchSourceJSON', () => { @@ -199,29 +225,29 @@ Object { }; const migratedDoc = migration(doc, contextMock); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('extracts "index" attribute from doc', () => { @@ -238,34 +264,34 @@ Object { }; const migratedDoc = migration(doc, contextMock); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('extracts index patterns from filter', () => { @@ -293,34 +319,34 @@ Object { const migratedDoc = migration(doc, contextMock); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "my-index", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('skips error when panelsJSON is not a string', () => { @@ -331,14 +357,14 @@ Object { }, } as SavedObjectUnsanitizedDoc; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": 123, - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "panelsJSON": 123, + }, + "id": "1", + "references": Array [], + } + `); }); test('skips error when panelsJSON is not valid JSON', () => { @@ -349,14 +375,14 @@ Object { }, } as SavedObjectUnsanitizedDoc; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "{123abc}", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "panelsJSON": "{123abc}", + }, + "id": "1", + "references": Array [], + } + `); }); test('skips panelsJSON when its not an array', () => { @@ -367,14 +393,14 @@ Object { }, } as SavedObjectUnsanitizedDoc; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "{}", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "panelsJSON": "{}", + }, + "id": "1", + "references": Array [], + } + `); }); test('skips error when a panel is missing "type" attribute', () => { @@ -385,14 +411,14 @@ Object { }, } as SavedObjectUnsanitizedDoc; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "[{\\"id\\":\\"123\\"}]", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "panelsJSON": "[{\\"id\\":\\"123\\"}]", + }, + "id": "1", + "references": Array [], + } + `); }); test('skips error when a panel is missing "id" attribute', () => { @@ -403,14 +429,14 @@ Object { }, } as SavedObjectUnsanitizedDoc; expect(migration(doc, contextMock)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "[{\\"type\\":\\"visualization\\"}]", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "panelsJSON": "[{\\"type\\":\\"visualization\\"}]", + }, + "id": "1", + "references": Array [], + } + `); }); test('extract panel references from doc', () => { @@ -423,25 +449,25 @@ Object { } as SavedObjectUnsanitizedDoc; const migratedDoc = migration(doc, contextMock); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], -} -`); + Object { + "attributes": Object { + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + } + `); }); }); @@ -475,19 +501,57 @@ Object { test('should migrate 7.3.0 doc without embeddable state to extract', () => { const newDoc = migration(doc, contextMock); - expect(newDoc).toEqual(doc); + expect(newDoc).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "description": "", + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"query\\":{\\"language\\":\\"kuery\\",\\"query\\":\\"\\"},\\"filter\\":[{\\"query\\":{\\"match_phrase\\":{\\"machine.os.keyword\\":\\"osx\\"}},\\"$state\\":{\\"store\\":\\"appState\\"},\\"meta\\":{\\"type\\":\\"phrase\\",\\"key\\":\\"machine.os.keyword\\",\\"params\\":{\\"query\\":\\"osx\\"},\\"disabled\\":false,\\"negate\\":false,\\"alias\\":null,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + "optionsJSON": "{\\"useMargins\\":true,\\"hidePanelTitles\\":false}", + "panelsJSON": "[{\\"version\\":\\"7.9.3\\",\\"type\\":\\"visualization\\",\\"gridData\\":{\\"x\\":0,\\"y\\":0,\\"w\\":24,\\"h\\":15,\\"i\\":\\"82fa0882-9f9e-476a-bbb9-03555e5ced91\\"},\\"panelIndex\\":\\"82fa0882-9f9e-476a-bbb9-03555e5ced91\\",\\"embeddableConfig\\":{\\"enhancements\\":{\\"dynamicActions\\":{\\"events\\":[]}}},\\"panelRefName\\":\\"panel_82fa0882-9f9e-476a-bbb9-03555e5ced91\\"}]", + "timeRestore": false, + "title": "Dashboard A", + "version": 1, + }, + "id": "376e6260-1f5e-11eb-91aa-7b6d5f8a61d6", + "references": Array [ + Object { + "id": "90943e30-9a47-11e8-b64d-95841ca0b247", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + Object { + "id": "14e2e710-4258-11e8-b3aa-73fdaf54bfc9", + "name": "82fa0882-9f9e-476a-bbb9-03555e5ced91:panel_82fa0882-9f9e-476a-bbb9-03555e5ced91", + "type": "visualization", + }, + ], + "type": "dashboard", + } + `); }); test('should migrate 7.3.0 doc and extract embeddable state', () => { - embeddableSetupMock.extract.mockImplementationOnce((state) => ({ - state: { ...state, __extracted: true }, - references: [{ id: '__new', name: '__newRefName', type: '__newType' }], - })); + embeddableSetupMock.extract.mockImplementation((state) => { + const stateAndReferences = extractImplementation(state); + const { references } = stateAndReferences; + let { state: newState } = stateAndReferences; + + if (state.enhancements !== undefined && Object.keys(state.enhancements).length !== 0) { + newState = { ...state, __extracted: true } as any; + references.push({ id: '__new', name: '__newRefName', type: '__newType' }); + } + + return { state: newState, references }; + }); const newDoc = migration(doc, contextMock); expect(newDoc).not.toEqual(doc); expect(newDoc.references).toHaveLength(doc.references.length + 1); expect(JSON.parse(newDoc.attributes.panelsJSON)[0].embeddableConfig.__extracted).toBe(true); + + embeddableSetupMock.extract.mockImplementation(extractImplementation); }); }); }); diff --git a/src/plugins/embeddable/server/index.ts b/src/plugins/embeddable/server/index.ts index 33eaaca9dd69b..aac081f9467b6 100644 --- a/src/plugins/embeddable/server/index.ts +++ b/src/plugins/embeddable/server/index.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { EmbeddableServerPlugin, EmbeddableSetup } from './plugin'; +import { EmbeddableServerPlugin, EmbeddableSetup, EmbeddableStart } from './plugin'; -export { EmbeddableSetup }; +export { EmbeddableSetup, EmbeddableStart }; export { EnhancementRegistryDefinition, EmbeddableRegistryDefinition } from './types'; diff --git a/src/plugins/embeddable/server/server.api.md b/src/plugins/embeddable/server/server.api.md index d3921ab11457c..5c7efec57e93b 100644 --- a/src/plugins/embeddable/server/server.api.md +++ b/src/plugins/embeddable/server/server.api.md @@ -29,6 +29,11 @@ export interface EmbeddableSetup extends PersistableStateService void; } +// Warning: (ae-missing-release-tag) "EmbeddableStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type EmbeddableStart = PersistableStateService; + // Warning: (ae-forgotten-export) The symbol "SerializableState" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "EnhancementRegistryDefinition" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index 87cdf5a8b0c46..c02ce76340da8 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -324,7 +324,7 @@ export default function ({ getService }: FtrProviderContext) { references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: 'panel_0', + name: '1:panel_1', type: 'visualization', }, ], @@ -384,7 +384,7 @@ export default function ({ getService }: FtrProviderContext) { references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: 'panel_0', + name: '1:panel_1', type: 'visualization', }, ], @@ -449,7 +449,7 @@ export default function ({ getService }: FtrProviderContext) { references: [ { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: 'panel_0', + name: '1:panel_1', type: 'visualization', }, ], From 7e2ffc054e532fcbd47029fdc4eed78cf6650269 Mon Sep 17 00:00:00 2001 From: Larry Gregory Date: Mon, 12 Apr 2021 12:27:56 -0400 Subject: [PATCH 21/79] RFC: Object level security, Phase 1 (#93115) Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- rfcs/images/ols_phase_1_auth.png | Bin 0 -> 252971 bytes rfcs/text/0016_ols_phase_1.md | 323 +++++++++++++++++++++++++++++++ 2 files changed, 323 insertions(+) create mode 100644 rfcs/images/ols_phase_1_auth.png create mode 100644 rfcs/text/0016_ols_phase_1.md diff --git a/rfcs/images/ols_phase_1_auth.png b/rfcs/images/ols_phase_1_auth.png new file mode 100644 index 0000000000000000000000000000000000000000..5bf4b210bee9e9fd82155c006835a3076c8086f4 GIT binary patch literal 252971 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

6&A+HTYFO>}NP<9^HjISMlTrM7 zDw&TgrlUD&Sg94oY;L6jR@uJfOHv6}n{PB^heX)=qRJ=BTJ*XD{_6Ty3oM%=x#-I= zG}IgeSjbS_s-WrgdY8IOBxvf0kH-_-)o7^Sn>x*cErW%uKcN>{6mZ}Gbex9(?;ohKK{ zD?3E`P3JM;c}v|dCB9^e#_Yu|!E_cUU2_|kHi#HIhU@?GqXU>M494JE+DKHI|H9 zaMzUBZF)&i;N?S+`XKYr1SyQIamAGIU?yY%*7#7il~B11o=dXN$7Z2X?Pl+eKVMLc z16Dw}DDOQw52TIQfJ->cK-@^qX~YKXLBt?=;%-|MJtsyL7!R ze_A2w`GeJjA4@y>+(=@FB(7SZ(q@l!yg7ku!)4GnmAkN6-rH$`lrIt$cJLr@X{7v3&x2Q|38?PPHD{MBzfyL7(M$c$TokxdFyEY*~FD zM%K|cI$uT?5~%`T($=V02EKuJ->e@9C)>ts5GR5WHGz9$bZZ47frBX9UC223pp7Kl z`(U;2ekp`F+b5z>7V{hf8K6QTqEGwez{b*>5^rYfm|NnYQ7Xl|=)d?$T_# z6T_;65-%QR$^>CexrT2G&aRj?e|V7tKsVR?5wDxF!#>$~K?F}{r0fqWA9{m*3t<`f=5q~H zxtK}&4gp;srsd7-8tpo{Bt|c!_4{xRH3}(m=Nmt2Pnz~$&vvQe2aB#+HKZ^Ocjkz< z4kH@f&R(%ky|a4rt0lJb=P7gzYd+Tey$y=jAR z2Idi%osDjwZFlXp5wQ(V%UgE*%ex;5HPE{eR?2=c&O0z=fRBE-ml|J`KoldKKsf${fG2AB<3AP`*d^axibs$v9kVe6!lA=y2ds6ink@``E zw70s*g;jo99yr-4W#JjvOM9Xf(E}(Zi{#&9Cv`p{Qpx|+#<}OGe~Sl^=??>a&e_8! zIgOs!Fui4PxNe?vi?WKWEw2b4`r{(9&_+KuAAD07-qsx2#4yjBb1Y)_lZ$N+in&8D z8B_jb0hC<2VwS9y+1QLew&+^Vnv^^iN_@2c;rB2GqUS;7Y4Pl^?A@Nq z0HA;#(SKQ@l=wM*qj~!q%Dw?*WyWU$eO}Yx60$vqR4%f?$1%w$ZLo?|*4N)nt6|Fm z_N&uTLBSDEDrbaH`mb(F)-I;agXG2+Z!As^N89p+h!{{`5X$b>gh`0N+L~Wc*gu@s zqUT)nGOvBH^O0aO=+a2Q;&^k?_0O@`rkHJYzI`e#JzYf4`APu2O`0p8YtOw3f8U5^ zE8kAg?t;JM?!R#`{l$tUg!T6F)tA>`F?YFc`~4TSNj)i?d#ob!;RWX5kzgQNfO}{n z*Xxonit1bau3b2%NS(=haa+hZ)nII2(ks_|Vj zwDa8~c(4d8;vcsUof9X|{C~QxP<^}G7|q2c&Qh8H?Mm`s>c#N_&5dmP`V7AKCJMr+ zz;+tkhP}7g6#=Ha4sPyj;82tAOF2h*#y=uBNQY%h9>u@59^8<{l~lh;X?#r2zc1)+ zj4mwc@S_rse~mTrO$u>x8$isln=mbc%)4K?Wy-kV{~F7i`z9K7n;O;LWdoCbxK@`{ z=$7N`yfCoM8UGG5WDk?d3OxtowF?ew0ujy`}L%^7GKkvrU6_ z<}y;R?s(^ATzl^FuNJZ;IY5a+P8Tpl$-@P;q=qR}Al@g3%yaACC{kErFsw44G;5s* z3Q#G|cREm;P;bk)5*}?yqdj~vylqOK7E5S>SvXo6Xlg8XR2na~b*QNYQbDEZYVTsX zZ>AV6y>-7=L43N@x?0)kx2G!6z9&v)5}53S^#GOIsHvXxMHYW2X|zP~eEm}^@1t8{ z0k|*;!`$v8l9d92sI&d(%jG;${mw;6G^m75b<~T<#q6wxo2kz)9Kq>VAmn zoN#X%e^}A8h*uMhFE!`-iE62`#K1&Hmxo@p0TQ=yODHLST{piqw)eokIg&?#dx9wHuZtnhJIh+gfd>!BvuZ_G5zKR-Vp zrGLpm!de@&rmf*81f zc3BuEgb@pJ$&BiVxqR<@$w7Y^Ovu$+jrg8luTWqAsNp2xHcA`8j3;D(3lHH^XOfO{ z>etG4-=Zv^9}sI*t1%B{3(zvDC&&1W^S|y(yNFa=?u$H|&)f&0rNvG`6}{cp{)sX# zUX=_U)8KiKqcrp7P>)9v(87}F6)bkfxRXj=+ehVWweNB_t3Y{o``R^Kt?qDseq84} zhJVKp_x!Ud7-l_itj7Y6y3P`KX|dh3Rd4_O$6ChZ!|6` zku_;#LM`ONVBG7Td;ODPNs>OVY!|E9(>OP77N3uF{8F5ME3JPkH?M@|ve1SGRxC%> z%(e8%yc~yJ5zx}^t(%zA_g-VZ&0y_<$qKg7V3OX z(W*A!kKBM&D^3-qaLIGRjTAd#3nYDrz$O>GcF(<#*y=h=qF=02b0Yh2jo+_21CHLj zEDS_h<`lp@`@KV^lFcRd%lZ)~rw$tdp#M%q`BvC86I}|GGIAqRDZh{cP3&s9qDVYu zQUn!;pCbBaiW{Y$sL#Nk_?#Sb&r6;n<2&6_+D}*aVO{ZdU#~ulMVt^Tyco@_I}i_0 z+=9?5c7Joy>YOTAkMjWOI{KzB@qHF@U=P@YQ|d&DPW$Bg%HFIIol4rj;Azb|YlOFS z&@CQFMlb*nSeeew*C4Ji}ng<-sHx}`pXM;FW1_h(-WV@ zu`!^KxPS*8UBKqcGNIuXGZ~7Hd23Dkj`2D(>ngnV!TTxX6sW5H7*CjPzGn3Y?S;B0K~3ziW?ahrCZ)?>^*cY5efC;7caLl)xIbU(ho)4V&-Y#~^8K+T3a z%wCRFvN?)8))-AwZoYx`lk3U0u5923`x6fDlT$RN$7E!%+e2c-Xv+IAWw+hZSyH1! zm+s&UFBw?s&fd!W!AKIjl?|M_$oFJ>p0~ZqfA&c4fdIs9x+Ry)tuw*i_C zoy+h{cfy))b|5PhzbRtGSYZ@sqUJQijUo}E3dg0z4a7c>Ime}48M%{|+b@uZjhlz{ zmU-QUwem{$q+*Q!Je_V2-rf^y+z17G)8)Xj_|6|(4PP?zb-y)T>Kv!c$t)fYD$f7r zPyK9}m603OuGZ+A#=HuLFxUai>TA-+QCY<)Zpp9F;bnp^oCOHoyl@gPudz>o& zv);1#lQ(HAc!Q{R{oS3U&(*wQ5U)s^XEY68R5kid?dFKx^s!lafp;}hi?$`ow})sM zQ$V~>Z>BlCS*rh+10(R2D;N@2^rB$?gBPJK;>N)zQ_aWk; z?-P@~rQHg{2W{~5e54^V3~>7b2L7l)m((nj8C+e!rr(_;t&JSEtN5J)2u-RBoDZ6ZE` zRWRUvk15OX2Y>mgaJ9T3mZsClgIHoWkv9dxA`mZb2DARrOJLZ~iusv85V~dDQ~lSo zupmHfS^*4FZ0Qk2H2DoFB^+~w9Vs0jh>N&v3L!t@1#$lC3q}D?#Jg&DQdnsxxlFUd)bO6&vkxluG5Hgql5HeASm({tQ zJIHPsR+Z-eUI#CQHUkdC3#Erd8+ayylOfZ1>LrMzxd0vnPMz=#SEi`oAAw@z(zw;Qt)p z|488fsPO;T!2h2*kwy>Fpnr1#{EtIv_{Sq-Sn__oYhoQG8q6@c&U&vgwoQ+Ic>W=W zoem6^1xTnBNayL}M3-)gj_PPSnG$OD6W#6qtd?7j&RlIUL|Lh5vBn$^9&{him*Vq1 zwl!$?teK5fO+WoTLbe#0eGxEdOoXB-a8_WTc@QvYE`V_$1p|M=rW!m35@`v*PlW~E zT#B)5#zFGML%y=xkV-Zz^?w#`?B08h8?(eLfI%Tp;gm-Q7Lq*VUDNWb3#n}a%LMws zD7eM;w~7$>;?^51B>Ck*ISWAB%xCk3+@RC-u zRA+_h+E_yric;)tBpaMp14OQds|t`H6M!GeP!xeMR2>+Im=o^ciZ5f=f_Ha`*PCsl{{@<=ZwkF8aHN~ zwQI+Qb_&Di44!8f-F|Q44;^+&G;dp0XgU;R4*s73udXB3Do<2tR&?@tc}@Twuk>~l z`-)u$Dpn4 zA>wi-)$}i07H2Dck^6ugdB4{gEMlF<_q3*G{wGI4G_L&3QI5FkQlPC)bN3O${<@Vq zD=40S3iw})W|L09F;cv7)#h-;z2LZwFUQw8r+(I|EV}Uq)EG5G<4$QIjNA+ZtKnt* z*G(U>G?f)!L`fvr$%kY1UIl~6(O%HkL1#UjMkwNJQpV{Dwh)F@>DS43cpsQ(V2l44 zxO@ILE6R*2y$`W+gH4eK&dwu(VQ)xT7mTZkcp*wwY5cM;H$*$omw130@M4J6sIu zC{~KStYCvM$|RAC%Xw3Tj%(V$rXWNQR`cl<(7|Ll-F$b@Qg8aBFm$|gZ;Zhb@>=t5 zd@uvNI!EYMn7D-U$6cN@@X{}bQ zhA_~(3Mvy|+(CAtP;NKXae(;Kc8)@EGy`C|ahk>AM04iS6f=;s}_*A0z z9thYpk%h^Hq4$DoyHK?5kAhHtwN;uz!taB?ruf3~(FpVLUH8$@I|sJ3rdXb&vDJfR zQO5M5Nd(m5f=e_};Z)6#2A3W;{QOn`u$HY;NS$8nPTd><-7|XqpTB9d8w@Ti_y@73 z|H9XtU+c2yMeYg6KSheZ1f=#Wz*6&ph08rRX13>_&Cp1HU!Ck=++3Y)RGG~7)&}_a zCPYJb*P@|o@93S!4Z8z72ScBZekXQQ7RdtiSikhPlP3U& zLil`#kAR4zbr4NrG`Ssw|30f^{7TuVy0J2d<8g z)aM&YZN2TmUc2BQzX6F#AI6#}pN#)G-`?Ov$s1up{;v$rxiq{f`ZRNL_FpRk;Zt@Y z20nfxH2{xNxXBdP>1z2L<0@4nPkd$!<6DSs6eND@yEG)c%t$gC%Gr}j^W^WF8RUTd zV=z$jE&?3o^C=wgAY~C%+kLx{bnf8$`tj%t@JEo>Uo;h`AOt1+uqn3Cz@w{BXh|>s z@k|1I#dVw`g=T&);fAs;$^Cv5xtPMIEBElSv!7xkkG;ip zL17tX-Yl#o#MR+BsK-%rLhd1Ks@#9Jsh1dTgS~LD^1yN^iN2+mr}FKpnm}8UHUtzi zWDoovC<=C46|F|?>x2|rJ>ZCLxB8rKC92ZywuY`4Px01oz%?T*Dp}(7uPIDTpD**j zfj6E!AXvRU-qMEi;4k!Bc1QRvyZs3>cAh{<ZC15cM&GwRnq$c18!LeV>2)u!{+v?{H=s8a3&~r9%bD=g_(~ zLtnX|#*s{+hbM@2f0~b%lL@>Y8kB{q^BL5G?i`GtV!)~v{m0j~w`*w29Sq0t?Wn>F z`b{5rYhW|1!cj?3%A#y3?u|dzgH2(vp=(0iL@*0kBrd3@?>*;x;!T07l`2)Zf2_G$ z`;oce(Wp53_fUB&-~ZE#{|nsFAb3w!IAcw}>WuM5hRWzSGiz~0%K?y0@8@;^Kz>f4 z)jwogV;Aug;b?*5mjq2Ct1CO?KuTKftyJ9Ctcu$TK7M6` zy;axj6+?Eu(KDXB{j@&SL_vs`6x&t?{c0^OC)g`nMw50tf=U8hcqI#A{A_;#DIKO=c2L?s)QV`+k?ASb+sxOw&WO(R^LaqzxQY?(tpY)C0TczHH7_oGG z5HBU!TYWWf6(!B3q|Zw*#n)LSsSm&uNmOK=6?o9UWCT2A{~ZvGsCOJLxH6g_YYtP0 zCy&3SyxIycqcZb-_isny^>0V=&YYzXfRMG_>d5jb1IPAHStP%&+P&qMs)2IadKtOt z-zzqSsKoWlF2b9(@c%we=W3HM-~OQgYyVXOsEWMf*uy2A-aw8a|5zK+MPfw%BQn*D z9UMD513ba|T>O!uwi6EG2bzWvICiKfkh*({?F>=Sxsameq^Q!rpDelCqxD*)-xQK` zN2<`D2QEK&_AB!C1$ecVg}C%bionwSOD*CC5MfNxZxGM^H=l_(ts9|NJdZj1$J`E1 zxmLEKs32a-lK#{s{GGG+L7n6O)0cw6^uRz432ch;o5=WKeF|r;u$tG3-(|4gEb-Kx{aUtF7-~Yd>BP4*k73#bPcE(hT8hg#CORfNwYvM(|5)0o7@$O;O#xvNL;6*4 zf9-1Iz$gAbt~n7OA%R@#9eYtVjk1-8V_I$jJcUuoI~1F$MWoLIM7Q2DNKz*QulAqJ z25>F^??hH{9ylz_a&GRzd|<@v=SFSq6b1h+zkRl#5cL$*Fz~ajr+2Enw{KYXjwDh9$T+5* zmSahPvv~@f$6r%`NJ5-hR&RfOCLYQXV3qVe#&ji&3sU)UfM(w1FGj-tEPRl3zNMQq z#N$4SPf&YQXecm=jQP+17S6)UX44T6#`J1{*NH!oTZW%7Fp*0v0vP@FgVUbCs)!z zR*gwOBU$exCfGOg7F+lOu#F3DMT?AHwwjU(*t*yWihF5Yo}O z;SPqV0GSmlgC?l7gWa@Ypl;sx!FuFd==(-Nek}X-Gin#5;{uz&rH1_CyM6#`0mLWk zV}5v<)efCo(B(1vF??Z;U+!hx{x_^N4SrC5yD2?q+3FA~h>CWR49xgK)lW{sFXT?> zz3Ohi?&42as^mrm$0X)K^{rT@fOeD$@a6#P#CP zI@0$I7BTwm^^xY#Th5F=pBO@90B;3jA`&%NBmu$8vD^@l9m@lVVg;lvd=5XjkfR9c zP%)8I841Hj8FB!wWcNe0xB_MMhxz`i#i2u=d9M7EO7Pg%VjrKaI(u3AU> z78`tkd>vJDt+MLjRmW_AOuNXm-4juzpIb{}d&&TP^*e@}CJE!*k)hOPkXRpg|AEle zksY(-Cwog-X_pVv@{*ql92bMr$P8BNg2G)_V7)maE>to2_qoU={m%&*6r+}17flua zqGPx5WtDz|f!hA5I&ZgVe_M33cOBe5?W%lO6V57KqW2&y+e{!>sKZWq=%*n=unopj z$s`+JHt;-ru(4|6MggSIE3?fG!Y7X#*Vx5J3qkMU_R{rxs!2PBe3roY^JVG+^=pA6 z+5CN-t4t#_%?<~D;e&u_=>pO71R5!Eb0GyW!TK4n_8QTM^$0~WYA+ceW2*sYywBfZ zd&9IzQEetr^=nPoi`xS2a4T_G7UaXroXcNhfrnq$t^r{N zP-azJR%ksKLl8M<%1Var&9xE?XYdjLXl?bX9hCN!`WGxD92!CM!JW%#W>k9%T4!u* zG&5{U;yU-YJXapXE3HPaCfi9Lb_tJ~&E@;H&mzO*k?~Y35D8$m#RSD!A zZMpTR6;@NFxOCGv_G2Iy-!0qfhq*3E+`^9>I1Saz%S19*XaI!Jj>y|8USU4gCln zi1UpU<381d0+V93TRp0dDDM7kX~3fp3}4V3z0p@!n4UVDqZ01jRTU8?(s zzg2H3g&hrKOSo*SnGqz#jE``8+->kf$&0qDvrs_SQjLN)x`yOItrWydP)o#PTL+HK z^Xk`-eMkyhd{il_fw&@;CZeQmfHn@=$;o)l!oiNJ18&jH2;@ZTFx{wR!-2y5eP&6- z89S4L*l47=iWLOc`GjAS;jtRTG814`z`A`2t0q;X`(5sD`vbSwFJoGRy{<_G)y{zdKtJboX%q(b`a{MjnM(hc^^$V+9gBW8KfvkJ%{OC;|%~_sKbE9{- zwn*X`=2U^OlJv(C$!iO2Oh)ob_eQkuEq|#Ias4wkUcWa_Je0yroW`Y{O!+#RLa6d6 z4(YWXp*jexQJ_7kQ4%W^=tw8;mm8xg6v{O65MXvMXe?T}jCXLo>y`4pT@~vHI7^?~ z#DqwqPSeEP7ozCDVwPrll9owX2_Qsj35|DM&$Eeus1j;kGp^R!$?}$jmu2*P9J;U7 z9{_UtTqprKUyqXdd_fB~KXy5PxnGlH8BspytVym%XpFVd_eL&=*EqRP+WwvfF<$kT ze6WUoGz-n}*}$7;c73ud;V$&@wnCM^%)4(1TJi#ra(Hi09q<>!A2P?v=RK-9f6_6S zbB2K=2YM^2ko1?M!!!XWcKt9m|4IcM3-nC zCWTp5K9kS#o9=OU3PL~95c*Nc1cw#}ULM;m1}z(4IbBNndefRUV09DhEx84UMdG3X z?(cBGD=(ib>UPdVOYvtki5?}KfOgTE5$NK@8TmJ6*%YksGA0X}exSz*S+0_aR$2lW zBXM$uN}s87ds#nsi>qH_#?#8s=y921o?s7zZEqnFDNeOk!gsklFZR*WX?)Rf5(2}2 zVrk2c2vgGq-Ci%=-_Vto14hITt)M^+8AMEhg$LWHD_f+FEox_bOOa?e8-1(qW0xJ0 z#tC_y1f!CfsQMq1ti>b>+JJ-qkSqb!i^>=@}a0D5^q?mmeb%ys%)e_k|7RSgC`(F~2$KoEH0&1Xa1BvW9|i zM+3aCuy|-X(ZIm>n|gW%**U%4kE&vO_P0_QK%2XE&La^~6(XE6K+|-Ik{~(;gqpn{ zKH<>$o)>EI`M3_{EpxtjV*BMMyuqQ)atSY(noPQ?)9Mm=iPj}y95=(<-r zfxM%L2D0l#8;%6XEW|B8tN%#CB55kWCb$A>MSQQmVH&jgv%la< z0j$gMIP~%uCf%NE1y`;&Jp$*4!kDl3eh_o$Rc|)s`)U|Pkb{o6sq9N@Oie_No<11xQRWcX%yLEL)Ssy`f@-sCjAL$6{FqXK#J67Io7-V$e6CZJ!9$-d zzGY$&N_aY=4TExBLcx?Kt{U>ktnUIgq4OwQ+OvqnW6JmtgIP=p#dTYk+` z@osyA{dj-#fRWxj?;D|vF79+F%VRO8gY=KHxdfnt|HOck0Zv1oLPRI|e)*8i zkyJ7da<&w7NA2smgBr&9DcD{t&L?vpGc5yoM^iE)zlug-Ct;xo_+qp4V(FY#E3Fey z>tTR+ot+Prf>0HJ!`wS&`FisUYbBiDb2N^AHdtv@$QzPtv2f`OQ7KJ(d|`S0`_|8v z?~Gs2WeJ5s(6+GJ7jf|VC>8(q${X(sS>s44@5}X~Z_&vMo=AVoGyi@*Q9 zsEDjN@B#UI^3YaL@nW3ZO`UM9Y76!xlcZ^DkqA2oVC62Mkc zR^CxS#PG})vBv7Nb>0O(jOJT!>;QD!h$nA3wno?WGziuIb=qtQC=YS`;brFDh$`cb zCs#xY_Xqj?q%Hy3jaHk>vD_JVyxZHI`;G>U5YU;w@sJqel`-M{R@U1WEoVecuXidR zD&lNFU}ippMeHzmx$d>_dm)Rn2xX4voI@TxQ_8FNZbpLmoU-yPF(^svh53h6DZd|z zFVvq-YDC90Wd!aT>0n04q4x20_XW0QCQTn9{g%7Q zjipSkD%t)pgSXrJBj;7dX1OLz=x+R{}yjdf_ImoiUj_l zIB*LNmny|b!$iMHz#!B+B^X^RG-q_IhIFT7lk~q9Lgdf5q<`e zEu2D{*j{s+F!7{~AOoAR-01eUh3J;VZ*uuK?%~$I2ZtlHMG9rtzmki+dXYU^HlY^Q zuZ!l>2FnoVHRcWJ6k~yIsj1c-D=n ze%QpMUZ}Fdb8nI;6s&gZvzvUET4(Z+$iv}#Plgna{QF|Nx?iHEt<%@F)GcV#@ZD&9 zYH`s;LBvf{GvdDbCdob@Rxg@QuW14LSZu$&qqjHJCnD43{ge4#pZaBYE75dI8zfQc z)L??;Qi=TF2fVQ9BlzFbn3@>gXA55Zr(yKuWYAzYxYz@Y>8noHogoF=iES3i!y(44 zE>0G!iPuZze4OuG2~a*My+s^myUHtaee5$ntv`BMRDu%o-qe*grU~$P;v@TZ8{weQ zz}L4Hl|Xn=UkrZs;W;~ntx zqUwis$ymZRh@W-`-c04F1G$J$36WId%g0~!$lsRQoo;PM*QVu3I3%-?=v;`_`uSmp zt(K;Z6eTiFw$Wzvt4D^~K;c%K97Jy&A(0TG&H8NE0zDMdnFJ2$`B{$zbbVfRl zzohE2rF2Fnl-!Ou@LA8|f&nr5*KS{Y5I1jC{P5ufxcPY3Pl8Wu@;Nx9Mdnhtm5EZgJp0Qw;sN@&8-PIEj z%elW@b@SC}LFhFR7oqQuj|jTy>fbjOPnyhYGawD!e@|lNl9WXr-Yh>GRzeoasj1`ORlH1);ee?YB&_$34 ze1PmugmxsOMkT-)J)+bx8>q84k7*l;lTmAbHLGf|?AVtN61xqyk=h!|hk-eKe|8Dn zw%%uyj{H!f3@aT$C$9~MEfA&^Wg#SylI7b(9Qdn_V52R@=l~wF{@f0Y?9}jQ=6M1t zu@KdK^XNUBaV0>8Uc92(sD{IceI{s2rE8l{0#Dd~@AgD5e>>zgSQgmJJ#LH& z@-P60^^=UeIAL`43_L!QX!CFuABun8EUoB4L_2-CtxdA{M_<#gIEkJEWCWzq>-qH~ zX4u!$Hyux=S1QW#KQt%)S1k#B@p}fW^SW+Z#n5rdF&S)Jm^)oNl9qYCyX`RqNOdZ` z^-;8VH7o0?62(P)d=4uoaIGKZ7b$+(2)aH{mOSlQIA3W9UDv>}nDIQ?sNMI=o6VFh z=XVZ4(bUj_LA|%nW^wru;zgA|z39*a>MZi;{&&6yJ{%#swep5dKJV_oB72hS)uOE4 zWW91m%RPX{?L4SJAT7V*yt3M&0#?3T)OVBlEV}tA3l4W9QS*K#>1e=`7P&}NIDcX2 ztd{wZ8}3K(&kKei&T&@F5!W62j6B%!-!BN&=nA?EB;kC98EaM%p1Z+3jWse{eq-Ez z;5v?EJKI#zEN*XzxasoDI5K;1(*r}r6fBJ6n$j1jv~koSBjU{KJ4?kZlJ%}r=5q}e zUmtN@JMl;OaTy4w`#rUNGv{ySMxe~>xd2k_iIq3PnbVCg9M@2rY%gb7jw!_-ZoW-9 z_&MroHQ`-7=%j}2)ruN+^Y;)%U7R=A<`;-?&m$#lEA?68{dSZ(0(Q`l9rj(KS~qC= z+Nw_`x`Tq&@+M$Wumy5M9feVF_8cs05ub+|0S-H4yv7t`mOGfKUHl+nPl9X@mogf7 zb$PtZ4#}ss!#0tUc?bUMSuc~UcBZ|X>^xs0`NL06Tkn?Z+<8AOi!4U(LR}S`6m*XQ)peH&I_D6rIL$*1tNr{I+4$@6gy6FSpjr zBjUzBzc8V?vqJ_h$IgAD^bKqQuT#5g1wWf~B)$J<`w`PF)<{hA)4S1gi({rWuQmru z#TDoGn;q2%%kt9pL`^tZw6!CUbbVz2Zc)-bFm3pe@-n6$ZxjS>x*p7i+qUIIP0ZBm&o0oSp2RNC58@=lIjpqpXBdPEn)n z3B`-a^%OVulZ%95X~oxaqsi_lH;Q zCV-WLOoBl9na1I|wi5b%-83wB^j&3p#yQc#;&<;5~y~5{xjZlq)fI?|^_0 zDq6otQj-LDXiTrm89@hnZ5)JByQFiDV0qkJ*kZi<$}MorkLx@Xwqs7Nj7Yx+^ME^1e&K#HM(1?XF{yaod>I3Hbn7~c>+nYKV*IeM*{$K zi|uya=jS!oe(?-vKQl+m1Ns`FyDf8nLIj)eslzDR-nVBVu6tM*58KN!pA{w5QRSm; z;O;Nrs(U(hg^T2R-3rjoa655^hZSOS8srH&%U9D3EbL<{6SY=SGU^YK6*s zKd!!6ydXyR^)LKZ2>quaew!Qu@&)H`&eEq352z%)`2sJGD0S?g^0AOB-NHFfGOVIU6NN#FE1u5ga#y5(WkapZ5EXRktE zHo0YmbL>KM2IEbYU`Otad)-`}wLbg|7G5whdD=0b+KrtC#>?-uDPM=G(S}n+YOv!K5<+5YN)pH^XM=O{k2}}-wY=f_n)NJ(fc+Sl1^5bNu_o9njEEh@*VCz z>vUgVudTgjh#Wd`KPY5zZ%jSkvaK}IjV@`tAf_~QH&)6;jV06=LPdb6F%_=_Ve~zB zJWqBce>N6da5RO zvwoJr*^>;}+THw_gZFF6D4?itxpdXSVyn82U^MX4BTCBlRnlB4WQJJ9AYZ=N_Cdwb zMjQNq^^Lu<&phZ|-jcE}oCp(Q{+A4I2fyeyl0Ctz$1PTXIp41DQ~qQF3AxJ)!iDMX z*GFLuf9B3?C0h(|4r1S7f4B85LRv}|XQ~n)NPP9?#{O)>zD6YFv@|7|w)TE?_ZktY zcZO0U)Ofe<7p>?~&+LUY`_|7~jci)>qFOB2+_L%wSo5kCdFs9Sck36UL>*Mtra4~d z$YmGnmg1xl{z1QAHzF6AYYyveG#NImzxbo6Kh}uLW37Edapg^^#Jv=yqaL^@QjN)?qXx zZ^@4?tWG%#mL4q{1aDl5MN9$Wn30d3MQQMnvQ$U<5o>mI@bz@8!bVNUcTr1|dds5* z=j&o6g%zG2KQTQsS!p>4+qXFLq6ItL9p)UcF18FmKU9&L{>j6ail1V4wEZOhZPD4|!~w_y=jjh$sfJhe1h-z2 zR+fB0V;iLOsl$!-b$3=v)~cRVr4pn~eOuWYNVzOj6*u8VZ*n>)OEqE60hqUWMUdmK z(P@UrZQP_MJ|ebhYc~v!p@&<`Pj^SsDr!mXn$56ey zc;{LA>%`X!Ef-yU8yJLfQ-L+SFJz-^5{XpcT#k*l_P8qz`u8n-KV5ngB|K@mbv0ff zC?bUGBWrM&NAX&5&KSzsp_)0+$L4TM9{9d^4Eu%?pR=h1obbZo83I8hdpeu_iW+SQ9!CoBGcvbGbkAqBYyo^wz4l?@HwGehIsw{H6bRx9@1 zO;InpEZHAab$v`HjV{K|l>monF ze6AVx173GE`iuL}b1G=seY=2nXuVh@eqt`I7Cn7}@MW8#6r(Wtpk(?;pKv-Qqdrg*Gq zb??5^my=N-<>Tq`{6hbFfE3=|??)#Elup=js( z=UBzmsvFLv69gG15HH_gP=P`mtSxh{E*RWD>aC;~Eywvxg&MusR`iapd%LB#f3Id| zFxdKm@r;TM)pM@=zCQhO+O3;B9l_H*^GUj~B`KBEJM1eZlhKt|(5LI98~*R%DFtWU zH+w%Ha)A<9Bmy7SmpXuh+9#0vLx+Emoq6<017v3e^;c`Mh}5IoOzypTRuAtF+51k} zjbX}e8yG_v%DZP}c)R;crulC@EcHF0)58}E29C3r#|#L8QrkhRQPL&(Cy(g0-M)rf zB^brOZJTc8;RxwTw4c=tni0cJw;aypVQm{QNQ8yOtETMe zaKblSAR712Me&a$-Y?$~CyFuRO}(>GUp9ex8#6V$cjl_J%@#Cf%dEY5M&LOg!RI)PK~qA6TS%7n<`kB zN&9Vj&X|NrdckjP?`7;NkD~W1QjTb>BwW>_6E=A_xIMivtaHbEvUNsZ?ERZxGhYL8 zXnAngO;S?7-%G%TGYFq53TU&{MUaU@ag}wdmMr@<=oy_?dzs(1i4h}3zEHpyo`^fN ziK)Wo91|W-c7Dn*8SMOI4#f$~<@Vi+Uw%5z(>z=fx!35q-Daw@lJ31#j{zt{x1dG! zZ_Pa$RxcIyjoe(>yodW)KVLmNY$m@y7uIaM-}8vE<>#eP%vpT1b~MMAy;h7(MB4jE zY3oJ03mAN-s9LMe+Xy#_5JB_DcYIdP2ZT4DBO*UMwV;u9H2$mv5p~6k8Bdn9pvrUzH*S(1STJAp6v|Pnz>7>D;TuL7 zX>WIwHeXGS7QLLo)3;?5v=zv`EFt0Hx|+RO%)>Gy9Ov743xDyBy;pps@WFG8Z6zOp z9T7M$rfP!k;n)Xw-df@~@7iI_$>n{AGwd&8=o%L7*X=?)z19*xzxO9@DdY zaG3R;qpEGFqZy{$de-oOsSr1)K4n;yGso!3CwMw!V~*@%^^^~L^Hc!qP< z`vUFduT^$ESr{9-DLI@$4SX#eWPx?WcTKY1kR-LM=~LY$9r*5#ZL)IC35RP*QnvQP z*Y&3HKJ`ZKz0;eP&XZzu%5phuDHu&VykbaxVJE*)e5m$7t!ZxR;~&#*e#D-Q09lE{ z%7m2_Wi1c;zC1zhs{`kXGShtucI4WMbOvX55^x}G5c$l8R^`~B3Q$vrf&EiBUySy) zhEs-dEh_L&bQ^QH-EEzIWSha~n>Clr`)n`<}(Dd0nPYiX_!)_2DF0NQ7&3EJ} z;;K1Ny!M@Yy8A3Xp)-N)HJU44l1RoIe1v$(=7{aV?q^t1Dcx?L?LHgrqvAp04O~CpH`@$to1cRg_=*!dvs0@)?`j z*mecl1N)uYci622dBYWx67cr;EAgD1gey$@2)TcUUgzIUdgpbBAwtbf|guSR=5BF!m-%d2WY;g16J<@>FX7TeY z-OaGXYRyqE-{sagV)^D(dXJ}*wn-A+6Q7-YRzEPdIQGzB5ag;{^z!1!)h$FaC?%6axtX7L#|=wh~K zpo@!9=#lQjc0^u_xYtiZ!7IILns3z7GmO!SJT-TC08nncw94nLPqNC-;Zci4wV*Q3 zfYI9ZC5?6&6vt6-w6Wyg(!c>>pi}u7&it_Bk`H2Zk^GzO)R?<3utWfHx|=*G_iEca^z7jb z?ihstMfj}g9p6&33o|`h)p zQ2hLXou@3Y2cIBuaqNM05;GS(2=m_0@6g4z+m^7c1%0vJ>To$-EA*YHPOtoDHmOEEGtF-iWi$&nP05{aqfd)Ua5c+i93apov24i!Hm`G>m2hy%{@)w3RW zETMT#aMlEzC5z|2(rRCK-`IlMRAYghW@o0zovgtK+}7omH_IOCbBXkk+h0O3+MH|d z{1!ic%y<=;TvCe(5NQC7DKQ4tG4)DtlKX8tpT9t`@u~dQ#Fb^fZYaXjSH;J-l8;{fin;N!(dGuw zRgs$K`7Bj})2C3^&V`KalqoR+`>uagTSj?^Dat)k8qDdtY4>B}hx|f$4^Q|81IV5) z{A(Z|%Mc?0cfMJ#L_GTGbll=qhPPSvoI-}2`^DineI*7iRDOa*gso^W;#TW2$h~*~ z&FuL5MmM#^TyXZtpTa~#d`?_$>y9+t1jDj)wb(+=s+%k+wo|x>UDX^BuN|ysC+82b z9^Cz~^tP^lsjcIInTMTH>QSY$dk~B)DJUJkuXFg{c`Lyroy=R%%4m9fyXe!J@dad1 zsMy?v$pt#%^yo#e5gZFh_lA6$3F+ZLux2`bW6}xCWqo;5rK=Z3X#Xs1BFMA#YP*K~ z=1Vcla*t7hUYZ)Za0(u|mEJ^v$yc;;A&w` zDk`qMhtyjKU8Z1i3TGdEbz{jAtt$v8u8r!RaafAOwYj9eG>ql=~sP>#tvgP|J{HC}JJ_|mGKQ>-h)oc99#nUJ}LH^;JwuHF6v?$sCOf74{0F6|3 zpNnSxjQv@rTCuaLy*&%Xh=32x@s!{36Y5|^-UdMfw^%6+3IUJ1>;Qj$SI?g0psfJ% zGC>&FD;ee+t+l2;>)WD3p6!la;;ZIMJ*&uI9B^H~2~O;$)bM+$Yz3G^LUC&8-mJL8 zuhZ41XyuQ)fO&92tMqXx&4fi6XPIhq}If$YAOKVYSdp;-231w!;m-tp` z6RMwm-C7>L;dEv@C8S$tNHWI4RhCu@GF!5|)jimYTFP#d()4i?2c7}1%<=GJpQB>V z^zV_uN#EuyK2h49k*i-Nj8*7qk!ca296`+tFahJY{4vxlZq7T1NyC}}OHaMXP&wW* z9-DlFZAN1y4?iW?Q$#GJ4q3~U-=GG^zP_%Q_~wlrk%1oGuYpsAbj^{jr28K~3|~$J zHqO$uA!ZYpt;fDd)`YG`7zL=aM6E3*j=wDH5};uA>;cK`U0mzb{75IT0|Q=cYkgo9 zM{jGrf>XEcQ=osmP3&?D5`pZZJj(*53Rpn$pXdFq3lVPLFoz88(g|aGn(rm`eZ5MF zx(=FjHA`Q{jxV=Oj((fwKmW!s;{QjA9hHc0_;(`S$oE3%u%&H8{#l09eCyk>f!tJi z$HTcp_YHs5p>LKN)USfnIS{ucsgq*!m^q{cP@%*lB@4j{8FEnfZ8ZIOWuv4mT@N?I zH4-uOz};;!XiI+Zq@fSK_zni&ZaizP0?8PBQ!6H1`vn`E3QNsAODE{ z_$YcJ%CV6V*bws6FD9IU|(nsK`{K8Xa@p$*8`$FB=0souUXWM%wx8WS z&zbo6JA`OHRz2Clu+93xo6eYuav_kHn)ugBC^i&WCne~6>4oq%XGa(=QXy@T(c@g+ zbuNSqr!Lrr?ghM{;sfl!cU8$4qs>s&zt_P0e+2mNLW__7F8c{L`LTy8j9GOl*J`Th%lcSbo#u}~>jrpFE|D8&y3-Aps3XfZGAZuVp_5I2nKblXGpxdAouqI z8#L(a7?Sz5+s)$|pEcO(JWO60;t=f9NqqSs4~NZ}_Id~oSjzOgAx6?P5361hRy3Z{ z4ODkU_He!3aCP+EhP*e_a9em)^_=RUy^h16_S=lju01f6*vS>K{L8&ll_MSzB7z86`1n-W33%oa;yzeC9!B{vQ%tSpPxK$%wF%(2wU=X;M12^=om+Sjm`R(KlSBK-{>S<5hU3=g)=mo^FO~g`z&gHr ztkQdeAR=4Rh7}U_U#<((kMW4!BR@PcvK>iwxqP(t;NF{~DBl zdePzr3SS&}EXzim{+33JXEN13><;Zs{J9#IC}bPwI~zJ*5uN!mvHKmn{IT4}ds<(@ z7hmK$B*-cP4$+#otOei(xo?(r?Cftlt1YgRaOn5bYR{^|ghSbet6vRmYW*Is7B6l% z($$OnQPR>mrjnb*=h5AABrp)mD!;JC&M3X@cEeH8|GfNFK&JA!EjBC_PI~3J?y(!Z zS34<#tNiHT=jjJ?-Me;H=OD#9t^7&##RySw(B!n~^LkUtXw7Spw^h%s`W2Ovp$uYZFBky?Sb`V=;wA5e0(SurrU ze`2n_4&nn>9hIeCUmy1$F8R}?uO9g%?y)C5vJ3_6c@|ZMGx58Bbf|JLUr!Y=siGXj zKdGzlZ{Y15WKF9doL{&Xj^5x%VpklwP%a6aI?kCJT{|nYu$DWZgwjQqs0&~0EUF8++(SF}XpjlDj= zb!_}=kM#4-hIqcw8Hr2dG2b_fA(j_m3*{*-1{7R@cRg~C$)Q}k@$qB_8{>qShO~aC zUkfv2wo*E^9@LHADPGoUvi)vUarHg$Bs`;oRZRbO?FCQ70`4zKhdmdx`(cWiJ9L9h z;x`*W$W?Ve@{D(Y!ZdoZ>33=kCzY@(eJ#S9*Kl)`>ypc*^d>G8doU9oV@@VaKyeZX zA`bthpjxp0k*Ze@MuW`{MoOo2vja(yK_BrlRANo?Uo3cOa;Mxr;q9c`Br--e$CAD* zTV|rLhlHFY^Bec?iE%vtg7r%u$_lnuQI2B|$WCOZTj3rv(G{BmnkFkgt^`a{IKmv1 zul;mN1fOS}k(jh(m`kGCbnwkBq8ZX-*>%Oa9BHtu;{xqh|HKYS@zB^j%NXN z;>Ux<4NG{AvAVuC8zxWGFy`h9JFx&9MgCU@Da!dk|Et!mN!-;d8~)YKv`Mt=Uo31;=rRf#J7RVI@v(oQnK4|<#^aKo?$ zD@E9cL_RtjO@83}kT-joZ-?>u*|D9?%LeMUaB}&qpoFZaad$0d>T6tuUN6g#$-Q=< z!(vfI4aGbBC=SEx-p_ng;%;P4reg1M$tyC93-I&3W9yoxxnDxjoX23Tz02~YmgDN* z>gFjBF2l;82oXk!g`B0e3WcN6c7X01#=VH%_AU}!DEo+%*93>OA3Ws{5A=IND1l?1 z10ni?(d;I1)C-+%v65t zk^5#?b*1F1{ei^yCgTJ;r%24uCC0K^=Qcb0U3$_E44J%RJRxB0i1O|z_3)MZGUZ6e z^^mbO^UzbhP?J*-A5vNlvJ8N4uTEsu6p`p2D9mA7Y~DF6)RK;dihmj6ADRn5EJPM5 zx_VHKF{|mIj#yx>fL&~TrvM4{>Y*;2tI81eL6#~IYTZ-Brs6 z6q=c6wB<|$NXQ$Bq`Oe#M<8kaGUrBxU`9Kbx@;hM5XWD7l6sVIdtMuNe zUCD=7Zi%|A;PF5HNhYAVwtJz|=Acl!>!rObx8`0OJy;;yAC#T>ux<$C*3rWjj!IiV`Ma5Z!0KUK>6LzGDf@MUzpywfC)==pFzd z-a*KNT2djVIGu9)if0EspF^cIs=Pn^?|!x&SOM)2(|r|KIpl)hEORivPZTvhW^Wz; z2qyGAQVpcZ6mft1H?`aVISI{s;_x;#?@5`bhp5)s0jX-6W~COd>M4k<#(LstI^**6 zgIs)%-=2+JOmA$(L6(2zhGo02N87xav?%oc`!^})^k^?zr_dh2wKV?g&Jd4U_PJ`l zd>TMu?2h^HVPPvaFJaAbO2H57zyD+E$KuEG$=Ltmuv!vQKB?XG_GDRG<`A!X-1qQj z6HB4n`ZAZ^y0)G7a&wP1aGD#NHcavprt*GS&rmZZdwQQAIQeROewoTc-9dEP`cw}p z4;GAqmbUR@`sx_`os@Wg2uZ(^ShbulzA9p`sM`(A#C~cB-0nL|*s#-dkN(H2t?b59 z>RRJN5HUpLb9+&3kO{1ZfDL0AK_2%ajNA+Rv}`pFp}f_qm8rE&o(2}{mq=IOOB8B# z0fIxpublO5L;ExzY5_<5d*i{HcR(Jepns#Vsc8R00y`Mdz_HRL7Og?`@?Z8kpuF&) z2@t)9m zoB-?JFS;Kw5<1-zXYZ4WcmAsY8y5Cen|&|74|TTlo6 zz)yC>o2WhhbdSWN8#NABsHqUUFFX>ICcA){_{U_t*w^$&*^mdA$fY%yu>}^O-LW87 zI0dJ7@ec0}TpuG)$17BV@ga2{doCTkBYKgE?nPc4!|8<$b*P&i` z1flSWmg&cJ4gh1H&!6~W!VWmGK{Y6jAD;v@&loDye%JPZb%4r`jyu-@3fv34Kr@uL z5Sxc-p{~z?$ZB>h4KY#sw;Q1j7xklP3v3a?r5l#rT*}Sv-Mr>lE|T{=Lvbu+yKNqO zBsrFw$j;jZzp&B6?VE_$d>NBRuUlpdh%f@>Cg1t;Q7^g#9L%iNKUa{D`n zQOEyW1F-Gb+ml_a6^`XHfH@0g2Tm!9BW*b;T)?c#K9YH+2A97kHszv-O~89+{L5Gm zw;SNIaCzdE@F5R%f)kOoUsf02GR;%=;AnHnI31q<6U9cnAroGNqHeug$W(yw%*i@{ z`@d$T=1MydBRT~zsDekykObdl9TuqsQ7@L(i1e`@;U3?5MfXAjE{bLD3VH%Wq+_UW z6H_FY1TDv|N{z7x;&{`<=YZ?VjXr5XhTMa=rX@sj({6!Gn6S^e`?~zYavQ;@HB%H^ zyAPrM{6@N4)`UI5q=4llFpnaM49R~o4m!0J5)3>1(C)f5jjgN1BoX^jS$pe%3 zYyZajd2#=~A>znGuS|zNX14?igS8KV)Kk;ue^s>Xi54f@@lY-hga9>TIOh{GekEUsfO5;hT zK(iX*QkO=q5GM@WXJXzj4P50!uMLi+0M$*+uET($Q^Xq1^ib@9OFBg|HP1}(ksbStN4L12>DDS@J z)EcF6wtL|fBq0#AZxl;rZuLWL#H9*xv-PQ>!(y|6koMK>q(kWWOq zR0UB;V4PYM_Ol1$EtQuze+PT73YztV-$Usy<&4oC13Uk#n=_&XBAaM@moblSf)Ohzj>&V zpavIZqnwGB5`a+VRW7Z?>${8N9`ERWw~*YLygd;&70`tF2bCttqp8+H25>l2&^Th{P( ziFpR{=t(IzCjph*7btpMGD)~y9+*V(t8Z%KA<9n^r@(s^{AJmLsGOuUY~_m*Hzz`w zBXPSd6^#cTTtoHXBhesJ?KY~&rH<^;dQ7u=E!}GodWvtL+`mFZ8;q*4A`~was zUxNd~{{{*k-xTtD^CJLa+sywU$Kes`xMlk07lqu-B^2~Nj}F^YE~@1Wj3HYnqIsI!LZJk#cqh;`*MTs+&5)|)Jl zWZNI{HU#$R8=!Loot?!3#>vj|A>rHv6tkmu|IMopw>c>zQ}v^A!j>puC@a&mWb{86 zzIDlZ8b9El`MbN??d{3H$|u1utQJh=qdYDjYX5(B4}J!d*fV`|2}!NXYLNl#aLxjt z(if7}-Sc#P-o(8Au0GgTb%`F|(L$oow(t_Ud{p43R}GfVL^+9ThXUooj#I~-tWaqN zWkpHBz*MOgDJj~o6B+RI*z2PL7XQDj0G{&ya*l=Nft+6)aC5+>w|HCGLW9Eny74LY z5+bn2@o$39X6A$u1fziF4xHZWbxp!ieFQQ(r}42tn^{@ThLS@GJPsY@RZ;sx1(+F&OR#fJH+SFxo=y6|2DgtFQM!OCrW^}hJ%k4!T z2j*G~4{d2ws`KJ0H+@qDf~~U+y&AT8fMoHHeZ2-xO4d5hAT?)-+Y#T zUvsjNf+lT!ZL7a(rX-+tA+F+og)Ld;FlzvJ4VYjx3zS&`hEN?0sd4bEpcQ}(yEfSx z@ScBc80GBd63<+q<0#6!;L~AiC4q5(39MJA06tlQ7K*wb1lO>*HcGJTV|XwyP(6J4 zD9g1(jnTTy{f|WhRxG8rJWdp(+~rYQYdRw~xre|Cwxt$sqX>(g6gH5WM_wDKK9qs_ zkoPZ}&i5MIsq6!k=%uVAf^d1!<+2t6He5%L~Rv$ z#$B6=vM;G|YtQ!t*-Y)^=j~8NNoqLYL(OfEB$VL-M(K^+55D|=#bkEUf3Ino4tS1F z>$Zp5adho&+=bs-0WIf=CGoJRYxwR1OlMpurouxd4ndSrVpF|7Mxc(DsYXziYwsI+ zPcxVj;5ZH+sL=^q{&-@be7I4CG4;84qq2@)EJiyO_~U8r>zH}t9)(o}GpQRW-k|u7 zCjOyk2x{yl({ys>hJ8hGP%G6(k3sMin1|dpTrD_5%@~jFqi1azzHeB2LmEQ_iR>>dpWmrM(z(;yu^4s5Fb zwM<@*$DvhsSHr!%R=x%sH-0+6fgElz5K<_4^vv5jaNNE`4ON|w>P7tI=5W#Dn`(yTA>w*=KdAO>QWsAqLDl5GV=X z{P;Io{Tnp>V*ihziRG`i^FI%hvq8io>hUj+5d-pf;{HK-I&790vbf49r(?$Z46J_dgYTDZxF8^P?RSc9t5H;)n*B{0Qh*395Oyo+!(OLo@DcF~CcIRKr zivOq2{2HFm9R649Gj>0dNB_9{%M_{{IjBe;XeEe-HnEbV#pJ z&R<@kp`l$}IULia{7aK$)8NMEsfV5z_sQEJ!B@vPzBxo|u>lE%)y4>LafyAmomeVt37pcH;fB z@@3~aQKwoBFrwBo;?Xj5aVzEuLh5pd?`+=7vDakPD-{tR@Y1e{3$nam`RQ>SOOn%>!92C#F8GX8Q<0LhCT&VjU#nJVm}b|Y(WF_^dWux=Fep` zP&f2{@)%@}pcQ2$>}<_Ep8Wj=G7^cbMvuWHS$&aZvk1E|pjzQOy+_2`mC z2wgPVO!Ds?9pwV5iAD$!v!J=pCJ0^l-j7)1Oxrl zh|2Op+m7<&3#jO7_6=wuyuCbs97sU_OCF_w@f;)|K!!sZRqMg$33yJ3cu1`R&_~Y( zr3`n|Kx1S8>4u$)jqwua_ffS!0&sprdtaP>B=uFg6?f;U6kHC8sFBrrF$IJ@Gyt{n zmT8sEq!in;__@^~!z~PSf+yam@$az-?POmqwuVZB>RR*J^hlzy&ALg+dqgNn8*BQlj23|G!M5Oqh{;0?e9$r zP4{1cZCn0k--kO>x@;W~9Qh%+wCRsIGk07tqGYf59-CF(OlfN*K zeMttTa3z@v$?s`0Ng_N!*-gN3se-&fGa&adyD?>?8g?O z2SOH--Xa>gN+X3@TuBg*a_I%0TCzJ$7DJi(g-?J|hT~^@Nf&?_^lo_UF5HlmW$f52 zNXqgvB7x#J##n$JP2_#y7bE45_VbNx%iKB8CU?|8`%DUou>>nlNEU)pzE6Rk5bMZ| zNc8+FJQ7~RJwnDO4z>ZnCwCUW8i_Q~E-_?#g$5zHqwtE4y15KFUFR(1#cdaWM;Ub$ z`cUlcK?tA+Dh)!=Jlb6k?kJ6^6ObH&5?my|{NF^w7imEEp!8FA&xXgM+frVAfA1)O zyX8oc`4w>^^?htpU=dA{I+Z-s1X_h#g`~ePhPhyKo$BVS1 zfmqHemtzO(XD)3X6$el})Z$PE^lU;9KD88>PdVUTl1T>u&w3ct@%yqD%INbHAt?b= zNmPLVNgh~=N$>n-S`@yi0Ti0ccg*}+-|PP{N3%IImQn*k9iTXIrmPl266WAbkdY*g zq!tKDAZ5$|jHO}l@im|(bpRB!*2ha2fr3xi4j(xV9_ye*b0yG#Q3Yd+GEX=(*x<1j zo`g5U>Q1x&px;Ygo$`R6d8+nZ_V(sLGGzpi)l{Nnb^h>t7P6*Da57;bEXSY4h6jVq z+dDVV@$-KlgTkhIA@Hw-kZ!4cl!%2FI_6djMmrdp!YY|!&9`%Jyqu6^h`Flw*#8OU z^2uuq&81453-nnWmc(T?Z5fS&&naJ9Gzg-8>5@5)XBvCWaKwV%{ZUtCQUvBnNcE3#JHOQ+feJ%0%TgcO(m)SZ6toPdK5qT0%mQ*MBy^GA zZ_?x3A^NZk3eOIL20tl-c=x~`xUuP5SDD&xp`UNGGMV6`(=T4Q~c$7V%6xZzHS zCA@*&i1Pjv5-^|LCt@L+DUP>h4vqbOd(n2>Gg&q8u*swX<6bYV+-4a_5 zmTu(K$lcj!=48CZch`$)M(uX6{d@7N^SEksk9l^q0HZ%7e&QGw;&VR6jc8pS7pI3O zhfMU8Kdq-cW$BxlUDrnhJ&$Z=+~%Uo9cHj;y}x0$(ytDQgTcBKe(!<2gi)3K?96Kl zV?{(+zTb`83f*O;XkTw|t4K_}2Hc?d`+5&&j!_a<1UDQby-xNVy{t#_C4ofMJX|7< z8C0sJ!#lW9I)1mJeksht%CqlJW6Q8`P?ZqJf*p`RoehHnIfkllEyF;ss;Lh+8glKY zlmB{64r|}3xNrOEaf3W56dxILgrRgNUr^ecZhb(_IOx$^;jm$8gd`8MK``{}i%!Mf zX?=bDt)J$?;YB(Xcg1g#jKdvGbjx1fcsDV5SFClqD&pvo&Eo#*K#AC!Ny^p+u)w}> zJ7S7qs)b`uB;hR(Or3+ZSg(6ZBoC81Nd5wB6j3jG-6rhF_{fg#J0%*$U7+FAMk$`o zLWooK&{z`Fm#J2v>LIeuZ7Z`S5YM^fOt)kks3a{Vp)(Drl~gB4be7%nYrB;{9v)z_ zL+p1Xb;<+OynZ}QQDFK*`^YXEtS=5zZucEK1m3&(JA_wd(P_?Rh=JDq^T5vva%cS| zeDG~n@J(z7|3zttZ+E*vI`UYr0zWAX-s4L_=9my(@Sm;|IUlg2C?{zB4EAMEuwn)y$Ptv5jL`AP};ZCxI#?ddI<~E9ni7Dsk@pe(8V~ z^&Sr0oQN$t3HPdMrH^~0P?{l&CKZ-I*Gm3|`lU7L>CL;hn08|_e-tp@IRV9*mM3jO zMKw1-({<+z>H4BE2{CN~H7vFO=ZXYB-Vi|w!*4jY628*9Q6gC1J6JF`FJ&=qy5|!4 z5aQA3E<^=ljzr(UJIHuwjE)x0`XJm7i?Q&uw5&tGl$UeW7h?oRsJ_m5HVV>%6V>R^tvV*cdmrkyT*U{&Y}~l%<0jXW4FO%~Wyw zY%n8ZzHB3E;)p8fpTFhCl~};1wU@_vI9C-le?SCSIU z$toYUgh$1a20GVq4!3~6^Ii&4Qdz%9*>CYyV72)GL-8aUNw_R_eo;pR^)Ry|WiGaw zNp(UxG5SYOPn>Z-8M^1rw(r+4G*73hYSpI;BS!zdvKU9?N=Q}@trIr<1oFgPt zT8xbUA4^irU7!`!vz4ErTdWL}=D}v$o}9j48&ZmB%qxR-l8AR)f;cke@`wy$v=0$y z5a$(qs1r6MAlOx5SMv9~0NTTimP^K&`T4=ycPP7FED-#}tAi4G312$1Nn)Z!%`?%D z-$hVDs->0b!*OB?{ShXPlF4%rf`N#bPu8Jlz{32jg*3JlR+{}c0+enWt1~dT`|uI)2QL-EK;5e4HP~1uxb(InSt7f38Aab z-P%KTxnqdS3NAm9HEYp;WL-y9?z8WT*xBE)&3OEc8(DW+$KqDg-Ji4xgl)&=-JRoj zaQFt|YfNG;E>C=Kh{@XFv=Xq;2qJz9yRQeu(_=m5(h1b0@C8M{_7C3$fKfILbk~=x z#xm_}n2k#`@(8MvgN~hvjX)X1Uj21~4%^=xwk`fQp7K+Wq_g=^&9eYe!%)TS%BtlB z$$)ISqjBr;;!1t__~D8`=STUetS`?3m zHnN9fzfJK!K^LS4vQx5P=hzCvuU)kkZnR^}JeSffN}T>h3A1@>hV|bHYT?1=Q1=;m zor&ll?ev#u67OZ-*NTmd#4;BCJ|anJm&C-x(y@5S#JIUB3Ptv6{hl9XSixXggZmO$ z#-@v*|BPzT{W z9L;agE(pBlCJ21cZQ_pF#*kAhc)w>CPGm&CouNhelF-2QCjv%q$BySq%g=e_U8qWAZj_~v{_MI1^ zXaf#NNnXb&2s2>TVC=p(cW$eBI_u7zQ`?Eh_7ePP2iWp{I1mBMY@H6 z-7H2aywrlJ^s)2Gl3@oiZoi>azL0{gB%S2B?EBjfQ<5av^zM{B!yI)`SfB5kj>Uc4 z-Rv{vR)*FgiO6V-#;TtK`g9Houq^rLeyxiP9>UPaJN^&R?hLR12iR(XDW?UUv0w66 zRIMMY!h>5i?jC-8n|6fTv=v!&LF}L?)6j;Ac(!EBQ;1O;zjg^`N-xCcHr>i%X zL@zvfx#QQV>^u#C@1SrP$PE&wDlk_gG60N|x7~K!nYH^Sa-VV`sz)LT zr^R#aCtGnlcBM_ej=z^w_ZMZg#G8%*Tb1_f;!^bbdJL_E&%KB&VxGC@QQ~B@GBpJ0 z3T0OwktlFvf7d;HyM#H3d&hzf zO`Kb|xt#*1YX9XOA_6RC%Z~S?#gUhTPk7-3rLAe=4RV=lJhpt3SU3OdFNNl{4CmM# z3RURrv_{TN2KeHs`aBs@*=<~0M0P!`-~RK^<8P2wF&Db}JRtvLxF6!z4h&Ced5pXI zAs=>|=XTfAdCqER?|UHs>OL!WaU$50vPLeboy5KPm3x-19cdl>GAEkV;+#RXg##jB!?Kfxrqhhj%GJs)8r)~%BC!Q2xD11l#RmqnI6Q+Tk zyCszZlrp5ewF+tCzANs{$M3d!2tB>>n}4($CGS{ZO1H)ZH#BAi)Pfbq7F%+n(nIps zZtt%#<0KGDL2aQlGe9T>Carom37|wn%+1e#Y#m#566<5#@#M!t0g%SF% zg3}(KyOaF8tz$2eQu-vf5^jCY0?Q_xYk`Ma^mS&=xp7&|Q`mBY|@qZP*rBGz~8chkZwXnZlqVC{V0zM>C+__!(`ex*mOd|Xzc z{e0e%ko-8Ba{>9p?daLP%)hT7(;fYWPkR%0ygP)~ySfCO-vT^RT&~>ENfzOI5XdCg zNDLMJK%{3jm3tGikrjrOV1WAD*);kx=PB(yyXx}V!}OO<5Qv`Rray~9v_K206X(G05It=GL{)q zhl-$pli#7H2jZvy09D86H1+a~Hj_;W3U}hCz$J?98cz|C|3YAiT00*t6yGN$0%v+; zuOZ1+1JLr-+vG!nrXq;WLfqX>L@>p(&=QVl>e5H^& zEL2mVuv|&OkKpUV-d%qkcp$XBrc7MbMXFz8e&(dxb?oA478NW>Rbu9w)hOBBsvJ}> zgS>v7j=X2f~6sUUZHMqf>WSDgr;5lyl<#QJPCq{aE zrmzwufCnlA95gHK-r#YC(Cc1)+5Aa{Sg7wWC5 zoU60-hAJ$yzD1*d7z?u7=*bU*Yk<*kQ2h^ZErh~od)DmL!;}1R?=0D%dXTJgt_~Dp zY}|*XC<$Pj-qvzN+&~n&RY(?0Nw9CvE7Q3Z zpzrRPsbp##Uf#SR@OwdOm&RBdc;YiRq ztqK4^&rQh#wgp?>_CCOnhaNPRfM^`QQiuamX+brs-8wBC^owSMP7^h@0`soV0QD09 zkFX%y`8121Ngj;6U=9HttgDHbr38pD@=4II54fuLX`GHg-=ctfV&!)Me?fpu3Rwhd zVF4Z^0QfU^$P_v!0YZ!|uO7~V#K6JSJN!V4HlU&|8A0Y)jTuE7j0GC44jq=tf~zW; zP6YvdD2Zkp#+7M_8S*_1SJfZw7a6$4n?(NNqjV!oeh=HoVGsSN&Ou(_`nm->%n(uW zQAH$yRaDRQ5HN|*f6NLX`)TqKx>YTAk@og!0q_&yDHl#*7QZ^$rhyHD##R`q5JS)4 za8J!X$!XbspT~iD-uehJfWy{xPV(l3)OqXci-2TC!rY!v1(RGD{0#SFF+OT&xqe@^ zz8jcVLR6C?2}*x6JL^giE9dRNy`Imn@xM{Q<5)nFF(jxKKF;rnd3-;8#|1aKhn?IJ zO3j~lgUk_%`+w2(mQhuIQMa%lNJ*!3cS<*cba#VvmvkfDA>Ca{O1E@~fP^63&7r&A z&Hs7s9rw%oiNOF4=lu5GYwb1XTyw>BChk+sh9xfR1>(`ISK+el+`c>m{z2(u?T}J~ zqKh5XK7C;gug~-agRbe~edUP@ zDz`;3_)MX(2Rm4QT!`2$sIYRzCY&C#fCIb9c13VT5TDrKr8A=9ESs~2D7w4FZnGbF z?z3#?%Ay^cLZoaiA}=qC+-*t6`0{R{Bk}=^`Tt_PAE*8wj2A2;a`sZ7HHtMZP7^A< zz*6X#JwQm0de*PNm!(jM#fQtU&wZi;IWH^rOC<4S_k&6m1^Vm-|6?wRw4?{&nY4)u zM6eCIWzQDQUzJDyml=EcPX71a);jnjQ-NnQY5U|eYgLvO0Y>FSkefb&&&~VD^1=pz z(*$+Dp^JJ z99f^2ONZM3P&ofX{4}j3$NWF>8=TnL0OLpZ?0^ZxuE4A}trN>~C!!`S@BH!sn!S`cD& zU*b6oUgmB$dEM*G_%}06g zQ9Rb$K-LkE1IY$DWuNMQQcv_r0r-din_G9R0L;^^tP28?4oCDc!@518m>u#g6Z@Ptcs%&w_c|@IR>r2mAUO_9b}5gdH2#x|a2FSjElxH7cQ3~iE z6xuhabnEtLBAM$TZoM)-0awib;-6C!RNty1CmiQX4hbnmCVk^i|G&T2yzpv$O!4w- zTTsTv`a;GEo>3dF78&aFn4a zX;9-VhMg0jar{5L`r+33Y%@LtPwe)7tT89+Bqz#!IdK6&;1+KmX53+=QyEMUqV+u6 zOk%efd*y^x(mwLbHxk@>a}-B+z`yZc=Hp?9Ut=OLM7Py0zvjQpe|I zJ46Czfu%0rAO#{}8PD^r!Z()(%3ww|QziN_|4;(B`SGZpn+lBQ2+B^CJW9WCKlSh4 zs}>Xu)AUBCdiYz!c`I$xtaUT(5=cs_;-Jw8S}-9DkhA`xea zhM|ZjP>7ez)vhanA$PZ;YG6@mWm6cjsO9jub#DsOthVzi@`-}06?H!bWY_&mWUgm7|L_{H+)lQ0QlAt%& z{!ATQv0VD()}7K%4nSH$>@TYeMQ`9EBHAlL;z2F06qnOc<})*|)+xgjNg$k1`0M)y zA`}wzN;;O^LJ4QR@fxE{I{7d9BT3W^xAT4u|=)!0}{Pz zEg!`pI`UE}Js}6$Pd&@?R_uD06$kiNvK7pkfFmtN-?s(Zf0d zjJ%(<0voj6a#%DHpBmSFcNxCN>Hnk?)nM~C zpU-t)3CtvSRkz6CrL`&Bql9>0LyC18Y*ed1n3P+VpSALRG*f|}6I-3%LgFa1;Qyxj z{f+D4+#ds+utB-=uMF#u8q_6TGS&6vHg^X;r#k`UY}QOb1+OVMtGx>efc+wGqd8GA zOkp)OfIP}bn?Cqm7R1{_##|taycv9Uh<4}mGJ9t3VsjQBe3e4;g?Ta#8Nq_l$t-HPTha%efv^-aRp>oN}z z)qC_oaxZDeX?Cg9>-`8hBnp?M}?|S9368^v{cLcK@U5n43nn<`lv9VUV z`D^npPaFYh^-jQcx;$RJ-skuEnw6>AKnsHhO>yU5ScWe82QZ=!Z&5*B5Vd(D0OU7{ zV0KM@ zW~0gh-)5~2(R^G*$yVv1EfhUfo4AnoHX}B$vU$0t6aQO^s#1Q}orDjI!ag@5R+A-# zWEV28;fRyTpR6 z?l}xNiIjh52GusWnC>ffq?@9-SBT_xxCFIc0c-dl^tqxU+|VcZOKoD9tzKI{An&0P zl)>Fmsan63nMyh_gnqW{Sm#5sE?M5|FUa3hC(C==EN5i8(oGoFs|qpohStkHmOFhj z=iRNvScHX&ql-6GH?{mqF^-}21P*?)#|?QeA%D|qd>=z3IQFgCYWu>R9AY+-Qh3IR zui7aJDIfATCxab8IT*B+_Af*ce1oS!;d6eM+)uY0hI(w(6Aq@Nra1A1&190d+xy7;zKO z4f&_Gdz=P}i%5$bgt5G5`87fk>bI$;povDCz|Ta)>k~fc<6fMV)1D*4VYg64326-9 zeeSTDqPAWtp)#XS51z!8DeKA;-hQ~+;wRGe4f69Oi8NQp?SJfEJ$pGeAoqesMI z{Fr&2>)Pgx{#4+;lLC{RXLYWr40#Y=tU4G+px9m9!{l&XmN9^nGJG19E_XieR;!jK zXgav8{B+y%k4_`YBjw;OWo7r)X>?;Z%W7lqVTl{%d#n2nPJ^9Z(3|M2k9+K9&S~Rs zX}lU8L|tt(@&V(0Kd<-StT^Xu2(*dmm4v+qqjpO>Hu3)#Q;w-oN@QlmxT| zC7w|!wQ+xKo4(ttb-Yc57jAYBTkn$wq$$|!cImN}bxxIt7QjX6>$MoQRw>{p?G2yA5T%4U1AS6B) zC8%yM^nV~ygC7!Eoj#?g_t^G0n{QN?aP?COBZu4>5efP52>RV7WYj|{y5dL5(HzmC zf`=cs&Iy3lC_IYFcf2Zl>OO~m^)qcm+rb>_yZ6C=?X^F69+VWFDErQ8xjrKLsjhGD zN!FW;S!D=1qo|iDed*&yI9FDb@2c_0BVOQxB5c$eZt>UK=WjNgbcOw&G7Y+Xtkf0mDXckof^KF$!yk3} zZs3c@y^BJB?n9A>tOTP1LA2wD6;J;uO$@+4Js1@P}O=7ueb0PK{vy^0mvky1n3Zeq|QmwY4*z zO9xa#Fw(VLAEIDSnJ6S4ZYwtCfQroH3CJbpeI@Ld+ohZAmkQF@Og>{%q#KO}l>by; z&`?}}OKJ4jx7z?WQ_F*~M_a}-{^Jg>g@Q9!1DG0-rAk*pKG(D0!Q~>4*?tX0F@St4 zIxM&60}r-4`26rw2*Xra6gov{G5w~X|C2xnB6cK*1^whaHxF;RwJHBIGDsp9NBn+P zCe?mmY*W5S;ttGmMA1#C3nFI@?l#nCiUaYX7n&+cod>Tb4z<~fS6V$l2gMbv9uWa6 zcdO6s`ws8x*vS%kn+Ow@WoKJo0s_;OAcji>!v4R>Ki;2T9k1ka9T|v);FlJ!h`veH zZs?l6T%Bh>%kayfR!kGcX3)5};oW69?7q}mk4=5UU^pjgxAQx!n!|cTuZi#TSsEFK z^O@!R*kP}ThwRobso(Q1`G2#$b!RjgE%0R$$=GAk?^N+aAGE)UmzvwPsT?SD&`M@B zUpy`8%C-pOR^(%qPkmlDXfgNYbDqq!*X!sThXWVk(CBmts$c50o5F4$!pOVJDUg+p z-^a)-`Z>_zE#Ew+ErqEGAKMy=4=Q4VBN8X?)Ma^4zBdsSj8)=mscpG?+ZDLU+i34M zES>VX;V4oA*a@hm`;Tc)n;-Kmx0ffjO zLK*yJ0eH8%9#oGb6!6a~nPEZu3wlY}5oqMITPS!k;>|m>FH3+>74O}V1AKTIEdTVV zyj@BA7=fV-eBTg`@i1j30-#=70uAJYe!nN9O3##2GQDuuAfQpVOjdyciD^RzxLjC) z(c&o)0we3bgIswkg9_Pn5`jr-osk<3ej*L>`rId^KOgarmOq|vk0to2LfVh3I7E-< z0{Ap5v_63G&PCt?b;^snO$-XXLchm5wzawH2j9WDi3)A@$ynsAk>o<4SkxMLqX~L6 zSWd9gMyhljd?#RZ8k`WYZ8I_gpK;_I*y4U&pWHdm^KDUjLw4%oH>d=veCOZ%Cx;HW z$FR~hwjg2o8lD>&cf66BTjumV*6lN&a4@QUt)3Mgf`SA)PUS}WfjYB7&F^KW3vyVb zE9Vd?R3|ued#%E^sa|pBNC;~^lB7~YWKNQz1R&9WjQ4nZv=Cyy(k=lvzPvW+C09a^ zHLMVkSX}g_kXm)|24U~yX=%Dm6)7^86@f{wF1st76hbh|xUoaP;ULM9BUl{DG@tl) z;1QETWjyDpzdGXi1UgsPInb-#LB@<;qqN($`&Buc4MY1|lY|1f5bO3TgF`RNl@i}l zkG2}a4mm%Z40?u361D_s8quj}=|U^W1wuQDBp1Zr|E`Y`V&i#FGp;>G1)0#m<1C|L zGU7^EN}shwy9ASSC)3D$;r-r}{5JgkfP}Su={4`aGoDqHju|=urZfB4;h*4)1CWP} zEYv&@2EpS{M$Wju&v_#61Eax#R;%+4N(fLuqN;%bS6h@_;39W%v8QT1S%lEdPfPhy zCl`X4%68e3{}7Za?C%#?$t7eRKS4o4sUz|A-AUgglo-Xn8YwoI>jJRdq27X#yU16a zoObvce>>p!Y(o6vKSbwNG5b-=rbL6HQ4|c%Y^=8iCDoz=dI>EURZA7^q*dQ%N*MLk zs3(}LPb8PunVRK#ki_2+!P~Ti4eTp6VjDb_2f)~?04c21$!fQNFf-}0yF2ayV+=>l zRD|@R3AJ2tcpKDPb>$WG)jr=@i468Z-#Rr(%|By%L|bfLdgF67U`$l!4IBoLoVhb+ zX*D~Nq2?kON$!kgw-ga&@B?uHS-fgAH|c$)P6N4A8e0{N%NeRb_R>Zcud^j#ofXTp z09DA_`^>tE5+3)R9>aR?F<5hhZE+y5IGN*uLs<8-Tw%60Xd;hE%S(uzUv! z8uYfF=kk50o6WU5(sh+f|6KLrR;Vp~PZ{l% z?xI+aMYOY9tW8s*)kXYfP|C(FkA@qvbNd6f?kpq_TNfp~m$(7hwvTAl7{mK$o>QUb zl%NlD@(QTSK1c9Tw^PZcY;O7i2g;$IDWHyQxj@AHN3)$SG*gVx17%h zNDL()>+(uR=wT|*;Ya(3kitpVxa}A7Kwthjfx|=|)X`jRkT`6MMY>BVKvNH{WPSVCd7=Lp{*E+~IG%@}<4dU{g$`FS|GI2?-iLExY-ii!0!8dpI%dMb-hd ztMy;AQt4DffzEIc7MCZLQW}H#3tS5>!X{CMp!N?_^e|{!g8072pI=_9m46neUtl8; zk0BuY$r|swMzE~7yZpz!-r}0L7r0m1VP3)_tS`5y`${l3Ia*yEkCx!>y{@w=S8c&F z8%{|yXIi2?AuR-&L_AZiqPo)da<>q24*NTDdcF3yGnH!5VG6GkW~rr4&&_2<)lc)k zvV`BCGWaK0vY+vjLu#n1wX4u54!YA5o3m3`UE|Y&lDIAZj*aAyY}!4XJSBBWA>ea* zGIM@c{bGa6xKty#z-YfAWHX`__U&xQR(3J}^GcBhZ(GiN*u~z*@2Sjr8=AyZ)!hP$ zmDH>l?sICh-_)brAdAw#kf#7RDZU(KtKLv#A5Kr;kUU*yp}0ShAEXYCCqMx-^J{BG zHTW?R%ShnTohDEfr~a+b7QK1l8#4{VqN*{NR#AO*nXmhij zNVz_K3;>VqemsYQfr^1JqdiQOD-OMH_ed3UBt z$~Qut1_Q#zR|;#^dTbOu`}{spLPiNgUu2cp_CHIA-1L=bI{Y5_n)ZS@hz1+DPUgn~ zrmaDn!UI~-IZ(i_exG#Q#ENF>6v04Mf6s{jP8u+=9~6_-;k78x(jxdFSc#TtyLug7 z$qV%_B6%BZBf=m%`mDG8F$AfiVOM)=cqYhlL9FIR7>E(~pjvC0lAlJl;>Qt4?jy1+ z!%9M9<#&=aAGvGZH0IV^&D-n*dA>fiCnwY`;WWLyB_|SaTVB-1Pc(K)jQw}e9`n8utm z1Z%>)PzbkpF!}487bMQ+>qGMdwY#)+w+BnZryg>=-m{{)TbR7w7K?8dn|f2id4;h( zv!0E+W0dk*^}}f9C;D*M-T(zCGN-WD8w!H+%EdW%+mTQ)32n3fJ_oR#^88{Jin)cL z)1XB+CN~M`qOap~gdR*efBWzT*(`PY&lBdjxN-Yq1_4`wGj`#a`p*X+WHory4wkDT zu${tuJvNAm**8BNAWhCBiDehI=Vsyze?<#R5`aBiq-e|1* zUF<49yz;pk^U30NE$L3ItXPcb5&Xdv)g!SP8oc9OuX`r*?T41~38M-(I0229(s>78Gx zar}jWvp1kOLkVmstM$GJfb3f#1>hZ%st5HUld<#SaL=-fx*3j}iHlivP zP9V2$iLF|b6GP{vaykME%$s$Ht*r>|r?&xcUOaO#m^87)n)sa7eKVVEgDrqE>Q+7m zWo`3q^iZ%AP81H)`ZgCH0rY!V7$?PCI-+7)jC73RCKMy0c%GY?1{SYJaRiE@HJuVa zS7i5n((9yMJ1-h1&xPe z^=v(I(+5exlWD)pGuZt}3tF>Z38{f$@b(cK1^{9DRSdMx8YZ$^;~$j^BuWh!OMC=M z{`y%SE8z2%!J#5>TTc=ad+Vb*8Fo5yAw!lD&amkf;Ki=Dq8fNbzH#ie+<8ZNCGXxO z1gYig*nR7Rf0*hbW?Q)YV%xn=Rk`AtRgMw5c6HQ{CgWeTN>+9};Ur-G@Q|LECV1x~ zWaT8WsOU*j;-xO*=+M+Jqq#{8JnitMYGNEFTQQTr>qUi)6O20s|9-v*v1z&XM3N$| z$JC18r_O;t2xWCt@(ruk%UspR9t2HJgET$iJFzvi;P_MWlp_&vG1~q65rTOO>>STd zqHlPNd)`_wDdmS*rzZeXAN@j2J0yVfC0=aU8%+t>{AKKG=^9(<8P&|q}EXIHwK*Jd=%6I)WIyCH|4%Sag1QFLuMp~$k^AReEA+LG9869 zQZCB_0ba)mLapok0l-(>)rM=a?i*B6oAy)s^q|e6x0Zz*o@t>u6^UA}H6Y9kh{Nzqt}uh@slQR1Kt<|J-P>vv?VDym%xLi=}1z5(>NbADsa*04{z|Dx z1^H)wHLIc3$E98cnCQE=hMNMB~MK zBtq?_e}cUK+$GC4R|*R)@}6Otq8!ZYR3cIc82IBHKLRxFBD?=*|Hvsx%h{g`>B20T z#V(&)IXisyQVO|jM$ta0`sWL#yD;?re^UcfqjZJ01#mr2uRxfDyH(n8pMDD9AqFXy zx)St1txi$}dreWx={cb&?AE-3QU3wdou_dbDe>-mlOf;xqr}Oj8V2G>Sg2y#$RYE@ z%J+y+Ch#z@>nIVt)Jq^V6q^02kf!ZiNwYMDIs;7?0xmSv2l3ymj&PLWa2Q_}7@Zs@ zwUc5RUDS*m_Rad!$&)^P0*zp9b-U3yfMUa@8iRIx%Nm6JuYxg6f?a1yXyQneEgJ&} zsO7Zm=VvS2U=hbVuPLZUjT0=%r|heL<3xC`7Y*-?6rYdQ+SAI6Y#TJr$Ch1FoHIEl z{tj97hxhZh^*M7~IEjkGV#A=v5m?G~ z!g?x59q-tNBr2`)S5qveP?LzYhF`dQhF7R|xvZZ9R3rH3>m0AtG6Y=-D;GSYn;1SP z<$i0J5NUThLe%9!l#IY*?s~jxv&6`y5%j(b-U##d^f+cNR>&P@Hb@cmYsb_IZ>y_? zLKBrIo^*z1k5?&GkO!8OJDWsZr(?atRP_$bhEGQG5w%b#jG%(Be@76{o%hur!+rD? zgGT_2|mV8&d^2ATjv&=h75zG8(LOz0K^1iTQ~;KFxmD#4CG_4%Je{YKl|O1 zgiQCH4-8^phK45;o}hLzT*7`_{wcSZFB2(!SgG4oL8+>f)m3W&qoB1~`;LUp8{z6G z95qgwNt9t&2IvSxRb~p&ND&eHh~`D&gUQxm3-sn>Zxkp#5*8SRv!*oO#_xcxN|-@) zM>z}rE3gZMJV((ohB@9EaoyaWuPn;dXlF7{x1Nu)di?CK3NlC$mi@7d6O|@O`lWA7r}fJ{3dXGwGrYXy=oePV25LrRCI9vM)%6e@ z`+(jpV&e7MBMcpXFfD(aK|y5yXO#+`)8@#|iFm9_>!w=cK`IuUWgLVk_@xD|?aNi2 z-D7BR**~wgRcXmHyCQhb==N(o)~_QP-?~{4x|Q<}=@1lRXyV?<__IGtva%5hn&nu2?Q(dH z_%A@wcLc z-(OeTFdumb{H>f{WvgAncDk<4(VS(2h5nMn@7Pm^yn)N)CA>0g9_P_!pZ zPj0jJ*jlIEgY8JrC99PUnIIVp+AriK@q1jaW}$rLL?`R5uE9EP)3#gu12JxyJlTE! zf<3WzD>K(0x)FB3pM`1tA1{ERf*_bCk(UU(Sxv@{8$9iOk<^04>;p63V24QqiKp$& zRfrXUZH_SLvad`pVup=Ssw4Md9KD~f5f57NLzMq){?T;`xUYBbRxcdX(k{d zsPD^m?+km|$?X&L+%HNh^fHc2zu6H@4eKHA(B5%hF7(KsoR}V-Dw;h~2mu62PzF7w zB|1ES9Ncz6-}BZaWw217SPBdRhKK1hxMe3S72unxz(BJ*g-yxLm6PM}i*|KOh8a@V zVP1>fyD0;mf%fjKw%QLpY?PUdEu`k*<+Sqi7xEkD1~ey`x|wb)18@XFA*B{x#@*a` zy+xvO2;h$QhJ4-HFquIb^lPlji$9F8k9hrJSl|O}yG)T_ZXVkP5Ykebqin*N&Oa#h- zJ;p5~yZIL$0hSZ4;$a*Ux!ncvWQa`tu&FP3_`}`Jl|#mgGmY+|_t0;t*LRKBPN=)Y zO0(CrFwg`}aLq*gZj^m`Vb^ByFI_*)-qzz~Zt3V>KulVThk#}_wR}-nx+hMb14Hoi z#wz!xa*lK@*D{W9Fdn|#a{eEXqx{Enc*3{5K3f2l$F_OJIko{N_pa`Jnsi29sUtq4Yw6IVx<=|I!y>+|dB zDqZ34A<$*L6ngf#WE=4LTNyLuKa0;r*+uj)g9)!!rPgD8)`?-A!bW@(b+!(@AYsSF zGqSi7rwnljSEupK{LXod^J|8>b(rq&Oh7}|Uq#)@dCook8}B5(RFf&A1B@@nv)v)H zWzsQR8c)j5`^@0c&~DDS3SxZuDYY2$&Fx-O5^N_!sqY6curJ%``uK?*Y$|$pu<=_k zc_f?d7M%kzC}lZrxpncmC6{z)eS8=h>8shSH=^n4(r9Vh*t)u|Av+rWLCxb`NQ;Hf%`?TdM1azEmLM zj^=+O;PXW6ymDT<1qUk4l@i!Z)L@pj$%83y2$v%6^k&~dFe ze&zBQ<&a5vAA?QHU1d;^LzLk(34a!@Cg9Eok+MI_+Dd5$*MO3*p6qzkkbLcW44#(n zzr#PLE{ic;2(Mj0Nl7zELJ0)!4mtk|T zy6@}Orou-?>pxW{sZ8I_mhO;C=aF@_wvft5BAYRQlEjo$+HL%nJAhhF++J(s80}PB1>);nsL>E{0SNoC!sCj~2De*L>xp)v z{%CATcTPwn`lM9ZlyN)iuz+%@LTW!oEv%PF$_-R;epITmpxByht3T~;Ax#&LD{+}` z7PHgDX|Fez%4Bv!cQQE%fBuBZS*f3ch(#yO#D7&3&!}-Pv#sveJvs~nt^56hvw;Sx zQ`k}GY)Qj(kt>Z%rVzWY*_vDfLgUr*kN%X&TlVw*fQ-vb<%F+hdyMDw!WI6S%aDk= zlrwU8L(M2@NDwUQ2j8&Rjj!H3HM$)xLUH8lk$pti3WS{T3!Q~_TlBN*Yv%*(gyT79 z)I1|VpZ-!@jGg5H<*L7%#K7ntooezNqnmf%e``xuotY%)r;N`t!Ao*x;{h$`&ewSt6k zpfzv)zc09^bKS-m1_0Uk{cfX8Vu0PfiaATKdsr>w;;wzQbzxvi4z}r~_7(W(NUu^r zDCm2ZwwgxoHL6u_yJ0|L-3D2VYcm6T50^K0YgA^DO+mz?#_b-x$_;Wp7M?g*z&qtw z&Q93fO-)R^jLPb2FSW_mDPq0YrAt7sU)vheELPlVabKe6wd_1$8HvaCgLyrci{0RF=r-T4Tia*Cm%%LPc$yhuW*U~>WyV0SWCFp(sop5=?f2Yh7cv9 zr{qN{2>I9^(5UE9L+~6=zQ`m{6U~$@^qo}hQ*ELiuWXU{NM)LqfB>!%%i-i{i3=3> zVj8@q&Zyjy#nJ0ll(B7isy}K%MEdArFD2FW}kxZ17ouKH( z8Dh>AmSx-ME2fp>V_|>yZhX-Sj?yB0IEz;La}=E2%Dz)m4E)hWnw5Ypzss2Dq&Nv# zD90Ub-dB)m;<*`|xC38xc1*ZL5zW@pSVf0aYcpQLCy+ZwG=87$4J6wSA26=Cm+Ud7 z!I{m2iU={5L@*fwX8Xm_A!&(5e!*zY@UM6Fr2jQD5V)U4zgP(2H&sa%^PA#?71?(A zJxSoAF6Hl_uCOAf8q!G=NF|Zm2pHq=Y3$`5KTj;lD(+sj3}TUaX~}#g!wHF6S4Fx# zAFOuS7!|uaxRQ-bV8x*NjTI*PKs@@5`JJ#($Gsc_9EtJqxyiek7JbQhQpnXr_s<7V zd}(~I9D2ZCyS~$)+SpoFia))QHI@$A}hT;)bFrMed39+HJpxQ@Kl(zxkUbShL@6L(&=jxA_5Ze1!Ei5x zZ#5KRm0HXd@Dv4JB?V_&zs~0-l?$Jee2`4R)&c`|^AW|LKINnfPYiU>F!kw8M;%=; z9h9>pm8&(XAVZ=N2VtCYf3~x)bb7`p)~gmNIGBY74R6)a_$$vju2ses3Q)H;r6bc7 z4V_|8UK!Cu2}livAz079O=oKS-Wg*dETi*$Y&-hX|mH@tMrOeZGHN9h!_+Eez|_&n;;4QvgE$&@jdgW)4<#2 zqN)90;U~}nM?B{zzu3{g-)1*2Hl`u~|!PEU=ejNcvnhyWRh(z1?HSTd?rA4eiWjfc;Swn>i@u$3k zjT5O|UCN`B346oYsiRO^U22s!Pzo3Qt|TiyAEMqs@@f)9mHY6F_PS>*uz1~ga#&2~ zNZMsKlWX-Se7UoQoKgzJ>$XMS@Bh`6jHiNh@HedH2wvy5T7@LnIEgYUQ%b2#T8zkC zr^CQ;>2*CFYZ6D*>(BZHvyzh=P0qJ|P!IH_f|s&v{Gn6prElLx|w{^^aHGO{x(_5I7n z>tn^-GLxuQ7msIVKgF&m{OBLsL%*fra4=VgBr+8Y{6`Fy{maD8=V69%vX1fuYy3%g zKEFq$DExiA51Q4g$f;TUKaoPfJ}w6CLE1UL01dAKEV+0hP;m|i5o_A|h`xOf2h9IZ zsldvTLbXz3Wv_=_p(6pWbPFh_xW0k3)x$fFoiFTJqON=V(*?;Uit5BsX^(R^udM3v zNB}DIfr9#X$tM2sG<-iy+^E*8zWeZ6)W+J!P{q~8m8$`EZ@XD`bH+5z5DDac>1;3L zf;;(zGwYf>onljik*|Wd^4U#a-D>)g`hsx2o#B~e#o)#c=q+mgdZT``o{wj-y5$Ax zdl*7ODn|I!Pbg*Y&4(3CN|+pzevik2)MHH1btu@~ou; zr!6j$JV#R)sk+C0_%}7`ePapd7p+#K%?Of7s{B~VcTq$uXAL9C^w`?C%~f7A*h4vG zbFS0V1Kl1gx{jiP2&bAj_;&5@tF1v8A{LYQW0-s%oLrzL(1=59Q5BwQhQ73xva!^LA7=cr!lTLhQZF-5RTe_ z0fxKdEk7o9?aa+5{3kr4>)qGNCkEsygzrYoPNWQhg&1MA1kgxFaj zJTh8%dVKzGP`P1{@j**TbTAIRK`6g|p!o>E7_4R{J%iKwTh4K*z59s9N^8*mwK_Y{ ztLEc(cw8lU)jAnnByemK3)Mf*+;PIt#&V~X)68k*x)Dn+=x#&!)2KXZ#IGzZP`1!-$d*syeU<-ISwz+ESv%d0Q6~v8lg_?V7$il=5TUAv8 zlts!wakuw+YBh?G_x*9=OMe8&W;Y%|cR-Ubs<}*D!f}hytkbdd5ML1I&>E@ofF3u} zFlIJFRT(&Sv^TA@}|N z&W-=&8LuZ%>nCNh5J9VdqS`IKTBnh{zmU3Qi$TJ(w4~fMs8H` zhm&5Z@uJJ_c#K3fZ@fxTq6ovpk!lf{6c42L?39ykw%*T0`WxQc+8E^Jn&--ZGnMja zQ87I}TdDQYx3zvfwqo^C9nIYmCW+o~MumI55@P6GR?EunRSnCTDJ5vJ}!tNvq*mubprCG*i`pQ-3Y z$W>EYaeed_U%b+m-{g>JHc(Rss<1OZYXHgHjxmRUiMuw9;*!dkgWgBmR> zLX;Yqb%O>|W|^;)(g6)0#zXq~ztK2|Y@?gR8rDP6e+Sx_)S1BCW|V;#kJEMKZqX3n znwnQunM?L+U8u&JAd*D5(`+oj!f=$N#^GMqTVcGjgU?ZHou3}(O&`vn*n@?alG3DFa2>Da;ZXg0U)p)*O`f_H)GJqpu77}$;J)3JjJVf7W1(Fr9ZY5 z!OYwkK3yX{65o|tF~C0-Y1q&j&8MlAQy_KSkbJ2ASIXLNLVrQ->-C&Y&7ms~Yi(Y1 zSgi6n(9irDdA~KU)r>i|T{CW_v|$6)vD|O8FG1XiZsB!QwbB8fewo+hzRJFHDF$e& zMsqI{kIn3yKwRmRlksyLN16A1phvxBGunK0*7=b3{ZCYM{3q`3s@N<>Em{QL{S3+&S(AyPJ^&?i-T!_T&3jMh2={I zTvZ2&o+cuJoe3zELw&pKDWm)%`NDlYDsJv5ocK}^^pWK18uj~+{Q66Q&2hvNAwYWF z7iG*k{j`>M)r7~vw)!teOh|C>Vv|h=SE%G>%0RZwq1sP#oS^bG9<*Y`P*hxugB{xJ zN1!$JdAMb+Asx+E2spOXJFH({xkwswcYA?R1Oj2~*{0yw}luuUv1-fS8 zCQ(j}^S(M8QlM$CnjC|rDgQWim?UIqe75Wr;>N-!9+NKPlQbu6ck&04;F_YGf-zjK zR4G?)Ju2kovccPstug#f`npP>m@3HaOgx|{eM2jo{m$fh5^1{G*jvbepz_z!S!DA9 z+I7Yj;*SZTu7tk>g5Fi_qdDHu*aJ$O{V?yiX2tGYN@BjJqK7wekquaFt+z1t5z$`; zdAqNxTss(ceZKSvhPxf2+=t%*Y{~K2{E%3I5j4^JBE`5!4<}7;qII|L^9TG3ECW4V z0ghs_BAF*&Ej|$wqswBmsgp?yJ6(_hcv&U$H@SFbg9}Bfixmho3sGt30Ab26OeLOo ztq(z(r-1OFM63#!JNrKjLD|QiFi?22lI>1fgv;{eG25wpJJaEKa3>kB)q+PSp?J3Y z()oRDgAAB^%u<()fH?zcq*PHv{9H{$AiSor`FqK`%?>U--SJ^jo% z`iEDS6>HF}=)oegn6I-Em!4yZ$q|S%{+Ots?=QiUu93h+xCo@rH@v|Kb*U;p%CIS@ z$EfF~dP}_fw;!*5oSIxo)!LbNn7pw)PgvEf$4d-1@kwhieQjnzmiZB*$No=u_*yb!V-3C))h`#qgfoqBhR10uW7P#oYk3B9l zECOD$Hu`%|fO2c`^AiLG`gEO|I5U5vBBzj9}N{uXMZA89Srsn?FPMrc?Mqt5zsU z)`;=J#qqZ%U-O+nL%L1^=rvuFl%0kGfnc=F=U)mh!qpkc@`Jp*gtkqSV8?X^eGgGy@o55|-VHR`+5K;PKaUjP~$kmaD_OX87g{J>dzJefpcdSLZ9a zTDE_KqD|#2#`fJNUL-ww$vX3l%trvc!_ZSS>y--{uCi(+FZ2g7I+3vD@u$)f@wv~wUlXHkBIi|69;o202){qjWWoC& z=uqn<$v6SXX2;7zqYVqzFl$MKZ##}>CCPn1@7V}t5D46*OiAhH2dI6?$y}7xx6yA0 z8h+snvs1TLrehX)GU7O!Z_~ZI%7y(wS-d;?CrRTBS<>zDd&+8}i-#Jhsq&2woNJl@ zr{K;*GlB@GCHfmDm~d=Q!_67gt0C z5owSX5D_G$L{d`e?nYW#STsm?OLrsPu>_PBkPd0-UNnp5KNovH`+4J>_xwMckLSz2 zuC*_n&fl10j&YBBj5!7lq-lrGwI|tJ^@Y&vMZ_N*4|y&nrJ-8O2mQ7eXWNAa@CoYU@B03|=e0swP3KjVZ8YE}r>umn`?-@=N5WId6 zmZn3NJkoDTVm4fCG_ye(?jzT1#92KZp;cbs+~Ro?uGe-Y4!fMm%wx2L8z(V@eT_G6 zrrosP-79#2_L!5Elf!&6N|$1D!AW=Fh0K;2`7Xz$;&;3*N8SthnByClu1B%n=~xA4 zaB|+bun6w6Q?yoIgGC0NLPk(sp!2&H+mN?&oYeUt*7lhw&V;`b*%~i5-6B&N+G;0tryXZ?Hxh!UOg(su+gD> z0nPzKrd-fZ38e1-kPCQx%*8Ohz~ZzXN+Ey1BU@?_So)xQ-|dXeUspzA55S?^zk{}Zktk> z1RDLovo1uL9qu0YNCNUlCjl59T4sh&09sq>s~>M?79F5YADvan-@S{JV71->1%Ye& zvx_uRePpd+q@zBQb_BWdF{^5*+#hEQJA>PrHmSq+Up=RL@PmTg{Na}}jbvRF$r)Qg zYgf$*EEg7BK~wH`CmD~yV@A)fI(4kHe@=fJ%MgSqumVO1zA#JSEJ(h|X(pYUShrOc zS}wTQ?9fIJ!BK5BsltXZRT{v82vDNkQLO#mp zv=88$V!>S@R`UJd?iVuJvp+a)9Qr7~k4$1RZYj`12m{598pBySG{(Vpk$xV<(qEo@ z4p6KIZy8?|UQ4cq!d`P(e{Km^ID2(47~2#OY9vpQWoLjP#N1udwBI+Ex=FSRE>cBGvN0@sC;w_YMMB#5TEVCIoFptC%( zKIkS7e9*Gi&e2PS3cJ6G1m5pw95FOLVE@WXiO^)iKB*b-cAy<~3ny;~u15a!X6%SI z1ZEd5s_7M@BP%m#>ZMB>dLK(LXCNiaFKk9IF!(M85yNTG(Y?E(Gd_=P;X8@DV?~6i zuC?jI%kh#P5mw(sT;o$SKb_&o?~6@3J>jy{gz38X7&f)Zr;dwVtt{2CXCFSNA7^=K z#1I?%xCAQrKs7bnapa{YH`J%?P2~A=YRSU{*i>T}Y&Xp|${+N4_d_S-)c7*J0u18= zN)a_KC-;C+9z1)hv0`b}QWWP1>AvcAKAhxG15u*Q*mq{bCW|NY8s33Hnk}W4rn~)& zN1qFO#X`tq5J{K_w8ikC-Gof{gb96S@Aj9-Rk>vvP)c1qubs_zVsY5?kE7Y#K9pl& zQ5rm(@;oQdCH3N?CslfDM8L^N^kk?6JVTE!QEDrBa7V$T`ixXFOgtEmn_lslL6T1` zf&ODQ@XtymGSq3}%eeg;dn6PRd^`6aQz!840DJ6Q>=L_@ z2~YUykx;&{7sKP0#O03Daxz6GPKO4tOc+`uVf6zvC!b4_<7I#z0@wtvK`SYW?I%!U zS+XwS=Fc|NKWsm!7SpyPN6~f#eMbq+QX?QsbP`c_7VFZ zhnYLOfh|+;F}5seO?k^mo+m!YE7V#)UM}idO5=mzJ4x%Lo5P6ecTvV9hQ5TDn1xMX zhX_mh>J|Nv3c|{J+{Kz^wp73Lra6r8WD0fX1w*YtCKlR&NwAWc+3T2XT8Zcs3f!Bw zLg|b~Qv7kU$t@D3j|WSDxBbJ#1|}%%1)E`*bF6-#RLm8zYP~cC&#NoH9>uuST51Vn zm5y4@1fTsN>M8sD0t?vjOaO%U*WWzBRi*-v4;!*iE|u~+s+LsyEsM@;((@sH_7x;F9M7xZg6|lfMZQ?L3Ra?6)834b!xQKzogbJ-C*Yp2bagwHA z)>EN}Op$c&708XflKWKl7}%gTj9mg$t`yBq*@X5*?QwO+r`bzE%%jT#RAj6)j&6Uv)ECHr78!m*m1vI!>o>zH_M8`%&{f7rVowLqbD;qquRbV#` zHc8fLb`k@bmpnO2VfogapCv*iSs&T*jBwJ>2*solI)rA#YQMpsb_`1WaHG@+O>ncy zB*Pti?lwZn@cnWL6eVnvkcUMW4x&~{7hv-HWr;=@?PGjM7N0>E(&?0pOt2sb;@xAe z{e3E1gZN3fp}b$!WL4R)@G`zc-CgLVLSu(Oib2NCllwlVW~GV6*idA zO~VCp$8AiCnfyL{=C3EMo;iINAJ*KE7-m++GGqLp9tFHLW)JBtG-v7^MJIzDKcf%l z_pCR;1TgH;bTps_i)C$l3W=TK;m8j6jBK?uto=zDi6tqv*tuMKc#US$G56Lk)z?RA zN*dEJn?^Q-wOU2qk6PkvG%lO!Yfd3m&WNWMPD>7(65DZqDSR&6B?CXjl2u$Y{gn*r ziirRm4LMJ!b-Y_cUmte{6hB;Kh^^>XofR-9T z3Hw=;F|Tx+j^dqp{l^77_V7Z)tviEe31cA`qdQA>@jt!38NE9H7C!0aD#3gmoe8x) zI5n$!|1PMjbrw^M0G4?#>&s!+)hjve$yEQ1V5Vq7m(3rTMhoO_t5f*u^%l>(FQ#j> z?LW%rt;j{-@9cdS7W3&9kW)2WV2apIem*lmzqN&@Jie3ZKryE>UP2kw{NV@bgi6d* z66UYzPbzilx#ZSK^y1Uh<~-7odZq9)@y$=ocUz>C#M_3d6J@_}yfE$2t=fGX`rKUsFdz{NrHW+_Os$*RSU0Ai(4((WTdk4D7 z*oJ#`KT4406!`%O4d&58)WLaFS&<@u0!nLVhxm646S}1yZV>q(i>&DdJ(fn zWNfp1FSIDCY)lql*LT__u?XiMF$8qW-=y+ykrRX(;_N_5`2;UNR0xqNpC0YA@qW8N z-j8m7s8%bp-4J*oo@MZ})#xtNKgw6V{vuQ4u)xfT^NP}dF#3+=k6OKc>v!3Yq_e-| zWxu>o<(Wii>J-pax055~ zfqV1GP zFN5i1*P)ep5WpB_W_Pa?awVTSXLjEua)f!1(UMwgJK<5RmVy_hH?s@$#Rq+@e8t^| z4wmn|{9J!4qoc#i4F^;`4pGZ(;#KpllWg)k7~aQPg!BJdkx0LzJ8qHnSU7)(OPQY` zhpAdK!-myiOH<=Wq2)2)T`V?+tZ->#$(Ur)FCW~Ha_2nynI3`OLr}^St}VYd0lwDK z_~&l3l^CB9de24AC1lFuWbv%3rxwc1>sU54JmEom{YGiFDc>qPj@*8}kXP7aGaSdL zC;KKaD5>FGvhCs}!GI4QoudA>HM;vrx2woQaq`=*w1(?6x#*8n7ng)>%Ty6I3TdWTr~8%gAO5p!7y9>F-T$^7>P55Uhcm9 z4xMiP*@X$??JU+IiDhgn^L1tg-{y>OgR(7xIKDBI|9pH4^7R%+rVI5xJwkSpguj&Q z21>MQ2wX5pb5XYZcz(&J^E$q6-u;ccLa%Il>^)M-W^W>+auVGVYC4o%6f$y#3#-=N z$v1kK1+C=x_$A4qdb^;dS{cUELk|!6;=J#3;b{L03THW3NMtj7n$*q$+RWgKyktJ+L#}Hh>XQsezDI#tg+W4mq90TfzgVpnwU=-}v5xSpQ63#n zUPb$O)l>jpzrR@YwptIp#O3^LLxfyL&BuC!_Fo=JpP8W@@%TDC{CJ zz?h_fWy;lI>NILIj_D|X`wE&kdS)x58e<ygNah|SA^Yeu&EkJqGJUj4(P@B>(Adj)bibr`xuK8 zou1H!r#{>&E|@S`xYp$uOv}>s(9lpHVyC+X^M@O0(5^OT*E0nS=V4?|xXo&bKP{ar z$95;*wR{6tx&H26#05FKQBX0ooINVSDk^Av^oYDzyWtW0_Q&IJLlnbquIw|bBcoJ; za_1IPx&zBcJhP_!@UT6tjcu@*)*(rWlV2aOwXU;GqYFfdFi<%15bMP$C*F4P0 zvc)#4zVf4Homt58MkW1pmOEu^PM!NqSf6da@}CDsFkj7{6ni@`Tc(q+XVIZLf~TqI zr~K~6E)|To6bC#f#9o*^D(&C-wacwm?2fenT_=Cl>tas(Zs|>W=KLPo=3_oFKc4-x zS60Yk7-#PjX>SCZp6@Kpne3H0aTyYf$)?iA4&1NuRCI+KwucthN^a7pGS5xfu&1Q+ zqh8kP;oB|u6y^Q6X?p&h57>X*d5p3Mi7$+wohEgtMqCSZ(jU=^lbDc+Ogz0JeH6G+9K6S5_3Tg)b$h;-Fp*27fN7u2 zEFh{ybGeqe6zeX5yO>t#5H!A|1NC4_QK}VeC`#$gE6(}46D6?^nos#o*I7~OFSZBb z(N!iryJCHl_vot8`6{XLmqc)E;ag!{H}1`iJVqIUzAKF0@m=A*-xY6P*6}N^degj* zqY`27v1T_)A6Khb`ZM(fG*lZ$-i%D5egLO7E>`1LHOO^KRaA*>`n3P;Ad}KCu)5_m zr@B(QkMD9HPB1}X-uT*mGC$(s7Cc3P$iF-Co(#eXN-UE9CqKWST;EIGO~?J0+i8Z4 z8PEhj6}>in7vFo)#iqNT;^r~oUG*ZN0S`2?&`&lHaXLRLbc&actSobH%AE(DaTa=Z zXixJd;jOASmcwN|bFNLJnVPW#SK0bI0AM0vtbDzE>yg6iG5Uq+*F+w^_%ZBiyHL>8 z%iIbS({!gW2GGwGjh_>{I$GUh3>IQiSnfWRS9h)vS~35sE1-1U_jAcqqmJ0L7awQq zS={`uSp0h)i3P=RN$O$QH)Wze8fsS)>7^sb$sPggtOf?{UUQod9>ASqtvVJP#u|)i zdR62OPc5gJPPMVeM=xZjYX<|6h#Ob=wYIx6uhs__NgOa$7DaSNBJ5aKs8t)2-JpI~FJ~_F&MImAEAwV${X^RZ12>1Hc5TY5NXeaKk~uMo z#;sS#;d=903)?QwW;933{F_`Ir)!swzQv4q>?FzW%obkxi}JTv&l0~M`G8GMPeI_k zV(}*4i+)Cxv2*!7ZZ4saYk!UI24Q4u#z~4rOLR2GyI1Y6)Oh0=)V(PhYudyu50G;h z93_v=&v?{cJCQuz<_ScW=*rjDLSB)qwDjZQV}EA40r~EAx87x~u-t|=Q_?%$_qfjT zO;OVydynJ-CMO~E!6BiSqcPHl1)bqf0y``m3(*JU%b;^ZHhV5T1`K#&NXZ0&)Ywvd zu;uk^p=0K)qP7~--&cjWUFG__W)4;fI`t!2OoYrSZ5cy~W0uk54LpZ*<8+ zQ=0`n&BkP#E_}`%J1!?jDhzrlMIy)*o2~==s)|B7sy|Nr!rvt{x*H#q9q^S(LpxRZ zCpF(pwY21pi}aXNNwRwwTfU6mc)G{o1)3m1f=UJezy$N(hSX|A6b})d-7}~VyfJmBneR| zX%>!VPgJjW^=Vtn!G5~XY;>HS@W!aJn!Uz1<>;Hmz_&OE&oz#0m|6J&+-Qe+j~(Xl zfeza)*1G%vt9Yw8)d_j*<+#{L-pMdKcC19*j;@sXcMIWBl*goj7Lar_ba;ehQ~rs^ zfO_K(;rdVK%@vK6>zg}WN|Z7$(;x3l+)KtxQ4EN?Xk>N!Wy`EojOUSh9`JJNC@6bh z5q?s7U+$>2No#@h-GvP3S@?RIBSTgC2 zD~WvV@SFg~S#LeR%n)fa-)M}MC!I>wlmN4I*$9%9icGWLNsLb@KFVqJUaK(dlB+k> zAKlbILW}N-(s>Sj=A_p~H}eLySgpB-N!@N`DU1NdPRi@Ky1ZF_y6dr*lAeLK@v|zP zO7>c4Gmb{Zmj^@BdaN0WzCEWs9jmXlyC95bZ_hI>sPhxh(EDG4P_~Bx$IDE6EaN_%&7GQ@$Yjkmm^cDbS~^%5)-)P z@}J0Q)m=l!d5i=B$X!qoZ32ycEmjZT9FyFz^$aHH;kC?$fza$!Ze#MKR(bk_kwmUc zOcaFVWV!ssAJpr&%H9gkTeu&5euLL6brvCv3ZZY?-oRzuh10#Rx2~I#l$kE6-Wn%N zS1H@63Y-Jd_G3Z8OPoEL_5~fud|7xANFF7PSxs^bMi;8zQ31w{M0_As&y?6$bKDRajtP>l| zKD;Zx_)Iix>Nrf?K=S$1cY2dq&aWFya67}vsGp3qaHB(RLPZ!_qfJ1it_{zk^JUpL zNYhh=$I!`ZzRf%QH#v_%`q;RxxbVz2)Fz!pAE|biFLXRL6tg1_fBDczDwz2T_ry8n zj3hyyic4Y3k8}%2(aT0nGJ0#VdJ72huhjW^E1J$n6dC;?x}}et^Zk~-aa*^hZkvYG zu7uot?2e&sJMlqMkIg(=8J##O9|F(8NfdV$QK#Uk(_L%P4*hD!$<4EO1}&dwidwPYk&s+f#MdqT~! zF_9UFdyL=cxMR-X?WL^tQ2O&ny9nrAwYpgC%APMbSQ{Z7!mHla;ZF0MyU&I@e+BD$ zoORssD`y2#(uF!QdXTd`Y3m%op%S$p-hHVVMVR)ax8Gq{*MP+@ua#z^ME*}0*j@AUT!!Z`?`(%5D zCQ^#ErPJ<<{$1sth!?es?C97{=QOlGSTB6tDczZ?=G|PYx5!3vIU)%kFdx0kXv*cX zK5**%`}KtRY3u`Hd_m0>(6tU*{fi}R?gN)|pN)v;7Mjb+_C{U8f~e{FeafFtzwLbk zb$!FRvJuKX=fu`cM(z=Y?@zfCxkA4Kl!(Rbg8XUg#AQeUiXR1TC$V~+71f#|^~e(OAnib}e3>^nRbpPz&zNzXW(QzqSjO{r-*~W!gyT%sMDSR$=5tHq zjeOmo^Y4kdlV38a0fB|VLt>R`{dhiO-}7v5V9PIe>0(W9>o?nfJ#}5;kgv0OJnMHv zn~PpB-D~tSO;P=wRc;*q1S#arTl@W@v9Cp?Zu3h4ahv7`N7&xA9oZ!?`Y+u@bLH%W z%~F056{_mW&DL+*Gn&!AGnyM znpTk8nGH8(KTa=GzaUY41fnJa*!u6UnNuP26%%)2OXMbFZSqSdrrb3#+4GY~OO#T% zEgoOy9665gA2iRLRpk!>uITGto&_f9vCGQ9dqaQyKM~qO3{?5Y^{Q7|lNLJa>=7qP z`J#Id@tDHr?uk)(w2GAW4qfj)>#Va@FPp0mig~6;IAAf}NWHGMYwn?hL>o@_<0L!& z_-*(hg@EG|%cRlHea~>B9#GJ$$SI-B`77d=jvpfyZWKGOyce!q>4a?}s2o%3OR3CTwMqItHx`}SL*nlY8MfO@ zu49t(xdj%fG8G=)Y-9^L+3>w_3xOYAVa|KrG-WRO(`K5X28w3ybym6gNxV?YBKZ`CeVFx4@G%{UKQ43>4LmSw1Ys5M5) z#5!H;rX0QbmQ|UsB9kMX%!wy?_|_3S=~@EB;@7Btk92E45^atbg)G)oQA>UvrNsE? zVhm5u-~ThCnZR%P2CTml%9!@0v(i%FrITwnkK?xQ%R2Cqiu`1D@i_xbHmOUuNl1R# zdDs1j{B;jCy2}C#yPN#W_tpls@(**LKB~3!vqbGZXUFhiHG4AW1eq#iT|6zU?r5LDslydt7UNY<9;g7Lqk&KYKY$=w6pfc(Xzi5F7gIMm|muFPui zqan8K6bSiGgM1Z7xhEmNCBzF1r!pkO$Y^FZ9J9_c6YakRpv>2@g3+pPD@@DI2YQ+# zNG3|DK){#KU>erQ_01Jqy&gJ(>io@W%O#d)lQiNCM@O*Hv3}4WasML4 zd419oPa#jnf(bjk0~rsAt5jFGu2xzu%arnyYMue|aD`30C{c zZp^2^m=}37LXz)xj(+t1=7%GjE0--*VAcHfU9>Zxpf703p;;&iQnh#y-tiejnF6j zeGZ188axBfY4qUHBd;sAaQq*U5*Vx@cU5ns zzL?oK$#iy`r?ZcO>`n$uVQBpE!QA1Cm)(vt*%HJBppVN~hCEDjqD(KL5=#;^>GfmY zNC?oX9K=I0Vi9s$1%p-Ie}WC;KX^FKm^ysJ#CzZ|YJ+1+#^Vb)0ffQI%>!j4~VRs4_q1(!Ws4@tqL;5SHH^ zXZ_~0CRdr^R;R=1)<8pjQSq12CQU+N=z8~`)>dtljtR5hscWE9>QITes#cQ&cA^9g z+6d!PxMid7j%4^kRgdyul{hnK-?_%oeG^VPg5vPiW+ISTyAh9CJgoEVkT6o0$o$+Y z@8c$c$*7Hf7cx6i^_{1TrcSG)0>7&a(P4AVL7id5S?X+ud@141-5?)V_c>N~k`D$; z1Sxr3<7RcFi82`o`AX6?F-c6Hpf#A-{f4DeO3x6t-Bi9 z5Z$6x{9A%~oevx32E*RpZ0`iFwP-H?>{$a@)L49KMJhx8N1HyH)u!^j?|D`!O%|*g zN@sCVb8H|jkEPMF&J5TayF;@t6fIs%UL{WWMwP+WWsGu{-p4%GKiyj}mU|Z>%-7x? zd+qmy$K$fTkwAQNv^jvnoOg%4dYXj!R(&qH7oGPPMYUQy_@s8_J!uAgZ@`inizR|N0E*&A@Ejb}^=P@N)Nc4jP7rd`o5xKh4f+<&+* zZOka2C4#owX=Oq-lBfF0ToZpSHd(<{f7r*0R=jASRILIDjNp*3TER<8V##_i+uzTM z2||#-7FVaLdZD}!xq~3j%uAjUqO0W^ZzT zEf=`YV2of^W@_sK=ylJP`ZeORQz=9>5TJN+@hwhWZi$MA8z?UBhI|3Ne7t_|!ThZdyZ1Frqhs=NM^9Rw6WWOsf0c@@z zfv=bMSM(uy#FP}nOj8xB{dlRRoV&VO{Z^3LA&*}^^Fk@})zt;uc72d?@Kd?{T5Mr+ zkJ69iI;%h6eXCPm@emfD#-sKST#EgrE^*L(BJ#O3@rS+y?8!=_5n7|3-2_fPbco{d z7WU6lW@!Q#`Pj4BrG$VKKj>&95a7RLrw&BZfV`a7*{aPi3z(1O%9+s=D)ybJyom{a zMJHl>*!jWz>Ax1+s`J17wDB#@YCO`zNRU=5A@*xit4F6kB>C(I)fEv!(V$Md=A4}5 zHZtWpjCCaho^C)9D!!7(z{Qb|Dh;jOIIda1OJkTh<}mp+U_Vo`uV^@Zj5yIuJK^`< zey%TUq>x!d;Qclp*66}H2S!7;j`GkDJ7X+Z*qwdatq!P6D?XkK; z@Zg8E^Q5X(aGk@_`8m-ceAu`iP=4U$q(N60ynIw@7$rf0(!IRCkvq@c;u65Tzh5R) z4IbTl$CBXu5b4km@b;Kbf9N4r>(E;$r8W4qi`ZewSk*tPHM6VNCl^S4e!(zvUyq2z zC=KN)TEc5TTKphCs9*;%i&3W8ooFw#Lp%^*#ri?-zC(KMXz8;V@&_;TC-3d2w$2g@ zxPK&oxlld7baV%{`*t}QY_S<(2Dw5Msl@!d@4TOb6k%wI8k8vh7LpbiC^Innfo6Ud^o1hD$yXNgf=;_g8v7ycmKwEVd72>2c5@pMyE{ zdW67--^E>}yVL7MhYqCQPg*W2Y% zs6y)l)G`z>5?;xIza$NoM@UO(1Rs0+`1g zG?Zz?YA6V;u<*#=1Dh#HJN{?2+@GzpB1R8I^cR>A!Z~EW^Zt7~9dF?~a1iw4Kw8oW zX(i;}VYrvaFoTe5g$0R{E;3N%)xt7v3sC$pe!5`@N_B8q7wWczfhr-EvkhihBscgh z0c4Lj40f>mt+b>O(n`#K$|A7JFmrVW=TR+UwMPi87{8QR0GrX^+xllUNl>*E)PolY zzy(4$s|XbfQgl*?0tcZ~45XEWkQR&VT{RRb^?DK@R|A9rMwP)%>QmIUAHM`Tyg$-q zj8N%T>j}iwM=ZC9r{B)kj`YR+`|V+vg5__em4uKMo9tUXk3K-;-F^1tBVx5D2(2hS zOKk<4sc{+qXSMj}=n&j5bXb0VP=s(0ck3>0{$36hU>OS3fV9{V($Y^uDu0v!)Dt0B zgnh#Xwk0vYd9@f+OK2|TNBlylwC{W=!UZ9g>!525bPc>uKlayQ4Ep*(Z>7bKkd|@U z?bWq`gRA=+u^Q{YtR*c_lH=lkRzujk=b)W5u(}B0Anq2@bKp!x0Y5BP0iXsu`GbjgIQLzfV5m1s%^?z1F__15d1TFv&&h6cjN@WpW?pc`;&-`zy~7p(sK7Md#32^nnNXs78<=A|>fvMa zekA^Boa5SiKkB%?kxv`}f!(~qPg&imT4QKsKNbZSW^MGw&r*=@h>Gaq?kofF?E{9w z>w{O2=C#!i0urVsC%@#U5gm@=R>1OIM__Q;FVSfZJNz{|o8NOY5E+lh$6fZIZ~YT? zQ>@ir$%L=fP!v3XJAYv=C$-)@nt-z6{hV27pwTfRNs8*lP(nWK>Oxz<66pWGKAan3 z;`Nr|kR7!rBQeIweBj%UEWaS4{m}&f_b88alq_CON>guC0UwD?&gb$$b3w*)u!hpy z<-6m-$gk7I4)o*n)C190_v6j6)0KGL(U07Iw0z4w@35x#M<*EhmZz@XcR4z=D?$Y} zewT(j_mmI;v!P%y04=+S@x1`Ah6H!3%RfyW%Iqre@a#C(xe5n4_cVooEe?Rk{{^j0 z_G|rVcTwDB%-AM@ZSU&iMHr~We1r=F|F6PkVj=}x+JCNDJ|f`#?-u3O{{CfA9)tk< zA%Ks)tVfV!_nA$$fj4zH4*^N$)axa^)JrLb$0^4E84z`u_B1&pT4AvK_chTWTu*QwtD8z7 z{(-|!K9n0E-g*{!L(|rp>fA%~l&eN>HXRhNjwg?es+nrgtP`a>yBw5*GFPiF893?I z5*Ap_1>ZP4t_^rrC;?(9d*0ESnE!;CdK}d2$i1?{Wzhe?D?Z2A5b&Fau*xJcpO*XY z4OzN0aD3-oIrt=zoaQ=1Gs*-1|M8UpJ+9e{7o@i^)|W+=ZPcv`jR07-=B=j;LTaWx zx&42y=m+(60kXTBrHKRT0`=a#{*XzB_|Z3l;h!T+0E-s9j+ZL;jT+Ok$E;BHPTL1N z!~8Fz0%HI3lPi}akXLDrSirL)^&k=eHq{t|g+9g5cx;Qr*3=VAkCSa899o%BC}h_F zOh$Q^#7_6>LwANu%kbJ7h+$uUTE|-K3=8Q?WTV!oHg&;`Pxp&L#Ivh(_p@JOgA1NX zUTy+wA zEDt}o52LM`q3H&8_l@8N1={3uN{<%Ek3qhJXPP$4-Pj*~M&B<~$VIXpg96h5+NI;Z zOMO!_5aE6P`t!jp_F+bsMqr;+Gk}h3!57|QZ-58l^WH_c#ypoUM^K{$^7qKze6rzi zL?>Vf6u3SW#^3k#Cx(K@r0w;#OT5HTU`vEk-#ADOq7(TM4{8YWe<*P8f4xFUn>ID5 z{-(CbFwM@Q6CkJ2sYBS-H6A|fStpxOPfl}hFdm&Z7%jROK;iMdZr&pV6mb-0OxIWh z3}uTOg8G54a-e}hnz?W-_?HDi>>{j$ik^-2jv-YqEpE>*u#cB7Di9S8X!=ah(x%?+ zta55~Na3qyUFp;uWt0ePv{11?w}oG&Ob5fIYnUfwjzcj|N?zcx`M&p=1pqKRPq-EOYn83O)*b5{0ygCR;o zQmNhei4F{WF!9bX!8;c&;8^Ap-d?fq5tadXycY^d$mfmIkN|e;V-vUh0>9(7np_Ha zS=$)ho5lw{ziZbKjBmcg3#Nvcjuv2}V3KcFW>TJvdPU&UKIXKZ+kR6}hH3ibj`=vzkhVlNfi2wZ7bO5*#=O}bQ zXe1^;nc}cog5`tTWUD(Ea)R)7U|i8+;}eeV4Pk1u1K_kWL2+z?fM@fR9E6DO0hkk{ z0H$};+uj4sOYATe0{Ws~XQ9NTrk-SmBjU!2!B|b6zLA*x0-O9}CNL%?d>zz?qRH>N zg7zbWxsrrO+mnj;`y3YI?Z98l*O)6$bX=dUQ4GeuB_7Ilu&YP%vC%`|)^!H;8F-VQ{m^c+kX|wGQ5-0L0cmQeHk#>papIel zZ9rR65<|)TQF#A?|N5D?Q4}Ij7!;|sNoO@gDZqxo-W9}vzAZ-Rc`~WNpp@1C2(3Gn zrw&Tw0*8fxqmsXpBVyL!jbI@VBY(}{nviegOOD1Oi%Dw!;9kkRa5m}{H?dg^i@s2? zR>QE71jgADR)d0(d+#F^-yB(GI6h8=K)}^d6hEr(f?#4UvjW!RQ=-Y~^3PUnp(X%U zQEb5wRua$FRR5ykJ;=G!`oIv2D)_mYuTmg)_sBK| zxe1yb5!oBZs7RP*Cm`F!5PI~F3kHXW>h7Bz#Z~<$M?k#wDit#KW2V7gJ8#rDn27Cp z5|?$UOkpPFK3=IctvoU@rz{1UDPf2n4{*IfYy*2gL7d_1f+=k(@lHnr2Smzd^4#X@ z*9X1K=IX*rW+L??@yo-xl7=ApbGn`#!Fwvdi+|icGQ(UH?gvG<6Lr&DFPWx-<{!E% zTd&B>%}6CKG@Z^yHB-oymUmaVP~kp%v5r0aVqo6YV8DAg$~S`k)_2sDYw^xK3kA8Y zyc{?;*9y}CzR)3Z2to*bBVBir>~MKN9w=n!F>kf~eFrb(X$1CI!_pv8vL=c10M4UX z(Fri*a=Vw2nm#Wy>qi{vs*m3T3^azXR5JrE4uK1xUMUC|HF8opuRs+A`BcLcqvdwfQ zZ(|u}=(vC(7XlRhP=%qm1C4Uhjm$K|khg4aJoAH+dh;>Bu1E~B@uN%2lB>A)jnMyy z+;0=AA3?HW_l>I!$mTA3=3?5+(=>02&TS1g#YMk5z$$yV{jK9eTAI=abcjUi9L9sk zq;in)Ob)NdWN&gx_l42*OSPc{z?<&(iD0<_SR-Vdwh#kg7uqq1nTgv4QGvs~zC0XQ zAIXm>s*r4x*pOtiq@-;F1k&4m2Vw?x-Kt32c3va7BProL8@SYDO9ZhQ=vprZ?9FH@ zt5C>~))(w=7WMlSQ`Aja0sKl8M1_O7rC+Bzh8jtZ=CZi=Mo%*7xT^2Ts=WrfR``#u z0p18BE6|3p38(2dn1M$i2U;>abMztqR z#*|Be&OI5YZ!kcMulr;{sE~n#n{y!xAAlN^Ebk4m{{S7wTtmpK?HSltvi|v4@+nD0 zp@ZS*@-1QVcMD7yY4N^l^{$@{7Vyxe(f7WI7^UfCsPT5R&Qb|#f12yke$bv3@2r7b z5JJw7_8AY>Naa^J0U}>MJgX4|Psk?wZ*JsJ+6*a46ORC9#e!tB{udDGF-fy?RsaRY zE3Yu(5DY1dvKDc+heyjlT4!U#)h_C#nd?vxa8|}_L|je<*^{Pj{Xk(Ud6-xd!EKy{ z?A)%@l7xqDl4kHNR1J_rLO<&<&>>63p=z&WsbfQ@krDWL$pPn)BtTNReg6Rn)@`xS z5WsNgxniHCATZpcTpa0JdhZfYE{I6k1|UJbTA>7cOk31vaWX8jvOqcboPBE|0E{Bm z1G|57>#Pt~4y0Kb(Qw2xgy(jucOKtj!dTZW1hl5(*m0(2P^_g@TV0J=l)+Lhfs`X zq?LoQ2r;g1pa>6BqC?(4y^(wlLKpK=5GT;T9XJI$d+^uJ0NekUW-!dq>@5R@5Fw|x z4oh$OeH#Rw7rn2rS*7dj4dg(FAd~=5+x7a!JEY5jE#%Un{ZYQm*`(Ej*m-{S zMne~HR`wCM0*^!>W~+Ql$lr>p8Sozvvs1x0F8bzy`;t%2`af4-!6-R9e-_{$9z6s1 z0->`Y-1rBtgUyYC7a(X@v(ccE07bZ!5?%WCM;iEAGGLLB4_ZE`)q|i8klX~t7r?#D zxKt$(qe~Yd^DVK-B5WK2&nPW|`-4G+`K7LNK=4BWH#pPv(V~;T4p>?H${n%t#b;ODR{h9e;2BFAK;omgvFe@NT#sjZpW$pgn(Dad= zg%{AUjaW`T5bYK~V7C89YeI_o1>}R_AZl)&OhD65GcGs_$7KMC0r!cwYQE)zv63K8 zzg_V!FQ$zoQuj=)9{MBFrdvo6@gpIh8>H}$^lC%@#h>*N62VGzNe#I zmB~QoSLGrW^U)WowF&wEDG;Vy`6Ip1;Bn>hh?pJK^Kw4`kI5Lhb}ux;?W{U}>eIb~AbsLs zE0cU>cK55wm}Cx%IQkiYIZKQ~0OdoLzds67Y`|O2&D$UKSjlDJq6y1BdCBF_P&fn~ zf$f6uNW&elXV6{oi`D=HUIo*`RRucm--q? zv59UrB-gm~(3djJr8cr4tQXN=#CgqH>TNPFHy{B)!Ku~vt`*K^ z+bPB+Cn@^oX-^-WPuU>o?SIm5#M3gE+`rc7L0DC))!^L~K_RT}h6M`TegORwJQSIC zULylFm%auM7|^7j?8`ZG2Xdqx1*C@WY@P}S&LknSRvfd*N8c_Q1pvGCyD5`nQN2FI z_R#1TuX_|LB+U=XZPjcrS%|UuGUC%5B7xVXOOqS;>108bpmd@D$7kwDlW??6%@^>-4_EW?jyCr5bq z;rVa)`Vh6!SEcu44!^%S+uv(&wFK#%4|SaD7T#&%z?TOor-MvZ7}Vl~-; zZ7|>V?Jj0^KV56Lm8G1nTVg)*tJQv>u;>u`BuRPw(fd7pXAT1f&K>F{r=N zYW(z*^)Q4z6DaJT=Z`v+mVUPK;k`q8i^vhaRn%?00_@(qWKKqMez$@=k=-faAhSVt zZ4|{gi_X8fQ;z(ZbO4t{Mw_m(v6(<%e|^5i{L5l}0_-H*QqI~dF&`1O0A?B;a_F$l z2pp%i67s<0e(rnEjP-~KFi&hB@GM2HPkA;dCx{Qcm9drV^B;*CXVSrI5vWuFTymL@ z$E9FsnOftgLYFshy^#Tnbl+~Rzk~}tcph9Tr(uEf=?^Adyes43icTmR^%gtnpme3c zYpE+DLOxq8Q!I>F=zHq0ait;2D2XXT70>K%lYnkyt|7p*li>fb2Lw&tyC|WZ!YpSU z=fl+%a>x&tV-pZEQBeK`ztU?!Mvz+w7Y0~yWx`w#mII|}18rh2@%i)?KzzqZa|tBkq)*uPW#_vAzfzPSC5R_ETkNdYMnz%Pvvr8B+jy8M%3U^^c>j93_jujDQu{~*Ra=$n!jq=xZKgNG9 zo%HO{G@a#hL{jVfzlufvbJF-|IOl50Oy>pTYBd-@JvgY4wDkY6_m*K%w(Z)mV31Nu zOLuoDLw61c5>g^9Dbn37NFyRGLyClScS#F^bmx%L@two{+-t4v{?_-c_5OaJKW=l) zFka`J*OB|NAIGU;n3})e_q*j|-YW`F=)SmPe879ii#8z33#td(7mwf%NqCz982I+` z^lgsP#}V1EyP$6&SFf|5$MXO)*nSS2MGB6NFaF9d3UJqW<`)V?mFj^ppQkc1#m&+( zhjFprAuQRV6X{D(kIfwBA|lY_P=3>HJ5!5a=d#nDdDr9ZLA$(O)7wX3i(L}i>oGWd zBP>#`fA9tkWZJK~IP9+mBtZkxL(2rVL~ipR!KpO|)P{|oAPWp>iG9$ev^Bikv9Y{2 z?g{`ev3Nq6T)&avbI6}M_{RB+x>20bO3uop1uLJ{g&Ojj}kCPdfpDLI(W0`jp zevUDUjVuA2VCrnP2`1s2*sn0cRuA0E7YvyS*=R~sXBCU;uxTQ-+>eFsV=oIm6(E$4j>6z_5$Fyn< z1;x+Er%j!}<~{X3*X~z`w$~ayf}0Pdwg69Szu1nJD(YiEChT~)YBSw%gF0wfFBQ42 z_JPy;i4!gE4Hv_SPCJT5xD+mE&G)6pX)Sngo2iNx zfKe$wjDw|sx;eTT=~ypWAEX!n%@YWwLr9Sji@A2SZsv*Mv{)0brR^F>(7BviEg(LQ zg!qT;vKvQ(AJ|MW(P~fz^nCnZFwnf-xDF9?Ium*0xsTs?d7zl1^7*C0c~+PO;x{oq z-907&4|?`2G|eI@jo#I7-eF;|KfC05%&L8-BLe?LNEZP((fLH0EfDY% zKyIkoW01uSHAE-3EHYn$6z{7voSQb2){=nJvQFi3%> zL0Z7!R9p_Hl#d7T2>IU0N-QY7^<0)1*f35aUpQ%KuHZA@xBQu60gk8#RT4^uw*mRL z`++E6^MauE%9k6Iupj!PcSNqHUW-^^Rqp-@b1B2`D!k{0LnE&u6&=SKuiY46AmS6Q zOL#)nnSE95@S10Dk*Ywe&>u9YPy^h(kp>-5oNkF2Zit%r_Tx0oAR)9rO*hx1Rue_7 ziSM^?=@v0eRPZS_@MNXK1xr3qZW^~o;UTF}UkUMTZyah>pYS5viiCwnoKVk?eP9sDN`n+& zW4i`LkHvbn(T2n}Aic?u+Q3v2sQ+qv^c?Yh8bS*3&m~*g`gP8s#5}qC_gJ;xAcW(7 zsI3|w*o>JBZ9$W?)%lqw9}3{QTe=MyC=EuGLSu3<@tRl~U&Y51&ea`b8yFZwEY%Ta zTPz@3%ocHhL$_^d-{kS%8pkJZ_|Q?fp++W|GMqHi6r$ddbKVyZ%gvQKM7yHq1N3@S zmv=&X9b2^J$VHEs{2RI7?T@Ql3teH7aaXqDcCzL@FMc3$bNQ}496utY_X64c zpk6)DjB@ITJ|>9d$E|X_eBbH^=)5sOk(7s2b9F8p&%Rsvg<_X1*5So`V9+o6K!~|W zobpK2#8`^;x7+<0_v_F>kqg#&t%*Qf440>Qh+q2=_5^t`ACr_H^DfFm>CK`qxWZnh z?JAdFU8(7fr_ar~zLKW~JkgnL!6uuc_PM^KSAYKOz_4ERMlGOcAPV3#Q%1lehNHTW zoL_HsFA#~swc>j}Vy2s+D6EAt2uR+1sXQ1GW);j)BAs@A;M8P^IrYOo7jxvDojF7K zLy~TXX9(IwjSCWnOAjO=A=MK71DihJrOUo23HnO*JM%Y#yeiBBPxog}XSU1myGfd5 zrsw2W=YKHPB~(*}Ko9*I!4SY3KoW~e1U2o6rCT4*7I;nD?Q20CZ%`Dt@m@C3+U@X{ zMCvR*?hDc4$n^7LZa#nTxTh7200;@gT~N`T!Xo0l(-w>=6~Jw&%HX4sqw@F#J10fH z{&V+57(6WWZC}(da3IQG7ac0H<}ijPRkY#|q?^(m?opLtBRj13)Sto}WYs4~?}jQ^|s#YvjfIS~Sv0o(R<+`Fo!ucb2!0ol2AWg^+_T$$6whvOM2kJ6lDJ}*y1WAd8ef$pVjDJTPGvNXV79y6y3wlR+eaV_ zANNb~>V^Ug_a$FJhD5E9#aU)UaOXw#!9?S43&;w!%+)h23a_IW(RZ2l$`)nzgsrQi z+914~VW7^uv${b#0SXnvd2iKCSbn30aVH+ySOk19kA}}rcV4@4C_*Ah1+dhzP7UY7 zf$W4EBVTpt1f+aW=gX z4GjW#?zgkZWdyy@OEWY74*ab^&<~#2k4dYO<>vc3E9iM2caTUDxvTESnzQ@w{`TA^ z-;5HCHp=eI1YH{=+!=^%clhuI$Q%#!>AiZG1WV@)#Qxq${CfB)lY_fJ*}^2xr(hcS z9G+9JRmY}7Z9rEj7F#72aoY^4tKHd|Z(KLzLsBq#&q5f!JXV=r9nJr4cYgHcTwdLd zoXV_*gV|I@F1ra=EP{Slp*>u?&N}TR#tOz!>E4qYmZmrW)%L9C9q4)S=g1^J^Gd^C zjea|Bv%k4=C+9I6h%KAJ`%;Jo*jFT`R!s;uJ1+R9^dn4W@y~83E)A#kkz>93OPx@5 z?}7;=tf$XPO_5)VPWwq?tWxT5&+VE3T%IM~Ef|^xzh!y+s&FI;o>21DKno~8O?|N` zkTB!icy&cEvdR>FeRZL$JmBCt%%8V{k%AcOAa5^{bsNxA;9DHFN?Z`#kDcH&!4zfY8&Dm}lfu?||XzFnzl{2OG%-uA0w`Ay_s?srjG{LW;V zWfLXhT7r3#%@1Xd>Uv8j25NDd4?HQM15sC&`RD85IeHM&N4JXmFO&_VB zIBSow;=YzDbqVOjOsqVFQ>_Zo4Xv#qnPt^2T1bGH5hLu&T}1wuHc1?|Ak|pq`;oJPVA%c``?@O#RH~foQETfACZGNj z8tgL=RH|{#-Eh{+Kpqb)dlBWb>vXxLi}(bU-Brmxgwm*ilRQVyZvEgWsxUP|r#xP9 z?HHxq;cG@Gyt>1ot|8I8?ckEYsn1nL-RK^q-j|YP(`TnzWvd^p0seUY+qO|!BCb2_ z{Jk_pw!UjC`pP!@o*cQ%MY)F6j74; zej*%rUon`!gy%})X31|iMO12M-HJ`o8E-FrL0@{%Y;&Z2e(#erIj69a`XfXlH&zb9 zaR8M{-mw_TVyh&tk8tdG*{3j~xiQ_~{@7!UxAoZO6M{OgHJeHq$yh!uxn01`gwn5$ zF7rgx`-!PSQfp4F{GNC4F9*Eu^2{g+4YQ&o4kQ?a9MYg+!#{vj!tX^UU=cO8Bv`&UuR90#wz7j$u-z|4vAe5^<~_%0#juU3=4 zs5y0qMVf!q@4NQ(REGEuB}PIpMh*SRBk|jm zS!yL1F9^XNk9=_EVX4WiMUwudDZJopc6-+O{X`8ZRJD47YkR638}wNWsgPa+6b8ek zty_X`3`7pt_;Cx(djp3J{v9}cZAo(HwrFkrsc&2zbXXkU=wpMv;x8}n)nU-Q=Q+W# zqFuh^hHgEPjd~LHK7!%(Vp3@lgW1yKmjm^Fyt@-pAr#LZuq7du*cY0Paoc$yiqAyU z|1Xj)pd=HcVeL@e91e;OH(4pNS||RJdu|u0oW2shN9{y>1X|;(l~Oo?;zbTv-9up2Vncu~~7<9jW%{tA7JSKe$VAY0~ zubVE|;!;Xu?|eBM4(U+a`K)+KYbb%H<)DIh)Kw^Uk}YU|%7{lRM;ZTC8DuyA)qDEx z^&SGWr#p7XN6xTPbdk1)vAw^iGfCi2i`EPDOmMEuD^O0N2b_iZvl*8;s-Ely3;pdM zPpC^uI!Z|?jMf4$lQ}I5L>MWrho&-+YBGAHJ|mdIJ?O`{?}O^FIq9LG@Q5}boKh0v zxiwyFp;yN}?ku-vOKuzW`{pgmM!0t|zpFHQF~UJok9qz^sPlyxEgqhMTNDu&G7sUd z#K?(I=)6Nc8=HoU^9zR5r%sVUb<0yAek9BzbA$h`!cNz^xTpKo{Mi$Up$aF(3T(Ft^IKP@ zQw6Rt1ga*y^;fby3EI<^Iv@>dT+UzJ%5%FzuPpQgV@px(LVq5yoqRiYf2nk4qtdLn zT=X)wjUdQrz~*KhD;wimvucV?tDY&E3) zm|3GcJ)cDnVP^E@SjjBFI(1vu!=I456?kx9r-t%=xu_Eigav?DZfRwM_*d;)It-UE ze83~3R-$97+XuuZUJ*tE)H)9{v5M~F>rmP%`fKH4u@d!UZYx{|z*Gxe*0*%G3aCZ4 zaRg%yZB0S@1c*FA`5!P7>^Ob^5izoEa!%G1NDE{U6bLi2qF7Ri9g&C_yLEc0?i05$ zz8Tt3!DbS5L$vEgZ?bah{{HbmQPBN&^o8&3-*8S&f}Q|GIbo;S1$t zysjJ4)AGku=SLJ~SQ#zavr`!lvLF2HN9f~%rNmJj0Q`!2rXe9u zi?$L%iKVnLe->iFe{!GSX2wYH1d(vq66xdxBRp{CYwjnY0oI_&o9w190px8}5$*0BXsM6CSdkM$wU$`Kl>`4}_)Wt3>i3OBHY5Jo>x9g-Alh4SN(|ex{Bt@&ewIl-E?@z`9Dc~$o{;%iA2X|zQ@V~_6n&kIVkY+G_iT+ zFQkY~MDPkAMTmw0GV}^046JNAf*j7yiX2o^2*zwB9R&E_{Ao68Xx!ISDiOP~;eq^gqpQs?atX`{#SuNi?GA+@ZsCX9WBHHp z=3YkRN>fM3dI!8#((`#FoAtPBKGTnsy~HkH3O^kYp~icl0D?&WAuxNDWXw?MFEN+= z21q=uVG(BWPa=mlGS{f^c{;b=@%+!x@lOn@6}y)Gde!J3xlPgKR5o-b;KROpV$ZXt%Hb9K4LOU7&`wLp z&b5I2QvQ`f38y@ojO)>Q3}UR*^b`)C{0f*xb<{exnmBy_<@Yy15Dv}E)eE#Bnp&5? zX93)GXo#pv%X&58<$8HU_w_nuZi%GA#l0pSi(2}UQ7)a9r1D8qh*D3sG2BJxgDUQN z%Ee-mv3C6yeHkp=k73YYA-ykpC`XC=s5nN`QqEG-!JA{+Go0DjZfR)vQ7~Rkh%PZ& z7B}Bxs>sOog>g6(lxk?w{-Un;tK0tQp09D-c<#8#hu0Hv!zl31HeX+;26sZmPGUi%o!ur8-jRitODf*H^X!ZkLOpy zdVXbDW0;%6gm;gsdInTHe5M+x1|c2rEPxBYp32}S(qiDWp)8#aY;cOOTvI##l&)5& zS?p^fGxy+Ow=^y7<`{oNU=|83+A0%aVI+Ut^bItm3C}dIigrc>yDOFOzz?a2Xof^8M!O zyg@unxAcZ8vfu>k4ZccIqSJgj;n2I;r~Mb~JR;rfK3j>rb7akbK2^QQ+`?wZaPbLj zZv|yLkV%oHxncoe>D{$R@UKbI^S0N_m-`JY+M-|44In=L4mMRDId3mUkgPu*^JR6Y zgeUK-FRY#khbC|e1IPqVEz844R$sUjOUTS8qJ0CM$vGP`61U)XzRrIACa^jQFAI{KkiGAZGS# zAS+(^0qRx2`TN1t?RuYg%ywl&8&F%_sT3owyu=G;t?|v$S$w?#*ClWDTFu?E;Jp?k zs2K3a(^;y0Ghdev+q+oZ669yZek~ml+K4e|e6>)gJEy z4k~WqYkpt;dtbAik-YV4CBpodU_O0Thl0Q7&)~w}Lo;>W$hx`SLCZRQR2LysTGc-1 zd-UT>6iDj=s;!^HzuQRMzkGLeXR+h(dcRq9_^x%kggQHD+3PF^dEY7JIF@IsP@=X} zz_<#jQ}yONnUZh}`MWg^er*XZ9s>C5`b!-iDB>C3BF)+j?%$mY><5!rSiDOpu=DX` z{=8%_XTVPN2#swxF_y^2)b=@Ir!TJAq?(q`fEu{qPjo%DM{}qhmoSy{v@x}~+#HyU zVfcI^nsX#wJ!1#s`r1;Lr^y*J$$B2C<|(cSSwHKp`~V%~I($Jk+99&>c!R z_giUryFZ`K_|r3p+hn#}z^*IRZvV39E=1kEBR@$5M;IO-({|X{IugWlm*4Q`3vCP( zR$jK|L%akxcR#$|d~AgTv7}Z3&q@J_uuXyPuxzYoL+{Ao@^s@a_?35`d1o`IA?Dk- z^Elx}3IZQ(ipbxd{mJc*c`BUvUBR#Y?Xpe4!Re%8{Q7r_n$gbZA=Oo%-aZSoFL`>I zSNm%^jLJ=T9UQmj-QgX=DM>71xZsn9u{vyD-r-?u1RvjG{K&@M>3PVWC7swr^y;jI zkn_AsG=@eND;ki=f#k?^T-G2YN1dCT2eMzJU5aN*pBH|Q>5vG;r1=2;Gy50s8OCzK zW%<;Sx9y1@_JRuX)pVL0|q!W1J| z30asmTD3Igs?q&5O~de7HX0R7q-sC=SvUBt;p7IQO%A!i398*``&KftRQNhgcWmX{ z2%DWLVn*0wAKl9;S3KZ(55#>-7_V%eN%WLeM%geN@U703K|3O%3wc4_0^N_x@=2O2 z0h1=4E3CAZGgV%XDyu~NP!Lwylg6}{4TZwP<_Xa-MmvC(}M(KHmHi)hq zS8ubMemRS6zD`S4 z=pehI2RtecYN`MrJweUMyjW1kAwHx3`lK$EN^B&(Wl*HEg{i3C(^_=ttwHIWJgx?G z7rZqn`K(y!gF|7wYL6Wv{>YNx?Ug#}KQ{%4TG%h%FIZV!^5Ky5hnhBb*`}e_S9gPt zEyK!8``rKQu0#?t5DZ}Li`J^pzJNtP>!18{&v{og{B&PfRydq<&lIqs35nmWeS{&n z9k&pkAs?gtY<_FQ`h^=;?Mo9AIRgERidkhtM$)h5Hg$@Vy=-N&c4z)%0;PZ2g`Fr| zrjCo1qFgPWuFzip@_u9wh-T79&7+d%=# zcwGoMSG;LZhgG)$bkLj`o+1e3Q)&BcaYIL}Zm&ES!ps-4=y0ecAGIr|NTc_I%*VK^ z$c$0diWM*C+(jX}irgio#QWx4KHlOI*5zbp_$&{zv?S;Wel5+50q9%D8T-_^3zTqg zp26=Uo*6+GH803G*SmJrY8@@3>nD5KK>fgm6S@SkWSPhbTU;8;*zTku{^od|*9Mt^ zzCIkdE0=mY_t_Kk@#MEKP^wn{eH6?>-5m^Uj{U!EPLbZeWCfG`-TSleZ9XE~>?c<2 zkK=3%*-nq?suZ?r4J6^jk(i4$br24jUq4MHPr=@Q-8ftBwK&CM!MF0bl1Xyi5_+;h zT9V(kBRa6ev{*MN9MPi4ULd){!|cqhpNl2?_HyiLd*ntKnio1bMA*Y4;p9!Y=m(c#b&sefYH_SIP^{Ay%LkPdU+ZW)O(haz8Mpa!MnSOMXW2n&G&KGMA;y zFW``+v8|g*SgqRL=xfWFA;lVCHV8 z5lIC|*>K{;c@Welah}^I@NW{5JnstZ?Z);<E5_#~8stqCo$Py~t~>b`_m&ijg9E$5n-Ul~D6k*(K6I2crJ}?xu_HvKad0*SOTsKWF&EhuFWty1}MjC)% z@{~?F^$v==xtInT#!MqqHdg-OV*AD+0Oo^#~?&(4S)EX^Af-0 z1|n1J`KE>#9hRxhi3x@1g2>-%B9_{HGn^nai2Ud6v+=sB9@Kj``TcR_fQ4_D3rDtV zc2FE$*3y+l< zn_}|8SqCy*!^fS;`*E0bfW(YL-zEcG6iM*w7EZ|i6G3}Q0y|arFz$)(Vys7bV2owQ zFRr>8?m%x22aRpNYI*LZ{i`qQ($r5Dcv8g- z7ocIiXoYkDr>B8))h8@b8GqoB^gY$p>h7e|*aDDdHK%E^ClCj{zQ!>_z=DHSZhX;9 zY;RA7NhK=)@-E$;7J42p__D(ZITS~{de+1jhVE)qqO0hJV+$|&a3B-Ow&B? z{fr78Mrn2WPkUX> z3+==_diVA2qDr;<>xO6XBAQu8oArMx+};GIyrKk@Hqo1-Asp+Odaf}@pc5tdF`!Mg z!_^z~cb&3s7!`u4w2x2M zYIx>&%;ATh%6A~r0Rkr3rYubrZ-d6;B&v1IP3h=dUuK#Jzc+LRmmKP&@!t4wPw}9` zITb_40m;exwc#I;4|@FEX|P9pV@5YXFWq;?P8@h5r;f^FMTt@F$AoCcVQW!jLV0GN zW%DfJHrPXJ`rJ1biECZqf?t|8G+l!4Iourfy=Hdlqu{lj7DTc_Ge?$CPh@kQTAFO| zaOtf!NBUmJac)v^%xb7;LqFZ{Ys)@(yzQ;xCW^&!^8N(780RXX(Xx5E!MEymB|jLE z!VFGe-5sdtTs&L4xBnaSt1yx`JdIrr^O`rkSJyc{#}s9eNUby6%!+8n@)j?V7rE7n z_WM7tIWvz~aDQo_a>YkaBJ%op^{dn9$fT|82sGz-oL^G$nns{NcJ@KEMPHnJN$}8v zHLfAjKfhsY>)S<_kJ>`5{zlO8xI5WY$DIY+B<A>ykw`tKS=wR=#+%l~nzV)pz7-YYq0)Ds*^75*zvbuhm!ojH+1S@!~eeBz~x z!&MK};5(i{)i(3F46`59(TaQqEP-_~!56MYXv!z;)VhGPYTOm|MF(R-{T%Nm0+aqD zg|Nq^-Bfn*LZk5o|Kc1y2MVh4 zGC@b)mNZfvc-hH)*G_sUs*9fk*qpUKyM1k%g7gcQH;=PtRgyhO!CSY+@%-;H_0x2;FDhpXAV*1x{pd)NGh zQQSfyrv43hXosHgnzcNo&G%#VN^dSc#;HYhe2N;LZg3gtN~fJZd?cZM9E>#hYZ(np z$$)zk!a+xFM!sb2P~_U{^15Gp_!sM{<0QO}ISpt!l%S(NxhEzRluSgutg>ZO?=~tDvk3NEd`;+D>q|&qh)}o?CP-b~CHwT5dtVZBLG(M0 zIF5jsz7wx=bg!En%Xf*t$g$BE*maLe8F@|79_U6$r-8QSEL;lKFR2N2C~e@d)<+jr zBMX17Ec&8F#VpVhYmx%Za{449JZPUSMC(`E``Pw=j%)PdG3@{P_1*r6k4DyStf_ia z(iOnogE0Cvc;m|Sw1CM&>|BrLnS!4S+*@F8=6O0D4-q-K=rFc0DbM+&|009Y`crb_ z@nbJ1R?#0R<;~6ps4l;|sm(&AGl_9S`vOY4<38_ASFL0(Ml0>Lmszt%3^^Y+%wc7j zVH0Kp%OFwxJJ&o7Xk_a9I2wbcv)31E8^@!C`8GdEKkG&99`>KZF^fgi;&Xo_CuB?@ zFR(`tLX6`W0_((T7*G}lh!heSC1^gG<&<%aZF}xuiCIhb4+=HN8-)7F;w>( z!_*1~N1|>NhH`qwlYDs$@c*Sz=*lO{t-7Hv(k_m7vi*a?RnK6enU2JESlA52aN)AI zw8Gl-EPvLAB?)&}`Om6)`j8~lD;=9hpAMd5i%Kb@lV=6f6+>0GE`Qg@9Y)xguQ%9@ z4sY_2;$7xY@|xlXm|GtN+7mLKEK^StOV5N9JU^tC-5B;LNBgkx>jXE&?2qwo!is;pBn zPy7i&HGRKV_$k^6Xz?5=hw^3))=rC0^3cz%s7)z7m0Q`Y%+?Zea`r?Ks}_ogCL1=WzxVH*{UXm@m7#| z;KUF1&N;Q*DB%3aewJpeXc^xn*fZ9nC-IQ?IAqK6=ce7Gl}KRCx&CvDXs3x~ZyLSU ztL)52*9m&ce|oiQ$*xCuRSjj~7wRX71lF;5?sIpQ4Mn0zzLG6EOaS+ZRsv_0O!>i**mB=V_^??=k|;B$GeatM)?3M3IhUak{OFq0@l;#&ZvkWcfR} z5pwUTQ1T|~X!1eLi@ivD5IxiKb3#J=yk$`AAKqJ!(V(QPPA{-9KzMGEU8}*hH8*zM zvi#F#`!^XZZzZSU=%>Ke6PFk*0p!3sYN|uxEeZADMLw2!d5tEucjrrvDz(MiJQK~d z*}sB#WY@c*bw~R1ANkY&Ix65qmyRY_RHcSjwv3XA4qR;(mW=rLl;j%lsn-as8es%z zKFMyFNcw_CA^2&jtLMv^G!dO_`mDU0{0b73DOPI-7_~j6D1ntUVUIl9;?1#g=Vt}F z&m!)8$r0E;8O9hhIr4V*m`8mxU0u5M$lu=OJ!kiwKrm3yBCh43q{dy~4uBDdZ>S;uQmn z2_v^>Iit``pNaA0Lf^YQGa!)ZjFZMF1ujw0wbs0w094FmZme!t+1$ApW<1poQnuEn zJ6JSaPQ5p$-%$!5|1#!oW_{^5RDs(2ntSi|-=@7l`zq~>m{>Zkg@K8B`vnR2m5$0I zqb=QhJ#U{zuT_eIL<)JYZoWfk6rbvKF2&)vgVCa%_zZ+kgUjCP1D^z<4XzAP zNKQ1cYIjw9zSmPc{loG7Vx2Xi@EG;a#;9yYMaf5B`=(9_>IVz#;`=EN_v$G}4> z-j4bTH_9U4UACT|o43iO2$<6+FsaMvYt}mp%Y%2@zqs80 zBqVJt8-B{u`b?UqIRB|cXK=?U>uTFhr2-g@)Sy4bcb^_19f@->jhbvG> zog8Ft@5u!|+U5@hanHF|v7X-h=4>*`sPOi{JcZMnbp;GV-4r;LClargWmD-x8+tK| z2)FT}1iK(64?oa5L1`G>qgQlmh|SwAD?e(BDdP3&?5D-w>2Kp`WlZ!uZ?A>|N#sUI zqINw~R}`6ILv541J_%vQ@rj#cJ;&JwW<%Yj;EYx={~rq8NH7_JLA7?3o^txfq~KHJ z1$bbJ8~Glud7G@r^{Mfjlh%m%5((Yf8tu65u(_jTpl_~SXB_|iSn8LSl`LyhcMZ#bug{ZVY&`G#m5(Gkk(Qi z?hNzLJrBPU^^p+LjLr~gs8a`gJ8LhNz+5&xwMPNw8d8;brym&x`9(dZ?rmy`(g>^e ze=6RsSMjLoSj(4b$d%{zsGs~XPiXYGP^3Ypre+2^%@*y!+DQa<$|7KYru|tqzRCsD zX6W7%@h1;FnU#dj#Koq}exf^NSIj6b@e!6E<=2m*q^MKdfJI~!Sqnd7e z_qLW$Vw9<7@2r^zl4S1@@{m+!#^WgK^^2EktFPG@JDlVvQk<7db~lzQ^Wzz0Wj0To zq0E{^+STe&)q{hrjEg~Z!{U4K_jg^=AUbvv103pCb#^&lzjLehZsC~`D>96s_;^Rr z`+QHI_1e9XGK2|Fok>*7(wOWDf+P{2?K>h?mSvI0=rY!?W|3l@2M3RegPF5go%nWN zo&~Y1<<|@3wNa=0P}GPiy6DLQWmLKOG9Xqw)gT3MDZEmf3Dm$k{ns(0k`^+n#wU!f zQ#6%ebjaq3iqFgHQ=VR(GNu{5ic@2dO0G;gJ#dJe(Tk`E@E$rtO}oM57k2ZrYx95xOKSC*DP1`aDO<}C{*GPw9`r;`gbi%Bj!!s_oYuwy)t zcmz2^Vajjod{1h*_aUajagw`Nr(VRAWe>-+o4`v#PZ!0Hm zUeYI}U+WA5jcT$zF^(Zr)25Su8%GyZmv^DRD{Zpv8qb3Hi*#i_34DcR%SI2ARhfO! zEzgBn6MxN)v}9K!c?MC5uxv1r8HnXPh@go(p`|5Z}@@; z{OG-X6KHuc@2cjJx;94IU1@F&y{Rr^cp>O^K)W;bJ>nQXqZoosK|ed*t0`In+PKt% zF^L($c&&IAE%x3|0wvcUHP%RWJn*4^h}pcJlWCp6&Xo!dLysH6v(F53Kk6JJd2i_y8uJzMnGee<;DSr#oYDX_^0M-3h8{JU*yt1jhOX| zTiSI?|KlTg)ul3OcV{PN~}tHiQ1UY^%w z7P;Vs2*(SSJ&G4h$x#kw3dTRBemRDyTq(|7I;SP*<4#yT~m5}d= zek@E1DAPGTl~lgDy^`n(FU5cjAY`ItrE0MhW` zky66*aSx)u+r0?tPdC6WSlokY-F59v#B}wCQEXm-p+=U6t3N87wluv=--}*POI~0< zp(Q_b2S*Le3`0N5c%ZpSKIlzk!&vG09C0uclLV3@DLRGtA$J_Q%i4Wc@7{4b(Y%Deezv=E5jUv* z)1^cxt)Ni|U)HlY47XW5vCPo*z^mtv{^Io|kfI|(@22Z>mYs7ihN}j10WuNC4=SG_ z(uC{=Lr5_BxL|Ix{=A00K9#D~n$Pg=q;$X=lmHy0v73H{77md|o~q?|j+GRU&u@A9 z?x)X2MwVaUzz3@DuSn3{2Kx!qe65ss`_-QwfB@SYH)WpK(Id=StUBTqCXw17eF>ub zk@RBi9KeQ|Vx;dXGA*!sn6->kP6xPE&mcpq?gboP1TlD?&pW)UyT1TW%t)jFgJZK= z?lIRO1&ckFP)EFWC3%G^O*I`P*7?zI=pIxDxeWXVD{ckPIIMKWQw#&?J>4fALZBS| z>Q*ZfT5d^7YCZbMm_1Pn2kj5vnFR+P z9@x*|+H0?&NjZ&=_7QrT!Tlrwe@}zfL}X#K54=6}!XU-@(w``PA{p)__ z2I69#_(6D|J`S86DcB@r2oq`1;SmI}TC9cJ)q1KX-H|K{Fh|8n`n0$4A0PD4GuJ$N zv+AbWa~mUNZu~1oS~>#WPu*eRP~0MTZPAg#^%)(Z2t}ZWE4#``D~gi>yQjz==3vK$ z!Re7oPtYLB0(n|QaYNp6rMe89Akp<== zF8p9Go{x`;|Aj#Z=He@ikNU!#G&P?C92x-94?R`4ii&dXL0RI9V17_LMEZ;a*JPBI z4^SY#|KUel-ZHHmtHCnktk`iujQ8V>q_O2NxY&?VF| z%2oS~cwsc8W6>3ema*dmcUG*SF%_;PT~dbYj|YLPd<1x0d+`}W)^}TllS6*9p(-QP zBEXyzyGj=ajtKNZuu1ycb_iT+>4;J(M7;j~;LVv@&wWY~rHok8SEy1{yolGnFr;AB z$F2IPCaJ_A@r+vc)A;<-B^l`=cMv! zI6O_X?%{x_^#Fwc3M+h>Jb{7Is((!WLIom}VSv>ewuV57_u|>eP+i>8Hpvsdvs(S& z5Md0gv#x1_5w4;wz{wezYdnHwF!GjVNZ?C2a5ymun=dRTSm{ZtesFeKR;nwii*)-8 zgKO;)VG3!haC~T;&1nYwG-&m~AOhG}7_CEmpuZv+CiqK+&gBl91cAl@s{NBE{zHz4 zi@nG@T?BctsbL}Z(fwT`Y8noT$*c?#bXAeP&m~Zm5f{i%0C6(_eBd8% z(Sf%DdmN}wGZ5tg)HFnOG}rMG+198 zvcxD>@w#OT&+WpeZx=3N;V9$$+!sdna(J{A7Cq3df{yAURG~DI75fRXBMH+~f^pV3 zDfF+Nz#l+E=BZ5r?4$2=wNRldqYP1-APwGbsQvBko@U_A3ku##!^)8EMot$&eu5H0 zIZY;p@xxz%T%mo2pSecJJF|w0S2C>n_XDWQW$Q|?J+^GenFy-#+IH*8y+M18)ZGAk zmjQM?mD7xcJi$4Xw1LT>KD2a^C-`d@l+&NfF_xw1urqMNGGY;K!EXo?y!M5mc2a`i zPh3#XPRycKhREa&B*4CU*7q;e$#aPE$yb5qQG|Ed+xYu~52y)x|3}_oDuG379mG-R z=|!eGR(l(!ZmmF0&(aA@lzVl61{(Jyf)p@8uX37-w`H^~+O5GJWRtsUEA5DEJtNii z+UCIF4s>$F&pv4_AFig;S2ljR7$8yImnei#9U$O-S+RBh=_?lC|L_&PVIlTfQqx}k zu-k2%Mr>m+_GXl!h3y_D-J&h2NCV=7;TIzDp#e4;4v6O?PWR7;MGnBYTUpLUBMPZ& z8NGeHaKz&U{})cGJpUdq(vrP^r~9p1RDbd6Xo~#Pz%|G4(3gLoK z2!HH1#0g&l=Pl9VR;2be*s{ywaaw2aa=XJ+dv0%*F#Qj`O9OiM5ABQt+KKoT3js3b zCJgsor!w({**wqJBe!#W?W-jFXxW%+5IdsQb87=KEfF?g&k>bff*r6H}1R)WZm1KX?|;u+B$<&F~pj;oGCSJlO20&f+TnlwrS6?i$l*A_2gE_ zha3??zK3}${VoXU55y*k3P1Fk-$>Q3PTkvRQ+wNp-8%EMPE1AP;0}5WDsv5Lr!Ow* z55#NN@K>1J$P46wZGRpF=2``cKWOGb4cU5#>N0^EfZ2@l@IS!QJK((Pe|-Qp>)$5+ z-zFYm;=>8aJ`wMM!_qA43w)Ta7) zZ7N@fnpdw*;`bcE0;tYb+4$+?hEtkn$EEJQGzZgN!Zc620uK%N<_g<>1IYh$lgd%k z99Sn~-JM%e5*E0MxdxFdARvE%$0`3!DzYEJDS=T6ZU#`F zrc)&gNGzMnUZgT`3CJr^A-VtmU-q*{xy@rP9emC^%>9Sr8$|!c$c4yLkZ~T;-y)N$ z7RYL-%1EIP9?e_&Qaui`CqyPT@8oDjWos{6!AdKGTrKj5Tl6Lh{awWDl0;-ofe28I z6kVAT9ZdcmeuWHq$QhW01DBv&>KCg9>qXMB@~#p6>$|h*e-~fKuy5~lTf$=G^_L2Qv&0V^^Q!65!dKg5H6EyVL*Mbu% z?E#U58mbG`hqq>w(*P6pE#hmMPXFt>yU_pjZvA7caZ(UJzO?5NID2Ax|MpG`gkax; zB_4%JSfKxZKVtBhz;{m={@1&8DovCbq+N+vI?T|M9`GX^Q2yo`xZd7r55V#+l9_1# zUHI*CD@;E)PX6`Xi%0+K-MXZX9Fkzw1=86!3bJs3N!#I3TKW?Y&jqOeXN-|5bPX9|iO70?>tfCdFqf z*9f)GWb*Lw|5pwDf84D|!$91Zp$JJfb^A*d|E-p=|jX{D3C|NutjDo{ehH%-A(m@vU)miXZOzxhJ2zz zE$Uw{ThycIbe}ivUT&;3JL&&)<46Y}X<}5mD~K`?zJC=2nE-go_5Ph1V^Vz{WR70^ zrERoHssOnMjG53;yPsx1M`=v_-^7t0L14C!SL6!=gqaNn5W;Q?xIN{BVr`E0t)WwB z0CLrBgsG#LDf|J#GCiy>{WW7U769RQd0rG~v@iXse^R}f$L>S^Ri^e`PN#_bq#81G zvTo*|P`Y=b`_sI0_@6|gz4(TR%htGE#jm>tO^8^DOZLT@=`N#=6~Zk6ls8cbpvlm` zobg`>+(+2C+DRGbK?DG){XTKG-wX6Wz_vdn0Grm*iVnx=Yx9C7swxk&*(25P?RGmwCqY9!)i#p<@2JjD&nMO^s9 z=#(n)0OHw-&&L&LhNwRK!Vr~c$!b|?6BZ;-Ro&_=`Uit~q1JT1P&*8*v34A|8m=_d zSAqO<%;(2oc^1-8j7(z0#k^%m;`a(1Ff&I-`F^HlLi~5fdCT{4KQ#cLE3c!7LNP|d zP?+C5%FJZ7F+ZxiWWonXbT6Lcj%3El`Yz4mFxT9*z%3KAkU?CyKJz7@!wB?vEXI#5 zX)$$=fbOZQ43fzqXJDQ3E4A1*l=sNpt44bjXsikjr$=gcCLx#$w*L=%?;Q>2`uG1P zk{}^U1Q9_R5_JnAdMCOl(V`?GdhcyS5D`R=AbM{ReUwo~i#obs^fE>nZ5Z9T#(uu* zdCu=V>->5CI{)kyV`cBX?z!*lzOMKC^?ARkbfJrRi+J5BSP_FihEpW|M}$gP0-N_o z3pV0`IlOisYMO@rV_zgJ0d+#IeH^mo(%#EK>*IwNhozyy3pBALaU%G$x0VzBP%dFX zIOhNZEDmKmFA&1})o8S=h`(ro@qgS~z)J=@|GY)QJO9fmKu&P` zWO8xoqwD|fVxau=Bl+oOCSspzuGDQdkldS<>WO_yWrj#eUL7q~az{GK&;IyYzc*^& zvNbC?Kg28C23ikRJc0AaeOJik$w-NDDyH!qV)k$vg*To-H^=rqy*r3p_<}cxQ+;-> z7weU`dpxlL6MoBD4L+V;sATt*t+|%BLwU(1^*c9~y5rSszxt+#`6kx7Z7ANPf1U+O zbrU9BkE(V-xp+1SEtfhlOqFew82^GIV3ky{WFx;**Dg>&l8<_25U8EkOEi zYn*HlDDv&cOw~OFo^B4@7j&*@r+nK0Vaira zfH23S79zwM`IGbgiHQ!{27nQ6xjo;OlYbwnGVns^Qp&gA2_JSy)EX`kyAQo{agh+* zZlBST|9tC3jMsK&qlyvP>{A0F3e1kkh$tKW2u{VMwNF~47XYdRJFVD=-3zmdvX!*v z`Z%jOhP4k+seUeVl^`Gf26ZwA(!LhW0`TYL>4N;7z~c&bBq&Ihc3mC5Zr zGQy{x1FNNp7jL@&CKjM+lu{Qz{OiF0+~u8PJ+DHLa4NrhGvr_{c+U=PP-T<;=^Du@ zEtNI=^k93z1wj(nxzcUT`?vPb%*G^UBA|hssyh1X+5TPjcaIk-Wo;ASv~E=zU`UU#KGjX@&Hy% z)T;7!FnOg>xw5XORd|1RTSZv>A3^@>%&RBjC_abMfn z3HYU%tJ*3}8U|z2GTJ)?x9BQ2!Cx|Cs!);?9TU(krd4I5Cz@~zBjR=n5}n3Mg1ku_ zbbKNHX)=%yn zfkI~bRW@`ZMS8ryiywl6a*v*1%Bq;g*lae~op9c4+*&OB>=4iCtvz#ih+6viYX`7? zKw~p5OP$?29#Y_z9luriAzZLP$evSvFpEG<4+EL&gs_geH^{^8oy_wBM6&$Pyh76% zuJL>TbP8gf%;x%nL_YM~G1LvB*$)?(&U24iK6R_>!l8$P*+7k!mB!5ScT>y`7IkXnG5Ca-RR(0vcJwINhDo2(?7+Z+|?)KNx5g0{-xGBYJ#yH6lcd#xA;?nPe5 zO(KXte3#vUZi_!SiKWVvhTL+)&Vc9eWf!bVL(nGqGn5e#ZR)pzWOPW{*<2twEX=cN0@BMLe*9YC4O(*}n zo$HGkaCEmD2^Vv55`s{|r>1LUeL$yN?bDQzRD&Kp69KE?m$c}6sOzU;Q!{#5&bTe` zdRb9*f%0HPoKHb9CU_Z)T2eE5E>a!=P6rLBMKX4mnz$5WnLKCpLywqI!KszdG5-|yCx5#lTZG;TSey_O?qE;w@4dQsb9<_**D~q z_}KZPcAM|G%d$F(Z9kOxUJe!sZE4_liT*#|t|x<1q+o^xLxBMWR4A_I_yEoFu3%Nw zTrSz`Y8aYdADnbJF<|P5-_{ZJ-H6OBNZ6fDz}gW-V=usoK`sfuF-Z~$Nz^kL==v1b zVMBjq*JCw57t6DW%>Ef9j8mPZLujj?Xz6%PBf5tjyI&xB5abVNw(D;*awnUwcl9m& z_567RR*5eLNvPf=d1P{On|MS3IV)0c#kpIwgwTZa^w)ybC?eUv% zQM`KA{k@J=A6c`;OM(U^ZYzexhqp`}!-PqUF>pIlu&Ws`Z$eF9Lk{-B4PA3}(D3Ux zYWCMF3uT@hBHWH0oLflFl)B*Z@>bJXPqc{L=`t-FQZ%KFSBY|8+)?z zaTh#lOR217BsYam4uS%2VUUF%8Lz+m8tR8yf@q-QBA1U zsmb2HP~!pzHG;EWU~dr%WuE4cfF?yLKmqAfW^)RUDY*+btXhwwBA?@Gp}shr{R_bC zYT@$2+5$_#SWoxJzaY&Ncv3p#z&=K6^Qdn=R+{66dx}SRoiqKFh->D}-Q}Imp&;cF zW7)*Hz!pe_d6TZ!v$GWdwk>N9<79bAG-xnZVeXBP(>$<|Ix5<**$kpJ>SxW>ES^v2 z#BdNo+meH7?vADbW{x$&5B2Mih@9~;W1}4@lHQ*+cOhgu)&WK1#28;ffFp(O_6c7p zUXq)GfDIK@HB_fa_f_h;`qK5&McPL_5p}c9IzFbe3b#D<#8b?`-r*&S<2(SEEF9w- znSckJfZtFUerEq4ln=2x-M6kA7p?~d26!WJk7+T;n_y~WL_X~$Yq0qO~9)d#C2&Md|j=h{` zB5ujDoYhEY6K^CNVrmV<8Z~vdI~w)1JQU)~i@)TP489we*?%J9&6`jAgf~}Umtr0K zHeopI^-V1)#`z}LGmmizjH}6Q3`d3MJ0X6NkMR+j{;2CF&3!N$j~|yBFU`>c8`|jF zh_pWXPy-IU2qySc(e31o;QouFkw;>bV-@&_PN4h^MdF9| zKR1Zf%#l5Sp-BS25Pt<;BG4W3`_+WIUi0-FbR<$KS8c#>m6vrvEr_JA*U|yh=pJ*+ zFl2!>R5>L}IouJ&)z5KB`bua$s2ED9Z`?3=vkdlww@vz%CfU&kcGO zwp*Q%UWkQt&s4qH@d-WTu4`%k@@rQkm5;393*P>9HDD!Om<8d9`Me9J;Mu7=2GQku z0!(bk!)zknn4Z=J?PHmI)-b|;%GZ_Damf4V@ z#-FenFW$W8D zF(S;nKGSx+OS{qt4hME|ZrTp1)cD|qz114#RD(HB1tBu5&j)byQBv_pN;Ld zcD&Lr|4R_zU3$K1@TT&Vh6aC6sk0?kaX*oA4lu`G)f0mr*vznfnXvWs_(^WlFA15o z@>JhGdw2h56AZjErCwi=SAlgyL)dPWr`!GSOFPROb{8E`h}5x;irqV|ofO|#8}H91 zEPLj-Lbj5wiE%fm(oD2BnaimKfd5Gd<~q2XAbVkc;K^1IE7<^$RlG zVkp1j$~^MiqbnK*j&D{C;-%l}-W(PlVO-y489m-D|9c_G1#c=5y9?&r63au5|1h|L z3P{XVt$hScqv@5QZlJU+0~-c!k}Sa)X=4Zcv|QeFG_zV9WdBDY?FTM&q(vR zNp(ic2~6@1Hhdu9mikp1`Bf6CgrB~}Q=7nv4ZyQ6tMzz-2C^;5?Bk{9G*^5Wl&hU8 zcB0KPZ8UR$od{t$5W=XI9xvs@<^fo53=?e;_tT)cCObL!(w2;~hzxjN8Uf@7% zHTbEsz#KdnH*1;ofC`LY0yXmq{tZLvh`XMGf9wHOb%6ur;c=km3;5&!i~ogOC|Deb zCi52Ygcg9Z@bC&n;svgi0?;jyt8*AU0Yt2VXWjyGr5#wt!STQ81@b1-FrE{dy?|m4 zuy`;oz(L@$sks1K4PFPyh?c()MRM>zd@=_}nWhAE5|6wEe!vqzpK>qeDxmWCxe6b3 zJpV?7_ygj-gVgNtL-G`us6$<$uZnhf!Y6O>f^P#8-r$E>OS-tFz<(#=iU0efZ2U)f z{*7rEKT^~@bc>}s1gL^?W=|gk)-fwbQ2|vD{~u$k`M*tcFw=;N272R>HwlXi0?6Zn z2KracffI$}`M0eKX?8FD1MC3ZxWeT*yWsB%`dUu-E0P&4eAPVqrqs~Yu$HM=G{u`h z8w$(?@|J!<0L{`}Tu{u#1!dyH^KVTRaM26uM+L7uel*8VT~c6vQ4-R9B)^MihCD7HIgo7f zU%$AOet|YfFkhez|4-jL@d9nQ8(1fz29#^gZv8r^r5lC~K6>XdfZMz&2IEjWz`{k& zhdvc42wLYY;>izVkBn%%vd!Y*b-+_XOBPtSE(-RxZW6G2u~dlvMKChC)YL9Lvya=oVM2M{5HrP3SsZ+aHuLb^n>1|0~yy#N! z=oKI~@z=MuEBKbk;9xt@n7pkWIeBV_I5l!<@%tvVCcQ$n3BY`y0N<6 z9nWVVWuzOATQEpy0Jbn(yk-OmI3l3K0 zP|*k{>D#h9-xdm{8GP3A2;yPki^cf_@zosGlp_BwK1jgJ;Qtmz%`?R&s>VxjYg3qL z4?W_neQzXJ2chYxm1-oa2B;mcJrQbo@QdBW-FM%OP@Skwj^-GJKUaYb^5?b2Pht1I za(MDgUmlm0Q#^-#h(YD>W8t0AC61#YR-G*%ym>;FU!84(;emL+FF$f?#bGDXfcbGv z<~$5yO)V_gG-?n(6agrWSGPa-VG~A>;-k}fZ7UWJM9%Xz6UF`Og5yyQ(MLrtC7}m_!op>iLpju<;A#Yl7IJ{wkhFtBr~&;y*Jiw|O_=)Mx!kQQ2%u_TNYtplWEbpT zo?0itZJ+yf4(G=az7^+K^Zcp$fuU|uUX85Tqdo*;EyK_DR?aMHQ9EbZ45F~>Z`T_a ziXZBsX46Uo#qy7j=!EysRL3&FjJxIDt$9cUrd^{&ODVuE1+8g8?p>4nhWKiA(rflZ zW4}{byk+5Ax~eo$>&gzMV7@p_B_@&G`-|GYU4+7Mv_&dyRh>gN-UoS>9d!N_e?=M{c9hu>3|L3}Y3%zi*tGvf3hlf5(jc+(J!M;0?g4~;E zaBxxqO&MuIHyJZ`;+WFl?lieYLH^FiznN`FVLU;ioM_>%^a zs=_OFM_MJ3FGtKC7WZWA=g+_o8J9wLm?5YdoAGD*rSF19SB?Y?Yj?P(UjIXD*1#`n z7{C2+>Ic8*R3zF4?{oWM# zYIERgkh8^3Fw;sgImh#wr9a)5IWc{TD0mPF7Ry`wWN(iyZ-L%kvz^}F=>u`iyTqmh z-ds!Fb4C~JtWhGLBLTirNYeAb(1PX3J)-v0KfB;8l1<0^p+8J-=qYMr{E78kMQrs7 zXY3&j?k;ANA$VoW9%X@Q_j>~FJ9Nmh+5j+?ROxv2%QtA5hU4L<7+v#Gj`hAcMcHWb0qj@3Bp8G2U0TAR7Jxtgq zdfN8IYW$~2Z*sY&G;0V&Co-~|Y{{waJH$18?-f_?aO1Q9PRy#QOtHhZ z5_7OawMOFT(910m*VWr`Prb=u!t>H>Mr9Ry-13SPza`R+3lXnI6mhG?q%_g2dbeES zs)JJuiYF>;<#}m}GS-G>Ot^V90wk^y=v1WhM+kU)BqYBlpUh#B2uM$qp~bEW zgq(Vp5E@^{>6L#N2qe!L?BC{vby1;~ObIfB_j9#$`UWp;Y^243c{F^gN@=U}pU4Ll zzGIB5!&4Km56KTTK3y8v2OZOU@%jVbQz2MBvZcqR(wj?Z`ZLkBL@m6W2CoJ*sPi4m zbL+!MP|KPGq07Emd!u@CP1D^{-zzBBb(m~KJY6a%*WwoZ0ldV&`KRUKh z`BP9CPSp`Hppx)xO&+TtMu;8lK*pDN82=c4R@WYJr^pXws#X!rbEmDV0P($lqC{ta z-d+|X=87I{blHZ>D*6TZ8C?FxAgH{@#GxeYW>d|V5CrZ(5RDT%DYNLnv0bE<|IF3X z`X6fcSBilQVIL))_RvB@)P+D#SMbg{>m z^eln4BvBie&DqP=kN#@=kqcZw^hSSeKbrW6_9xH5{qR*T(i-$xLMRhl=!8M=!5{Y#PVmp*{S~ z9ibmvbql&w1IGOe3`#Ed8q_q8;vVu4|9$X}bC-vZ?}2?W(SWd98MBo>xGYfdQ>N~7;qvE8uM^cgU@Atew9rV^u#@V z-HnS`VbIQ~GUhq=w8}z`v3^ zA(ngtlSex9EI|&{ecox$E&t99#pkIU>lB+yfoEL7q&?UaJ&10>jvByhm%~z!D?{>X z$u3X(=j~EOOCrln#_tvEynox3SKN$=|E;tp)aV5Na97Ocs_4$2M@Oq-Cv~l7c{&01 z`bhb(JCfqIGb-G!7|fR?ac?)x4K`+UxLCkSYiVZdLPET6d5~Raw#p9^;$U)y{-DxT zf)X-@zt!<}{i6hnd-LRsf8}jcEo21D`lPN@RL>-of5_HkRlBN>O!cV}-59*OuV8Cj zddppN!exw|*B{Xx&o%HXc@q6+PCB+k;z8}<_c#8g_XIuXUpU@9`(_M5zO30n%ITF) z6=jR(P3h)8iK#k=2kp9?tPG_2w1=+?pwiYV=+&=@ZHl{L((Va5Tf)T~(dzyTlaTcD z3cqV4wApVr_rLv}hwS&z={IV9q|x_U64 z1rMQOT>*xA1{OV}30&VNhP4NclI9gNJgOkn(z6!WAptw(4BTOkn>!QMu^XeG?#%qU zK?6AL*>YwQi=hdGCGs!N{63XFbkg_v8xSy8WjCcLqPK47K1ID16b4UDy$S5q@-v%R zu91Q7x2b|@noXp3!?s2wgN-(i?s6K#A`2?HB=d)IZU`+0hr@))hssLn(J=SmRdyvw zXx$V31(m}eRd&ZAyyt_n1AY4Wa-QRzdus)Re?3D4rc-eylH!LdY$ODFmLl@PAB zABo6SOinx^V^kp*@ZgJtoK_cx)IHi5KMg*69UOEiViNUhdECBX)dB0Z!F;4r?kIou zslud>)KJ2=%;u)6)y%Vg?X%FO(3K^W{?+x*a^FkFGjf+oCq*ka7rr~v$e;KglF+1I zZei7!6AmRRD?VOUvVGFL+sgg)g<%6hP`*i2MupAXo1tGjnylY4+$+sww4jB4+QJoKV zzO|ZN50kq6TkzR5AgX{B@?d|x9C0fm)n_h<7Q2@^A_^y44`zboou@gb0E$Tigd|wD zeWgA+{UdIdz-ykvW7@6`iYEm_3TKH@eEfUV1$VyYCX=AdW*ZKcg<)KOJA*6kn*=wV z!Oqo#U#nQe1eqr=)J0n+6XWn{9|cx?r2!pVjGS2-Fi6_HO z6Pg`=?Vi}o?lp!ZJ`>)&yCN1*+WhFjuePtffyTl{QF*S%->N*fBW!yW+xRKpd}&un zDna?xDsax1j;9tM%O~`)>|9iS@2)^*btNs~(V+#Z zb|XF&8-E)df<|m%`Myl4GZ`h%S5a--ANDY;G}AD``jSdjq{PB!@(l8JvLV|a;f~@Z zmCIblB5LC2lrdt12FRLfc$1iYe^}?j%3jJ9mvqPxR^cwPT6kOJN+zpcMlZLzsKd0; z*R3*#h2Xtx=1^K$QBHB!`c>}o@-#(t@Y zpB+4MbZ4bO0arHbx$Q(hpqC`^fEB%ceblrkieYuEgb9CK#)F0Pr4zQ3RU-x~f(+Fb zHP<1tonu6En4H)sfl}hfAYSd{fcbru&G?Fe?V&35>tcOba1583EBWZ+=f4 zVKk&)Fn-t<`QQrda5;$&a^fL61H(-hR%e)vc=g~9YJ}vB%Ah4C7d!@o&InQY^++c# zGN3>{6N?ePEKcIiMbD1-3|fEIl%`9G%}e`=HvmhN4M?RZkgZl&ve(PGfppFVmFGv@ z;*jazn?nu0-iF9sXXboq4H9?AcQQJ`NUJJt1+-lx+A|?m+1*}LqFe4u+qDL=cb3BRN9g^MqV?|Jf6&}`$*dLF5!JWO3cr{Heudv$Bd+5 z-YAzL@jKGU9TC%JS}iH-&duFr_SWKBrPubObw$p1$#%`vGBL@ML&sDf2Cwilvd~~s zdlHp3#q?COyQaPQqxKtLL5bYjWzUd%^pzI6$u}&ER~B-5hw4sBjCZLkA@vPs&*ql6 zM6c*o*^Mi!ti8@YhmD4&_|8##l^ByJ=3n2So;kZWJaSqZT)2`kqYN!?26pzzvvmEM zhr{cgqeuN$_p(KHeNKrs2aIp`80}O}Y~&*rS&(#G!lm^hMDG5mCU`l(8(je7(+`w{ zFAPR0o{4Rz;02hSJ9iH53mL#R3^UAvdO>vGItYK+8}+uUnQwZo_qTkZdwyC4jDSm% zdrIE_A>l-WIU$IW-2>L}Q&8f26jW_99_?}V1)RAYrkv74SJMS#HF7Br}&WKi_rkSci6p?WUg-`ad`*5bs={X(yAtx(d}=ex<_vYr^mO3pCC&OPrGp_xMs$+a~+ZU@K>L zD=lZgkmqg0gkz1a@QNF@8B-C9wEM#U;FRmSoZ;$oeTJCH3JVIi<+Fw<<7~ALs_C1M zDfy?Gp7tEdPPZ{g+X_%@R^hay2%|>>;~Y!J=LwgGB=zw6H*B}lW4qW(u5w;(!4bQc zmuTjGJA0nUFa;X2RtS=f34iON)a**5i77C*Zoj>9_1CSJeeh(Rgc^YMx#lJsjCkH5 zum0v}eFsN+gsIzf8R#{fX7ZTzPc!&r5CWASI6KwxF5K?!1Tmwl+|O!Il#Q+LoBI%t zsPQ=~J}mA8AmMi)<>ng*@_6~1Fs7gWQP@)FyV$yp^PO&Pt=}e+hO;HcjYjsqW`4O~ z(Wo^M5Tg$}q!Lcif}euEVXab)Yit^i=&3o9giE=Fn;Y%BqhT&ewI_UUKkat*Q|&yC zfjNl(Qjx*UiUw6)x8_Q8dIMec?n>IU62Se28ycL$Y~~CIOp)V}dZFGcP7VeY%nOY6 zy5ZCGym3~;ztn6RO`_hlPIZO**X_Fs;bb_FnuBI~8;MDVn1k}NNyRIUIm@0qziWxRxzlOe!M>&Zvkq`@+`tD%7 zwe8|N4vV1im6fgRJPdm~9O;e?x!gFlLu*z);BAkN&U8R^qCqNFHn(GLdytd+An5N_ zVH)ZzM%RNZD|maxjn85$l?mW^RpwRObHeuO8l67 zk`-slcP&wCi7Zbnq#cM5oXMnUH(jpy8@B`WsM(` zDx<{|6beP)mbn(&o2dT4wiOM$cE)M*lp+ePr$!5WXsP?#Q|W{F)gI!ueBDEd-5Cx1 zFi5>bm`CNd7B*qE#@n;xatk`@gBlv{+ZKJ;N-%au$^faibzZVTh3zdsSe#%FwQ_uz zO`Q_?K0LYUnQLCpg_8`axM|d9yE#*5**C~984SV~G9z58hqR4c5vZkoOo@^3AEV7N zT4UgE;;-Y<LlCpm&z}m1Ak-e4nVThlL8X2~t_GyZVB>doKOcNpwW; zjUAGfEHZ_G`393_0g>K%cfI@sDZRGPX#^F> zHw=M}B%8;el8|?x1iR}yCkwl6pN;p-CSQQWteFm_#_Nxlu8$b`)u==QIAxCR8S*8S zx#7kK`>Jo=7NNq3roOfJT)H*>b_kvOTMU{kVZ*F0n#IFN?dh)$I-IMZA9;i|i{wsN zsEzJ1zvyHU7r*5h>E61ZYlfg$D>tW+&4?EL%ybl^?Lc(9B`+odycg?ty^8vLTgCrG z1s?09au3=1zVHq?oBvq1F$S%^Gg379<)B{6Bscx_Qnc3kFAYgaE>Eh;*boiM)vDb6%(g}6 zE1k}7Lb`cc2>aFaBjB%?AVrVnM$Cm8|3=4>$Eg35qUXM~>K!}v;1nb%-ijYOE#e$M zmdRX=RWbNf{T}Uv)mqy>$}OAAMJ(P&J`$yeavT5(zkl~?U5&Hx+G=m1((joTF*0td z4ywJKtT+)LxoasQ5P-!WInYC%KQ?=LV-Dhuj@HWh@VYirrZ1}=D|3i3e!ZSK$xD+t z*AmRyh0{BYe$R;CNJm%+YygP|D(n}QS#r!wVKL(D`#yaB3FpCya54K^LSG+ml42#lf?>-% z`NjuFiQ6$9CS^&h|Ur$Dr1gD&!c@Grc4I2t}eayf)F~8x5gVo z^o&)bz3=Z#Y>7n3fq9IOjyh*A$OngRXN}-i8jN%>S@vOSZ^jMIUpG5ctO&0kp6YNJ zr$=}dr;qcZu6|ZwUc~8DCKm+>s|7x{?z);-{f?9R$s0ZnDw#3SU`Q$Ne)awPK64$iaiCfn7--L-EP*^%=o!b%isqZp2n9*pxp+9&S88*G&v!JpH^{j0UOLIg<8`3M67F=@EM< zCd>_O#$lEs@24xp|8Pf)DH&|~F1TBx%c=;u8U-7wHMH*v`fSO;?5tQCX z*0lXh#^k6Z4HoG&KaBNjtYJ7y4T?@MJfV=KOF#!Jl!sTO<1jk8nE-!L^^z~;xe$WgtXlj_oXx<`1+ zHOyOSeZc3ZulIS5&$Sx^z152v!SlYDzxyPoxu*-xsOw&=mJMNg3)fN+!y%a#^LY>W z{Fb~<>&rgW37W)s-^Tz!aDRS9t}EA3A?dl{?44tUU^2%3!|lqG-7!11*JUocM*hmz z?MF-3MDV+p7?L<-Sq+~#p$^3kip78bPoDd@Bjnih5?c^`AjYab-Y9@ki>wlZd zB@sVm&Y5!@P=et%Zg;6wPRyu`@4i>eU{^gUnX0#9H4`iB^?c?lH}6-|DZpYqXXn)W z-ECJowyu9B5#=_7UB7cr+$#{s&3E~(6kC3dyuxu5!6+sZvbX%B?M0&{A`bQZG+1!a zg&>Jl?cP}6cu*P>tf`rp5+dnf_;qc?tHo>j#I%!4dbwyE%XC6KF6#J1ki*p-85a4cer%ZJs1u7*uR7g6E zM{TbrK+B7f>yIeIIMF;x?A%eUjaC?zAJK?%5eX~R7+I~Dn;dB^UPpc@bDDZ>^``vc zQkQSiw5;)7q?TKFYK>j5ba&;Ft>BhY;Fz&h@#Ng*{0?L`U1-=KN%Kkha$|lI`@G8< zR%Bc0hH89?#M_N*saG)nR%NI_-s^8}8}-kp6LvWcd;3qswGnc1-C>+Ad&&UI1N*-GlpMUT05^^wNCt~GM{ zy8j{T}wu8csa~~;j!zb z03e5fZKGw&wVDy{{y*d^?McrDP~9|Wc(OsPr&uyr(fWf|_Ix&EyNrJm=VyNy*(nvN zFA3Nmyl-2(X4w};jy1FP)Tq`mY7{Dp3KE`avB`Z6Yi}KE`ZV^U{q)DE1Du;wYj%b# zCGv+s(=q8ig_}&?QcQ$@s>)D*6gGt2MmqNF&)x=bEr#B%Q({^}9M-(b)Po(Pe`(+> zVy-r-p@(MD)Oxy=R-OK#!XTBLy8Vte?8FxDVdKR1k3~-Czgoix~cJxeupGW z03};Xo*$R_^e7V6oXIkTmyNdmOEx<0HuPF`zWOwve)Xo``Da@lgVHr|kBH~-s#dnw zlI%a#2JTX{F{k#WYtL--8x#NjD^>uzKfB@bS)nNmZs;3)w3r@XC{!`XGUXd|Ao3#Y z4z@}R8G#zj_m~M_d%As3EMOWVH-st~ZaeZm^`eN-Hcqb`bS9UiX7q;MusvR4)tu`? zvxNDmZE7I8aZZIt=>f+fI@hiC-&^}yd@946o2_GrmYW~A4~6a{H;a85tJu^BBN9Lc zZ=FND$8!m2q454H3T#~zMUNU)D~&m*eeTOu^`+EGFf}wtdDq|~#B>xjjGqU3Ys6n_ zn9;l%o#bE+1;X%S#?;oeS7l#tRv#U3R-Lo;Cok{gm#av--bDW4yjPE+4*4_UN@jxa zicEh2wUWgulKL{Gk6hB&;(rM;`a1iCg$w@>0ZFE=SEoRq^P^VCggxX$lSxkJ*GpBH zfzL`{Gdm3;SI5{Rk;Sh*R?!*-8fH7jcahTVCM-x#yOie<*3LIa@`~O0bzoIS9?P$a zm(H!d4l7e6xo<#)MY!W9VVe~5*-K}LY3(M1S3YwK#_9@ajGWZn%V@IeQMazq=0=Z=Hbbqlv_mI@1-e=u(xPZpl(NpMc| z(@^=<$KHF%Rc+m|gZDt+G5DvKoGdck>D+RP&4?4%vOoA~e8_S$zcBE0Ti21i_hVUs zyaz+FDzbQm{s+{%!Pa$cB>UJtHMb~8!<$7W?e8+0HBWD&YWVltmNapGDqNbtWt}Ef z;`*1Sv;|!MyO2yo2$*AL5;h zIepK7+@vpZ&ZTl^cQBnlyCthTq89y_$5*JH0Ji-)EB-p=`g%`;686N$M!$Zg?RBP! zm>3n#Bm;q~VjxgP28=xh{|FYHIUJGx7*gkIyZV0qo9N6x(WB;nUUWqY77{%%S4&Of zGUkiVqFkg*M;?!7Kd2dq@pI}lIyn{5eiGfn=NQ3KAJ_E9P*gHj*llBtjfs^#z9+K} z{t@}Lmngg0s0i9ZPw}PRyZ@H^diA+3v@9rSUr$NkrKtyIhx4wFOz{3jhRQL*gTl+5 z?q~HO*0k0M?Vb*AoO)zAJUi`FQb#%8;C=kT>d4UyZC9e3zr(n+cT~V>OKB83x+#X% zh}%SFijP<$;nG~Xlq{IX8&C7>#dJ5)Pz`gX9SjChpTpMVgXkN6{2kIP(RsDJi8}8O ztYSyc2PsMF#GB8bu{FFsQNH}Bsa4cuMcMrQyxm`B3z&v^vP@gu{9u01qMbVWHtV4ZvW@YYAUDRk$Kf%n z4=`$hED{S7ae>-6 zUA>=}CA$22XZ4yxiZz1adBr>#twZ)q_e0LsmgL_z^6YDHGjR>mJUkgiUA&Gu#N!Kp z*fHFb>0lq^a(H-MQRknt?odjs&2E-)VBJ=V7BF1X9&U9}xv#(dq}E4cE9~vk-k=`b zeCy=Luq`%$JBng#J2RLm*a;p^wEuzb`aiFCu$`Kp>|J4(f4w1u99OFQddp2U(l2i> z6zl!_>H)vu{06Dbrukog+*aorO@y2=$K|VQ zAE=8fKO)55wwfZ(m*kx*D?Syt^*0AzaDtBM$TO|plu3ORNVYMvv9oZdT6K?WtFqM> zH5xu#WTeezR7+5KQbr<6*)V~+Ubr*lcSq&^Z>#m}V5sTlSt4*~hhA?yovf=tCpeg| zGX5SrGv*oB$k(+Kq}y_AS=OqRG85t`>cXv!D~x(CwvIm|L5_`3v*V&3Ha_Ukb}$Xz zo(1b;7A@r?cFYrp#Wee+Y4_pcE_v7Nfb>61qD?dYyyW9UH zadVrU|8tr7CFR;u8?l$0*N|&h^p@!ARGqszrN>gpahwuup4-yA7F5@ARd~4ZshUdq!wMxr>Oj*&E-LDnKW0CCt40k!)G5KHc}=i#+OKq zl^8PL$$>ATewrmowXw`FtNUN>+suB^$4pYXz<uQ$?S%gpN8aAZt{D7`W zKDBxO1A5%yI+^1-oFVDjwL5)0sODI9JO4ck>8)y8Bl!&jzO-TVv;No`c7@^tu(s*i zig{xHVOIVAb3SCmlRaP@(r01#+ler>$8yljMX!HfM=s4Z&^otJYT@nP{v)pFUlYVs z1zXGvMPM}+T1-|%_TJwbOT}g1@Eu%y>PvIwouAHuMg%qMuQvnxPgfpYruzKkk^fdH zBje?m#jZ7=#$-QX9|HXVl(g?#lntC<^3xV)*Ux zZC<078;h*usRQHE@#u)%>Zh+(U&M;(l>Nh#g?kANN}6$xzhkW1=z!jyg*+^|E{-@q zin^}# zH!@`UcHs^i*cZtM`@ZU#HG(-QkUr#9CvYUEW)$;~uzt;vg<&vID9l$+a>6luWeRsT zR~?d0hu;Wz#`CVBxQx)J)r7_>IV6UKO8kwb=zd)d6>jkpsl?RnAG1V#q9)p#1DU}O zAQpS0v>wvF+>-Feu}>5i6p!Z`PvZrgHGiTz2o52*LvWYi65O@XCRlKH zcXxs`9^6832{aDD8`nne;Wz)8yXLid=YG0h&RPwtPoG_J5@0}GFQJOtr7 zUx|YCn*(`Kl4TOh=VJRyVv0iGC`Oq#cg+Ty!hEVpAc4m1)g_4IA@-j!i>AZXMShmc z7-NDji}sKeQYYdZvys@v2A=@~q3tY#%;4F%ZlB3B^D%mGw>bIA5}62jmY@d}9A(wz z>Ni=f0A)ux`TIppp;y{uej_Z!QB#NM++PhsR!}b516*}1{D z^Uo*)AyhB`5r#3)O46DLw{NS>SYyO=3I#nMs6hd!b#j5^;^(={9X?}VRMh%MgT^D_ zP!-dUZ36=0BHn(uC@-E%L%BZ`m#WeRQxRyr@;&lf`^!jltd?>chkAHhsnhhnir5;C z24G)`O3wyVvq=2abns!i@OU$DYt%yv{BxX5%$gTBh0>`UYRRl--@Dj>O5uJw-}bmH zL9jyTR9~TW=Mv$|_m73znPIYd)n(2XP^Pq#L+vnJI)*+4`~c?64iU%r{sbpw6?h;;z;b_z=U@3`Hy=N9!noo7eP34q-kZ`5OA0V@HHs|h$w#V zY2TB+kYItLEEwcS4OHmK20gcVT^G0X;4*^ks~(au-d1x z{8)C8;dCy%@?F#6!Hy!}UT^EL$@x>~ z|MnfR$St4>&hcBRk?|HiOdKA{0l_hYWE=YFW z{prnzcE!V%w7h{RQ7&G502}fk)Z(#AKDyJ8#7-n94UCuN(&BquXACCGL(QjCtbUhl6b3^4O zR}xeAf*Q?Rp2u_`iRzT=<5_y6NJ<&jAThv*L##sihPxjsu0`+n&saK(uKFHs#kz^n zLcD2feZ8UeH-TN706MV58cv9`pQq)BOuy9W#m80gMI`R7$iKpTFd;FCDMQSs6_oRl ztW}ztLo)4kz?vsTQjxW0^`O9m2YQ=gXV#4b^onC`c9mP3Sp#MmD1i%tSnqP*Av5Ue zPJ67_u|2bU@10(RACI=bW!uoJXYXS~?`2sR%7&_qY$6}~&jUly%EW^^5dC0=jlJpjfD_1?lzUp$)nug%y6 z6Ynf4Fz&1PMzaOf&n3{sAJxLza6!b`{(t4mvGjHWtQvM^BlpiEcdB!me|VogFTD&U zIBrpwk+FR{k0Tw&`GU#d94|Lp_zlvsj?i``%=1>e{w;=1u6mt^(M zx$9-0E#(_sZtU*RTc4{-Qi{O6>$bu@AvP>75@DzgFvgjXnF8mQgr9>J`y`p~D*Q!z zb>~Bx!ec}HGE>&9#fkn_ZJ1!&AMfFFeYCR4vKZOFE4~kfY_^1|Z)%Mn9OqPA3@Uy` zTQ>!wsSSxrShelP@ip&*wArorW_GpXRLm4U4>V>LZ*91#95TOqubw2%+{~qVIq4WX zYbD2(%TDpfbtx=fK3)Npc`g$G9$8B`K2O9o3iTp0s=>-c!9XefCSvi&|Ihn$Ez|y0 zV#X6zu>Z`v7#D&Zm>#DlZ?kq}q7joLg#aX)(+puG_eFSu(w~B$$u6Ik;Y3#)usxiC z)LTtK+cC(R5Oi^^Vf+(ffAw*}bEoaCy5m;f3q~PR1~tc-t*f)`5}8tOHO&fW7iA(9 zY{r^9Cks)sW%F-1YM8$9jBVLGN4idhg=J{#@-5-7BvHAS39-yy)Ycqo{iS1o{WdZK zIwZfcGo~^<`_5Hw(<7K%=uxrP73a40%C!4yCvk|ESM9Jtk(?zFy|~W|b`xh#?L6XYxY})< zLAtF8lW+=?Zk~=av;tgrF^rK(TfPeif$w!HOI6N_{)9Lh<;Yhz-;*jgxUH8vv6K`10*s+|4z%FH* z(bs&YSt&!lY|@C4K~mThF|*tpFm$<2{EsgiD-7htBz$%9Yz7H127@%8IaYsJ^8x4+ zW0>o@%ds9{8_Sy4@t9^UxBWe3Sd<6JECxkh#t1s3d0|KI0e(HTy07%KFl~oVc034_ zu=Y+nF`y8N&eq{bv0vis+S_z+sOu$9s@Zu1n{$-+aqd6?Yv~RMC|AJ^EG}48kg4m+ z$YE0U+px$ZCbBtiJt6Pw8qT^)rFkC*Y|0ZLkMT5CZ$j)u2gLoCXED9eM?xDZSuOa+C&lYspGB2bdkK)6rnxAYC887VZlQMdcsbwy1bzU1+#*ke(RT2T+_! z=F8E~`?D4L4U^rX$E0x(3O?^|!pF5Lg|4xIU2K;Eb_>3=NRGk0`^%X+rMBDTb%;ul z;p+Ak{Gffl2&-A^16Sd67H4CbG=74aYe?=eZL}B2F5k60d2$ldt1!qSx>t!R|K(K? zYW~ZqWmVrmt=uYYhECRwxNo$-_5V`Adgy-T#F+{?B!#TvjmPA!M?Q1AVt z-WX#YtCdVE)(u;)!$Lu)w|Bm=r!z>)G5ho$rI}%+#6xH9iWa4oX zFCGzgDL*aXnDNQ1BqQWNXdStX5_ym8)Cm5Akswe%)ur4_#0$r==ZPcsjo z`f1UC&RBT?BRfj~%(z;P@N=uhMl!ssnrGP~=fcl=eXfcUIX9&Cn{HP5G*{XNu@q1esK-^`0JCfe2|QHTY-HmEZm&rdg#rt zsvvb@MSA#O>_2jvzn2R>Zj`ltbwJoZ`r%m}AOCFEneOZ}2kD=ODd{rJ6AsIfL7Ojr zf1_i1^(vW-xKJ35VY#+HT2ySw8DaZ#AM)Ks_lEqe6W+gNdTsoy({R+S7QtnvhfMdv zDQL%EYeFG|v)OB+>Wsvdbb_w?BGj!0g#a%4M^(o4WZrSTd2>mzgN#$VY^K!!{qPOq zenWZEI1EoyG=!^6T^SP7B73H33j*9KFGVkilwa=q10nn6qIeg?4FxC=h9>FeF7sMV zzNuzZFEiF&kJ{I|N#_NoL!)#pD<7N;Bn3q+-FgydshFk+)Rnb;K|&mhR`_y{vIebs z%ejuFegm+VzS0;1oXdAtG3wR&nd|6cmJCQT&?b9*#q60!r54v7nU?Bf+hjLmuTjPN z{dtL-_HPieh7PGtT=Fk9S8uZDu`-%Pn(SB39gycb&COF^ayu=H0@dW8%U?9zU{CL4 zXHA`1R;u^SSa66+h-C6RI z2{|cYksA@Ts78a;D*w{$xmB5rq6HPY9-?v(wpP733k1^eJHcFz2DQd706%2~fk1A) zuSB=GZUB+h@#VoZoR{}%5bqezuvY>*-GG2 zEbQ`9R%~?;T^-KZ-QvBK^a2&WMFI7a%f>pLwrd5iRdL#AR#^%TEAwYwCKatOf_W8O zM|>p#K3q=qsMKG8;X`CCvD3WSG1-IWcOru?%99fV^l(5ikm&M+oLOUgp381Hnzs54 zA*SFfFILdd%r*B)*gB&Q$~snE9EXz@&pnHG(6NKCcD9GpSag#WtLD7!*sugyZ)L z6r(10AOipTE1`M8*z|6BiBB!}Gghf&lpx0QUb056myuai8+)Bml_V2JMqY^JTUP9< zH;%z(<27Ibi(7xL?!mO07#MZEy>d3tG{$%})f)b@W)WbVd~bcJTmPjbs>fZ=!j9$Q zvSE*$Owj)Qy&RolY8q^%osm`v1ZgDnpoTGISA%_RQiVjX z5~HXcn3)=>jrOC-5b!|z+HWgD`K)-KI0TM+u-mm0qIr2K7Y@RFyv1iVG4f;aqC`MG&?P4E3wi@HAXmj%d*Y3cpnNb5-w2aQ^Ll|x6C zEV_1=@J6}8(q}zRK#-LjV50SD?iHwTlGkuk96M3E0xCzsCx?}@GL$5SWl;;#JEV}h zVU+_p94Xv-3^>k#)pU=RO9?XZkgkJMWd)$0-ZItaye!Zv1sS~SemK=m43pXag(+xz zNCmT6ts1@qPg9amN1QR;dC(P(#PIZzg9I*o1Sa>@!%OgZLv{MMpcWvva#)k|MRytsykOcIl&5BIn^oQP`-}f9^@;PfhwO1Xnzd`ExM7`9PBxb z`AOSrC#a*2T<<&C*f0iPEj~4|Y&YMcni2}Us}K?B3Cz&ipv`@`!7FyT(N^b{`jpMH z*pNY-2@`oXGq*wYN_qGLYN>W>_qkaD(Si$PC^WNkYI0(+^aQjOL>HOtGqN)FkSl_@ ztyIPSx)*dQ$P8TV{koGiORG}`7 ziE;c6HwhwkQEL==s#fszi_uMcxQp^^wGd4g=C3hSaX zDRn)gNTzklV45YT$|&nqpFJ$@Lz;N8V2lSya@n5`TQ74Fj);hev*O7mSFynx9R|Ab zlUVAH*Z$`GMQYIHM5}1InAJNek}wkG6jcFRqp~IAT@X?vT2_oPl%Z_Bb&teq!1B zkeb!VA}G1|mm;@u#dp)_0PE$x@SYHvmh`O)h-LBMrk_Fq?P|fNL_GABSvcA!-@tb; zVH?2qIZ=j*uz*u)sUoB96h+!*7+83{4-oqpcu{yZols^bu&sTlP~Q(vR6#aKUJkc+ zSTRnwHVwDOp5W2My^IpHU+HTIe#7|nn$54}6Ew$`=bEu@_|u2aCf`E7CBj~rGKQgk zx95*o8WJaaG?IK1|EfHhB>@&@c*q0l5Dip!pGFrc9X6RkBg~COh7X-&%q!$zuv>=m zo#s6}?1lePn554R4TWbQSiwv+WFA5vgnSu_!^1xU81@O(rp{PL_DV3dntz zl!4sK?y3t|U4ifYtHBDiE%)2i)qT!A%mbwx!Io=jncX4gGO47@^Lrso@~j1+`zhY8 zR~uMweV;VUn8K%cHWIb*1y0T5rzyTaC7nCeQw1aMt4kkiM6ty($KUw3D)J!#rQZgB z0Q|8Wzz4FE?zSSMVv#|!^-7<)Vpq*Nty1mW-F7m1X4-`=9=||=qS@YRxs@bV_0apv zM?a`d4w?!nzj8mAudN3^stoH-@NT$-sdZj;@u^j~VYxC!ZwUE5QbW6#3O7@M#*PC9 zTydX)Jd?PX0U2VwTbt~8j~A9#bz={90!y72F=^O@-y8d~JQ#Z>{ot0j1*r)@a|vTO zmzqW(Eewrb%PX3>LP#o{P)`rdu{Y;*FHv^##cPe^^OqPqRR*mx^eTn1h(h)^tilf~ z){pV9c<9s-wBsGu^H=X`|K@kC!t0a-g2PhSPdC9ru2Ij~X)#~DQxIJcxE0`QitUFr zQrzf;wnlmGh1aqDe2E%DB$Ufo&HMZ}sLy%el{gZjDbj&#;`~6!E?4xhbzf{~E5ZAh z8ZYrcCXnYWII!Tn$Iq#}nfqR-mH08r=iH%v4DDV&tvrYBQVTT{mL#*A>4t^`y?;o@ z6{E}$yrcc7Oo3w+1F}7y8ME9%IJ9oqve@vv#kF_6J9`jlp*{AiofrpG*I%K#WdIH_ z(<*>Vxw-<1RDsg=NFNjosRUcD1^VVU@BSDB7N?z0{*EB5U2L*Lv2BmXSGQO%By6-W z%us%@Y0F$Mw@et0g(xXkFz>k0(%}_j9PzeFzk_C2{=e)z((BkE z0Q_sw4JiRxGXeD#?a8rI?w)_XCn2OwE|#HEibw3$EuTk7A)(52X+YIVXLS5T$kT$p zIhl0gcPaz`Rvg{cnT}YTZ|15U$8+w*m2ULFqj%w#Bb!?941(lRFt6UTgw1b6CAtJI zF)BNBPz)VLQ@~v8|Jnjr)}~6ORH{iN&XJs>H?WNBX`MI zqBH6k`3NTSJF#nXXp?1ba)NpQg*?EYAO(Vq?h(A)B(IwLmzfh^!&cy6oDQXZK@KW5 zK=(m46%zceB{$jU^cuHTv#mxq=u!9pzPaj+lmgM2`>p_FU(`K_(|r`@8zJv(ZtvQh zLeUGXL|d!OA7h!j(H99+dmDuzV-nADAJm9<)zdhSKL!l(67(w?yoWX<6%d4P%Tx-; zy)r%}yZ3~;qZZ)mLW!P}cse})0t>({^pcj_q5evwBQJT!I%VPyzW$cQX}x#lgp24j zKHbwOVnO`&r^DCi>V?lHoqJ%*Df7JN`LrJMOEKMPk7jb@bPbM#l}lTbcT*wakoR~X zTG(EpkjE*_jIC07nRlX;9=j`l-OZcIjG9IB*Q_eVAA0jw=Gl)>4snql0HBXjy_UVQ zWUWEFTDvZJ*+n!3f8o{!XtFsVS;9bU(nt6AEY?i(=+2 znH!ZHv3Rs)aW{DNj__-b)WQXxaWCOy|iuzqh`y~Us}>}uZV z9tDP*Sd5DEx4)Yiv+)L#oI_%v`DbIQRrgDYo8g(=GnN*mw!qL$jTZ8}=&rrnx=8rf z{BMvRT4bpFNBbpZl2oL%fni81FJGHZ1o6166@q%Da&(n$$5TQ-$?ct5}+5&=5RD$hdn%} zkxxn>!DNt#U5VHP^FV)#EF3Ml3)kXFV5^rO?{1i%A3xxQCH`#sTmMJK;FD*3Rz|1@ zhglnEoo(YU;ZqJrY@`O16lCW4q+i(*^g(DOA7o6j@BTx8!9tfm-+))L1$-DVh06$P zkE>BvCaj*cGI*z45(=0u>3z3M2iqW39aBU8Ms>LTroE4*2)KCE;IJ;h&z8nID9Dqz zO8xR(y%&_N3wsQmtYep2ZQDS*T=I)7(XXPO?W4gi?hrWib-d-}WaQ+tYAbr;mI%57 zm|S|3jK2Tka(CYdN>4lszvL>_^qv&ftp-VAPZ@YBno)IXth1o8`?v^AsXRED#68er z@)X_mk89vughr$_S~|g>pyI4P<~O?$@{S{UG_Y?x0+KEneoDScrc>|JvD-24+I0{Fa7@(5@Qs~M_F6~gWE z8_RX2_I{=U65e|IvSg;u*g${qDG>ve+N!K{esays207r<7j`{61gfe#O$K8q*z9zH zG7rX<2Iea)bDIyII9E46ie9b9Qg4!iuM za03oJ*O&q_Y0>RR?pbU1UMjVdTZM4FD>*>LPfW*=zIYuCMHMg3s&3?PLmO=JZ+f+o#9;aoh` ze`dKNF*ycLg;NSeiqt9L2s7g zuXQPZ{stdC@=Nb+ zP3-4SE`+5x4Gc|yIFOGSW2jQQ@f$dUGpWq`obBQ@y8^9sDBop?BHRey=g*c7gJx-q zHNl%@3&LNiwBAFHBK8uYdEt=k`z;!E#(VOMH>_c0b5+%4)X@$i=^9$i2F#az*!y!m z4M)pjgHm4~4lFs%J{xG)fuo_xM`9nk_086Q_v*n<&4(_r5^q1C_esCum)%?74FcUA z7da;`}x*Q*Ru{mNN&2Gd0kO0ryWlauTda{o0_MUK#EgvH-ddc8pbkU@e9LW{Q zglhzGTj-sPOdTc-B+uc4`BKr+@qa(WJGvMB>^PfQG|<-jzSxvT5QY==P83QH{t|7WBi1#ZEjz^& zr4#11V%JKSsY6l-VB*N@xGUv|(vjnKUa?;tJ%#Atlqc`-YJfhUs|XF-*bER5Vs@z4 zam_Ygrza3QG*!t{lQ?$L>-GV#*MQSa-)s_;k+YK^*J~ilC7bU6lvTn>nk?wLf+Vh{ z=%R;wPHxcfb93)oCdvxYOM>T-!`LPyjM~BLDF8E656P_#BvGexJK>zJIvBF?Qk5(})M1CDRsX3I5Xv^eEIAN?Js(~Daz!Q7C-$e~8QnwT#=`#M>fK3A#* zJD<+ArZ_>I(`JN)J$|UDZM6SG8Aza>{Ox!A=)NZ2^zV-_xP&>@eqYTBCdWRjp=yo; zs()^JLpQ~TkU3&MSqnVk*}49OSE&%4@Z#9X6bPwExan)ZaUfSk&MPRXGDAs@cxxC0 zOsP=5!+%{`<}hGI<9@bLWgg8}@e><%hPl-2BfHG(c}}dRNn9O30lJoz`nhKV;O$WG zZuC5{i{eZfCS#6TCXz0!HyeQHAs(ord>|4LEPs59`G)IDOW2ewR79>w@s0e(_bVd! zCdHsvcQgR<=>Xm64XmT?HGf_H&>VuprOnC1Y)M0C&=$Ft^E(>V?~Ck5rN%eSrm1%; zvPkZInE-;aX>IEdvO1&6xtt<>{Ba@qM0X<(U8$%a$$1~qTLCpBM08r%W3Hgr0@7`j zmsHvh8$8n?*dj`Rn_{y5dX&1;H9_An=cW4#5&$yGHJU0IixXUZF-0=E3GK2U)D(O@dAu~+CLnT zeU_|qVr@Q*pb(1!fbO>tQ38#XR=iNJ670WpCr8_3yJ;yo18iTy)R?30~$WaGdb~IMSFeQ**Q??cG-L9e9j*kUH z+vnvDeoh9uBUB$sJ8ouW{U0_@s9U_YvHBt@vZ*A(K7{v1Pq2ty>|c(O2EIO~8B+O7 zK;|lRh|9R-;8^#y>w|Xt1&TyCZh&F)Md>_x5~cF1N{BA5uk7?&&xjP4jRlJJV6G^d zH^osDg%%U}@cP~`Pd$7>(`x-@8d?(e!1t|oGb-Kkzq0Es4w>h$x$L4+edHKy1YGou ziStCUAOLdQJ+hbaKikt10I!JDOjG=CM@u!R!RCe?U$Mg4Y%m_0Jje+0S;;#~5HdmC zTWBb^&0-S8x%#5oS*#r3DLcdT3uOb_>add6TTE%IOog4|-t)4)T)Bn;D?EQeiPGB1 zA6x0k|Nfj>_cgxvK0lgtGWXuAKpDS4D(p?_E!s#qLD_W7I*?_5YC3oV{XlfPJ_rsv zwL~0^ud=BIHcZRy5t0vl%m&Mm`s%N>%oc1B;lH!uBmoNU84eC(!A^{Xqx4s41T3l| zv$d`RcIWYwvs(ATi-dCc!mguwdIvx=;kkNx(IhgX141eT^Yn@*>PVGRC^l2Qtwu7| z;i7!{W%dc>dG1!N4$1$*4PXaSIkL<{%Yan{6lot*?}r(t>+ahFd6(f0+0Dcj@W#5H zHk+{!8NWk_abK8ey1Ca@C|?LO3QLIVK9=4V!}Z-4)q*ZO;+%k7f{L#u6f@ZQ!Q-d$ z$OUUW6)o0?(_N(KCLcZ{c76Bz6k}Kcq2|25lda7z4^^Ae-iKyk}Y@cPJ zM$=ovTj888chQrTL&~-G56(s}xb|C{cYs`SeN&QS1q}OeE|YL9{mC26d9R!|1W#s4 zP$vnuTr3S{Tp>34X*zt38z(NiUy&C@x$ZD^D!q}!ZU7%6odmYq1e74X@cZ+u7hi?I z-Mz*Xi*rr}B(&7Rn;fP?%$1N9yBwZWe!EYY`zvC%-|h>fW#!A9^Pc)LH2!m6#xS5x zADU^rfHsB9P%jI6i;e!sUFCD@gi@`%@m~GVdM4H1N~1EarN98gM$atGEQls^tY#H> z%%EXZ4D%kyLB}UaaKr8;UO8$BlCjkg9rC*=mnFfi-Whm7(Fy5ARvsaPiMD?ePNsoc zSMqawCPx;{_oRg~=~?{>soM*Pja~zUp6AEU2^iIv)uS$Z7s;>8F3MhM9IyFHRL1q# z%3fx8Y=*Tg1RIoCNc$&!LrZ3PU=HWcb|BN>~VyHqh&YnS%_9`RE>Uw?_1>c!Iynx)-%i_LA+b-mt$*vN|#F zUK&Bvlqe`3)sbjEB}ibF{e=T6schPZPPs+{SzQ#yi$cqkV-$uKGYb z>z8L&mJ%`vh24F!s)B0@F!{A)&id6zNVP6|HgA)qsOolA0rUoa!cTy&L?rSwt{|X- zaUIJ{)nqLM`-Ivo2ajjm-y>BfAU(_KP zlTogH4(dg9S-8)Q zq1A6|vLcL%tZlK(P&p+8WJs7K*p{*H?h`m9mJ z6<+t6Y`&BGYo2^ni3mR)=n-@Bro4J5k{GpgmQMZ-{Ra{?4kbc)GE>1+u-iDckQ_=4 z(#juy2~MB6K`%5s&=CY;V7pLHzf=@{ZWr_wmTVAu=4ui>H84Y}1k~do zas`4F0D$KC7(%zdcNPX}Y1X`RrTUtF^Vck`LOU78j}!@nOQU7O$fD30Qor`I#ioj4 zAPySwIk65L=EE59Kwl2w+g#1F9X*w(2VY4uWO3)~zSbmp76t*`xZ%uLj{nM^kGtVO zpSg2#;%b)&hz@jY)^jpwYnAYh#z_0zgvGR5mqMA!Z{AGXmpZ{jIy?S%27R}f=f}OE zu=v|FuwTt*%DmNPU^?V&i?t#NpRJ$7pd(v^@Mk1e)MgtPzIMx3$3gLQU8D7?&bI)a zi@X%T{Q1$Y(dK;HOj=Ty0ztKd0w4gmVR1cL)-!mCs3Hf&{8lK&nc@zFiPy6fM`y3S+4cTWi*d*E>u{2=}-hP$JX#$CUO>6mJc>UH zCE@fUSBtUVOvZ}vccsgf$&k4+H8tY&@PFcv3#Ed26vyC;tM6PRs(#&l0@M4+07~)+ zDS~JAA<1%?+{Iy~ic<_y#pe!73fAUTYZIzc@fWn6(I@$%(_&OTvXowy?-t!UiMGS# zfbXMKF4&c`bd8H>@pCYg+4{N!u^wTS!G3&){yfUT`HCU96i-s6elAQ(8D6- zv_D&E++B?v`8d&m$99|j{oP@h@U{kKsmIiJHJ~kHq?}kT&vMhqy-$xqI~bdyNQOeK zAWptcb4ZX~Mh?rM;JIc2zN(Uy`iVN9rj+8MGI718Udd zJOudk-)Gzb3^nDd!{}^>H3h>l*l~>z_9l{iGMV`SoCUb{eZ>nr5d&TKIsoc;1m23b zTUK}~d*uYMpAvoFoGmYdTtYSwF4yO-A+hz|EBpJ6lWF|4Z&wMit6&TSBTRHiHAEdQ zLBlRuT8T$MnXPSZF!Zfvu9we@Mg`=NnT}MK+vQk#v8bU$74G&l>#|c3_un`3XLpUt z%eMqfSGfnIAN-@kjga_pEUuNmkZ>8lCwQ;)bE72_$k|0UNT${AZmvrVPg_QsezlYo zMn(YHt>D9A&n64QiRi7SOL(1#@r>5(Fj#yi-F`X)Y?9}W?YwHGu3(v1dcRJF#g9G! z*2ZPO*IB#Pg`h7KFF4dIhpEtqUZ>thT6*eh;lf2a3BxPqwT}IXSi=hB=R%%J+12+$ zBTnb9K*X?}$*NS{zN1MmR-j|-9pyUKoOPx)chOuq`(zfc|e}7%50qYHllj;G|sbKTG zxJ?3gddfmv4{bkC%W@#_;#`Z^%-yY=bz02VByv5Mty9`PCpeqRwxW~W5XCrU0cK8*>_YB(UD>P$29%II`LS=@S z|GNLY|3Zl7wLIlrLOQC&cx#ira>XY{f$14Udc2N5)%T7PB!PfkZGlm(dI@?)5R>mc zKEdAsZyB8cIz_IH8U|r>eJIytR<6);d@u8!qYR*mDA=E?8oqx_Q42xIl`0-9J@#rY z1v(yE$hoO-<@LU$;D=ebYux6rZ!xQ>Sp7^1jQYWWz>Ksi|DycxzH?s;7|h_H*R+8O z6gVoKj+O<6QyIB0h*{H|(Ct6HA%0$t^T}uZJ=bGF8LL@ywo1XVoypKadkQ37Vw|Hh z^H{gljfu-<`rC4KeCjfN%JLI)c$3?H5W&V?!i4 zT@mqe48?oSfMLJDoL$shQx54Ce+- zj1fd+EG2+Azfg^5L$$F^8j+DE!$|;qoN&nk5guAEDwM{f3$>kA-t4Zrl{a1|Hx#?R z9Tci~z%3j~$P~1OtABgf$@{0K<qmC52yVC5shEZK9)_DdK5dBTxK#Eu=D$CxI!lmJ+RND+7Fphv%e^R8gm65-gmT#{1Gi)R@(iNdRX^W+V_V^|9{ zo~UH6mW4X@prJtP30<~)do~aQSX#BNH=7J#?M?h35&++341UGrQ6*N#zyEM?Ieb^9)0*WFyU(Q?IzQj#2xEf2zCZG1SwVI#)XCPlqY09QfUct2qW^$lICZ8+^SbSP1}OP2ZS>$_YO?|HZIf>Bv;b;+L4ShsGp5 zSJ$4WFuPt~oJbHbZ=(x0&r`6Z$c8EP3&z;>yJCLz1(J$1BY-sr|%lG~wxh5ek zW3pIQ3Sj8Xde;$2b{32NVMcgaMy?VZOsuFVezelb(y?Ow{K^zf5hKaLZu^^`O2Ow? zZ}1tQ?*L~P(1=GmJ@9M-DeD?tTQi=kFA=GG5W3WL4V+(cLQZ zAqTyCLSYuKhC0%LSGIxaz4`r-Iq6N+s(WoG0DhMSpISj}ifOo-s%Vx3MN$cEC6lPNQz5(1VM~>bE#Y&gDg7=#!N~=mhFR4hL;pYIw3n@)2!I zi-TOnIP(2dpUZEP`@DvFmxL5N-yQJ2oSDrYIM){c)tBjCoCIkV;%elxheDJsrtzv7Svyf*=GsHMF+3LYpZ z&{Ybk?HGl|rC6|M%~BcKS^_3$K-K4LHp#};QsHfy8`+e#P7l5NcS-0{_AYP?AqBW? z2aeVO1ZQY`LUX%7{CU0|;B|g*!{NCf*|c+*QVVp1&nLWBrjkize+TpwX0e<7+R>N$ zxgGU-3aFSuBK2M$-o|n`g1D4ZGCuvKHea{1gagnge1$K>cpz&w^#J#~kAHZM!&)0E zH}LrZl~{zikp);~PtuFWpG-GqG(+^)?O@D*ouA$ZujgXfFrv8k&Zh;Q)k2^p_oXx? zjP#=o<+Yc@vnVuGzKzh9=d!J9yO#q-wWB1pPrkSicf3YflHX8>tWWaclRqSylv{x? z&p~bAr!3ZD2AY&B#$xLW{g!TJGDH@78)t?$Gi)|TW>qdhg}iBuOb1SkA7art^CZISrPoL_Kb4t4mZ3jK@uT)ED6Ju3Pj}I^w_7y>?#~&+ zr7J-YwMSgV&F#Pe+=XP&>az>r{s3S@fUo}hDZLT!$FhAlKHysub{DD_%0L5Si(OMb z@JxpqLh2O%>wj-pN0STGF*y!kM5^5UAa#IUbTLGzC_8tOADrEsZOi6k16+o&>dNj5 zKJO&gN;R7NBlD}_8ygx&#hF4bE=fQmo%6tu`>p2`LMx1u|D=6mOy?VcwpNH_Hetmx z{w4Uh;f&ar&&(AJT~WX^FgRjQ$geyTal_Jf|CwT2`ls)v!~&l6x{gP{xFF#s&w87KdhLI^?9-m@1|)qx(WX7;uAPgB zDAv?M=oobO6dmYY!izQ>z@Mmx6Hix}c-q3*vi{l7>_vc5S7n+4fbyrkW&u?Sa~OWI z5($zRl%XdW&p3s?0K5Xv2Mz1ew7Tu%GZFFRt#yJ=&U@oSTj|KVqSz+t8($DBTA#QK zr(}h;zBv5dyM%zV6!wfw1<|8vq><0{>2^~Kn!zXago+789U#MfCSb>Kt9cuktiwpt zg;j{hgq6-R>DrFJuESa}{bReur?jVE|IfEHT!+2<{-Riplj+$+B})GkCPugdu;-9A zeft85*w}Tf_1}IB&oKx0PvK$H!m`|v`1*k#CIt3TAMmg*+)rgKvBWhJxr4Jip$dQn zx45;IHrb6RW@|hD%;7i}5C?uu3%jkIq#knL6Orzeb6*?k)$>gc45o?Rt&~W8dj8^w zU5~ycf46M{5W27nBSJ4=|Cg8-Vbk=0TP1I8K-NE{&99txyX;rl^t0@AS?R=wsVyY- zh-620jlkIF({7?Mzd)?Go*dv)s8wKGM;~l%*4}L=iC3swMK@v+S?#V8}TEGjDoU`y8*Eu zeMFH&14N^24!njb`X?(-SPy_S{#jdSD=9Hy2Kz+))3S*bxmiE(ny%a7#yk?>>GuxW zRC)x>Kkbj#ii-RIvD7;50r8Xe{quCdZB~a^3VCq8s4u?(KREGiQJd%OR=A)P(+{RXEgK zF`!nPR@GwQ?BT5RaeY#4amzg~4QI&(0{#9R2JicPRZqWz#iaZ_R}{7>;_;jpF{>I z@BeFZ|GR4ijQNu%(9eN0?Y)i40yr&jy8y!i)`@yT*Z-a^T9b(QHJhdxGD@|RnhhKX z0sq4$0Mz+;)}2ZkVyybs=-M_*ld7N-ZQ6tOq!#anB?gPYB=G8T3Jku7wt_3pKeK0f zDowKB>ASN#&aKd{y&VGVkh0moYzFWybCv+Z`(GX`HDzs22Maoj@n-AGTNU>pEeZwC zLLf3J_#f>DV9EdQ6c+;z1jq%cf{CeZ8BqV(#`HgvDHdr!YDs8gH#GPU;0gXf3A_8R z7fK5p1P>%0$(sbmM-U{>M8IxD{hP>6QkMVin0ivP|L$7LQ`xjU83tLGi_lfla_V;1 zM#wM6>7RNL#BcP!(e*1$qdu~LrDDZw6@MhuO~WgIx#!7+AC7q&FxLgU*q_@4e%q-A z{+sV8kawO7jV&aWWRP@}H0$)AGhZ97!J0L@9y&RFg=N$Jg1YP!C?4hdz%P{}Nw`X^ z=c)o9UcUu+MV>MO((Aasch_vLJL7)9vx-7k9)y>JF;8A&{X-W_+MBDeEabEF9JBbNDz`dnR-uJiX zmIEm~r(k`mASz)l zs6HD=kKy6TIS$zvE;LqRK>i;G0DRMbAAk%@q%-49gnAyxEE(!cV#_t|`UDbs^oM|0 zQM2m%T~aS~Ikbm8|C}R+gTorDTVaI*2p2|#hXQ4bp-N9z1ypx&oO`97!CkZSeQ@uF zxiTsXe8qooqh6eo`CSCY(z@@%3YSaG`rpu!PZsFVqHI`>WMu%BXcRQ7e9x*>(uE*s z)|Xfgk`X6eU0d6w0sRtpRE{jAj<%8;>*2^Q11kyA1JRM735DK-*SS(Lq^F~bLOtR2 zyF*=TSSzvhG zUjQ+D+TLapb;KTN(0%g`>-=pz%`oV8u^G)Z?=bfS$w`hqDAspf34f5Zqn&FzAl`@j%9w5c3PT-ffwT>{_l=Kr~0 zv*khwssQe$ga-lZUprnorZF2L3V5A%8yD-;dpOp*A3&&9HFMwS*0LA8uld~jjp@LR zHWN&UdY>0B;BiRaZZIjPBn66fpT#~2V;*Kl`1(w_Qx!!TS>hMXK4{-W^Q3hSDU7%K zqENSP6Ub*4=<+fAiG=SJ=$XE6GzLaaZO>K}rQ}56(0!F6#G=IDmXpF)9L4m)ZuanM zeu;-~(1fD>;SQ1AcDXZJc4xB4J=}<#jKfN;Mga2z@mjilGdIfQoY+@-tt`nHZ@=@? zVt(LNG1Nwxx5M3}zdjLhn5JuMO!qIXG82)ErEr0LFzJkZ_rwK*e4ERDJ}VdnV+806 z)awR=iyqxaILCl=&jJPQJ?e~m29pi^0vrFfmOuY(Em>(+VycZ_9D`vDP%zf!iUHrM z@Ggw+4EiodDNu>=F(~<o z8%#{F1cF*9%4`;a%RpEGCP>)j2Fmb{RIE^$1kCW$pe}Y$)Wbc$=jQyBGyrokSey@q z{e0A+^X`MLkQ4rwwl|lFEXJ#4A3s7#mp{D?0JYx3K@*R)0tgRzetKD-3iet(-$1LNuk#bxV8JHG*C?8;gpxrn3ouL5uR2R*yGS zdc-VeEnf*5==Q(id`aCfAmSEipbSR0Q)mUo+(7&47m^rU;)p1Ng|?-w((@Nx_rU$tXGYhSnE{ z5}j-Yn?7bw?3X`9G#`$oiv&NOSQENj#p1#5YW4vMvZZ9fQcrCS{>KT2jklA`)4-dp z(OwgB+3nU6*jyXj1m9%=V^g@Z^jn@>{eyGC=j17{WHGfa zTNDhrM)!O$`j*8}Ffr*du0}sH&Hi6FDGuD+G8Q<4@bUyDxVkNX$fvSRfcWfJ(5XQa z-xXJN>>q`;*{s_9uKcfBMwKYtxX~z{4@eP&B#Mws7F>b8Gb9Q-jj`5 zI<~p&#S*45klpV8F1v;zFqSD2W&;G@^Evmz7RE6dmeD78PVYd)*1PnJ(kC>@OeMw3 zQAg#75`(P2sP-Y$*D;2%z{oc#*LxggnMVydF|(jOC0VhIf;#qz{%!$F!NP67J=>cd z(ItkpXDqamdPMmauOX9q1U*l*(2nF?Az(Rj0YUYfixT&)nu$ss;q1Wb{H6f3$56S8 z?s0wYIh9FUT*mjWqDPmy&>t{mw_$vYC{s`D_mak zh$a@35CZRvWK3>@6-CVBMBbtEHFQDocY4Ajo=1wbw=}QiJmu`~bbaM77Au+9t`07D zVM95YS}$xG2^ziyh%iQt0@TC!At05df)4Uh*rl%7qZaPquUI|^vy^W905eI<{eY@fUdVq30VqURCYs5?<7{rWCY&0;JOFi@c&{@m@ z$#klu-cQU3wBo$mC1$2W$KfE}I}K_@1XIZFPN=WsaLLFWZhh9H~(s5cqm zFRzO_rbCx|`D0qLK9@fYNE&=7U(hE z__r~L-}hZ&;IivNIDl#`7a|`uj}NrK!G>8B6!*EaBXkk?7tVXX`T__HElkA7Un%L(-_^>aA8_Qh?fk3VjZX@wQ20nm>e2;+Nu|A{q{d;&FZXCk$UqT;c zC=t07eEvVSP8@(cQh=QTKaRib6^C0taePH2gDp(cnVjBD#=L<_` zKzmWV5QY^3_Uasga8wubj1ER5iEBf zzFZp+wK!u*thRp6g6EC~Y(jrU87CK(I|Ido_PrTPR!59^}qP z6<$i#i`t|syN0nsc!gPCwYJZZ8~C*YHV=@LtSgp(mkWb0cN$#yo-t@-Oku>h52xq9 zTbtY3ff_sUzwp{*nN--Aup4|HLGxeR#dJi2cu>*oBNM(0eT5|t4gk}TAD%=7NDTOL z;Wowh;s1|Y_6V60{;`c|%I_Lrq6C16Nb^k?+wdt;Y>P?f=-=i3!>Q_arO$L30QA%T z9`+izzFd<7GcfqAKg6uEhW4}z1az{;hJ@EWD0ca0yX4E*{72Ftvy1)LGi91&Z~)}3 z9)t+8V7cz_<^IK1k3lpf3R=0=*8%q2ruZMZxdl0 z(ZQv{B@Mn^;db!!;^1PgNs=kY4*$uAqrZ@hW*@#>Be>i=PZ4D&P_+G>6v^fY_}Kr* z4LsWNS8g29Se9{&kfQU%Ko;zS&my3iIP!xNO28yz2qVW6DE?jU@;-dIe+3(m5C3}C z?Q`$>tp6j}sWkr*Z18!wq`|iv5T<(AP6?Q`p<-U~Bn$XY9h6Z((b`~gv*zj6aN-*oNBgzBNMR&tT3fW5)l_a_ivBorMeb6Zq4B2l)U_`d@V;_&i+F;M+x_P-wx5X>XqsZUbVT`S&ML z(7$5pI0;|wU%6St&g}89(iwOU>V|o4%KsxbfZ6*iH<7Ag&xTazn2v?D6USIWg zw9nzOA{WjUjHYl{;{xX?Vjots;uXQ`x;+#{z_yx>CA9DM3~XOW3bfsAlRt%Tg}#l`?|H z1Z@@?8!}LUdMusAAH(a8f`T}H9k6bma%BH~F+PB~>WCprySv(9y2N%|>78?2q?D0we4gx zS|<3~PBM!GI3of?&C^gk#`o!f?g^1Tacz0s-fd^B)H#ZWIHBj>dkqo?X54_*d;oFV zW#1Xkmtr#?NKpL7?Pyf%v=*UupoT<^PRiF8u^0o>wg`l3v$WRe2E)z1O8HM3KJzqB&H>5)aV>42$(8c?QH*G{&2(wG*B>jK zaaR{`HY@mJM9oo9E;Yoa^}lE1LFD}#tc&Oka?y0jC_E!jM1XYrSP6rO+kqTdzrAfm zpt*%UMEwmP9n2CgSM%$RPbA+tZKi~IoK|Cx4(|qmas~9hJ@Ser z1H@1s2FN2RAm%?o!_S_GE|96(8AGPXVKv5lN)8Z<8*#7~QZkT0nZ-$f)3DB)XQy;P zx&OQpi${N9su`GYwsuzt+yoWC#3fY|*=YDL{{=d`{}s_9A#~IS`Jx=QGYNJJjq+e9 z@eeRRee*S|oCy#bbUL`h^u&7knOZ!x?D~w#3GlvTU&Q!zN;`l90dO*sV0#~G-u zEWhg8IEWGbS0Aw3y#fK>P(%fUw^8N@=^>vxfH{9IO1UrH6+v)Tcy_Rmrd?$hEh~Zw zVn89OM^T9$A5k3duXc(ohtfqx3Kikg*})*#k1c@9?14k>#s5I=|7MyMQrAH#YgQc< zzB70!32F=>8N3%xp~;rz7Q?F;S_m-&c3KydRk&7qFU|NxmiLHno}a^co71gPIF0&H zKGWb4P4WcApogR!e_L=h8Lvy2Ou{RX>Pg@M=4zH0ffay_kK(hl#}W(<4Y5G}N`ig8 zt=R9Db^&%5m!FUFM<22*G0X3|YApmGBy**geSL>!H}67o(Q(7*AzTJRK-0YF=>3G^efEsIPq$h^3U?5S+v~75>tXWLqx@e?bls`Np4aRmsj#$`GC4yzz!c=N(R&Ml0EaBCA|ydO z)WO$VRqg_`iH?WxdyymDeVg}@wRqY2N$LnBBkI_KXDtRdazMR@%pib+F%&?4ibI7I z{tr|FZY=5O4)8kR*fC&Dmi-rRKNxX8)E!4@x!awgMkB?amwMvbo5uLD$V$`c7FN;4 zu7Ph0DY<;{rARGWyWBzqOrWpKvS|o8lq$q~?dt?c!oMNuvB(5>9yy9Wh`@KhWWKad z!~zPIrP$g2#v55VsO@>4rx){Vmo--h_p^K$7kRl%pYEr;#eUCw2M5?pQv}(F2upmV zmr#0uCnjRjBl-2o5{CNM08@-ND&Id(HpV&XeVt{GSJIheAStr z`zx~0rCxD=vwN`1Z>{ydq+E=ZAuR^hduW*k?ZltX|UnpJ;jG#q7v59hoo;nY3A8NcK`{k zJ*9t3tHnYt(Y)sdiAjI5dLq}62L+0{Q|6mU3ic#g_4fOdHXZ>M#o66ZOENB-4iUfI zDHb0#7t9fXx=2N$Eb)#Y0t?E*;qRy4FLF^-jOJQ)@Q?QJ>%l~ILIDe|4UxjsqJ00O*4ETDWA$)LFX)XO$B)& zyVaz|xkKT*L4B@><$sUUAL}{thhjf%TdPpNiImb-|H8CKm(((%3F~&&^-qN*gBYW2 z^LP{PkG1dpEOxt9-Jd1AkC`If*katnllkYfo6Pi9cc9ZkD@nArOe$?`R8$Y+Y2;gP zc*=(I0_rDT@r-6j$WMe9lXUTCd5up;m-=UB;=bEf zeSP$^^Ou%T1{ij3W=m5QiOL)(PDL)D_c99H1zxe3~KEMLW4 zR=a!Bnkjq_0CVgcU_I0{%@5^7guU<&&VNW{_{!V+sBt}VGIP4EBv$0OGQ42BDHlRJ zsk&O(S)%-L!mdaXdCU3$d}a(&8u$5gL+vHTkt)4oxhTR-neQNPH`Bcc@+y9brn7uP z5Gm{Qi_)5@RGLBGL7--}2P$B&CH*vq8zmC#ez2cVpecrD_{ioZdCJ(vchyas>rq2S zY@%Ekzm)bT*&ndG`3O^F7U(=2ma*%8&G#kDbV#XnJv5o}8oAV3lN)i%js1(Pe zRq?S!SF^}f73H{>IM%vDq}{ z>^(yGBop#sX{Hf1N0XhwY!DI+EJ#fOL$vEw)9JLyx&+}41=Vq1d?j9Z-wBv{(QF=! zlBr+jgRjKpXf`z`VLYs_jNh7FQ8XedHY&(EbcKG`1meWBf)Zq^xh>@#zjI%A(8MYlJaQ9Kz2J#9B4_)Zc8WBD7T2R9R$FKMxJgow3Hh8lwUNB0`NEO1K&ukmER(Tf6CNS9Q> z#BlYv{e>m%N!BB#H-n({>C(4LHs)Mk+%bkP%=SgYvUe9>n4f4};}`HY#yqE#+;kPw zR#bc@GlTGDK(jjkVfILrXCC}(NS%yQVc%yfXo6nkSfMRZIh`938K+a~tJ-DbkG`Qc z%MG*j!u0p9)IB?2vz(2V6o|BN_xnaiTXnsxZcb}{D`y>9WDS0f!Sz7&IJw@z%W7x> z(WxtP`}ul>m{)nsPF|wn^}(Ja$im`~5k+-{eXURedshlyWWiOjYyBbo#sSh7X@ zi6>i2de2jetwl=KeqjAhqNz=*Mv?J%n$*a*UNgOmcb+qw%!-4DH#S33)sAMg>kZ$r za#cL;3dv{65CqR^BGoSQmNq(m=jvJ?Lpe~!S5t<#a}_$DQ;e)ecv`itO(cfY>4@(? z{cHriS^sYDaK7v(XT5iKBT=eZe$F^?>1nvJ@B8t4f9o8xC?5(v`IMjM9jWz_D!(4wjYdXTJ9j7YX9{6~z2}i6h_m_7G z*-px76mj<-Ix5Pv3UG7d|FPESO0nZn&KIXaTi2lE&cv1dYXcx~I=IPws#n69@3X1|)$)~5x)+cydmG+v&0sg)aA7oe z`POEFY-YDT!zH`WwnMZN_NbwOz{mXAby|&NM?CM&{{(56ISUHRKLd?U521s@hVugT z3;y_Cf3~wTkqc1K<=5ZJ226Z^bsHbTCjyKA>_B#*?7xb}7h%}ncciBhbja;;v>}AS zY-G&+>DKr8Yt^4F7Atm?=UK|?VxH3Ko*?#60D%*XF5zMrF zVJ}c@e_&U);TA?7MYYH;CO5m6Nc(gU)Ci^f&CO^|@L^bCD{5omZy>e}TWKTsaM_7~ zq#28V+*67OW*HfthTb8Mu_(`xWz!{vX0}|HUQpt<6e;3cY@Txh#lc!+jgHK)CM93U zKM?lP{sjpMsUx)-*eM=z8IT+#%|8KmLg|HZf%s+q?orQY=uogy81DmGKQX(TrwbKK z{s#lCshlo-AXvI!HU1*{7$zQ*oNK8FqndvQ32SDe9W@@DzeSy)z}+vePJ(iHmO9Xe zd8<6%x7Fs9g0h|uHOh=YY`=+-;vZvIG}Q%+nh#_M2O=Jujr(g==Syby4sDC7;!r)6 ze7B*Mq2ijw@JFydVti6qUQV@%A^7MrfN>LKHJqXr4`oGDo|HdR+|u2<_QhM;a32|l zl=sy8)-s|!%ByS#;2rCiDLuQTGMl3HvB{DG=6M!5jLT*!AW7&+TkYL)Bh6dIS6#M| z&1FIT%zif>{o`8I){^U`LE|AmWx09Xw39Y~k6U7~M!H(SCyK&zl=tBwq`d&i*m3>T z@s&_aR%sGmW5ss>ibnAV0|F4#a^F9g4aF66JjgmCd~YruBt|)GaHDQBRZ0TtKwZov zYg0pLUMn#FYc=ksnSa&O`=`g1_M9S-G7#jKh@6vwAoon9UB6)*F(h}n&FUZDAIMt(2u#z z$tj(kTw2s?th<=Qty*bfK39d&%VLMg55Ega)I)yiF!MhB)mxTmTPa|FYachAU#{S^ z{O)9d2NB7;K9E$vOFYtjtE{IFePKqPh-5uC&HMmDmcAO_usJF{o9ccQWAQ0j5GAPO z#Lx4l(yE|~h$@2lg#h&2gPemjz|-pCdS^;V9g(n=TO{mtHpbNp2{ePrZxV5QvLhZE zxygwg6_z|F&l=MzF#+g_toyyii|!7!aM>%)8yb5gD?v?n{2t^*HGZ!~ctmLFZx(`< z^^>b+#s9G)6GMkSiW_()QC|tn!$?v|(cPugVD={`CvDah`AhBY)N#Dou4c2QtQCzI z&86c+%AhdbGhXVh)B)Pj%U{s*R|84l?vIFVeU-a>A?v+LPX4kmGRZrQVyy0UXEYJ? ziVB!-_}hvAZwo(F^4;om$;xc?mrPt%Jj-A4$rs}aw-Y$le!2lx<9QN7IRky>?SJvU zD%l{&B*KkRWNeu%s6XYwte+_F=Q%6jV z=h@_^yC}|zQX^1pLJym3QK?BYhp7jtSekJ{Ol5Ifm<~)ua_iq-<2VeoI^WK#vZwhx z)v$la!}f9-n2CqRzp&N`B7?HX(cs2n30JFnGDn*x$&@Ps zhwdkAx4f@)?lqw<-!B?=SCJ8=9xMKDjJgqNxc0(lg!-2038!*oFy2U%F8}sitWfYa zLMClQN2e{1qUzXK2dWOHN|1y=k0|Ra&7Ejo$ME{P3qdbGK~npMKks0=COesrDdU{I zN)4X#8bLlV+ZKY?aEM)@s@LR4!az9QI;=e5X$r=;W882&?5~Xx2LJkfQpdjW5-He# z0Y}DL#QqSh{3DRrMawA27%Ug^y(Zl#TDR+KW!K%@cXfZ%BuzCUfkQq-fUk{sS z-$tsbcEaOP3Y%!tE^6!LlYSQK33_vyk5y$3T)?(HI4N&4^pJqRtEZua!)1iWT*nXh z`PV7zps;dFtUIntRr)LPs;*D3Kd)T+&v)*@_LvQqsnq+;mI~40ttj(R<;TITX-Auv zEN3g_OuJemQ2tB>KHnma^{x9uZ8q!dv{Jz?A6Z1&-2nQ6{H$7y$$#HQxVf+}!uFW`M;YCB+gV@A^K=Dj zUKHW_1f*>!aQz7{8OnN!aWd9<0&I=N`vtejr9;G-H#EvSlQeT52a}^;>CUhfjR31M z>j-r1UmI7K(|Q}Ph@3fdA~c-)P5}|^NEi0lGa{K%fpU4v`C)&7p}E7Z41RLVHHv3h zwxn`VIk(H!OPRH|q2s~7$wTGSf6Gp4-M5}!2qZAYk}6W@FD@52oLVOn3AwznxlRqW zkvQQRnwUR35p_5VN_!JZ4^uZ(u{EL2$R&(;AVzltH`|Suew@c+_wxuWlj^~@XOvU- z7s7^OO>B)0>K;peO+gm4XdxzvQ_#0CAolwYb}$F zbeKBR=4N98#`8xTf5p-JDm#*|_opQ5?lJ`hMlUKz>p5RU^FEcFEYuZn`Urljg!SJ> zO*zE7%dKFz>c(}9PF^VZwv|)$ZX&B}%J_SSLOg3ts)Ik_AH*WxbFSkl@oRe|*_Y}O zCfs=VpC)ZZ8-1gR6OZlUHglxe7VQ=IBc}x-GGtTV56}9+FvAWe-nS*UWDi&SybIc# zu6uR5MF6Yu7P=zoKJ#8KDshMzZ12XULzSmY8Jnnh0rzddu++$IwL7s25B6yZ{`%+! zp{lO399j#()iB+;Yp%h?q5E1&TN-$|ijqjVI#nyGVY9&u^)17?%D*<003~w8KL}`BkYFKvgT}AODMm_G}Us@#o zo|r|3;l;o=U~pN_jov^Z^9kroQd};Ov8+&6vt*dj4fVe}L69v>=-lS8R)b`4vH^-P zho_;^f+~gMB+In_A8k;oy;iC@%tm*-k?xzkon##|?9jKMIR$-qruVpGSy#lZRrkf_ z2$tMIpEt+5SXo0&`#S17-PwX8e?L`H^+*#J{G=K_JHt&U{~K5xH}6DNQ%BQqmhi=| z*+38v<>qg60)U+Q>53_koRxBzckzos_XS_PyFqITXOXL1wx%Y(z=f8OZsi!Twz0T?=ZEgY>*2Cf zNTTDnPtJbw?lziLFykhTFP{uu3^Obf(tUl`EeG4?w10g3!%{l$tM=MPNOEpmwM@Ug z?itg<8E(z(?!Ph}IWTThhyLQMMsXz^h?} zM)<4nR*8!XN(B%5&k8HO7RI0Ov`R%^Vpj~hpW1WrjF z*Z`gIjqmoA?G3vmu>$_;spZ5P=uc|{keXa1)+Sbdn8S)vHEn5O)Pa6`tAy-iUSnr{ z(cdP2=-^eZTpXljoL&Ep<4~;8+;~9yZn?EEH)ql~TfP~iynk%txVzwZtH~m-bob6PPaFfde4ZvgjN~cSl?`>H zH4rRH>1ZNH&3ASG-al%FQqlP8TH?shm#NP8OA~+i_sY;N2|@2bGG08<1Id~_vz@)D zr{Hwf-k34XegH_K_D^f;90P?s^1Bzw$<3SWl`jTs{}3T|TuBPyE6^5tj6-Jq1{?j4 z_&C$nluFhtZpwU#m(k9RIgd!3R@jrDz)Usn_bYyI*Z-o`+hXoNORq&9z9yYJ>1vcn zRVtj;=+@8RIAh#gR9+%2_I>p;nkWb$y4i|;IXbw0ZUOqaZzmP2OA1Hqik+#+Wpurz zq@vrM1eeHZf(8~G2VzHEwB`nM=DxIL4-e10>`idJP=)YK;OVH0!H%s_RNm z=-#<9$xlcZnOJYSLeD*^$A)yZd$Qky4SW91Tt{`255xX3vk>+Lhdu&OpN)MmUWT_n zVr!0JyMTO6{J4{u?0%DWX>d;qRw&pZ6HkUzV)Eymdet`oQ_1f}Lce@i|6O+WzDI^X zzd;*ss~1Dmtmw0e@Zf%8z$~6yrrzMVbP`Rl*b-y-32n?lrr$w@Ti4U4cX-#hF5;J5 zlIaH7XC6oVXeeLnLtT;@lq=>EEOZpn-J5Lnjb^44hkj|X)m0{0hJ?`AvL`}0AzEza z&EvWIO<9x6>Zf(j=$+F8$7+?|w@9Oy3XxmehT#!d{-vq=Q!W=I}bs zyPEe$@|>Z2N==NL>VWavrq33-{g-uXI`X`s#(sv?thZk_X(wiRSgKX5chzm~PB781 z9qt$Tz#SNN<=Rna5NrUZ(~276y3HY6`rxN8+eH~ zw@{0btZ?1gzH&H&PuQm_Fqo6`%44H1f-ZjUo?f9rLl_)SepLMjv93W1jl%o+)gNoH zx#>~|cPZ&vKcKwvDQQzsi#m(8h zg*+>!WvPP3h}T5+;tN)aN7=!p!`Y410l)7i;{+S7Y4^$;G(NNGRel~~SNPq4LU-h| z5{>H-o6lCdmcL6HMFeUlAjTWN%!U!`gxAhWCaMPP#iSSCCRp-QT~*lEL`-xne3x38 zs9~_+_0a#7s`j|i2|>zlC-jXi?Mve`im9q%%6eIL@iWnJ1$XpAwF}mme#TuPS5feRm+ao3zV2?rhb?p&6G1S}89BpSk?SAV_st z^g8zHR%@=7{-SqOX{r+Kv8vzfc2wev{#xJzqkHbpoDnlbM$*L4NN<19QN=TI^_hD= zQY!U&FDIOVDRJN1`6f#K(@NX3Mt>jwN~XtI62}^q(|CW;BM~LhjgPnCcu}NMA?$bP z3c=AvcrDOSxvYw|{0+{gZAmQDaUT~&s@|>@wyCrYtT+j$=k=i5mOsGV*AB0#G=~&k zTFlYGHIp{wPP3lc(p?*d5cyh@;6;{YE1Em1VVC8`(c~I)U+FER!Kmelrp0&LwAw9X zpU?z}IhA|rX8$QYjLH5puj=(m+CKjgekO_^5aUmKt`0=Dir>fw!NhWd+BVWziJTI22&3180`;6s?~sCFWj30mV8|43BGpt1_)a` zUTBz%ZIr`6YJaccN(IX%l+y4ssSCNoep7$RQ0nnTvbpEWAzyj-+4V(Q+>d};XZ{4K z!z>4XT!K%=vf7l~n_rJ(f6|o!mkAZPOl5kh$@mNO#B1o)_m(M~gG>b^Yrd zP!I1Gu@s*3gWp)>^dNxo!P2?oKl}yG{>EUMRE&bX!DMmkL+sO<*mhrl zH!ZsS%wuLCw%KV@twcMewXp_MUxZ3ubmabE?1E&d*Ek z`=2iqsIts0qCKU;6;`D7DD`NstH+9M2b8{FeL9Jsk3GBT!Yf)wZ3bC4sP&!$kI?0{ zEBjQ520;*xa;3m_Q{%T-*@Mb;ZmfN?`kkSTcD9?_Ok@z3&Ha~^ z-JM!0b?$!Tb*5^u(}FVgocydhk`IGY*&n9y)BP{2|z>qqmSIC?!if~MV0|&GyEu*z>O_my6wp55Oe0hJmcIW&l zk59Obpu{LXaO9xBGY#K90m2I>kn&OG)q(J8MMGU?clxm_Vg>K5k#ne{^S({`2k3ue z5MJx;)Q{PCO99l+7F_*gr=d5F?#u7q!JG##hbJ=IE9?Zgu!ZDw0zf3xE9>PyTuscw zfppb%OH6~5ZF<-W!f@FeC+HJ2wATFbH%q%yD$*hsRvZGvSY--DriPnC=G^ms*S6#R z|01>iv!)GDZ&&OyCt{MrJ%zw=lrM-@^RYQKZK?ILEEXPzhx69YPR0hx)WS10Zw=pJ z_buJ<@Xa(tXi&1>5ay54Pm$j2B&9toc7X2T^^GR;1NNz1*JOz?^{y^;HbVj; zYoR}vT$MOqdyicdT^AIvQxywv-nc+eNS&2h-jf0vc6AXJ0}~F&-Q8V|PSf)@uDaF% z$rtLBj76fC{20upz1X<>;!ARI6pym*ZzLW+Rbo5Z9fe}Nk=Pfms;#k5P*7rOteiY3 zL@$h5@Vo9eHg5(*9|N4|GyLE!F5ZqlgE{OOFW9$s@1nOQ0Lm=cr@s6KNt`7X%NrLJ z_Ihh(zZ0PLHGy?{{VbkW#PS>TM+rD?zVF6M_wzuU4PB;rPoY1k=EAa zl_#hFiadYcJlZtcc!q73@ca$ec~^P(k0H=^FQz|5c`3Fi&5ZP5q^5ULajPf^FC2Qa z(6`5qHOOH-VQ~2?PIHVQJ}J(#-ZprWUvjS!3Cu;>(93;&E)V}@wm*s*DOo!|UA{$6 zd-kRsjo`U~gaKkTBdc|Z_1Aw?b8RN5wMYQ+Yg{W}n`>e)E>v{lb_?oRM4K*=Dn01y*<|ocR zh40SVu3Hn2^SZ(P0honnZtc5Me>!Bm`}B2?{vGu8jQLWB=?*t;o%JQ5;+BqG*&ufB z%3R+x+m3nhVpWf4#|SZ%E^8ygUhiMN>q5ED-LSFKS=XumoSPhzt-iGkX}uKHi~$y zc0c!lm0_LvC`+dywOCgqJ0gim4vK;-5Ps0>#7QZJ^nLJ2*5^@cZqu0Lh}L*#uNk{$ zxUH>=TnBBDirtQ4G_M*tK3qpNC|93m5}VELa}Jdql23~Ddx9jPf$7a@V|56aeJdFf z@kO~{)?WG>PZg<)=tPaT4^8r9ZzL!ZlZceVO?EK^v|yLYv56#vWVTZLx>aL%zz7VE zzB~7o6G(ZWlqVb+1ZWsew;hr;QrXHPh}zHyMt*I{E*U#ap@g)gkE(x{~VdXs$f41z_&M`N>wwRJm&?i^Ci1 z{2meY+T)z0e~t?Yeq6^K(8*i5&nQUt`@%n*P3G-8ILixt%I3YPPkkt0nLs?(l_F3Ni4|960>QN_P z#5PXL9XANv5h2=)$$K*uZFEUDhey_1k3ZRv;$1U-$UOgSU^90I-Pc)ND_as_j8DI` zcFlMzc9e~`j#_^^fO3fbM>K;_KtJfMXm(gJd5ZQh($2(1x!+=7>m$dDzMe zmdXMC=%aWm|3Bjz9&WA}l2y{Cu%vJSxhaBh9ZNc6kRFFRA;Z995ya8k5-9#Wxo=eD z@QKZG$~*!;in>-cUqGg7{8tfDeEcx8Xrkpv$)2HFZMT9@)@5_;-5X%HAdcP(%)uZ)Gvd#jXs!WZvz*m#&P^v$6=K*5QN(0w|e%G#^^>;>QhFLcB zgP3%d_)KhyD^I8~K$?AX`h$igK4y$#6M6ZSF)F=Q3P{Gh`SkaSDeTb>+&ojU?g+t}LfQJ2Q4 ze_D4BBfK3rtA@6;3NSu5-T|@Yrs@ibMIKh925y?FG=oEOR6Os@rO( zxUW@yhL=s=7-E2%+3+}pnFg;xb-rdwb`9fFc$JOu(%UJv`t-e+)5E5q=bYK zih|oQI`^cl-M9owoJ}=;OjlILJdJ-EVko&r3`WMZa`YN%9UT<-56RsTB{$4)lX+Ws zay$7`14?Wmg1)?F{aRu56-~lthy&(@sov640`4(`;@e@e7p6yupcbZmuSKi6P9m!Z z1ja%Bk|s7pWgvmoREFR83R{=2B6yNbD9WC8!uDk0&JhE8u+h4FfBQ8jQ+FF1n zK*9i+UWClnkXQUXP>qMt>D|D}V7_KbK3VT?o78{OLd$?|Y=Gn(cK(N!)aYg%lijZY zr!(q1_HCeSKz}YcV};?(nHEu1JWlABM5rqJFyBOhPG}7BGs%|ixwtw}^U2QeaxRec zyHEW6Q|-8n0Z8b|E3yPYAM*0&o+ij+h0?>an?^=J(+OB(F(#-A zHRU0Nw`E@vpU|*fjE3X~RuuKCY{tLoytb=6P}=n#w*AgP6$~eTfB&dEw}%RM6?jO5 zrXrzlwxYOHLPOiWkuF1ndmNZp1sX5Bun9SBa@r!#-tc!kF=z9`??#5sWOE`{A1tGav9SB-ZoEXh@#w8{|DgD1$Y&$f5D zCE-zEnW4r;_<>i&d>`h3ID+p%zYx5B(2T@rJ7(p%&n?A#{?`|}RvX0v6-ntWcJFU4 z2a)Inp1ptK_3=TP&&Tt5??L&eGjsgiTb{jak}q`17)OxCR@+BMF;v-F148P&yg-+a z&Z-697?q0Jm)A|B{h3m}_ggFEYl55oio1BL`Mqrv3Di4pu_@ z>ejX|i;r!1)1H8su~>4{^pRfC1CUXa-%7C*!QXAERW@V-3BQz(HZUNkk-K?rJ!vmK zFO#~cTbR0+dVuPVH{4*|h3RJbRPVz=dstEE@?O=##N12q=aTd=*U%|LiS;ea$o=Sn z4|S`|^z`&zoZi35@|kl)lEwXJ2Y7W9*iFw>>s9LBy3{$Xu`Z|{3Zw>aKXXZmfP0)Q z)onSp?f-A%^21$I-N>mq&>A5L(T9f&uUx#*n026H}omQT^W_g1*5b&a=g1VLK zK&ZoV)_U&KyiD*d9vNl#ncf+fcIBr#ni^oly_jRiftmcsI`bUeeLX3$Rrja=dLMyY zsvt^Zqd&RgWmRlh`%d@n+J*0y6G?-AnowkW|B24g;;~tl?A>EAL6QIr!3*J{x+yPu zn6Yv!b|Ni2+b{Fbg&MXNKMh(U(R?C{9MbtVlyBx#ug+0dPbYF`$Ac5}$iG2YP22v- zoew@}x{*TOHSQeyp;zQjxxAJX{Ib3%v+1p;%)_(p;Tib6zr$I&ne^(zK$C@)p%x@= zzf}Z8dzk334^JGIxz}o6B4Uz?#LK4?0UL2)G7wTjy&xon!-Od{-*)OO`=o6Axu<=-G zcb;kS$pzY&ebHoHf4_}ah8lLW#<0QDo`U*ha=!Pn|R=$sk1xy67~%`yD#p^_}Z{SN;24*O~vG z_kHG_=eeKze%^ce{hmo6|7u!)*5Z|Q%LV;oGsHc1tGwzl_2=@7qLp~vZEPn8QwE`Q zaAYLuIp!p`{eE;u@sT5CztUyQjjv`#WbjWY@PD{*p3{)nk4xKh@Z{voP}flN4*{G@ zB)eqgJrWXfqvY%6zpA54O>5fSrh>K`ioMSyar>LjuWM!QEWf%`RP;x34JShjr>gND z*f2XF=1GM=`ine%Ts@lq2Q#~?ddy+KN~MUR=;qTGgr+Q5n3a-?Z+!kV)&z%n-0^v_ zQA9*U^+5)fPLb1-MsypbHsiJ@)}r#% zbx~ol6|?fVwXc4pES>t;IN|{3u!~2MNQ(a1{Urd%B6d_|k=|(xR?xMP`(hC@yCjr1 z$IR&ylCw7K%k#>L_1`mO^e&5?yvV$hsaeUlk61ej7z~>5@SVQbKH)>{Duh=bRK6Ix zL&{SzY`5vhdCm!7cFk*MsU>#+qj4r(N$HV)rEp=WnM7DhEr)uqJ$$ike-O7=in^Q_ zV8a8(=w|r0TQ=Q7e7Jb>B!7SC$Ue!@SJ5#|eNvk`_{zoj7mrS}s2$S{-YZuzFUvP1 z%GrPffljm})4;>MAuTyOUM)HD%O)NQcX*N_XXh4AS1=e7^F z7j+eNWn0fV_v-A{CWuY=>l*#xET74j=0FR&AR&9AHLWS?y^W0AX3?uFmct7kJYC>}B)=DTy%^q$ zo!OKiYEEEj-~{_>LQ(zGn@Qn1=^5)~JTBya2C()AYiZoDPM7cKcr7BpHF3ER2k1?Z_x1aAn z)ECrNHv}eCAo5{kCo+KTWijI8s!ni{OA{FQEs(XhDG2oNP#J?D;NcL#n@(;vFa1e} zvu5on-y7iD5O?Q9wzyM^cExfH1}9CFeGK$CKGIA16RKuHZB~0#-U_gPjF)JTl3fJJ zL9;s{W@3hp$K-NCV-(zcfXClh%w|}ul%(9qrK}cFDB8kHUR?iLcX0C0I~F$(=G1PY z`&rS4#7*Ss+wI+G?9Ls|P3g7J<4mO!-Ok5d^WI1xe zmi5>^_V9eSD({Us4<=MmXrYlrSb4mOB0LRk@WPX9?+HTzp6RyF^UARFc`l8G#~5Ff zb*ZSTFCb|R=H;Y4yjhD7-gHz$jzQ3^Z&dnxB@JPs%%=v5o4(GmU-V1_4dl0U=inv= z!D55y1-P`^Ik*n_?VhuDGlQ@~V`@__rNr-QV>+qiN@4FAB6;d&x69p-{XaIRtdIyZ zi!mAHT9rysm56dVK|%qXR>X7>-iys3ELp!oQ_sY-*4gU`vUhu@Cxby)tS5U79tH{O z(2%lOXS2O_J@~^d0#0; zDM+j?*)<Y5Ur))6eY10|o{7iZJZ&S~^2F(FE)_3$hLw#wy0oo6&$7DcXFE(m2bc9c z8&vK_8(aazf<63!q(JcQ3(Cy9wM`;yQT%dwgehw%mZ)d4OjXy-5Vz!qd^Pb1wN#@Z z7l`m&7nSvb*U!srLT>rF2De?dYyyuaf3AysZK~1SLLav51k$71`ehr3tH)J~ymd&@ zaas~#Jr$_ep!taPljzyFt$}N^G9q<^O?hfsGvP3K#fb zB*OH|I4>qtBid|}pyGfwjB%fm%UnTs>{<6M#2ubRm5ut)1tq+`(VX*7YjP<^G`Kx< zQYE+G7})xscS>pyCJI@rr?{Ew?giYKpn*V*A&|)+{N<7lTtds2ptXQoO(m5u3VY&- zWE4sTCJ@(rUrHhj{pA6x(k$xF$K`U+>R}6VUz!-)#&~I=t}mx&a=$$+xCMEM={)q9 zK@ivf^dOf~=~vhgOFD98p!Wii4AXFdY3yi8(3c1kOx>zfCO6{RJ<>o@P8XY1|AW;1~MDhy#n`SR8GST%B`%elw}t61Y~;kbVZ=SK4xB zHo%weyj8>N@HAfszA+oX9p5;f35oBq%M(+A)a#>@_R+1?hPa-gcm^KL!%FK5y9Re2 z!}E9LPwg6USWkCen?=nbrJ-yo$j07fGHrRjOkd5WK<`DnVwM@AoH449jORilRY)Y= z8jGdwtKgNpmsxCIPu~$WB8$n;e^MSVjmNYdwflmno2NpS{%x>ND;xhtt8FzkyQtfd zB-hCLfPsdc265o)U~Qi{%wQ6pVs4oYSL#G- zc7Q!;O5*}P1S1s=?uL2?qxMqWq(G;Q@!(S$EQ z`k3n>>(DZJVxH8h1OC!zI*Ptr0Sf?dtg1Ih;M`C*jiMp0wz&+HGGP$&1c-=7g5Hv7 zy$#J}{mmYT4jA@8?C@n6HI&c5{aB#h3?>PNi6TJ+0sD>&kiZh&o?2C1kjNs%ej9<8 zTo&p_FGH-Gv!VFiNc&1C;#s4;A=>|6@~UIdNS)GoQ=0a3t2F!oMEt_?D|ck8hhVD~ zEQOlC2;_`KBxN^Jixw?)N{ibn#Z)kxkLpvlqNx*mHVRw^uGxIijAkI{jk3ZBQYNu# z;sVjL@WPbqOa@KtaIzBc(lRax-7<5UT@V+)`D;mfCLdka9cW2c)=FLbZaXUa$B3+9 ze4-vFVS8hRl(oV*&@DLGK&y2M(*#fm%>H{9>UIc7O2oe=q0@3+T+=vIOTSA)Qs%Qo z{p@)a2edNG{(Bed3~&YuHMWm9Wb)_5^&t9B{O&h31OiO%-;m*AS=E zQf-o5pv;{U^Sb$uDgR^2|F!4-W6C*Y|IenJFZO9}ab@xV?FnbwB_a6OSvz7&cX-DB E8+Y-c-T(jq literal 0 HcmV?d00001 diff --git a/rfcs/text/0016_ols_phase_1.md b/rfcs/text/0016_ols_phase_1.md new file mode 100644 index 0000000000000..c1f65111df328 --- /dev/null +++ b/rfcs/text/0016_ols_phase_1.md @@ -0,0 +1,323 @@ +- Start Date: 2020-03-01 +- RFC PR: (leave this empty) +- Kibana Issue: (leave this empty) + +--- +- [1. Summary](#1-summary) +- [2. Motivation](#2-motivation) +- [3. Detailed design](#3-detailed-design) +- [4. Drawbacks](#4-drawbacks) +- [5. Alternatives](#5-alternatives) +- [6. Adoption strategy](#6-adoption-strategy) +- [7. How we teach this](#7-how-we-teach-this) +- [8. Unresolved questions](#8-unresolved-questions) + +# 1. Summary + +Object-level security ("OLS") authorizes Saved Object CRUD operations on a per-object basis. +This RFC focuses on the [phase 1](https://github.com/elastic/kibana/issues/82725), which introduces "private" saved object types. These private types +are owned by individual users, and are _generally_ only accessible by their owners. + +This RFC does not address any [followup phases](https://github.com/elastic/kibana/issues/39259), which may support sharing, and ownership of "public" objects. + +# 2. Motivation + +OLS allows saved objects to be owned by individual users. This allows Kibana to store information that is specific +to each user, which enables further customization and collaboration throughout our solutions. + +The most immediate feature this unlocks is [User settings and preferences (#17888)](https://github.com/elastic/kibana/issues/17888), +which is a very popular and long-standing request. + +# 3. Detailed design + +Phase 1 of OLS allows consumers to register "private" saved object types. +These saved objects are owned by individual end users, and are subject to additional security controls. + +Public (non-private) saved object types are not impacted by this RFC. This proposal does not allow types to transition to/from `public`/`private`, and is considered out of scope for phase 1. + +## 3.1 Saved Objects Service + +### 3.1.1 Type registry +The [saved objects type registry](https://github.com/elastic/kibana/blob/701697cc4a34d07c0508c3bdf01dca6f9d40a636/src/core/server/saved_objects/saved_objects_type_registry.ts) will allow consumers to register "private" saved object types via a new `accessClassification` property: + +```ts +/** + * The accessClassification dictates the protection level of the saved object: + * * public (default): instances of this saved object type will be accessible to all users within the given namespace, who are authorized to act on objects of this type. + * * private: instances of this saved object type will belong to the user who created them, and will not be accessible by other users, except for administrators. + */ +export type SavedObjectsAccessClassification = 'public' | 'private'; + +// Note: some existing properties have been omitted for brevity. +export interface SavedObjectsType { + name: string; + hidden: boolean; + namespaceType: SavedObjectsNamespaceType; + mappings: SavedObjectsTypeMappingDefinition; + + /** + * The {@link SavedObjectsAccessClassification | accessClassification} for the type. + */ + accessClassification?: SavedObjectsAccessClassification; +} + +// Example consumer +class MyPlugin { + setup(core: CoreSetup) { + core.savedObjects.registerType({ + name: 'user-settings', + accessClassification: 'private', + namespaceType: 'single', + hidden: false, + mappings, + }) + } +} +``` + +### 3.1.2 Schema +Saved object ownership will be recorded as metadata within each `private` saved object. We do so by adding a top-level `accessControl` object with a singular `owner` property. See [unresolved question 1](#81-accessControl.owner) for details on the `owner` property. + +```ts +/** + * Describes which users should be authorized to access this SavedObject. + * + * @public + */ +export interface SavedObjectAccessControl { + /** The owner of this SavedObject. */ + owner: string; +} + +// Note: some existing fields have been omitted for brevity +export interface SavedObject { + id: string; + type: string; + attributes: T; + references: SavedObjectReference[]; + namespaces?: string[]; + /** Describes which users should be authorized to access this SavedObject. */ + accessControl?: SavedObjectAccessControl; +} +``` + +### 3.1.3 Saved Objects Client: Security wrapper + +The [security wrapper](https://github.com/elastic/kibana/blob/701697cc4a34d07c0508c3bdf01dca6f9d40a636/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts) authorizes and audits operations against saved objects. + +There are two primary changes to this wrapper: + +#### Attaching Access Controls + +This wrapper will be responsible for attaching an access control specification to all private objects before they are created in Elasticsearch. +It will also allow users to provide their own access control specification in order to support the import/create use cases. + +Similar to the way we treat `namespaces`, it will not be possible to change an access control specification via the `update`/`bulk_update` functions in this first phase. We may consider adding a dedicated function to update the access control specification, similar to what we've done for sharing to spaces. + +#### Authorization changes + +This wrapper will be updated to ensure that access to private objects is only granted to authorized users. A user is authorized to operate on a private saved object if **all of the following** are true: +Step 1) The user is authorized to perform the operation on saved objects of the requested type, within the requested space. (Example: `update` a `user-settings` saved object in the `marketing` space) +Step 2) The user is authorized to access this specific instance of the saved object, as described by that object's access control specification. For this first phase, the `accessControl.owner` is allowed to perform all operations. The only other users who are allowed to access this object are administrators (see [resolved question 2](#92-authorization-for-private-objects)) + +Step 1 of this authorization check is the same check we perform today for all existing saved object types. Step 2 is a new authorization check, and **introduces additional overhead and complexity**. We explore the logic for this step in more detail later in this RFC. Alternatives to this approach are discussed in [alternatives, section 5.2](#52-re-using-the-repositorys-pre-flight-checks). + +![High-level authorization model for private objects](../images/ols_phase_1_auth.png) + +## 3.2 Saved Objects API + +OLS Phase 1 does not introduce any new APIs, but rather augments the existing Saved Object APIs. + +APIs which return saved objects are augmented to include the top-level `accessControl` property when it exists. This includes the `export` API. + +APIs that create saved objects are augmented to accept an `accessControl` property. This includes the `import` API. + +### `get` / `bulk_get` + +The security wrapper will ensure the user is authorized to access private objects before returning them to the consumer. + +#### Performance considerations +None. The retrieved object contains all of the necessary information to authorize the current user, with no additional round trips to Elasticsearch. + +### `create` / `bulk_create` + +The security wrapper will ensure that an access control specification is attached to all private objects. + +If the caller has requested to overwrite existing `private` objects, then the security wrapper must ensure that the user is authorized to do so. + +#### Performance considerations +When overwriting existing objects, the security wrapper must first retrieve all of the existing `private` objects to ensure that the user is authorized. This requires another round-trip to `get`/`bulk-get` all `private` objects so we can authorize the operation. + +This overhead does not impact overwriting "public" objects. We only need to retrieve objects that are registered as `private`. As such, we do not expect any meaningful performance hit initially, but this will grow over time as the feature is used. + +### `update` / `bulk_update` + +The security wrapper will ensure that the user is authorized to update all existing `private` objects. It will also ensure that an access control specification is not provided, as updates to the access control specification are not permitted via `update`/`bulk_update`. + +#### Performance considerations +Similar to the "create / override" scenario above, the security wrapper must first retrieve all of the existing `private` objects to ensure that the user is authorized. This requires another round-trip to `get`/`bulk-get` all `private` objects so we can authorize the operation. + +This overhead does not impact updating "public" objects. We only need to retrieve objects that are registered as `private`. As such, we do not expect any meaningful performance hit initially, but this will grow over time as the feature is used. + +### `delete` + +The security wrapper will first retrieve the requested `private` object to ensure the user is authorized. + +#### Performance considerations +The security wrapper must first retrieve the existing `private` object to ensure that the user is authorized. This requires another round-trip to `get` the `private` object so we can authorize the operation. + +This overhead does not impact deleting "public" objects. We only need to retrieve objects that are registered as `private`. As such, we do not expect any meaningful performance hit initially, but this will grow over time as the feature is used. + + +### `find` +The security wrapper will supply or augment a [KQL `filter`](https://github.com/elastic/kibana/blob/701697cc4a34d07c0508c3bdf01dca6f9d40a636/src/core/server/saved_objects/types.ts#L118) which describes the objects the current user is authorized to see. + +```ts +// Sample KQL filter +const filterClauses = typesToFind.reduce((acc, type) => { + if (this.typeRegistry.isPrivate(type)) { + return [ + ...acc, + // note: this relies on specific behavior of the SO service's `filter_utils`, + // which automatically wraps this in an `and` node to ensure the type is accounted for. + // we have added additional safeguards there, and functional tests will ensure that changes + // to this logic will not accidentally alter our authorization model. + + // This is equivalent to writing the following, if this syntax was allowed by the SO `filter` option: + // esKuery.nodeTypes.function.buildNode('and', [ + // esKuery.nodeTypes.function.buildNode('is', `accessControl.owner`, this.getOwner()), + // esKuery.nodeTypes.function.buildNode('is', `type`, type), + // ]) + esKuery.nodeTypes.function.buildNode('is', `${type}.accessControl.owner`, this.getOwner()), + ]; + } + return acc; +}, []); + +const privateObjectsFilter = + filterClauses.length > 0 ? esKuery.nodeTypes.function.buildNode('or', filterClauses) : null; +``` + +#### Performance considerations +We are sending a more complex query to Elasticsearch for any find request which requests a `private` saved object. This has the potential to hurt query performance, but at this point it hasn't been quantified. + +Since we are only requesting saved objects that the user is authorized to see, there is no additional overhead for Kibana once Elasticsearch has returned the results of the query. + + +### `addToNamespaces` / `deleteFromNamespaces` + +The security wrapper will ensure that the user is authorized to share/unshare all existing `private` objects. +#### Performance considerations +Similar to the "create / override" scenario above, the security wrapper must first retrieve all of the existing `private` objects to ensure that the user is authorized. This requires another round-trip to `get`/`bulk-get` all `private` objects so we can authorize the operation. + +This overhead does not impact sharing/unsharing "public" objects. We only need to retrieve objects that are registered as `private`. As such, we do not expect any meaningful performance hit initially, but this will grow over time as the feature is used. + + +## 3.3 Behavior with various plugin configurations +Kibana can run with and without security enabled. When security is disabled, +`private` saved objects will be accessible to all users. + +| **Plugin Configuration** | Security | Security & Spaces | Spaces | +| ---- | ------ | ------ | --- | +|| ✅ Enforced | ✅ Enforced | 🚫 Not enforced: objects will be accessible to all + +### Alternative +If this behavior is not desired, we can prevent `private` saved objects from being accessed whenever security is disabled. + +See [unresolved question 3](#83-behavior-when-security-is-disabled) + +## 3.4 Impacts on telemetry + +The proposed design does not have any impacts on telemetry collection or reporting. Telemetry collectors run in the background against an "unwrapped" saved objects client. That is to say, they run without space-awareness, and without security. Since the security enforcement for private objects exists within the security wrapper, telemetry collection can continue as it currently exists. + +# 4. Drawbacks + +As outlined above, this approach introduces additional overhead to many of the saved object APIs. We minimize this by denoting which saved object types require this additional authorization. + +This first phase also does not allow a public object to become private. Search sessions may migrate to OLS in the future, but this will likely be a coordinated effort with Elasticsearch, due to the differing ownership models between OLS and async searches. + +# 5. Alternatives + +## 5.1 Document level security +OLS can be thought of as a Kibana-specific implementation of [Document level security](https://www.elastic.co/guide/en/elasticsearch/reference/current/document-level-security.html) ("DLS"). As such, we could consider enhancing the existing DLS feature to fit our needs (DLS doesn't prevent writes at the moment, only reads). This would involve considerable work from the Elasticsearch security team before we could consider this, and may not scale to subsequent phases of OLS. + +## 5.2 Re-using the repository's pre-flight checks +The Saved Objects Repository uses pre-flight checks to ensure that operations against multi-namespace saved objects are adhering the user's current space. The currently proposed implementation has the security wrapper performing pre-flight checks for `private` objects. + +If we have `private` multi-namespace saved objects, then we will end up performing two pre-flight requests, which is excessive. We could explore re-using the repository's pre-flight checks instead of introducing new checks. + +The primary concern with this approach is audit logging. Currently, we audit create/update/delete events before they happen, so that we can record that the operation was attempted, even in the event of a network outage or other transient event. + +If we re-use the repository's pre-flight checks, then the repository will need a way to signal that audit logging should occur. We have a couple of options to explore in this regard: + +### 5.2.1 Move audit logging code into the repository +Now that we no longer ship an OSS distribution, we could move the audit logging code directly into the repository. The implementation could still be provided by the security plugin, so we could still record information about the current user, and respect the current license. + +If we take this approach, then we will need a way to create a repository without audit logging. Certain features rely on the fact that the repository does not perform its own audit logging (such as Alerting, and the background repair jobs for ML). + +Core originally provided an [`audit_trail_service`](https://github.com/elastic/kibana/blob/v7.9.3/src/core/server/audit_trail/audit_trail_service.ts) for this type of functionality, with the thinking that OSS features could take advantage of this if needed. This was abandoned when we discovered that we had no such usages at the time, so we simplified the architecture. We could re-introduce this if desired, in order to support this initiative. + +Not all saved object audit events can be recorded by the repository. When users are not authorized at the type level (e.g., user can't `create` `dashboards`), then the wrapper will record this and not allow the operation to proceed. This shared-responsibility model will likely be even more confusing to reason about, so I'm not sure it's worth the small performance optimization we would get in return. + +### 5.2.2 Pluggable authorization +This inverts the current model. Instead of security wrapping the saved objects client, security could instead provide an authorization module to the repository. The repository could decide when to perform authorization (including audit logging), passing along the results of any pre-flight operations as necessary. + +This arguably a lot of work, but worth consideration as we evolve both our persistence and authorization mechanisms to support our maturing solutions. + +Similar to alternative `5.2.1`, we would need a way to create a repository without authorization/auditing to support specific use cases. + +### 5.2.3 Repository callbacks + +A more rudimentary approach would be to provide callbacks via each saved object operation's `options` property. This callback would be provided by the security wrapper, and called by the repository when it was "safe" to perform the audit operation. + +This is a very simplistic approach, and probably not an architecture that we want to encourage or support long-term. + +### 5.2.4 Pass down preflight objects + +Any client wrapper could fetch the object/s on its own and pass that down to the repository in an `options` field (preflightObject/s?) so the repository can reuse that result if it's defined, instead of initiating an entire additional preflight check. That resolves our problem without much additional complexity. +Of course we don't want consumers (mis)using this field, we can either mark it as `@internal` or we could explore creating a separate "internal SOC" interface that is only meant to be used by the SOC wrappers. + + +# 6. Adoption strategy + +Adoption for net-new features is hopefully straightforward. Like most saved object features, the saved objects service will transparently handle all authorization and auditing of these objects, so long as they are properly registered. + +Adoption for existing features (public saved object types) is not addressed in this first phase. + +# 7. How we teach this + +Updates to the saved object service's documentation to describe the different `accessClassification`s would be required. Like other saved object security controls, we want to ensure that engineers understand that this only "works" when the security wrapper is applied. Creating a bespoke instance of the saved objects client, or using the raw repository will intentionally bypass these authorization checks. + +# 8. Unresolved questions + +## 8.1 `accessControl.owner` + +The `accessControl.owner` property will uniquely identify the owner of each `private` saved object. We are still iterating with the Elasticsearch security team on what this value will ultimately look like. It is highly likely that this will not be a human-readable piece of text, but rather a GUID-style identifier. + +## 8.2 Authorization for private objects + +This has been [resolved](#92-authorization-for-private-objects). + +The user identified by `accessControl.owner` will be authorized for all operations against that instance, provided they pass the existing type/space/action authorization checks. + +In addition to the object owner, we also need to allow administrators to manage these saved objects. This is beneficial if they need to perform a bulk import/export of private objects, or if they wish to remove private objects from users that no longer exist. The open question is: **who counts as an administrator?** + +We have historically used the `Saved Objects Management` feature for these administrative tasks. This feature grants access to all saved objects, even if you're not authorized to access the "owning" application. Do we consider this privilege sufficient to see and potentially manipulate private saved objects? + +## 8.3 Behavior when security is disabled + +This has been [resolved](#93-behavior-when-security-is-disabled). + +When security is disabled, should `private` saved objects still be accessible via the Saved Objects Client? + + +# 9. Resolved Questions + +## 9.2 Authorization for private objects + +Users with the `Saved Objects Management` privilege will be authorized to access private saved objects belonging to other users. +Additionally, we will introduce a sub-feature privilege which will allow administrators to control which of their users with `Saved Objects Management` access are authorized to access these private objects. + +## 9.3 Behavior when security is disabled + +When security is disabled, `private` objects will still be accessible via the Saved Objects Client. \ No newline at end of file From 987e9b879ed86e3ef8ee5cd6831f8297b64be3fb Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Mon, 12 Apr 2021 12:29:05 -0400 Subject: [PATCH 22/79] fix training quick filters (#96500) --- .../exploration_page_wrapper.tsx | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 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 60c5a1db9b93b..6c158f103aade 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 @@ -33,24 +33,32 @@ import { FeatureImportanceSummaryPanelProps } from '../total_feature_importance_ import { useExplorationUrlState } from '../../hooks/use_exploration_url_state'; import { ExplorationQueryBarProps } from '../exploration_query_bar/exploration_query_bar'; -const filters = { - options: [ - { - id: 'training', - label: i18n.translate('xpack.ml.dataframe.analytics.explorationResults.trainingSubsetLabel', { - defaultMessage: 'Training', - }), - }, - { - id: 'testing', - label: i18n.translate('xpack.ml.dataframe.analytics.explorationResults.testingSubsetLabel', { - defaultMessage: 'Testing', - }), - }, - ], - columnId: 'ml.is_training', - key: { training: true, testing: false }, -}; +function getFilters(resultsField: string) { + return { + options: [ + { + id: 'training', + label: i18n.translate( + 'xpack.ml.dataframe.analytics.explorationResults.trainingSubsetLabel', + { + defaultMessage: 'Training', + } + ), + }, + { + id: 'testing', + label: i18n.translate( + 'xpack.ml.dataframe.analytics.explorationResults.testingSubsetLabel', + { + defaultMessage: 'Testing', + } + ), + }, + ], + columnId: `${resultsField}.is_training`, + key: { training: true, testing: false }, + }; +} export interface EvaluatePanelProps { jobConfig: DataFrameAnalyticsConfig; @@ -151,7 +159,7 @@ export const ExplorationPageWrapper: FC = ({ )} - {indexPattern !== undefined && ( + {indexPattern !== undefined && jobConfig && ( <> @@ -162,7 +170,7 @@ export const ExplorationPageWrapper: FC = ({ indexPattern={indexPattern} setSearchQuery={searchQueryUpdateHandler} query={query} - filters={filters} + filters={getFilters(jobConfig.dest.results_field)} /> From 5879d1fdf79bfaf12f1be5876a527d38fed3a506 Mon Sep 17 00:00:00 2001 From: spalger Date: Mon, 12 Apr 2021 09:35:44 -0700 Subject: [PATCH 23/79] =?UTF-8?q?Revert=20"docs:=20=E2=9C=8F=EF=B8=8F=20im?= =?UTF-8?q?prove=20UI=20actions=20plugin=20readme=20(#96030)"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 7448238444b9e36ae15286aa2897f055f30d42a7. --- src/plugins/ui_actions/README.asciidoc | 73 +++----------------------- 1 file changed, 8 insertions(+), 65 deletions(-) diff --git a/src/plugins/ui_actions/README.asciidoc b/src/plugins/ui_actions/README.asciidoc index 27b3eae3a52a7..577aa2eae354b 100644 --- a/src/plugins/ui_actions/README.asciidoc +++ b/src/plugins/ui_actions/README.asciidoc @@ -1,71 +1,14 @@ [[uiactions-plugin]] == UI Actions -UI Actions plugins provides API to manage *triggers* and *actions*. - -*Trigger* is an abstract description of user's intent to perform an action -(like user clicking on a value inside chart). It allows us to do runtime -binding between code from different plugins. For, example one such -trigger is when somebody applies filters on dashboard; another one is when -somebody opens a Dashboard panel context menu. - -*Actions* are pieces of code that execute in response to a trigger. For example, -to the dashboard filtering trigger multiple actions can be attached. Once a user -filters on the dashboard all possible actions are displayed to the user in a -popup menu and the user has to chose one. - -In general this plugin provides: - -- Creating custom functionality (actions). -- Creating custom user interaction events (triggers). -- Attaching and detaching actions to triggers. -- Emitting trigger events. -- Executing actions attached to a given trigger. -- Exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. - -=== Basic usage - -To get started, first you need to know a trigger you will attach your actions to. -You can either pick an existing one, or register your own one: - -[source,typescript jsx] ----- -plugins.uiActions.registerTrigger({ - id: 'MY_APP_PIE_CHART_CLICK', - title: 'Pie chart click', - description: 'When user clicks on a pie chart slice.', -}); ----- - -Now, when user clicks on a pie slice you need to "trigger" your trigger and -provide some context data: - -[source,typescript jsx] ----- -plugins.uiActions.getTrigger('MY_APP_PIE_CHART_CLICK').exec({ - /* Custom context data. */ -}); ----- - -Finally, your code or developers from other plugins can register UI actions that -listen for the above trigger and execute some code when the trigger is triggered. - -[source,typescript jsx] ----- -plugins.uiActions.registerAction({ - id: 'DO_SOMETHING', - isCompatible: async (context) => true, - execute: async (context) => { - // Do something. - }, -}); -plugins.uiActions.attachAction('MY_APP_PIE_CHART_CLICK', 'DO_SOMETHING'); ----- - -Now your `DO_SOMETHING` action will automatically execute when `MY_APP_PIE_CHART_CLICK` -trigger is triggered; or, if more than one compatible action is attached to -that trigger, user will be presented with a context menu popup to select one -action to execute. +An API for: + +- creating custom functionality (`actions`) +- creating custom user interaction events (`triggers`) +- attaching and detaching `actions` to `triggers`. +- emitting `trigger` events +- executing `actions` attached to a given `trigger`. +- exposing a context menu for the user to choose the appropriate action when there are multiple actions attached to a single trigger. === Examples From c9cd4a0a99a10b5ca9f10c86f27ac22e7a524035 Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Mon, 12 Apr 2021 10:55:44 -0600 Subject: [PATCH 24/79] [telemetry] Adds cloud provider metadata. (#95131) --- .../server/__snapshots__/index.test.ts.snap | 8 +- .../cloud_provider_collector.test.mocks.ts | 18 + .../cloud/cloud_provider_collector.test.ts | 78 +++++ .../cloud/cloud_provider_collector.ts | 69 ++++ .../collectors/cloud/detector/aws.test.ts | 311 ++++++++++++++++++ .../server/collectors/cloud/detector/aws.ts | 151 +++++++++ .../collectors/cloud/detector/azure.test.ts | 71 ++-- .../server/collectors/cloud/detector/azure.ts | 103 ++++++ .../cloud/detector/cloud_detector.mock.ts | 18 + .../cloud/detector/cloud_detector.test.ts | 50 +-- .../cloud/detector/cloud_detector.ts | 76 +++++ .../cloud/detector/cloud_response.test.ts | 5 +- .../cloud/detector/cloud_response.ts | 62 ++-- .../cloud/detector/cloud_service.test.ts | 66 ++-- .../cloud/detector/cloud_service.ts | 130 ++++++++ .../collectors/cloud/detector/gcp.test.ts | 99 +++--- .../server/collectors/cloud/detector/gcp.ts | 127 +++++++ .../server/collectors/cloud/detector/index.ts | 6 +- .../server/collectors/cloud/index.ts | 9 + .../server/collectors/index.ts | 1 + .../server/index.test.mocks.ts | 18 + .../server/index.test.ts | 11 + .../kibana_usage_collection/server/plugin.ts | 2 + src/plugins/telemetry/schema/oss_plugins.json | 28 ++ x-pack/plugins/monitoring/common/constants.ts | 17 - x-pack/plugins/monitoring/server/cloud/aws.js | 127 ------- .../monitoring/server/cloud/aws.test.js | 237 ------------- .../plugins/monitoring/server/cloud/azure.js | 99 ------ .../monitoring/server/cloud/cloud_detector.js | 64 ---- .../monitoring/server/cloud/cloud_service.js | 115 ------- .../monitoring/server/cloud/cloud_services.js | 17 - .../server/cloud/cloud_services.test.js | 22 -- x-pack/plugins/monitoring/server/cloud/gcp.js | 136 -------- 33 files changed, 1348 insertions(+), 1003 deletions(-) create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.mocks.ts create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.ts create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts rename x-pack/plugins/monitoring/server/cloud/azure.test.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts (71%) create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts rename x-pack/plugins/monitoring/server/cloud/cloud_detector.test.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.test.ts (56%) create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts rename x-pack/plugins/monitoring/server/cloud/cloud_response.test.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.test.ts (87%) rename x-pack/plugins/monitoring/server/cloud/cloud_response.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.ts (52%) rename x-pack/plugins/monitoring/server/cloud/cloud_service.test.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts (65%) create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts rename x-pack/plugins/monitoring/server/cloud/gcp.test.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts (66%) create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts rename x-pack/plugins/monitoring/server/cloud/index.js => src/plugins/kibana_usage_collection/server/collectors/cloud/detector/index.ts (53%) create mode 100644 src/plugins/kibana_usage_collection/server/collectors/cloud/index.ts create mode 100644 src/plugins/kibana_usage_collection/server/index.test.mocks.ts delete mode 100644 x-pack/plugins/monitoring/server/cloud/aws.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/aws.test.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/azure.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/cloud_detector.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/cloud_service.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/cloud_services.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/cloud_services.test.js delete mode 100644 x-pack/plugins/monitoring/server/cloud/gcp.js diff --git a/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap b/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap index 2180d6a0fcc4e..939e90d2f2583 100644 --- a/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap +++ b/src/plugins/kibana_usage_collection/server/__snapshots__/index.test.ts.snap @@ -12,8 +12,10 @@ exports[`kibana_usage_collection Runs the setup method without issues 5`] = `fal exports[`kibana_usage_collection Runs the setup method without issues 6`] = `false`; -exports[`kibana_usage_collection Runs the setup method without issues 7`] = `true`; +exports[`kibana_usage_collection Runs the setup method without issues 7`] = `false`; -exports[`kibana_usage_collection Runs the setup method without issues 8`] = `false`; +exports[`kibana_usage_collection Runs the setup method without issues 8`] = `true`; -exports[`kibana_usage_collection Runs the setup method without issues 9`] = `true`; +exports[`kibana_usage_collection Runs the setup method without issues 9`] = `false`; + +exports[`kibana_usage_collection Runs the setup method without issues 10`] = `true`; diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.mocks.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.mocks.ts new file mode 100644 index 0000000000000..4a8f269fe5098 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.mocks.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 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 { cloudDetectorMock } from './detector/cloud_detector.mock'; + +const mock = cloudDetectorMock.create(); + +export const cloudDetailsMock = mock.getCloudDetails; +export const detectCloudServiceMock = mock.detectCloudService; + +jest.doMock('./detector', () => ({ + CloudDetector: jest.fn().mockImplementation(() => mock), +})); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.ts new file mode 100644 index 0000000000000..1f7617a0e69ce --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright 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 { cloudDetailsMock, detectCloudServiceMock } from './cloud_provider_collector.test.mocks'; +import { loggingSystemMock } from '../../../../../core/server/mocks'; +import { + Collector, + createUsageCollectionSetupMock, + createCollectorFetchContextMock, +} from '../../../../usage_collection/server/usage_collection.mock'; + +import { registerCloudProviderUsageCollector } from './cloud_provider_collector'; + +describe('registerCloudProviderUsageCollector', () => { + let collector: Collector; + const logger = loggingSystemMock.createLogger(); + + const usageCollectionMock = createUsageCollectionSetupMock(); + usageCollectionMock.makeUsageCollector.mockImplementation((config) => { + collector = new Collector(logger, config); + return createUsageCollectionSetupMock().makeUsageCollector(config); + }); + + const mockedFetchContext = createCollectorFetchContextMock(); + + beforeEach(() => { + cloudDetailsMock.mockClear(); + detectCloudServiceMock.mockClear(); + registerCloudProviderUsageCollector(usageCollectionMock); + }); + + test('registered collector is set', () => { + expect(collector).not.toBeUndefined(); + }); + + test('isReady() => false when cloud details are not available', () => { + cloudDetailsMock.mockReturnValueOnce(undefined); + expect(collector.isReady()).toBe(false); + }); + + test('isReady() => true when cloud details are available', () => { + cloudDetailsMock.mockReturnValueOnce({ foo: true }); + expect(collector.isReady()).toBe(true); + }); + + test('initiates CloudDetector.detectCloudDetails when called', () => { + expect(detectCloudServiceMock).toHaveBeenCalledTimes(1); + }); + + describe('fetch()', () => { + test('returns undefined when no details are available', async () => { + cloudDetailsMock.mockReturnValueOnce(undefined); + await expect(collector.fetch(mockedFetchContext)).resolves.toBeUndefined(); + }); + + test('returns cloud details when defined', async () => { + const mockDetails = { + name: 'aws', + vm_type: 't2.micro', + region: 'us-west-2', + zone: 'us-west-2a', + }; + + cloudDetailsMock.mockReturnValueOnce(mockDetails); + await expect(collector.fetch(mockedFetchContext)).resolves.toEqual(mockDetails); + }); + + test('should not fail if invoked when not ready', async () => { + cloudDetailsMock.mockReturnValueOnce(undefined); + await expect(collector.fetch(mockedFetchContext)).resolves.toBe(undefined); + }); + }); +}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts new file mode 100644 index 0000000000000..eafce56d7cf2e --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/cloud_provider_collector.ts @@ -0,0 +1,69 @@ +/* + * Copyright 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 { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { CloudDetector } from './detector'; + +interface Usage { + name: string; + vm_type?: string; + region?: string; + zone?: string; +} + +export function registerCloudProviderUsageCollector(usageCollection: UsageCollectionSetup) { + const cloudDetector = new CloudDetector(); + // determine the cloud service in the background + cloudDetector.detectCloudService(); + + const collector = usageCollection.makeUsageCollector({ + type: 'cloud_provider', + isReady: () => Boolean(cloudDetector.getCloudDetails()), + async fetch() { + const details = cloudDetector.getCloudDetails(); + if (!details) { + return; + } + + return { + name: details.name, + vm_type: details.vm_type, + region: details.region, + zone: details.zone, + }; + }, + schema: { + name: { + type: 'keyword', + _meta: { + description: 'The name of the cloud provider', + }, + }, + vm_type: { + type: 'keyword', + _meta: { + description: 'The VM instance type', + }, + }, + region: { + type: 'keyword', + _meta: { + description: 'The cloud provider region', + }, + }, + zone: { + type: 'keyword', + _meta: { + description: 'The availability zone within the region', + }, + }, + }, + }); + + usageCollection.registerCollector(collector); +} diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts new file mode 100644 index 0000000000000..0bba64823a3e2 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.test.ts @@ -0,0 +1,311 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs'; +import type { Request, RequestOptions } from './cloud_service'; +import { AWSCloudService, AWSResponse } from './aws'; + +type Callback = (err: unknown, res: unknown) => void; + +const AWS = new AWSCloudService(); + +describe('AWS', () => { + const expectedFilenames = ['/sys/hypervisor/uuid', '/sys/devices/virtual/dmi/id/product_uuid']; + const expectedEncoding = 'utf8'; + // mixed case to ensure we check for ec2 after lowercasing + const ec2Uuid = 'eC2abcdef-ghijk\n'; + const ec2FileSystem = { + readFile: (filename: string, encoding: string, callback: Callback) => { + expect(expectedFilenames).toContain(filename); + expect(encoding).toEqual(expectedEncoding); + + callback(null, ec2Uuid); + }, + } as typeof fs; + + it('is named "aws"', () => { + expect(AWS.getName()).toEqual('aws'); + }); + + describe('_checkIfService', () => { + it('handles expected response', async () => { + const id = 'abcdef'; + const request = ((req: RequestOptions, callback: Callback) => { + expect(req.method).toEqual('GET'); + expect(req.uri).toEqual( + 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document' + ); + expect(req.json).toEqual(true); + + const body = `{"instanceId": "${id}","availabilityZone":"us-fake-2c", "imageId" : "ami-6df1e514"}`; + + callback(null, { statusCode: 200, body }); + }) as Request; + // ensure it does not use the fs to trump the body + const awsCheckedFileSystem = new AWSCloudService({ + _fs: ec2FileSystem, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._checkIfService(request); + + expect(response.isConfirmed()).toEqual(true); + expect(response.toJSON()).toEqual({ + name: AWS.getName(), + id, + region: undefined, + vm_type: undefined, + zone: 'us-fake-2c', + metadata: { + imageId: 'ami-6df1e514', + }, + }); + }); + + it('handles request without a usable body by downgrading to UUID detection', async () => { + const request = ((_req: RequestOptions, callback: Callback) => + callback(null, { statusCode: 404 })) as Request; + const awsCheckedFileSystem = new AWSCloudService({ + _fs: ec2FileSystem, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._checkIfService(request); + + expect(response.isConfirmed()).toBe(true); + expect(response.toJSON()).toEqual({ + name: AWS.getName(), + id: ec2Uuid.trim().toLowerCase(), + region: undefined, + vm_type: undefined, + zone: undefined, + metadata: undefined, + }); + }); + + it('handles request failure by downgrading to UUID detection', async () => { + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(new Error('expected: request failed'), null)) as Request; + const awsCheckedFileSystem = new AWSCloudService({ + _fs: ec2FileSystem, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._checkIfService(failedRequest); + + expect(response.isConfirmed()).toBe(true); + expect(response.toJSON()).toEqual({ + name: AWS.getName(), + id: ec2Uuid.trim().toLowerCase(), + region: undefined, + vm_type: undefined, + zone: undefined, + metadata: undefined, + }); + }); + + it('handles not running on AWS', async () => { + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(null, null)) as Request; + const awsIgnoredFileSystem = new AWSCloudService({ + _fs: ec2FileSystem, + _isWindows: true, + }); + + const response = await awsIgnoredFileSystem._checkIfService(failedRequest); + + expect(response.getName()).toEqual(AWS.getName()); + expect(response.isConfirmed()).toBe(false); + }); + }); + + describe('parseBody', () => { + it('parses object in expected format', () => { + const body: AWSResponse = { + devpayProductCodes: null, + privateIp: '10.0.0.38', + availabilityZone: 'us-west-2c', + version: '2010-08-31', + instanceId: 'i-0c7a5b7590a4d811c', + billingProducts: null, + instanceType: 't2.micro', + accountId: '1234567890', + architecture: 'x86_64', + kernelId: null, + ramdiskId: null, + imageId: 'ami-6df1e514', + pendingTime: '2017-07-06T02:09:12Z', + region: 'us-west-2', + marketplaceProductCodes: null, + }; + + const response = AWSCloudService.parseBody(AWS.getName(), body)!; + expect(response).not.toBeNull(); + + expect(response.getName()).toEqual(AWS.getName()); + expect(response.isConfirmed()).toEqual(true); + expect(response.toJSON()).toEqual({ + name: 'aws', + id: 'i-0c7a5b7590a4d811c', + vm_type: 't2.micro', + region: 'us-west-2', + zone: 'us-west-2c', + metadata: { + version: '2010-08-31', + architecture: 'x86_64', + kernelId: null, + marketplaceProductCodes: null, + ramdiskId: null, + imageId: 'ami-6df1e514', + pendingTime: '2017-07-06T02:09:12Z', + }, + }); + }); + + it('ignores unexpected response body', () => { + // @ts-expect-error + expect(AWSCloudService.parseBody(AWS.getName(), undefined)).toBe(null); + // @ts-expect-error + expect(AWSCloudService.parseBody(AWS.getName(), null)).toBe(null); + // @ts-expect-error + expect(AWSCloudService.parseBody(AWS.getName(), {})).toBe(null); + // @ts-expect-error + expect(AWSCloudService.parseBody(AWS.getName(), { privateIp: 'a.b.c.d' })).toBe(null); + }); + }); + + describe('_tryToDetectUuid', () => { + describe('checks the file system for UUID if not Windows', () => { + it('checks /sys/hypervisor/uuid', async () => { + const awsCheckedFileSystem = new AWSCloudService({ + _fs: { + readFile: (filename: string, encoding: string, callback: Callback) => { + expect(expectedFilenames).toContain(filename); + expect(encoding).toEqual(expectedEncoding); + + callback(null, ec2Uuid); + }, + } as typeof fs, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._tryToDetectUuid(); + + expect(response.isConfirmed()).toEqual(true); + expect(response.toJSON()).toEqual({ + name: AWS.getName(), + id: ec2Uuid.trim().toLowerCase(), + region: undefined, + zone: undefined, + vm_type: undefined, + metadata: undefined, + }); + }); + + it('checks /sys/devices/virtual/dmi/id/product_uuid', async () => { + const awsCheckedFileSystem = new AWSCloudService({ + _fs: { + readFile: (filename: string, encoding: string, callback: Callback) => { + expect(expectedFilenames).toContain(filename); + expect(encoding).toEqual(expectedEncoding); + + callback(null, ec2Uuid); + }, + } as typeof fs, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._tryToDetectUuid(); + + expect(response.isConfirmed()).toEqual(true); + expect(response.toJSON()).toEqual({ + name: AWS.getName(), + id: ec2Uuid.trim().toLowerCase(), + region: undefined, + zone: undefined, + vm_type: undefined, + metadata: undefined, + }); + }); + + it('returns confirmed if only one file exists', async () => { + let callCount = 0; + const awsCheckedFileSystem = new AWSCloudService({ + _fs: { + readFile: (filename: string, encoding: string, callback: Callback) => { + if (callCount === 0) { + callCount++; + throw new Error('oops'); + } + callback(null, ec2Uuid); + }, + } as typeof fs, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._tryToDetectUuid(); + + expect(response.isConfirmed()).toEqual(true); + expect(response.toJSON()).toEqual({ + name: AWS.getName(), + id: ec2Uuid.trim().toLowerCase(), + region: undefined, + zone: undefined, + vm_type: undefined, + metadata: undefined, + }); + }); + + it('returns unconfirmed if all files return errors', async () => { + const awsFailedFileSystem = new AWSCloudService({ + _fs: ({ + readFile: () => { + throw new Error('oops'); + }, + } as unknown) as typeof fs, + _isWindows: false, + }); + + const response = await awsFailedFileSystem._tryToDetectUuid(); + + expect(response.isConfirmed()).toEqual(false); + }); + }); + + it('ignores UUID if it does not start with ec2', async () => { + const notEC2FileSystem = { + readFile: (filename: string, encoding: string, callback: Callback) => { + expect(expectedFilenames).toContain(filename); + expect(encoding).toEqual(expectedEncoding); + + callback(null, 'notEC2'); + }, + } as typeof fs; + + const awsCheckedFileSystem = new AWSCloudService({ + _fs: notEC2FileSystem, + _isWindows: false, + }); + + const response = await awsCheckedFileSystem._tryToDetectUuid(); + + expect(response.isConfirmed()).toEqual(false); + }); + + it('does NOT check the file system for UUID on Windows', async () => { + const awsUncheckedFileSystem = new AWSCloudService({ + _fs: ec2FileSystem, + _isWindows: true, + }); + + const response = await awsUncheckedFileSystem._tryToDetectUuid(); + + expect(response.isConfirmed()).toEqual(false); + }); + }); +}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts new file mode 100644 index 0000000000000..69e5698489b30 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/aws.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs'; +import { get, isString, omit } from 'lodash'; +import { promisify } from 'util'; +import { CloudService, CloudServiceOptions, Request, RequestOptions } from './cloud_service'; +import { CloudServiceResponse } from './cloud_response'; + +// We explicitly call out the version, 2016-09-02, rather than 'latest' to avoid unexpected changes +const SERVICE_ENDPOINT = 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document'; + +/** @internal */ +export interface AWSResponse { + accountId: string; + architecture: string; + availabilityZone: string; + billingProducts: unknown; + devpayProductCodes: unknown; + marketplaceProductCodes: unknown; + imageId: string; + instanceId: string; + instanceType: string; + kernelId: unknown; + pendingTime: string; + privateIp: string; + ramdiskId: unknown; + region: string; + version: string; +} + +/** + * Checks and loads the service metadata for an Amazon Web Service VM if it is available. + * + * @internal + */ +export class AWSCloudService extends CloudService { + private readonly _isWindows: boolean; + private readonly _fs: typeof fs; + + /** + * Parse the AWS response, if possible. + * + * Example payload: + * { + * "accountId" : "1234567890", + * "architecture" : "x86_64", + * "availabilityZone" : "us-west-2c", + * "billingProducts" : null, + * "devpayProductCodes" : null, + * "imageId" : "ami-6df1e514", + * "instanceId" : "i-0c7a5b7590a4d811c", + * "instanceType" : "t2.micro", + * "kernelId" : null, + * "pendingTime" : "2017-07-06T02:09:12Z", + * "privateIp" : "10.0.0.38", + * "ramdiskId" : null, + * "region" : "us-west-2" + * "version" : "2010-08-31", + * } + */ + static parseBody(name: string, body: AWSResponse): CloudServiceResponse | null { + const id: string | undefined = get(body, 'instanceId'); + const vmType: string | undefined = get(body, 'instanceType'); + const region: string | undefined = get(body, 'region'); + const zone: string | undefined = get(body, 'availabilityZone'); + const metadata = omit(body, [ + // remove keys we already have + 'instanceId', + 'instanceType', + 'region', + 'availabilityZone', + // remove keys that give too much detail + 'accountId', + 'billingProducts', + 'devpayProductCodes', + 'privateIp', + ]); + + // ensure we actually have some data + if (id || vmType || region || zone) { + return new CloudServiceResponse(name, true, { id, vmType, region, zone, metadata }); + } + + return null; + } + + constructor(options: CloudServiceOptions = {}) { + super('aws', options); + + // Allow the file system handler to be swapped out for tests + const { _fs = fs, _isWindows = process.platform.startsWith('win') } = options; + + this._fs = _fs; + this._isWindows = _isWindows; + } + + async _checkIfService(request: Request) { + const req: RequestOptions = { + method: 'GET', + uri: SERVICE_ENDPOINT, + json: true, + }; + + return promisify(request)(req) + .then((response) => + this._parseResponse(response.body, (body) => + AWSCloudService.parseBody(this.getName(), body) + ) + ) + .catch(() => this._tryToDetectUuid()); + } + + /** + * Attempt to load the UUID by checking `/sys/hypervisor/uuid`. + * + * This is a fallback option if the metadata service is unavailable for some reason. + */ + _tryToDetectUuid() { + // Windows does not have an easy way to check + if (!this._isWindows) { + const pathsToCheck = ['/sys/hypervisor/uuid', '/sys/devices/virtual/dmi/id/product_uuid']; + const promises = pathsToCheck.map((path) => promisify(this._fs.readFile)(path, 'utf8')); + + return Promise.allSettled(promises).then((responses) => { + for (const response of responses) { + let uuid; + if (response.status === 'fulfilled' && isString(response.value)) { + // Some AWS APIs return it lowercase (like the file did in testing), while others return it uppercase + uuid = response.value.trim().toLowerCase(); + + // There is a small chance of a false positive here in the unlikely event that a uuid which doesn't + // belong to ec2 happens to be generated with `ec2` as the first three characters. + if (uuid.startsWith('ec2')) { + return new CloudServiceResponse(this._name, true, { id: uuid }); + } + } + } + + return this._createUnconfirmedResponse(); + }); + } + + return Promise.resolve(this._createUnconfirmedResponse()); + } +} diff --git a/x-pack/plugins/monitoring/server/cloud/azure.test.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts similarity index 71% rename from x-pack/plugins/monitoring/server/cloud/azure.test.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts index cb56c89f1d64a..17205562fa335 100644 --- a/x-pack/plugins/monitoring/server/cloud/azure.test.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.test.ts @@ -1,11 +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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 { AZURE } from './azure'; +import type { Request, RequestOptions } from './cloud_service'; +import { AzureCloudService } from './azure'; + +type Callback = (err: unknown, res: unknown) => void; + +const AZURE = new AzureCloudService(); describe('Azure', () => { it('is named "azure"', () => { @@ -15,16 +21,16 @@ describe('Azure', () => { describe('_checkIfService', () => { it('handles expected response', async () => { const id = 'abcdef'; - const request = (req, callback) => { + const request = ((req: RequestOptions, callback: Callback) => { expect(req.method).toEqual('GET'); expect(req.uri).toEqual('http://169.254.169.254/metadata/instance?api-version=2017-04-02'); - expect(req.headers.Metadata).toEqual('true'); + expect(req.headers?.Metadata).toEqual('true'); expect(req.json).toEqual(true); const body = `{"compute":{"vmId": "${id}","location":"fakeus","availabilityZone":"fakeus-2"}}`; - callback(null, { statusCode: 200, body }, body); - }; + callback(null, { statusCode: 200, body }); + }) as Request; const response = await AZURE._checkIfService(request); expect(response.isConfirmed()).toEqual(true); @@ -43,39 +49,30 @@ describe('Azure', () => { // NOTE: the CloudService method, checkIfService, catches the errors that follow it('handles not running on Azure with error by rethrowing it', async () => { const someError = new Error('expected: request failed'); - const failedRequest = (_req, callback) => callback(someError, null); + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(someError, null)) as Request; - try { + expect(async () => { await AZURE._checkIfService(failedRequest); - - expect().fail('Method should throw exception (Promise.reject)'); - } catch (err) { - expect(err.message).toEqual(someError.message); - } + }).rejects.toThrowError(someError.message); }); it('handles not running on Azure with 404 response by throwing error', async () => { - const failedRequest = (_req, callback) => callback(null, { statusCode: 404 }); + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(null, { statusCode: 404 })) as Request; - try { + expect(async () => { await AZURE._checkIfService(failedRequest); - - expect().fail('Method should throw exception (Promise.reject)'); - } catch (ignoredErr) { - // ignored - } + }).rejects.toThrowErrorMatchingInlineSnapshot(`"Azure request failed"`); }); it('handles not running on Azure with unexpected response by throwing error', async () => { - const failedRequest = (_req, callback) => callback(null, null); + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(null, null)) as Request; - try { + expect(async () => { await AZURE._checkIfService(failedRequest); - - expect().fail('Method should throw exception (Promise.reject)'); - } catch (ignoredErr) { - // ignored - } + }).rejects.toThrowErrorMatchingInlineSnapshot(`"Azure request failed"`); }); }); @@ -122,7 +119,8 @@ describe('Azure', () => { }, }; - const response = AZURE._parseBody(body); + const response = AzureCloudService.parseBody(AZURE.getName(), body)!; + expect(response).not.toBeNull(); expect(response.getName()).toEqual(AZURE.getName()); expect(response.isConfirmed()).toEqual(true); @@ -174,7 +172,8 @@ describe('Azure', () => { }, }; - const response = AZURE._parseBody(body); + const response = AzureCloudService.parseBody(AZURE.getName(), body)!; + expect(response).not.toBeNull(); expect(response.getName()).toEqual(AZURE.getName()); expect(response.isConfirmed()).toEqual(true); @@ -191,10 +190,14 @@ describe('Azure', () => { }); it('ignores unexpected response body', () => { - expect(AZURE._parseBody(undefined)).toBe(null); - expect(AZURE._parseBody(null)).toBe(null); - expect(AZURE._parseBody({})).toBe(null); - expect(AZURE._parseBody({ privateIp: 'a.b.c.d' })).toBe(null); + // @ts-expect-error + expect(AzureCloudService.parseBody(AZURE.getName(), undefined)).toBe(null); + // @ts-expect-error + expect(AzureCloudService.parseBody(AZURE.getName(), null)).toBe(null); + // @ts-expect-error + expect(AzureCloudService.parseBody(AZURE.getName(), {})).toBe(null); + // @ts-expect-error + expect(AzureCloudService.parseBody(AZURE.getName(), { privateIp: 'a.b.c.d' })).toBe(null); }); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts new file mode 100644 index 0000000000000..b846636f0ce6c --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/azure.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { get, omit } from 'lodash'; +import { promisify } from 'util'; +import { CloudService, Request } from './cloud_service'; +import { CloudServiceResponse } from './cloud_response'; + +// 2017-04-02 is the first GA release of this API +const SERVICE_ENDPOINT = 'http://169.254.169.254/metadata/instance?api-version=2017-04-02'; + +interface AzureResponse { + compute?: Record; + network: unknown; +} + +/** + * Checks and loads the service metadata for an Azure VM if it is available. + * + * @internal + */ +export class AzureCloudService extends CloudService { + /** + * Parse the Azure response, if possible. + * + * Azure VMs created using the "classic" method, as opposed to the resource manager, + * do not provide a "compute" field / object. However, both report the "network" field / object. + * + * Example payload (with network object ignored): + * { + * "compute": { + * "location": "eastus", + * "name": "my-ubuntu-vm", + * "offer": "UbuntuServer", + * "osType": "Linux", + * "platformFaultDomain": "0", + * "platformUpdateDomain": "0", + * "publisher": "Canonical", + * "sku": "16.04-LTS", + * "version": "16.04.201706191", + * "vmId": "d4c57456-2b3b-437a-9f1f-7082cfce02d4", + * "vmSize": "Standard_A1" + * }, + * "network": { + * ... + * } + * } + */ + static parseBody(name: string, body: AzureResponse): CloudServiceResponse | null { + const compute: Record | undefined = get(body, 'compute'); + const id = get, string>(compute, 'vmId'); + const vmType = get, string>(compute, 'vmSize'); + const region = get, string>(compute, 'location'); + + // remove keys that we already have; explicitly undefined so we don't send it when empty + const metadata = compute ? omit(compute, ['vmId', 'vmSize', 'location']) : undefined; + + // we don't actually use network, but we check for its existence to see if this is a response from Azure + const network = get(body, 'network'); + + // ensure we actually have some data + if (id || vmType || region) { + return new CloudServiceResponse(name, true, { id, vmType, region, metadata }); + } else if (network) { + // classic-managed VMs in Azure don't provide compute so we highlight the lack of info + return new CloudServiceResponse(name, true, { metadata: { classic: true } }); + } + + return null; + } + + constructor(options = {}) { + super('azure', options); + } + + async _checkIfService(request: Request) { + const req = { + method: 'GET', + uri: SERVICE_ENDPOINT, + headers: { + // Azure requires this header + Metadata: 'true', + }, + json: true, + }; + + const response = await promisify(request)(req); + + // Note: there is no fallback option for Azure + if (!response || response.statusCode === 404) { + throw new Error('Azure request failed'); + } + + return this._parseResponse(response.body, (body) => + AzureCloudService.parseBody(this.getName(), body) + ); + } +} diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.ts new file mode 100644 index 0000000000000..82e321c93783d --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.mock.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const create = () => { + const mock = { + detectCloudService: jest.fn(), + getCloudDetails: jest.fn(), + }; + + return mock; +}; + +export const cloudDetectorMock = { create }; diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_detector.test.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.test.ts similarity index 56% rename from x-pack/plugins/monitoring/server/cloud/cloud_detector.test.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.test.ts index 3c4d0dfa724c8..4b88ed5b4064f 100644 --- a/x-pack/plugins/monitoring/server/cloud/cloud_detector.test.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.test.ts @@ -1,11 +1,13 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 { CloudDetector } from './cloud_detector'; +import type { CloudService } from './cloud_service'; describe('CloudDetector', () => { const cloudService1 = { @@ -28,8 +30,10 @@ describe('CloudDetector', () => { }; }, }; - // this service is theoretically a better match for the current server, but order dictates that it should - // never be checked (at least until we have some sort of "confidence" metric returned, if we ever run into this problem) + // this service is theoretically a better match for the current server, + // but order dictates that it should never be checked (at least until + // we have some sort of "confidence" metric returned, if we ever run + // into this problem) const cloudService4 = { checkIfService: () => { return { @@ -40,7 +44,12 @@ describe('CloudDetector', () => { }; }, }; - const cloudServices = [cloudService1, cloudService2, cloudService3, cloudService4]; + const cloudServices = ([ + cloudService1, + cloudService2, + cloudService3, + cloudService4, + ] as unknown) as CloudService[]; describe('getCloudDetails', () => { it('returns undefined by default', () => { @@ -51,35 +60,34 @@ describe('CloudDetector', () => { }); describe('detectCloudService', () => { - it('awaits _getCloudService', async () => { + it('returns first match', async () => { const detector = new CloudDetector({ cloudServices }); - expect(detector.getCloudDetails()).toBe(undefined); + expect(detector.getCloudDetails()).toBeUndefined(); await detector.detectCloudService(); - expect(detector.getCloudDetails()).toEqual({ name: 'good-match' }); - }); - }); - - describe('_getCloudService', () => { - it('returns first match', async () => { - const detector = new CloudDetector(); - // note: should never use better-match - expect(await detector._getCloudService(cloudServices)).toEqual({ name: 'good-match' }); + expect(detector.getCloudDetails()).toEqual({ name: 'good-match' }); }); it('returns undefined if none match', async () => { - const detector = new CloudDetector(); + const services = ([cloudService1, cloudService2] as unknown) as CloudService[]; - expect(await detector._getCloudService([cloudService1, cloudService2])).toBe(undefined); - expect(await detector._getCloudService([])).toBe(undefined); + const detector1 = new CloudDetector({ cloudServices: services }); + await detector1.detectCloudService(); + expect(detector1.getCloudDetails()).toBeUndefined(); + + const detector2 = new CloudDetector({ cloudServices: [] }); + await detector2.detectCloudService(); + expect(detector2.getCloudDetails()).toBeUndefined(); }); // this is already tested above, but this just tests it explicitly it('ignores exceptions from cloud services', async () => { - const detector = new CloudDetector(); + const services = ([cloudService2] as unknown) as CloudService[]; + const detector = new CloudDetector({ cloudServices: services }); - expect(await detector._getCloudService([cloudService2])).toBe(undefined); + await detector.detectCloudService(); + expect(detector.getCloudDetails()).toBeUndefined(); }); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts new file mode 100644 index 0000000000000..6f6405d9852b6 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_detector.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { CloudService } from './cloud_service'; +import type { CloudServiceResponseJson } from './cloud_response'; + +import { AWSCloudService } from './aws'; +import { AzureCloudService } from './azure'; +import { GCPCloudService } from './gcp'; + +const SUPPORTED_SERVICES = [AWSCloudService, AzureCloudService, GCPCloudService]; + +interface CloudDetectorOptions { + cloudServices?: CloudService[]; +} + +/** + * The `CloudDetector` can be used to asynchronously detect the + * cloud service that Kibana is running within. + * + * @internal + */ +export class CloudDetector { + private readonly cloudServices: CloudService[]; + private cloudDetails?: CloudServiceResponseJson; + + constructor(options: CloudDetectorOptions = {}) { + this.cloudServices = + options.cloudServices ?? SUPPORTED_SERVICES.map((Service) => new Service()); + } + + /** + * Get any cloud details that we have detected. + */ + getCloudDetails() { + return this.cloudDetails; + } + + /** + * Asynchronously detect the cloud service. + * + * Callers are _not_ expected to await this method, which allows the + * caller to trigger the lookup and then simply use it whenever we + * determine it. + */ + async detectCloudService() { + this.cloudDetails = await this.getCloudService(); + } + + /** + * Check every cloud service until the first one reports success from detection. + */ + private async getCloudService() { + // check each service until we find one that is confirmed to match; + // order is assumed to matter + for (const service of this.cloudServices) { + try { + const serviceResponse = await service.checkIfService(); + + if (serviceResponse.isConfirmed()) { + return serviceResponse.toJSON(); + } + } catch (ignoredError) { + // ignored until we make wider use of this in the UI + } + } + + // explicitly undefined rather than null so that it can be ignored in JSON + return undefined; + } +} diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_response.test.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.test.ts similarity index 87% rename from x-pack/plugins/monitoring/server/cloud/cloud_response.test.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.test.ts index fbc0d857ebd02..5fc721929ee85 100644 --- a/x-pack/plugins/monitoring/server/cloud/cloud_response.test.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.test.ts @@ -1,8 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 { CloudServiceResponse } from './cloud_response'; diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_response.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.ts similarity index 52% rename from x-pack/plugins/monitoring/server/cloud/cloud_response.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.ts index 5744744dd214e..48291ebff22e7 100644 --- a/x-pack/plugins/monitoring/server/cloud/cloud_response.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_response.ts @@ -1,36 +1,63 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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. */ +interface CloudServiceResponseOptions { + id?: string; + vmType?: string; + region?: string; + zone?: string; + metadata?: Record; +} + +export interface CloudServiceResponseJson { + name: string; + id?: string; + vm_type?: string; + region?: string; + zone?: string; + metadata?: Record; +} + /** - * {@code CloudServiceResponse} represents a single response from any individual {@code CloudService}. + * Represents a single response from any individual CloudService. */ export class CloudServiceResponse { + private readonly _name: string; + private readonly _confirmed: boolean; + private readonly _id?: string; + private readonly _vmType?: string; + private readonly _region?: string; + private readonly _zone?: string; + private readonly _metadata?: Record; + /** - * Create an unconfirmed {@code CloudServiceResponse} by the {@code name}. - * - * @param {String} name The name of the {@code CloudService}. - * @return {CloudServiceResponse} Never {@code null}. + * Create an unconfirmed CloudServiceResponse by the name. */ - static unconfirmed(name) { + static unconfirmed(name: string) { return new CloudServiceResponse(name, false, {}); } /** - * Create a new {@code CloudServiceResponse}. + * Create a new CloudServiceResponse. * - * @param {String} name The name of the {@code CloudService}. - * @param {Boolean} confirmed Confirmed to be the current {@code CloudService}. + * @param {String} name The name of the CloudService. + * @param {Boolean} confirmed Confirmed to be the current CloudService. * @param {String} id The optional ID of the VM (depends on the cloud service). * @param {String} vmType The optional type of VM (depends on the cloud service). * @param {String} region The optional region of the VM (depends on the cloud service). * @param {String} availabilityZone The optional availability zone within the region (depends on the cloud service). * @param {Object} metadata The optional metadata associated with the VM. */ - constructor(name, confirmed, { id, vmType, region, zone, metadata }) { + constructor( + name: string, + confirmed: boolean, + { id, vmType, region, zone, metadata }: CloudServiceResponseOptions + ) { this._name = name; this._confirmed = confirmed; this._id = id; @@ -41,9 +68,7 @@ export class CloudServiceResponse { } /** - * Get the name of the {@code CloudService} associated with the current response. - * - * @return {String} The cloud service that created this response. + * Get the name of the CloudService associated with the current response. */ getName() { return this._name; @@ -51,8 +76,6 @@ export class CloudServiceResponse { /** * Determine if the Cloud Service is confirmed to exist. - * - * @return {Boolean} {@code true} to indicate that Kibana is running in this cloud environment. */ isConfirmed() { return this._confirmed; @@ -60,11 +83,8 @@ export class CloudServiceResponse { /** * Create a plain JSON object that can be indexed that represents the response. - * - * @return {Object} Never {@code null} object. - * @throws {Error} if this response is not {@code confirmed}. */ - toJSON() { + toJSON(): CloudServiceResponseJson { if (!this._confirmed) { throw new Error(`[${this._name}] is not confirmed`); } diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_service.test.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts similarity index 65% rename from x-pack/plugins/monitoring/server/cloud/cloud_service.test.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts index 5a0186d9f9b59..0a7d5899486ab 100644 --- a/x-pack/plugins/monitoring/server/cloud/cloud_service.test.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.test.ts @@ -1,14 +1,16 @@ /* * Copyright 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. + * 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 { CloudService } from './cloud_service'; +import { CloudService, Response } from './cloud_service'; import { CloudServiceResponse } from './cloud_response'; describe('CloudService', () => { + // @ts-expect-error Creating an instance of an abstract class for testing const service = new CloudService('xyz'); describe('getName', () => { @@ -28,13 +30,9 @@ describe('CloudService', () => { describe('_checkIfService', () => { it('throws an exception unless overridden', async () => { - const request = jest.fn(); - - try { - await service._checkIfService(request); - } catch (err) { - expect(err.message).toEqual('not implemented'); - } + expect(async () => { + await service._checkIfService(undefined); + }).rejects.toThrowErrorMatchingInlineSnapshot(`"not implemented"`); }); }); @@ -89,42 +87,46 @@ describe('CloudService', () => { describe('_parseResponse', () => { const body = { some: { body: {} } }; - const tryParseResponse = async (...args) => { - try { - await service._parseResponse(...args); - } catch (err) { - // expected - return; - } - - expect().fail('Should throw exception'); - }; it('throws error upon failure to parse body as object', async () => { - // missing body - await tryParseResponse(); - await tryParseResponse(null); - await tryParseResponse({}); - await tryParseResponse(123); - await tryParseResponse('raw string'); - // malformed JSON object - await tryParseResponse('{{}'); + expect(async () => { + await service._parseResponse(); + }).rejects.toMatchInlineSnapshot(`undefined`); + expect(async () => { + await service._parseResponse(null); + }).rejects.toMatchInlineSnapshot(`undefined`); + expect(async () => { + await service._parseResponse({}); + }).rejects.toMatchInlineSnapshot(`undefined`); + expect(async () => { + await service._parseResponse(123); + }).rejects.toMatchInlineSnapshot(`undefined`); + expect(async () => { + await service._parseResponse('raw string'); + }).rejects.toMatchInlineSnapshot(`[Error: 'raw string' is not a JSON object]`); + expect(async () => { + await service._parseResponse('{{}'); + }).rejects.toMatchInlineSnapshot(`[Error: '{{}' is not a JSON object]`); }); it('expects unusable bodies', async () => { - const parseBody = (parsedBody) => { + const parseBody = (parsedBody: Response['body']) => { expect(parsedBody).toEqual(body); return null; }; - await tryParseResponse(JSON.stringify(body), parseBody); - await tryParseResponse(body, parseBody); + expect(async () => { + await service._parseResponse(JSON.stringify(body), parseBody); + }).rejects.toMatchInlineSnapshot(`undefined`); + expect(async () => { + await service._parseResponse(body, parseBody); + }).rejects.toMatchInlineSnapshot(`undefined`); }); it('uses parsed object to create response', async () => { const serviceResponse = new CloudServiceResponse('a123', true, { id: 'xyz' }); - const parseBody = (parsedBody) => { + const parseBody = (parsedBody: Response['body']) => { expect(parsedBody).toEqual(body); return serviceResponse; diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts new file mode 100644 index 0000000000000..768a46a457d7d --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/cloud_service.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import fs from 'fs'; +import { isObject, isString, isPlainObject } from 'lodash'; +import defaultRequest from 'request'; +import type { OptionsWithUri, Response as DefaultResponse } from 'request'; +import { CloudServiceResponse } from './cloud_response'; + +/** @internal */ +export type Request = typeof defaultRequest; + +/** @internal */ +export type RequestOptions = OptionsWithUri; + +/** @internal */ +export type Response = DefaultResponse; + +/** @internal */ +export interface CloudServiceOptions { + _request?: Request; + _fs?: typeof fs; + _isWindows?: boolean; +} + +/** + * CloudService provides a mechanism for cloud services to be checked for + * metadata that may help to determine the best defaults and priorities. + */ +export abstract class CloudService { + private readonly _request: Request; + protected readonly _name: string; + + constructor(name: string, options: CloudServiceOptions = {}) { + this._name = name.toLowerCase(); + + // Allow the HTTP handler to be swapped out for tests + const { _request = defaultRequest } = options; + + this._request = _request; + } + + /** + * Get the search-friendly name of the Cloud Service. + */ + getName() { + return this._name; + } + + /** + * Using whatever mechanism is required by the current Cloud Service, + * determine if Kibana is running in it and return relevant metadata. + */ + async checkIfService() { + try { + return await this._checkIfService(this._request); + } catch (e) { + return this._createUnconfirmedResponse(); + } + } + + _checkIfService(request: Request): Promise { + // should always be overridden by a subclass + return Promise.reject(new Error('not implemented')); + } + + /** + * Create a new CloudServiceResponse that denotes that this cloud service + * is not being used by the current machine / VM. + */ + _createUnconfirmedResponse() { + return CloudServiceResponse.unconfirmed(this._name); + } + + /** + * Strictly parse JSON. + */ + _stringToJson(value: string) { + // note: this will throw an error if this is not a string + value = value.trim(); + + try { + const json = JSON.parse(value); + // we don't want to return scalar values, arrays, etc. + if (!isPlainObject(json)) { + throw new Error('not a plain object'); + } + return json; + } catch (e) { + throw new Error(`'${value}' is not a JSON object`); + } + } + + /** + * Convert the response to a JSON object and attempt to parse it using the + * parseBody function. + * + * If the response cannot be parsed as a JSON object, or if it fails to be + * useful, then parseBody should return null. + */ + _parseResponse( + body: Response['body'], + parseBody?: (body: Response['body']) => CloudServiceResponse | null + ): Promise { + // parse it if necessary + if (isString(body)) { + try { + body = this._stringToJson(body); + } catch (err) { + return Promise.reject(err); + } + } + + if (isObject(body) && parseBody) { + const response = parseBody(body); + + if (response) { + return Promise.resolve(response); + } + } + + // use default handling + return Promise.reject(); + } +} diff --git a/x-pack/plugins/monitoring/server/cloud/gcp.test.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts similarity index 66% rename from x-pack/plugins/monitoring/server/cloud/gcp.test.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts index 803c6f31af3b9..fd0b3331b4ad1 100644 --- a/x-pack/plugins/monitoring/server/cloud/gcp.test.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.test.ts @@ -1,11 +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; you may not use this file except in compliance with the Elastic License - * 2.0. + * 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 { GCP } from './gcp'; +import type { Request, RequestOptions } from './cloud_service'; +import { GCPCloudService } from './gcp'; + +type Callback = (err: unknown, res: unknown) => void; + +const GCP = new GCPCloudService(); describe('GCP', () => { it('is named "gcp"', () => { @@ -17,30 +23,28 @@ describe('GCP', () => { const headers = { 'metadata-flavor': 'Google' }; it('handles expected responses', async () => { - const metadata = { + const metadata: Record = { id: 'abcdef', 'machine-type': 'projects/441331612345/machineTypes/f1-micro', zone: 'projects/441331612345/zones/us-fake4-c', }; - const request = (req, callback) => { + const request = ((req: RequestOptions, callback: Callback) => { const basePath = 'http://169.254.169.254/computeMetadata/v1/instance/'; expect(req.method).toEqual('GET'); - expect(req.uri.startsWith(basePath)).toBe(true); - expect(req.headers['Metadata-Flavor']).toEqual('Google'); + expect((req.uri as string).startsWith(basePath)).toBe(true); + expect(req.headers!['Metadata-Flavor']).toEqual('Google'); expect(req.json).toEqual(false); - const requestKey = req.uri.substring(basePath.length); + const requestKey = (req.uri as string).substring(basePath.length); let body = null; if (metadata[requestKey]) { body = metadata[requestKey]; - } else { - expect().fail(`Unknown field requested [${requestKey}]`); } - callback(null, { statusCode: 200, body, headers }, body); - }; + callback(null, { statusCode: 200, body, headers }); + }) as Request; const response = await GCP._checkIfService(request); expect(response.isConfirmed()).toEqual(true); @@ -56,79 +60,63 @@ describe('GCP', () => { // NOTE: the CloudService method, checkIfService, catches the errors that follow it('handles unexpected responses', async () => { - const request = (_req, callback) => callback(null, { statusCode: 200, headers }); + const request = ((_req: RequestOptions, callback: Callback) => + callback(null, { statusCode: 200, headers })) as Request; - try { + expect(async () => { await GCP._checkIfService(request); - } catch (err) { - // ignored - return; - } - - expect().fail('Method should throw exception (Promise.reject)'); + }).rejects.toThrowErrorMatchingInlineSnapshot(`"unrecognized responses"`); }); it('handles unexpected responses without response header', async () => { const body = 'xyz'; - const request = (_req, callback) => callback(null, { statusCode: 200, body }, body); - - try { - await GCP._checkIfService(request); - } catch (err) { - // ignored - return; - } + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(null, { statusCode: 200, body })) as Request; - expect().fail('Method should throw exception (Promise.reject)'); + expect(async () => { + await GCP._checkIfService(failedRequest); + }).rejects.toThrowErrorMatchingInlineSnapshot(`"unrecognized responses"`); }); it('handles not running on GCP with error by rethrowing it', async () => { const someError = new Error('expected: request failed'); - const failedRequest = (_req, callback) => callback(someError, null); + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(someError, null)) as Request; - try { + expect(async () => { await GCP._checkIfService(failedRequest); - - expect().fail('Method should throw exception (Promise.reject)'); - } catch (err) { - expect(err.message).toEqual(someError.message); - } + }).rejects.toThrowError(someError); }); it('handles not running on GCP with 404 response by throwing error', async () => { const body = 'This is some random error text'; - const failedRequest = (_req, callback) => - callback(null, { statusCode: 404, headers, body }, body); + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(null, { statusCode: 404, headers, body })) as Request; - try { + expect(async () => { await GCP._checkIfService(failedRequest); - } catch (err) { - // ignored - return; - } - - expect().fail('Method should throw exception (Promise.reject)'); + }).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); }); it('handles not running on GCP with unexpected response by throwing error', async () => { - const failedRequest = (_req, callback) => callback(null, null); + const failedRequest = ((_req: RequestOptions, callback: Callback) => + callback(null, null)) as Request; - try { + expect(async () => { await GCP._checkIfService(failedRequest); - } catch (err) { - // ignored - return; - } - - expect().fail('Method should throw exception (Promise.reject)'); + }).rejects.toThrowErrorMatchingInlineSnapshot(`"GCP request failed"`); }); }); describe('_extractValue', () => { it('only handles strings', () => { + // @ts-expect-error expect(GCP._extractValue()).toBe(undefined); + // @ts-expect-error expect(GCP._extractValue(null, null)).toBe(undefined); + // @ts-expect-error expect(GCP._extractValue('abc', { field: 'abcxyz' })).toBe(undefined); + // @ts-expect-error expect(GCP._extractValue('abc', 1234)).toBe(undefined); expect(GCP._extractValue('abc/', 'abc/xyz')).toEqual('xyz'); }); @@ -179,12 +167,17 @@ describe('GCP', () => { }); it('ignores unexpected response body', () => { + // @ts-expect-error expect(() => GCP._combineResponses()).toThrow(); + // @ts-expect-error expect(() => GCP._combineResponses(undefined, undefined, undefined)).toThrow(); + // @ts-expect-error expect(() => GCP._combineResponses(null, null, null)).toThrow(); expect(() => + // @ts-expect-error GCP._combineResponses({ id: 'x' }, { machineType: 'a' }, { zone: 'b' }) ).toThrow(); + // @ts-expect-error expect(() => GCP._combineResponses({ privateIp: 'a.b.c.d' })).toThrow(); }); }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts new file mode 100644 index 0000000000000..565c07abd1d2c --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/gcp.ts @@ -0,0 +1,127 @@ +/* + * Copyright 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 { isString } from 'lodash'; +import { promisify } from 'util'; +import { CloudService, CloudServiceOptions, Request, Response } from './cloud_service'; +import { CloudServiceResponse } from './cloud_response'; + +// GCP documentation shows both 'metadata.google.internal' (mostly) and '169.254.169.254' (sometimes) +// To bypass potential DNS changes, the IP was used because it's shared with other cloud services +const SERVICE_ENDPOINT = 'http://169.254.169.254/computeMetadata/v1/instance'; + +/** + * Checks and loads the service metadata for an Google Cloud Platform VM if it is available. + * + * @internal + */ +export class GCPCloudService extends CloudService { + constructor(options: CloudServiceOptions = {}) { + super('gcp', options); + } + + _checkIfService(request: Request) { + // we need to call GCP individually for each field we want metadata for + const fields = ['id', 'machine-type', 'zone']; + + const create = this._createRequestForField; + const allRequests = fields.map((field) => promisify(request)(create(field))); + return ( + Promise.all(allRequests) + // Note: there is no fallback option for GCP; + // responses are arrays containing [fullResponse, body]; + // because GCP returns plaintext, we have no way of validating + // without using the response code. + .then((responses) => { + return responses.map((response) => { + if (!response || response.statusCode === 404) { + throw new Error('GCP request failed'); + } + return this._extractBody(response, response.body); + }); + }) + .then(([id, machineType, zone]) => this._combineResponses(id, machineType, zone)) + ); + } + + _createRequestForField(field: string) { + return { + method: 'GET', + uri: `${SERVICE_ENDPOINT}/${field}`, + headers: { + // GCP requires this header + 'Metadata-Flavor': 'Google', + }, + // GCP does _not_ return JSON + json: false, + }; + } + + /** + * Extract the body if the response is valid and it came from GCP. + */ + _extractBody(response: Response, body?: Response['body']) { + if ( + response?.statusCode === 200 && + response.headers && + response.headers['metadata-flavor'] === 'Google' + ) { + return body; + } + + return null; + } + + /** + * Parse the GCP responses, if possible. + * + * Example values for each parameter: + * + * vmId: '5702733457649812345' + * machineType: 'projects/441331612345/machineTypes/f1-micro' + * zone: 'projects/441331612345/zones/us-east4-c' + */ + _combineResponses(id: string, machineType: string, zone: string) { + const vmId = isString(id) ? id.trim() : undefined; + const vmType = this._extractValue('machineTypes/', machineType); + const vmZone = this._extractValue('zones/', zone); + + let region; + + if (vmZone) { + // converts 'us-east4-c' into 'us-east4' + region = vmZone.substring(0, vmZone.lastIndexOf('-')); + } + + // ensure we actually have some data + if (vmId || vmType || region || vmZone) { + return new CloudServiceResponse(this._name, true, { id: vmId, vmType, region, zone: vmZone }); + } + + throw new Error('unrecognized responses'); + } + + /** + * Extract the useful information returned from GCP while discarding + * unwanted account details (the project ID). + * + * For example, this turns something like + * 'projects/441331612345/machineTypes/f1-micro' into 'f1-micro'. + */ + _extractValue(fieldPrefix: string, value: string) { + if (isString(value)) { + const index = value.lastIndexOf(fieldPrefix); + + if (index !== -1) { + return value.substring(index + fieldPrefix.length).trim(); + } + } + + return undefined; + } +} diff --git a/x-pack/plugins/monitoring/server/cloud/index.js b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/index.ts similarity index 53% rename from x-pack/plugins/monitoring/server/cloud/index.js rename to src/plugins/kibana_usage_collection/server/collectors/cloud/detector/index.ts index 5b64a0be96216..ce82cadb15ad5 100644 --- a/x-pack/plugins/monitoring/server/cloud/index.js +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/detector/index.ts @@ -1,9 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ export { CloudDetector } from './cloud_detector'; -export { CLOUD_SERVICES } from './cloud_services'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/cloud/index.ts b/src/plugins/kibana_usage_collection/server/collectors/cloud/index.ts new file mode 100644 index 0000000000000..7e2c7c891305f --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/cloud/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { registerCloudProviderUsageCollector } from './cloud_provider_collector'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/index.ts b/src/plugins/kibana_usage_collection/server/collectors/index.ts index 10156b51ac183..89e1e6e79482c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/index.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/index.ts @@ -11,6 +11,7 @@ export { registerManagementUsageCollector } from './management'; export { registerApplicationUsageCollector } from './application_usage'; export { registerKibanaUsageCollector } from './kibana'; export { registerOpsStatsCollector } from './ops_stats'; +export { registerCloudProviderUsageCollector } from './cloud'; export { registerCspCollector } from './csp'; export { registerCoreUsageCollector } from './core'; export { registerLocalizationUsageCollector } from './localization'; diff --git a/src/plugins/kibana_usage_collection/server/index.test.mocks.ts b/src/plugins/kibana_usage_collection/server/index.test.mocks.ts new file mode 100644 index 0000000000000..7df27a3719e92 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/index.test.mocks.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 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 { cloudDetectorMock } from './collectors/cloud/detector/cloud_detector.mock'; + +const mock = cloudDetectorMock.create(); + +export const cloudDetailsMock = mock.getCloudDetails; +export const detectCloudServiceMock = mock.detectCloudService; + +jest.doMock('./collectors/cloud/detector', () => ({ + CloudDetector: jest.fn().mockImplementation(() => mock), +})); diff --git a/src/plugins/kibana_usage_collection/server/index.test.ts b/src/plugins/kibana_usage_collection/server/index.test.ts index ee6df366b788f..b4c52f8353d79 100644 --- a/src/plugins/kibana_usage_collection/server/index.test.ts +++ b/src/plugins/kibana_usage_collection/server/index.test.ts @@ -15,6 +15,7 @@ import { CollectorOptions, createUsageCollectionSetupMock, } from '../../usage_collection/server/usage_collection.mock'; +import { cloudDetailsMock } from './index.test.mocks'; import { plugin } from './'; @@ -33,6 +34,10 @@ describe('kibana_usage_collection', () => { return createUsageCollectionSetupMock().makeStatsCollector(opts); }); + beforeEach(() => { + cloudDetailsMock.mockClear(); + }); + test('Runs the setup method without issues', () => { const coreSetup = coreMock.createSetup(); @@ -50,6 +55,12 @@ describe('kibana_usage_collection', () => { coreStart.uiSettings.asScopedToClient.mockImplementation(() => uiSettingsServiceMock.createClient() ); + cloudDetailsMock.mockReturnValueOnce({ + name: 'my-cloud', + vm_type: 'big', + region: 'my-home', + zone: 'my-home-office', + }); expect(pluginInstance.start(coreStart)).toBe(undefined); usageCollectors.forEach(({ isReady }) => { diff --git a/src/plugins/kibana_usage_collection/server/plugin.ts b/src/plugins/kibana_usage_collection/server/plugin.ts index 5b903489e3ff3..74d2d281ff8f6 100644 --- a/src/plugins/kibana_usage_collection/server/plugin.ts +++ b/src/plugins/kibana_usage_collection/server/plugin.ts @@ -28,6 +28,7 @@ import { registerManagementUsageCollector, registerOpsStatsCollector, registerUiMetricUsageCollector, + registerCloudProviderUsageCollector, registerCspCollector, registerCoreUsageCollector, registerLocalizationUsageCollector, @@ -102,6 +103,7 @@ export class KibanaUsageCollectionPlugin implements Plugin { registerType, getSavedObjectsClient ); + registerCloudProviderUsageCollector(usageCollection); registerCspCollector(usageCollection, coreSetup.http); registerCoreUsageCollector(usageCollection, getCoreUsageDataService); registerLocalizationUsageCollector(usageCollection, coreSetup.i18n); diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index d8bcf150ac167..41b75824e992d 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -6445,6 +6445,34 @@ } } }, + "cloud_provider": { + "properties": { + "name": { + "type": "keyword", + "_meta": { + "description": "The name of the cloud provider" + } + }, + "vm_type": { + "type": "keyword", + "_meta": { + "description": "The VM instance type" + } + }, + "region": { + "type": "keyword", + "_meta": { + "description": "The cloud provider region" + } + }, + "zone": { + "type": "keyword", + "_meta": { + "description": "The availability zone within the region" + } + } + } + }, "core": { "properties": { "config": { diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index a6184261350b7..bf6e32af0dc39 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -97,23 +97,6 @@ export const CALCULATE_DURATION_UNTIL = 'until'; */ export const ML_SUPPORTED_LICENSES = ['trial', 'platinum', 'enterprise']; -/** - * Metadata service URLs for the different cloud services that have constant URLs (e.g., unlike GCP, which is a constant prefix). - * - * @type {Object} - */ -export const CLOUD_METADATA_SERVICES = { - // We explicitly call out the version, 2016-09-02, rather than 'latest' to avoid unexpected changes - AWS_URL: 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document', - - // 2017-04-02 is the first GA release of this API - AZURE_URL: 'http://169.254.169.254/metadata/instance?api-version=2017-04-02', - - // GCP documentation shows both 'metadata.google.internal' (mostly) and '169.254.169.254' (sometimes) - // To bypass potential DNS changes, the IP was used because it's shared with other cloud services - GCP_URL_PREFIX: 'http://169.254.169.254/computeMetadata/v1/instance', -}; - /** * Constants used by Logstash monitoring code */ diff --git a/x-pack/plugins/monitoring/server/cloud/aws.js b/x-pack/plugins/monitoring/server/cloud/aws.js deleted file mode 100644 index 45b3b80162875..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/aws.js +++ /dev/null @@ -1,127 +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 { get, isString, omit } from 'lodash'; -import { promisify } from 'util'; -import { CloudService } from './cloud_service'; -import { CloudServiceResponse } from './cloud_response'; -import fs from 'fs'; -import { CLOUD_METADATA_SERVICES } from '../../common/constants'; - -/** - * {@code AWSCloudService} will check and load the service metadata for an Amazon Web Service VM if it is available. - * - * This is exported for testing purposes. Use the {@code AWS} singleton. - */ -export class AWSCloudService extends CloudService { - constructor(options = {}) { - super('aws', options); - - // Allow the file system handler to be swapped out for tests - const { _fs = fs, _isWindows = process.platform.startsWith('win') } = options; - - this._fs = _fs; - this._isWindows = _isWindows; - } - - _checkIfService(request) { - const req = { - method: 'GET', - uri: CLOUD_METADATA_SERVICES.AWS_URL, - json: true, - }; - - return ( - promisify(request)(req) - .then((response) => this._parseResponse(response.body, (body) => this._parseBody(body))) - // fall back to file detection - .catch(() => this._tryToDetectUuid()) - ); - } - - /** - * Parse the AWS response, if possible. Example payload (with fake accountId value): - * - * { - * "devpayProductCodes" : null, - * "privateIp" : "10.0.0.38", - * "availabilityZone" : "us-west-2c", - * "version" : "2010-08-31", - * "instanceId" : "i-0c7a5b7590a4d811c", - * "billingProducts" : null, - * "instanceType" : "t2.micro", - * "imageId" : "ami-6df1e514", - * "accountId" : "1234567890", - * "architecture" : "x86_64", - * "kernelId" : null, - * "ramdiskId" : null, - * "pendingTime" : "2017-07-06T02:09:12Z", - * "region" : "us-west-2" - * } - * - * @param {Object} body The response from the VM web service. - * @return {CloudServiceResponse} {@code null} if not confirmed. Otherwise the response. - */ - _parseBody(body) { - const id = get(body, 'instanceId'); - const vmType = get(body, 'instanceType'); - const region = get(body, 'region'); - const zone = get(body, 'availabilityZone'); - const metadata = omit(body, [ - // remove keys we already have - 'instanceId', - 'instanceType', - 'region', - 'availabilityZone', - // remove keys that give too much detail - 'accountId', - 'billingProducts', - 'devpayProductCodes', - 'privateIp', - ]); - - // ensure we actually have some data - if (id || vmType || region || zone) { - return new CloudServiceResponse(this._name, true, { id, vmType, region, zone, metadata }); - } - - return null; - } - - /** - * Attempt to load the UUID by checking `/sys/hypervisor/uuid`. This is a fallback option if the metadata service is - * unavailable for some reason. - * - * @return {Promise} Never {@code null} {@code CloudServiceResponse}. - */ - _tryToDetectUuid() { - // Windows does not have an easy way to check - if (!this._isWindows) { - return promisify(this._fs.readFile)('/sys/hypervisor/uuid', 'utf8').then((uuid) => { - if (isString(uuid)) { - // Some AWS APIs return it lowercase (like the file did in testing), while others return it uppercase - uuid = uuid.trim().toLowerCase(); - - if (uuid.startsWith('ec2')) { - return new CloudServiceResponse(this._name, true, { id: uuid }); - } - } - - return this._createUnconfirmedResponse(); - }); - } - - return Promise.resolve(this._createUnconfirmedResponse()); - } -} - -/** - * Singleton instance of {@code AWSCloudService}. - * - * @type {AWSCloudService} - */ -export const AWS = new AWSCloudService(); diff --git a/x-pack/plugins/monitoring/server/cloud/aws.test.js b/x-pack/plugins/monitoring/server/cloud/aws.test.js deleted file mode 100644 index 877a1958f0096..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/aws.test.js +++ /dev/null @@ -1,237 +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 { AWS, AWSCloudService } from './aws'; - -describe('AWS', () => { - const expectedFilename = '/sys/hypervisor/uuid'; - const expectedEncoding = 'utf8'; - // mixed case to ensure we check for ec2 after lowercasing - const ec2Uuid = 'eC2abcdef-ghijk\n'; - const ec2FileSystem = { - readFile: (filename, encoding, callback) => { - expect(filename).toEqual(expectedFilename); - expect(encoding).toEqual(expectedEncoding); - - callback(null, ec2Uuid); - }, - }; - - it('is named "aws"', () => { - expect(AWS.getName()).toEqual('aws'); - }); - - describe('_checkIfService', () => { - it('handles expected response', async () => { - const id = 'abcdef'; - const request = (req, callback) => { - expect(req.method).toEqual('GET'); - expect(req.uri).toEqual( - 'http://169.254.169.254/2016-09-02/dynamic/instance-identity/document' - ); - expect(req.json).toEqual(true); - - const body = `{"instanceId": "${id}","availabilityZone":"us-fake-2c", "imageId" : "ami-6df1e514"}`; - - callback(null, { statusCode: 200, body }, body); - }; - // ensure it does not use the fs to trump the body - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, - }); - - const response = await awsCheckedFileSystem._checkIfService(request); - - expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id, - region: undefined, - vm_type: undefined, - zone: 'us-fake-2c', - metadata: { - imageId: 'ami-6df1e514', - }, - }); - }); - - it('handles request without a usable body by downgrading to UUID detection', async () => { - const request = (_req, callback) => callback(null, { statusCode: 404 }); - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, - }); - - const response = await awsCheckedFileSystem._checkIfService(request); - - expect(response.isConfirmed()).toBe(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - vm_type: undefined, - zone: undefined, - metadata: undefined, - }); - }); - - it('handles request failure by downgrading to UUID detection', async () => { - const failedRequest = (_req, callback) => - callback(new Error('expected: request failed'), null); - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, - }); - - const response = await awsCheckedFileSystem._checkIfService(failedRequest); - - expect(response.isConfirmed()).toBe(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - vm_type: undefined, - zone: undefined, - metadata: undefined, - }); - }); - - it('handles not running on AWS', async () => { - const failedRequest = (_req, callback) => callback(null, null); - const awsIgnoredFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: true, - }); - - const response = await awsIgnoredFileSystem._checkIfService(failedRequest); - - expect(response.getName()).toEqual(AWS.getName()); - expect(response.isConfirmed()).toBe(false); - }); - }); - - describe('_parseBody', () => { - it('parses object in expected format', () => { - const body = { - devpayProductCodes: null, - privateIp: '10.0.0.38', - availabilityZone: 'us-west-2c', - version: '2010-08-31', - instanceId: 'i-0c7a5b7590a4d811c', - billingProducts: null, - instanceType: 't2.micro', - accountId: '1234567890', - architecture: 'x86_64', - kernelId: null, - ramdiskId: null, - imageId: 'ami-6df1e514', - pendingTime: '2017-07-06T02:09:12Z', - region: 'us-west-2', - }; - - const response = AWS._parseBody(body); - - expect(response.getName()).toEqual(AWS.getName()); - expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: 'aws', - id: 'i-0c7a5b7590a4d811c', - vm_type: 't2.micro', - region: 'us-west-2', - zone: 'us-west-2c', - metadata: { - version: '2010-08-31', - architecture: 'x86_64', - kernelId: null, - ramdiskId: null, - imageId: 'ami-6df1e514', - pendingTime: '2017-07-06T02:09:12Z', - }, - }); - }); - - it('ignores unexpected response body', () => { - expect(AWS._parseBody(undefined)).toBe(null); - expect(AWS._parseBody(null)).toBe(null); - expect(AWS._parseBody({})).toBe(null); - expect(AWS._parseBody({ privateIp: 'a.b.c.d' })).toBe(null); - }); - }); - - describe('_tryToDetectUuid', () => { - it('checks the file system for UUID if not Windows', async () => { - const awsCheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: false, - }); - - const response = await awsCheckedFileSystem._tryToDetectUuid(); - - expect(response.isConfirmed()).toEqual(true); - expect(response.toJSON()).toEqual({ - name: AWS.getName(), - id: ec2Uuid.trim().toLowerCase(), - region: undefined, - zone: undefined, - vm_type: undefined, - metadata: undefined, - }); - }); - - it('ignores UUID if it does not start with ec2', async () => { - const notEC2FileSystem = { - readFile: (filename, encoding, callback) => { - expect(filename).toEqual(expectedFilename); - expect(encoding).toEqual(expectedEncoding); - - callback(null, 'notEC2'); - }, - }; - - const awsCheckedFileSystem = new AWSCloudService({ - _fs: notEC2FileSystem, - _isWindows: false, - }); - - const response = await awsCheckedFileSystem._tryToDetectUuid(); - - expect(response.isConfirmed()).toEqual(false); - }); - - it('does NOT check the file system for UUID on Windows', async () => { - const awsUncheckedFileSystem = new AWSCloudService({ - _fs: ec2FileSystem, - _isWindows: true, - }); - - const response = await awsUncheckedFileSystem._tryToDetectUuid(); - - expect(response.isConfirmed()).toEqual(false); - }); - - it('does NOT handle file system exceptions', async () => { - const fileDNE = new Error('File DNE'); - const awsFailedFileSystem = new AWSCloudService({ - _fs: { - readFile: () => { - throw fileDNE; - }, - }, - _isWindows: false, - }); - - try { - await awsFailedFileSystem._tryToDetectUuid(); - - expect().fail('Method should throw exception (Promise.reject)'); - } catch (err) { - expect(err).toBe(fileDNE); - } - }); - }); -}); diff --git a/x-pack/plugins/monitoring/server/cloud/azure.js b/x-pack/plugins/monitoring/server/cloud/azure.js deleted file mode 100644 index 4d026441d6840..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/azure.js +++ /dev/null @@ -1,99 +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 { get, omit } from 'lodash'; -import { promisify } from 'util'; -import { CloudService } from './cloud_service'; -import { CloudServiceResponse } from './cloud_response'; -import { CLOUD_METADATA_SERVICES } from '../../common/constants'; - -/** - * {@code AzureCloudService} will check and load the service metadata for an Azure VM if it is available. - */ -class AzureCloudService extends CloudService { - constructor(options = {}) { - super('azure', options); - } - - _checkIfService(request) { - const req = { - method: 'GET', - uri: CLOUD_METADATA_SERVICES.AZURE_URL, - headers: { - // Azure requires this header - Metadata: 'true', - }, - json: true, - }; - - return ( - promisify(request)(req) - // Note: there is no fallback option for Azure - .then((response) => { - return this._parseResponse(response.body, (body) => this._parseBody(body)); - }) - ); - } - - /** - * Parse the Azure response, if possible. Example payload (with network object ignored): - * - * { - * "compute": { - * "location": "eastus", - * "name": "my-ubuntu-vm", - * "offer": "UbuntuServer", - * "osType": "Linux", - * "platformFaultDomain": "0", - * "platformUpdateDomain": "0", - * "publisher": "Canonical", - * "sku": "16.04-LTS", - * "version": "16.04.201706191", - * "vmId": "d4c57456-2b3b-437a-9f1f-7082cfce02d4", - * "vmSize": "Standard_A1" - * }, - * "network": { - * ... - * } - * } - * - * Note: Azure VMs created using the "classic" method, as opposed to the resource manager, - * do not provide a "compute" field / object. However, both report the "network" field / object. - * - * @param {Object} body The response from the VM web service. - * @return {CloudServiceResponse} {@code null} for default fallback. - */ - _parseBody(body) { - const compute = get(body, 'compute'); - const id = get(compute, 'vmId'); - const vmType = get(compute, 'vmSize'); - const region = get(compute, 'location'); - - // remove keys that we already have; explicitly undefined so we don't send it when empty - const metadata = compute ? omit(compute, ['vmId', 'vmSize', 'location']) : undefined; - - // we don't actually use network, but we check for its existence to see if this is a response from Azure - const network = get(body, 'network'); - - // ensure we actually have some data - if (id || vmType || region) { - return new CloudServiceResponse(this._name, true, { id, vmType, region, metadata }); - } else if (network) { - // classic-managed VMs in Azure don't provide compute so we highlight the lack of info - return new CloudServiceResponse(this._name, true, { metadata: { classic: true } }); - } - - return null; - } -} - -/** - * Singleton instance of {@code AzureCloudService}. - * - * @type {AzureCloudService} - */ -export const AZURE = new AzureCloudService(); diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_detector.js b/x-pack/plugins/monitoring/server/cloud/cloud_detector.js deleted file mode 100644 index 2cd2b26daab5b..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/cloud_detector.js +++ /dev/null @@ -1,64 +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 { CLOUD_SERVICES } from './cloud_services'; - -/** - * {@code CloudDetector} can be used to asynchronously detect the cloud service that Kibana is running within. - */ -export class CloudDetector { - constructor(options = {}) { - const { cloudServices = CLOUD_SERVICES } = options; - - this._cloudServices = cloudServices; - // Explicitly undefined. If the value is never updated, then the property will be dropped when the data is serialized. - this._cloudDetails = undefined; - } - - /** - * Get any cloud details that we have detected. - * - * @return {Object} {@code undefined} if unknown. Otherwise plain JSON. - */ - getCloudDetails() { - return this._cloudDetails; - } - - /** - * Asynchronously detect the cloud service. - * - * Callers are _not_ expected to {@code await} this method, which allows the caller to trigger the lookup and then simply use it - * whenever we determine it. - */ - async detectCloudService() { - this._cloudDetails = await this._getCloudService(this._cloudServices); - } - - /** - * Check every cloud service until the first one reports success from detection. - * - * @param {Array} cloudServices The {@code CloudService} objects listed in priority order - * @return {Promise} {@code undefined} if none match. Otherwise the plain JSON {@code Object} from the {@code CloudServiceResponse}. - */ - async _getCloudService(cloudServices) { - // check each service until we find one that is confirmed to match; order is assumed to matter - for (const service of cloudServices) { - try { - const serviceResponse = await service.checkIfService(); - - if (serviceResponse.isConfirmed()) { - return serviceResponse.toJSON(); - } - } catch (ignoredError) { - // ignored until we make wider use of this in the UI - } - } - - // explicitly undefined rather than null so that it can be ignored in JSON - return undefined; - } -} diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_service.js b/x-pack/plugins/monitoring/server/cloud/cloud_service.js deleted file mode 100644 index ea0eb9534cf30..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/cloud_service.js +++ /dev/null @@ -1,115 +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 { isObject, isString } from 'lodash'; -import request from 'request'; -import { CloudServiceResponse } from './cloud_response'; - -/** - * {@code CloudService} provides a mechanism for cloud services to be checked for metadata - * that may help to determine the best defaults and priorities. - */ -export class CloudService { - constructor(name, options = {}) { - this._name = name.toLowerCase(); - - // Allow the HTTP handler to be swapped out for tests - const { _request = request } = options; - - this._request = _request; - } - - /** - * Get the search-friendly name of the Cloud Service. - * - * @return {String} Never {@code null}. - */ - getName() { - return this._name; - } - - /** - * Using whatever mechanism is required by the current Cloud Service, determine - * Kibana is running in it and return relevant metadata. - * - * @return {Promise} Never {@code null} {@code CloudServiceResponse}. - */ - checkIfService() { - return this._checkIfService(this._request).catch(() => this._createUnconfirmedResponse()); - } - - /** - * Using whatever mechanism is required by the current Cloud Service, determine - * Kibana is running in it and return relevant metadata. - * - * @param {Object} _request 'request' HTTP handler. - * @return {Promise} Never {@code null} {@code CloudServiceResponse}. - */ - _checkIfService() { - return Promise.reject(new Error('not implemented')); - } - - /** - * Create a new {@code CloudServiceResponse} that denotes that this cloud service is not being used by the current machine / VM. - * - * @return {CloudServiceResponse} Never {@code null}. - */ - _createUnconfirmedResponse() { - return CloudServiceResponse.unconfirmed(this._name); - } - - /** - * Strictly parse JSON. - * - * @param {String} value The string to parse as a JSON object - * @return {Object} The result of {@code JSON.parse} if it's an object. - * @throws {Error} if the {@code value} is not a String that can be converted into an Object - */ - _stringToJson(value) { - // note: this will throw an error if this is not a string - value = value.trim(); - - // we don't want to return scalar values, arrays, etc. - if (value.startsWith('{') && value.endsWith('}')) { - return JSON.parse(value); - } - - throw new Error(`'${value}' is not a JSON object`); - } - - /** - * Convert the {@code response} to a JSON object and attempt to parse it using the {@code parseBody} function. - * - * If the {@code response} cannot be parsed as a JSON object, or if it fails to be useful, then {@code parseBody} should return - * {@code null}. - * - * @param {Object} body The body from the response from the VM web service. - * @param {Function} parseBody Single argument function that accepts parsed JSON body from the response. - * @return {Promise} Never {@code null} {@code CloudServiceResponse} or rejection. - */ - _parseResponse(body, parseBody) { - // parse it if necessary - if (isString(body)) { - try { - body = this._stringToJson(body); - } catch (err) { - return Promise.reject(err); - } - } - - if (isObject(body)) { - const response = parseBody(body); - - if (response) { - return Promise.resolve(response); - } - } - - // use default handling - return Promise.reject(); - } -} diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_services.js b/x-pack/plugins/monitoring/server/cloud/cloud_services.js deleted file mode 100644 index 23be0d0e20e25..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/cloud_services.js +++ /dev/null @@ -1,17 +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 { AWS } from './aws'; -import { AZURE } from './azure'; -import { GCP } from './gcp'; - -/** - * An iteratable that can be used to loop across all known cloud services to detect them. - * - * @type {Array} - */ -export const CLOUD_SERVICES = [AWS, GCP, AZURE]; diff --git a/x-pack/plugins/monitoring/server/cloud/cloud_services.test.js b/x-pack/plugins/monitoring/server/cloud/cloud_services.test.js deleted file mode 100644 index adf4bf2bb0f0f..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/cloud_services.test.js +++ /dev/null @@ -1,22 +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 { CLOUD_SERVICES } from './cloud_services'; -import { AWS } from './aws'; -import { AZURE } from './azure'; -import { GCP } from './gcp'; - -describe('cloudServices', () => { - const expectedOrder = [AWS, GCP, AZURE]; - - it('iterates in expected order', () => { - let i = 0; - for (const service of CLOUD_SERVICES) { - expect(service).toBe(expectedOrder[i++]); - } - }); -}); diff --git a/x-pack/plugins/monitoring/server/cloud/gcp.js b/x-pack/plugins/monitoring/server/cloud/gcp.js deleted file mode 100644 index ab8935769b312..0000000000000 --- a/x-pack/plugins/monitoring/server/cloud/gcp.js +++ /dev/null @@ -1,136 +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 { isString } from 'lodash'; -import { promisify } from 'util'; -import { CloudService } from './cloud_service'; -import { CloudServiceResponse } from './cloud_response'; -import { CLOUD_METADATA_SERVICES } from '../../common/constants'; - -/** - * {@code GCPCloudService} will check and load the service metadata for an Google Cloud Platform VM if it is available. - */ -class GCPCloudService extends CloudService { - constructor(options = {}) { - super('gcp', options); - } - - _checkIfService(request) { - // we need to call GCP individually for each field - const fields = ['id', 'machine-type', 'zone']; - - const create = this._createRequestForField; - const allRequests = fields.map((field) => promisify(request)(create(field))); - return ( - Promise.all(allRequests) - /* - Note: there is no fallback option for GCP; - responses are arrays containing [fullResponse, body]; - because GCP returns plaintext, we have no way of validating without using the response code - */ - .then((responses) => { - return responses.map((response) => { - return this._extractBody(response, response.body); - }); - }) - .then(([id, machineType, zone]) => this._combineResponses(id, machineType, zone)) - ); - } - - _createRequestForField(field) { - return { - method: 'GET', - uri: `${CLOUD_METADATA_SERVICES.GCP_URL_PREFIX}/${field}`, - headers: { - // GCP requires this header - 'Metadata-Flavor': 'Google', - }, - // GCP does _not_ return JSON - json: false, - }; - } - - /** - * Extract the body if the response is valid and it came from GCP. - * - * @param {Object} response The response object - * @param {Object} body The response body, if any - * @return {Object} {@code body} (probably actually a String) if the response came from GCP. Otherwise {@code null}. - */ - _extractBody(response, body) { - if ( - response && - response.statusCode === 200 && - response.headers && - response.headers['metadata-flavor'] === 'Google' - ) { - return body; - } - - return null; - } - - /** - * Parse the GCP responses, if possible. Example values for each parameter: - * - * {@code vmId}: '5702733457649812345' - * {@code machineType}: 'projects/441331612345/machineTypes/f1-micro' - * {@code zone}: 'projects/441331612345/zones/us-east4-c' - * - * @param {String} vmId The ID of the VM - * @param {String} machineType The machine type, prefixed by unwanted account info. - * @param {String} zone The zone (e.g., availability zone), implicitly showing the region, prefixed by unwanted account info. - * @return {CloudServiceResponse} Never {@code null}. - * @throws {Error} if the responses do not make a valid response - */ - _combineResponses(id, machineType, zone) { - const vmId = isString(id) ? id.trim() : null; - const vmType = this._extractValue('machineTypes/', machineType); - const vmZone = this._extractValue('zones/', zone); - - let region; - - if (vmZone) { - // converts 'us-east4-c' into 'us-east4' - region = vmZone.substring(0, vmZone.lastIndexOf('-')); - } - - // ensure we actually have some data - if (vmId || vmType || region || vmZone) { - return new CloudServiceResponse(this._name, true, { id: vmId, vmType, region, zone: vmZone }); - } - - throw new Error('unrecognized responses'); - } - - /** - * Extract the useful information returned from GCP while discarding unwanted account details (the project ID). For example, - * this turns something like 'projects/441331612345/machineTypes/f1-micro' into 'f1-micro'. - * - * @param {String} fieldPrefix The value prefixing the actual value of interest. - * @param {String} value The entire value returned from GCP. - * @return {String} {@code undefined} if the value could not be extracted. Otherwise just the desired value. - */ - _extractValue(fieldPrefix, value) { - if (isString(value)) { - const index = value.lastIndexOf(fieldPrefix); - - if (index !== -1) { - return value.substring(index + fieldPrefix.length).trim(); - } - } - - return undefined; - } -} - -/** - * Singleton instance of {@code GCPCloudService}. - * - * @type {GCPCloudService} - */ -export const GCP = new GCPCloudService(); From eb25c693409d38809fa919a29b6967bf47bf9ba0 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Mon, 12 Apr 2021 12:57:22 -0400 Subject: [PATCH 25/79] [APM] Moves the transaction type selector to the search bar (#96685) * [APM] Moves the Transaction type selector to the search bar (#91131) * - Replaces the prepend label on the search bar with the transaction type selector - Adds the transaction type selector to the service overview page - Removes title from the Transactions list page * removes unused i18n items Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/app/service_overview/index.tsx | 14 +--------- .../components/app/trace_overview/index.tsx | 2 +- .../app/transaction_details/index.tsx | 2 +- .../app/transaction_overview/index.tsx | 28 +------------------ .../public/components/shared/search_bar.tsx | 12 ++++++-- .../translations/translations/ja-JP.json | 2 -- .../translations/translations/zh-CN.json | 2 -- 7 files changed, 13 insertions(+), 49 deletions(-) diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index f6ec2fb24018f..78c8f151b82d9 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -6,12 +6,10 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; import { AnnotationsContextProvider } from '../../../context/annotations/annotations_context'; -import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useBreakPoints } from '../../../hooks/use_break_points'; import { LatencyChart } from '../../shared/charts/latency_chart'; @@ -46,22 +44,12 @@ export function ServiceOverview({ // observe the window width and set the flex directions of rows accordingly const { isMedium } = useBreakPoints(); const rowDirection = isMedium ? 'column' : 'row'; - - const { transactionType } = useApmServiceContext(); - const transactionTypeLabel = i18n.translate( - 'xpack.apm.serviceOverview.searchBar.transactionTypeLabel', - { defaultMessage: 'Type: {transactionType}', values: { transactionType } } - ); const isRumAgent = isRumAgentName(agentName); return ( - + 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 6d7edcd0a1e35..364266d277482 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 @@ -49,7 +49,7 @@ export function TraceOverview() { return ( <> - + diff --git a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx index 0a322cfc9c80b..d6f45a4a45cc8 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_details/index.tsx @@ -95,7 +95,7 @@ export function TransactionDetails({

{transactionName}

- + diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 0814c6d95b96a..9e2743d7b5986 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -9,7 +9,6 @@ import { EuiCallOut, EuiCode, EuiFlexGroup, - EuiFlexItem, EuiPage, EuiPanel, EuiSpacer, @@ -28,7 +27,6 @@ import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; import { SearchBar } from '../../shared/search_bar'; -import { TransactionTypeSelect } from '../../shared/transaction_type_select'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; import { useTransactionListFetcher } from './use_transaction_list'; @@ -82,33 +80,9 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { return ( <> - + - - - - - - -

- {i18n.translate('xpack.apm.transactionOverviewTitle', { - defaultMessage: 'Transactions', - })} -

-
-
- - - -
- -
-
diff --git a/x-pack/plugins/apm/public/components/shared/search_bar.tsx b/x-pack/plugins/apm/public/components/shared/search_bar.tsx index aeb2a2c6390fc..ed9a196bbcd9d 100644 --- a/x-pack/plugins/apm/public/components/shared/search_bar.tsx +++ b/x-pack/plugins/apm/public/components/shared/search_bar.tsx @@ -20,6 +20,7 @@ import { TimeComparison } from './time_comparison'; import { useBreakPoints } from '../../hooks/use_break_points'; import { useKibanaUrl } from '../../hooks/useKibanaUrl'; import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; +import { TransactionTypeSelect } from './transaction_type_select'; const EuiFlexGroupSpaced = euiStyled(EuiFlexGroup)` margin: ${({ theme }) => @@ -29,7 +30,7 @@ const EuiFlexGroupSpaced = euiStyled(EuiFlexGroup)` interface Props { prepend?: React.ReactNode | string; showTimeComparison?: boolean; - showCorrelations?: boolean; + showTransactionTypeSelector?: boolean; } function getRowDirection(showColumn: boolean) { @@ -85,7 +86,7 @@ function DebugQueryCallout() { export function SearchBar({ prepend, showTimeComparison = false, - showCorrelations = false, + showTransactionTypeSelector = false, }: Props) { const { isMedium, isLarge } = useBreakPoints(); const itemsStyle = { marginBottom: isLarge ? px(unit) : 0 }; @@ -94,8 +95,13 @@ export function SearchBar({ <> + {showTransactionTypeSelector && ( + + + + )} - + Date: Mon, 12 Apr 2021 14:04:06 -0300 Subject: [PATCH 26/79] [Enterprise Search] Add missing and remove redundant breadcrumbs (#96636) * Workplace Search: Remove redundant Overview breadcrumb from Sources There is "Source name" breadcrumb that is used for Overview page * App Search: remove "Overview" breadcrumb from Engine page So instead of `engines / national-parks-demo / overview (greyed)` we will have just `engines / national-parks-demo (greyed)` * App Search: Add "Engines" breadcrumb to the main App Search page This needs to be added to 3 states of the page: Normal, Empty and Loading * Fix failing WS test * App Search: DRY out SetPageChrome declaration by putting it in header * Fix failed test "ShallowWrapper::dive() can only be called on components" Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/engine/engine_router.tsx | 3 +- .../engines/components/empty_state.tsx | 2 - .../engines/components/header.test.tsx | 3 + .../components/engines/components/header.tsx | 56 ++++++++++--------- .../engines/components/loading_state.tsx | 3 - .../components/engines/engines_overview.tsx | 2 - .../content_sources/source_router.test.tsx | 2 +- .../views/content_sources/source_router.tsx | 2 +- 8 files changed, 37 insertions(+), 36 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index 88a24755070ec..818245bd50978 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -37,7 +37,6 @@ import { AnalyticsRouter } from '../analytics'; import { ApiLogs } from '../api_logs'; import { CurationsRouter } from '../curations'; import { DocumentDetail, Documents } from '../documents'; -import { OVERVIEW_TITLE } from '../engine_overview'; import { EngineOverview } from '../engine_overview'; import { ENGINES_TITLE } from '../engines'; import { RelevanceTuning } from '../relevance_tuning'; @@ -122,7 +121,7 @@ export const EngineRouter: React.FC = () => { )} - + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx index 56fe3b97274ea..6911015e39d4a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/empty_state.tsx @@ -12,7 +12,6 @@ import { useValues, useActions } from 'kea'; import { EuiPageContent, EuiEmptyPrompt, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { EuiButtonTo } from '../../../../shared/react_router_helpers'; import { TelemetryLogic } from '../../../../shared/telemetry'; import { AppLogic } from '../../../app_logic'; @@ -32,7 +31,6 @@ export const EmptyState: React.FC = () => { return ( <> - {canManageEngines ? ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.test.tsx index 3ffe2f3d43a77..8cb26713cb840 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.test.tsx @@ -12,10 +12,13 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { EuiPageHeader } from '@elastic/eui'; + import { EnginesOverviewHeader } from './'; describe('EnginesOverviewHeader', () => { const wrapper = shallow() + .find(EuiPageHeader) .dive() .children() .dive(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx index df87f2e5230db..bab67fd0e4bb5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/header.tsx @@ -13,36 +13,42 @@ import { EuiPageHeader, EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { getAppSearchUrl } from '../../../../shared/enterprise_search_url'; +import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { TelemetryLogic } from '../../../../shared/telemetry'; +import { ENGINES_TITLE } from '../constants'; + export const EnginesOverviewHeader: React.FC = () => { const { sendAppSearchTelemetry } = useActions(TelemetryLogic); return ( - - sendAppSearchTelemetry({ - action: 'clicked', - metric: 'header_launch_button', - }) - } - data-test-subj="launchButton" - > - {i18n.translate('xpack.enterpriseSearch.appSearch.productCta', { - defaultMessage: 'Launch App Search', - })} - , - ]} - /> + <> + + + sendAppSearchTelemetry({ + action: 'clicked', + metric: 'header_launch_button', + }) + } + data-test-subj="launchButton" + > + {i18n.translate('xpack.enterpriseSearch.appSearch.productCta', { + defaultMessage: 'Launch App Search', + })} + , + ]} + /> + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx index 56be0a5562742..875c47378d1fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/components/loading_state.tsx @@ -9,14 +9,11 @@ import React from 'react'; import { EuiPageContent, EuiSpacer, EuiLoadingContent } from '@elastic/eui'; -import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; - import { EnginesOverviewHeader } from './header'; export const LoadingState: React.FC = () => { return ( <> - diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index 0712b990159a4..4d51012f2aa2a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -20,7 +20,6 @@ import { } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; -import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { LicensingLogic } from '../../../shared/licensing'; import { EuiButtonTo } from '../../../shared/react_router_helpers'; import { convertMetaToPagination, handlePageChange } from '../../../shared/table_pagination'; @@ -80,7 +79,6 @@ export const EnginesOverview: React.FC = () => { return ( <> - diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx index 004f7e5e45bfa..463468d1304b6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.test.tsx @@ -88,7 +88,7 @@ describe('SourceRouter', () => { const contentBreadCrumb = wrapper.find(SetPageChrome).at(1); const settingsBreadCrumb = wrapper.find(SetPageChrome).at(2); - expect(overviewBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.OVERVIEW]); + expect(overviewBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs]); expect(contentBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.CONTENT]); expect(settingsBreadCrumb.prop('trail')).toEqual([...loadingBreadcrumbs, NAV.SETTINGS]); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index ef9788efbdaf2..b844c86abb919 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -98,7 +98,7 @@ export const SourceRouter: React.FC = () => { - + From 9b239f64cd5151f3752930b37cd83cfa41b1e595 Mon Sep 17 00:00:00 2001 From: Walter Rafelsberger Date: Mon, 12 Apr 2021 19:04:37 +0200 Subject: [PATCH 27/79] [ML] Data Frame Analytics: Fix scatterplot matrix boilerplate visibility with no fields selected. (#96590) Fixes the problem where deselecting all fields for the scatterplot would also hide the UI to do the actual selection. Now, when all fields are removed from the combo box, the UI stays visible, just the scatterplot itself will be hidden. --- .../scatterplot_matrix.test.tsx | 87 +++++++++++++++++++ .../scatterplot_matrix/scatterplot_matrix.tsx | 4 +- 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx new file mode 100644 index 0000000000000..10deaa1c2d489 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.test.tsx @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { render, waitFor, screen } from '@testing-library/react'; + +import { IntlProvider } from 'react-intl'; + +import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; + +import { ScatterplotMatrix } from './scatterplot_matrix'; + +const mockEsSearch = jest.fn((body) => ({ + hits: { hits: [{ fields: { x: [1], y: [2] } }, { fields: { x: [2], y: [3] } }] }, +})); +jest.mock('../../contexts/kibana', () => ({ + useMlApiContext: () => ({ + esSearch: mockEsSearch, + }), +})); + +const mockEuiTheme = euiThemeLight; +jest.mock('../color_range_legend', () => ({ + useCurrentEuiTheme: () => ({ + euiTheme: mockEuiTheme, + }), +})); + +// Mocking VegaChart to avoid a jest/canvas related error +jest.mock('../vega_chart', () => ({ + VegaChart: () =>
, +})); + +describe('Data Frame Analytics: ', () => { + it('renders the scatterplot matrix wrapper with options but not the chart itself', async () => { + // prepare + render( + + + + ); + + // assert + await waitFor(() => { + expect(mockEsSearch).toHaveBeenCalledTimes(0); + // should hide the loading indicator and render the wrapping options boilerplate + expect(screen.queryByTestId('mlScatterplotMatrix loaded')).toBeInTheDocument(); + // should not render the scatterplot matrix itself because there's no data items. + expect(screen.queryByTestId('mlVegaChart')).not.toBeInTheDocument(); + }); + }); + + it('renders the scatterplot matrix wrapper with options and the chart itself', async () => { + // prepare + render( + + + + ); + + // assert + await waitFor(() => { + expect(mockEsSearch).toHaveBeenCalledWith({ + body: { _source: false, fields: ['x', 'y'], from: 0, query: undefined, size: 1000 }, + index: 'the-index-name', + }); + // should hide the loading indicator and render the wrapping options boilerplate + expect(screen.queryByTestId('mlScatterplotMatrix loaded')).toBeInTheDocument(); + // should render the scatterplot matrix. + expect(screen.queryByTestId('mlVegaChart')).toBeInTheDocument(); + }); + }); +}); diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx index 540fa65bf6c18..b83965b52befc 100644 --- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx +++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx @@ -108,7 +108,7 @@ export const ScatterplotMatrix: FC = ({ // are sized according to outlier_score const [dynamicSize, setDynamicSize] = useState(false); - // used to give the use the option to customize the fields used for the matrix axes + // used to give the user the option to customize the fields used for the matrix axes const [fields, setFields] = useState([]); useEffect(() => { @@ -165,7 +165,7 @@ export const ScatterplotMatrix: FC = ({ useEffect(() => { if (fields.length === 0) { - setSplom(undefined); + setSplom({ columns: [], items: [], messages: [] }); setIsLoading(false); return; } From 0836e4d67b61c871bb3f32a86a39f508bed48ceb Mon Sep 17 00:00:00 2001 From: Luca Belluccini Date: Mon, 12 Apr 2021 18:14:03 +0100 Subject: [PATCH 28/79] [DOC] Index pattern and cluster exclusion examples with CCS (#61256) * [DOC] Index pattern and cluster exclusion examples with CCS Providing some examples of using Index Pattern and cluster exclusions with CCS * Update docs/management/index-patterns.asciidoc * Update docs/management/index-patterns.asciidoc * Update docs/management/index-patterns.asciidoc Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/management/index-patterns.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/management/index-patterns.asciidoc b/docs/management/index-patterns.asciidoc index 88dbf6ec8761f..3d9253025d3cc 100644 --- a/docs/management/index-patterns.asciidoc +++ b/docs/management/index-patterns.asciidoc @@ -125,6 +125,11 @@ pattern: *:logstash-* ``` +You can use exclusions to exclude indices that might contain mapping errors. +To match indices starting with `logstash-`, and exclude those starting with `logstash-old` from +all clusters having a name starting with `cluster_`, you can use `cluster_*:logstash-*,cluster*:logstash-old*`. +To exclude a cluster, use `cluster_*:logstash-*,cluster_one:-*`. + Once an index pattern is configured using the {ccs} syntax, all searches and aggregations using that index pattern in {kib} take advantage of {ccs}. From baac478ff37f75fe1d43b418a262bf5f44afe628 Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 12 Apr 2021 13:33:45 -0400 Subject: [PATCH 29/79] [Enterprise Search] Allow jest script to run on individual files (#96589) --- x-pack/plugins/enterprise_search/README.md | 4 +++- x-pack/plugins/enterprise_search/jest.sh | 20 ++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/enterprise_search/README.md b/x-pack/plugins/enterprise_search/README.md index 0caea251ec6fb..0b067e25e32e8 100644 --- a/x-pack/plugins/enterprise_search/README.md +++ b/x-pack/plugins/enterprise_search/README.md @@ -38,7 +38,7 @@ yarn test:jest yarn test:jest --watch ``` -Unfortunately coverage collection does not work as automatically, and requires using our handy jest.sh script if you want to run tests on a specific folder and only get coverage numbers for that folder: +Unfortunately coverage collection does not work as automatically, and requires using our handy jest.sh script if you want to run tests on a specific file or folder and only get coverage numbers for that file or folder: ```bash # Running the jest.sh script from the `x-pack/plugins/enterprise_search` folder (vs. kibana root) @@ -46,6 +46,8 @@ Unfortunately coverage collection does not work as automatically, and requires u sh jest.sh {YOUR_COMPONENT_DIR} sh jest.sh public/applications/shared/kibana sh jest.sh server/routes/app_search +# When testing an individual file, remember to pass the path of the test file, not the source file. +sh jest.sh public/applications/shared/flash_messages/flash_messages_logic.test.ts ``` ### E2E tests diff --git a/x-pack/plugins/enterprise_search/jest.sh b/x-pack/plugins/enterprise_search/jest.sh index d7aa0b07fb89c..8bc3134a62d8e 100644 --- a/x-pack/plugins/enterprise_search/jest.sh +++ b/x-pack/plugins/enterprise_search/jest.sh @@ -1,13 +1,21 @@ #! /bin/bash # Whether to run Jest on the entire enterprise_search plugin or a specific component/folder -FOLDER="${1:-all}" -if [[ $FOLDER && $FOLDER != "all" ]] + +TARGET="${1:-all}" +if [[ $TARGET && $TARGET != "all" ]] then - FOLDER=${FOLDER%/} # Strip any trailing slash - FOLDER="${FOLDER}/ --collectCoverageFrom='/x-pack/plugins/enterprise_search/${FOLDER}/**/*.{ts,tsx}'" + # If this is a file + if [[ "$TARGET" == *".ts"* ]]; then + PATH_WITHOUT_EXTENSION=${1%%.*} + TARGET="${TARGET} --collectCoverageFrom='/x-pack/plugins/enterprise_search/${PATH_WITHOUT_EXTENSION}.{ts,tsx}'" + # If this is a folder + else + TARGET=${TARGET%/} # Strip any trailing slash + TARGET="${TARGET}/ --collectCoverageFrom='/x-pack/plugins/enterprise_search/${TARGET}/**/*.{ts,tsx}'" + fi else - FOLDER='' + TARGET='' fi # Pass all remaining arguments (e.g., ...rest) from the 2nd arg onwards @@ -15,4 +23,4 @@ fi # @see https://jestjs.io/docs/en/cli#options ARGS="${*:2}" -yarn test:jest $FOLDER $ARGS +yarn test:jest $TARGET $ARGS From b4d330219a18cfddfe05e1be471f03b02056131e Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Mon, 12 Apr 2021 13:38:44 -0400 Subject: [PATCH 30/79] [App Search] Add header details to the Result Settings page (#96623) --- .../relevance_tuning_layout.test.tsx | 10 +- .../relevance_tuning_layout.tsx | 2 +- .../relevance_tuning_logic.test.ts | 5 +- .../result_settings/result_settings.test.tsx | 39 +++++- .../result_settings/result_settings.tsx | 47 ++++++- .../result_settings_logic.test.ts | 124 ++++++++---------- .../result_settings/result_settings_logic.ts | 100 +++++++------- .../components/result_settings/types.ts | 5 - 8 files changed, 198 insertions(+), 134 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx index edd417cc1ffe8..9ed6e17c2bcd9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.test.tsx @@ -9,7 +9,7 @@ import { setMockActions, setMockValues } from '../../../__mocks__/kea.mock'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import { EuiPageHeader } from '@elastic/eui'; @@ -33,9 +33,11 @@ describe('RelevanceTuningLayout', () => { }); const subject = () => shallow(); + const findButtons = (wrapper: ShallowWrapper) => + wrapper.find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; it('renders a Save button that will save the current changes', () => { - const buttons = subject().find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; + const buttons = findButtons(subject()); expect(buttons.length).toBe(2); const saveButton = shallow(buttons[0]); saveButton.simulate('click'); @@ -43,7 +45,7 @@ describe('RelevanceTuningLayout', () => { }); it('renders a Reset button that will remove all weights and boosts', () => { - const buttons = subject().find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; + const buttons = findButtons(subject()); expect(buttons.length).toBe(2); const resetButton = shallow(buttons[1]); resetButton.simulate('click'); @@ -55,7 +57,7 @@ describe('RelevanceTuningLayout', () => { ...values, engineHasSchemaFields: false, }); - const buttons = subject().find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; + const buttons = findButtons(subject()); expect(buttons.length).toBe(0); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx index 0ea38b0d9fa36..f29cc12f20a98 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_layout.tsx @@ -37,7 +37,7 @@ export const RelevanceTuningLayout: React.FC = ({ engineBreadcrumb, child description={i18n.translate( 'xpack.enterpriseSearch.appSearch.engine.relevanceTuning.description', { - defaultMessage: 'Set field weights and boosts', + defaultMessage: 'Set field weights and boosts.', } )} rightSideItems={ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts index ca9b0a886fdd1..4ec38d314a259 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning_logic.test.ts @@ -586,10 +586,9 @@ describe('RelevanceTuningLogic', () => { confirmSpy.mockImplementation(() => false); RelevanceTuningLogic.actions.resetSearchSettings(); + await nextTick(); - expect(http.post).not.toHaveBeenCalledWith( - '/api/app_search/engines/test-engine/search_settings/reset' - ); + expect(http.post).not.toHaveBeenCalled(); }); it('handles errors', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx index 9eda1362e04fc..5365cc0f029f8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.test.tsx @@ -11,7 +11,9 @@ import { setMockValues, setMockActions } from '../../../__mocks__'; import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; + +import { EuiPageHeader } from '@elastic/eui'; import { ResultSettings } from './result_settings'; import { ResultSettingsTable } from './result_settings_table'; @@ -24,6 +26,9 @@ describe('RelevanceTuning', () => { const actions = { initializeResultSettingsData: jest.fn(), + saveResultSettings: jest.fn(), + confirmResetAllFields: jest.fn(), + clearAllFields: jest.fn(), }; beforeEach(() => { @@ -32,8 +37,12 @@ describe('RelevanceTuning', () => { jest.clearAllMocks(); }); + const subject = () => shallow(); + const findButtons = (wrapper: ShallowWrapper) => + wrapper.find(EuiPageHeader).prop('rightSideItems') as React.ReactElement[]; + it('renders', () => { - const wrapper = shallow(); + const wrapper = subject(); expect(wrapper.find(ResultSettingsTable).exists()).toBe(true); expect(wrapper.find(SampleResponse).exists()).toBe(true); }); @@ -47,8 +56,32 @@ describe('RelevanceTuning', () => { setMockValues({ dataLoading: true, }); - const wrapper = shallow(); + const wrapper = subject(); expect(wrapper.find(ResultSettingsTable).exists()).toBe(false); expect(wrapper.find(SampleResponse).exists()).toBe(false); }); + + it('renders a "save" button that will save the current changes', () => { + const buttons = findButtons(subject()); + expect(buttons.length).toBe(3); + const saveButton = shallow(buttons[0]); + saveButton.simulate('click'); + expect(actions.saveResultSettings).toHaveBeenCalled(); + }); + + it('renders a "restore defaults" button that will reset all values to their defaults', () => { + const buttons = findButtons(subject()); + expect(buttons.length).toBe(3); + const resetButton = shallow(buttons[1]); + resetButton.simulate('click'); + expect(actions.confirmResetAllFields).toHaveBeenCalled(); + }); + + it('renders a "clear" button that will remove all selected options', () => { + const buttons = findButtons(subject()); + expect(buttons.length).toBe(3); + const clearButton = shallow(buttons[2]); + clearButton.simulate('click'); + expect(actions.clearAllFields).toHaveBeenCalled(); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx index 336f3f663119f..a513d0c1b9f34 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings.tsx @@ -9,12 +9,15 @@ import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiPageHeader, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiPageHeader, EuiFlexGroup, EuiFlexItem, EuiButton, EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { SAVE_BUTTON_LABEL } from '../../../shared/constants'; import { FlashMessages } from '../../../shared/flash_messages'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; - import { Loading } from '../../../shared/loading'; +import { RESTORE_DEFAULTS_BUTTON_LABEL } from '../../constants'; import { RESULT_SETTINGS_TITLE } from './constants'; import { ResultSettingsTable } from './result_settings_table'; @@ -23,13 +26,23 @@ import { SampleResponse } from './sample_response'; import { ResultSettingsLogic } from '.'; +const CLEAR_BUTTON_LABEL = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.clearButtonLabel', + { defaultMessage: 'Clear all values' } +); + interface Props { engineBreadcrumb: string[]; } export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { const { dataLoading } = useValues(ResultSettingsLogic); - const { initializeResultSettingsData } = useActions(ResultSettingsLogic); + const { + initializeResultSettingsData, + saveResultSettings, + confirmResetAllFields, + clearAllFields, + } = useActions(ResultSettingsLogic); useEffect(() => { initializeResultSettingsData(); @@ -40,7 +53,33 @@ export const ResultSettings: React.FC = ({ engineBreadcrumb }) => { return ( <> - + + {SAVE_BUTTON_LABEL} + , + + {RESTORE_DEFAULTS_BUTTON_LABEL} + , + + {CLEAR_BUTTON_LABEL} + , + ]} + /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts index a9c161b2bb5be..8d9c33e3c9e68 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.test.ts @@ -15,7 +15,7 @@ import { nextTick } from '@kbn/test/jest'; import { Schema, SchemaConflicts, SchemaTypes } from '../../../shared/types'; -import { OpenModal, ServerFieldResultSettingObject } from './types'; +import { ServerFieldResultSettingObject } from './types'; import { ResultSettingsLogic } from '.'; @@ -25,7 +25,6 @@ describe('ResultSettingsLogic', () => { const DEFAULT_VALUES = { dataLoading: true, saving: false, - openModal: OpenModal.None, resultFields: {}, lastSavedResultFields: {}, schema: {}, @@ -83,7 +82,6 @@ describe('ResultSettingsLogic', () => { mount({ dataLoading: true, saving: true, - openModal: OpenModal.ConfirmSaveModal, }); ResultSettingsLogic.actions.initializeResultFields( @@ -139,8 +137,6 @@ describe('ResultSettingsLogic', () => { snippetFallback: false, }, }, - // The modal should be reset back to closed if it had been opened previously - openModal: OpenModal.None, // Stores the provided schema details schema, schemaConflicts, @@ -156,47 +152,6 @@ describe('ResultSettingsLogic', () => { }); }); - describe('openConfirmSaveModal', () => { - mount({ - openModal: OpenModal.None, - }); - - ResultSettingsLogic.actions.openConfirmSaveModal(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - openModal: OpenModal.ConfirmSaveModal, - }); - }); - - describe('openConfirmResetModal', () => { - mount({ - openModal: OpenModal.None, - }); - - ResultSettingsLogic.actions.openConfirmResetModal(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - openModal: OpenModal.ConfirmResetModal, - }); - }); - - describe('closeModals', () => { - it('should close open modals', () => { - mount({ - openModal: OpenModal.ConfirmSaveModal, - }); - - ResultSettingsLogic.actions.closeModals(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - openModal: OpenModal.None, - }); - }); - }); - describe('clearAllFields', () => { it('should remove all settings that have been set for each field', () => { mount({ @@ -237,19 +192,6 @@ describe('ResultSettingsLogic', () => { }, }); }); - - it('should close open modals', () => { - mount({ - openModal: OpenModal.ConfirmSaveModal, - }); - - ResultSettingsLogic.actions.resetAllFields(); - - expect(resultSettingLogicValues()).toEqual({ - ...DEFAULT_VALUES, - openModal: OpenModal.None, - }); - }); }); describe('updateField', () => { @@ -297,7 +239,7 @@ describe('ResultSettingsLogic', () => { }); describe('saving', () => { - it('sets saving to true and close any open modals', () => { + it('sets saving to true', () => { mount({ saving: false, }); @@ -307,7 +249,6 @@ describe('ResultSettingsLogic', () => { expect(resultSettingLogicValues()).toEqual({ ...DEFAULT_VALUES, saving: true, - openModal: OpenModal.None, }); }); }); @@ -563,6 +504,12 @@ describe('ResultSettingsLogic', () => { describe('listeners', () => { const { http } = mockHttpValues; const { flashAPIErrors } = mockFlashMessageHelpers; + let confirmSpy: jest.SpyInstance; + + beforeAll(() => { + confirmSpy = jest.spyOn(window, 'confirm'); + }); + afterAll(() => confirmSpy.mockRestore()); const serverFieldResultSettings = { foo: { @@ -864,20 +811,55 @@ describe('ResultSettingsLogic', () => { }); }); + describe('confirmResetAllFields', () => { + it('will reset all fields as long as the user confirms the action', async () => { + mount(); + confirmSpy.mockImplementation(() => true); + jest.spyOn(ResultSettingsLogic.actions, 'resetAllFields'); + + ResultSettingsLogic.actions.confirmResetAllFields(); + + expect(ResultSettingsLogic.actions.resetAllFields).toHaveBeenCalled(); + }); + + it('will do nothing if the user cancels the action', async () => { + mount(); + confirmSpy.mockImplementation(() => false); + jest.spyOn(ResultSettingsLogic.actions, 'resetAllFields'); + + ResultSettingsLogic.actions.confirmResetAllFields(); + + expect(ResultSettingsLogic.actions.resetAllFields).not.toHaveBeenCalled(); + }); + }); + describe('saveResultSettings', () => { + beforeEach(() => { + confirmSpy.mockImplementation(() => true); + }); + it('should make an API call to update result settings and update state accordingly', async () => { + const resultFields = { + foo: { raw: true, rawSize: 100 }, + }; + + const serverResultFields = { + foo: { raw: { size: 100 } }, + }; + mount({ schema, + resultFields, }); http.put.mockReturnValueOnce( Promise.resolve({ - result_fields: serverFieldResultSettings, + result_fields: serverResultFields, }) ); jest.spyOn(ResultSettingsLogic.actions, 'saving'); jest.spyOn(ResultSettingsLogic.actions, 'initializeResultFields'); - ResultSettingsLogic.actions.saveResultSettings(serverFieldResultSettings); + ResultSettingsLogic.actions.saveResultSettings(); expect(ResultSettingsLogic.actions.saving).toHaveBeenCalled(); @@ -887,12 +869,12 @@ describe('ResultSettingsLogic', () => { '/api/app_search/engines/test-engine/result_settings', { body: JSON.stringify({ - result_fields: serverFieldResultSettings, + result_fields: serverResultFields, }), } ); expect(ResultSettingsLogic.actions.initializeResultFields).toHaveBeenCalledWith( - serverFieldResultSettings, + serverResultFields, schema ); }); @@ -901,11 +883,21 @@ describe('ResultSettingsLogic', () => { mount(); http.put.mockReturnValueOnce(Promise.reject('error')); - ResultSettingsLogic.actions.saveResultSettings(serverFieldResultSettings); + ResultSettingsLogic.actions.saveResultSettings(); await nextTick(); expect(flashAPIErrors).toHaveBeenCalledWith('error'); }); + + it('does nothing if the user does not confirm', async () => { + mount(); + confirmSpy.mockImplementation(() => false); + + ResultSettingsLogic.actions.saveResultSettings(); + await nextTick(); + + expect(http.put).not.toHaveBeenCalled(); + }); }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts index c345ae7e02e8d..f518fc945bfbf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/result_settings_logic.ts @@ -19,7 +19,6 @@ import { DEFAULT_SNIPPET_SIZE } from './constants'; import { FieldResultSetting, FieldResultSettingObject, - OpenModal, ServerFieldResultSettingObject, } from './types'; @@ -34,9 +33,6 @@ import { } from './utils'; interface ResultSettingsActions { - openConfirmResetModal(): void; - openConfirmSaveModal(): void; - closeModals(): void; initializeResultFields( serverResultFields: ServerFieldResultSettingObject, schema: Schema, @@ -62,15 +58,13 @@ interface ResultSettingsActions { updateRawSizeForField(fieldName: string, size: number): { fieldName: string; size: number }; updateSnippetSizeForField(fieldName: string, size: number): { fieldName: string; size: number }; initializeResultSettingsData(): void; - saveResultSettings( - resultFields: ServerFieldResultSettingObject - ): { resultFields: ServerFieldResultSettingObject }; + confirmResetAllFields(): void; + saveResultSettings(): void; } interface ResultSettingsValues { dataLoading: boolean; saving: boolean; - openModal: OpenModal; resultFields: FieldResultSettingObject; lastSavedResultFields: FieldResultSettingObject; schema: Schema; @@ -86,12 +80,25 @@ interface ResultSettingsValues { queryPerformanceScore: number; } +const SAVE_CONFIRMATION_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmSaveMessage', + { + defaultMessage: + 'The changes will start immediately. Make sure your applications are ready to accept the new search results!', + } +); + +const RESET_CONFIRMATION_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.confirmResetMessage', + { + defaultMessage: + 'This will revert your settings back to the default: all fields set to raw. The default will take over immediately and impact your search results.', + } +); + export const ResultSettingsLogic = kea>({ path: ['enterprise_search', 'app_search', 'result_settings_logic'], actions: () => ({ - openConfirmResetModal: () => true, - openConfirmSaveModal: () => true, - closeModals: () => true, initializeResultFields: (serverResultFields, schema, schemaConflicts) => { const resultFields = convertServerResultFieldsToResultFields(serverResultFields, schema); @@ -113,7 +120,8 @@ export const ResultSettingsLogic = kea ({ fieldName, size }), updateSnippetSizeForField: (fieldName, size) => ({ fieldName, size }), initializeResultSettingsData: () => true, - saveResultSettings: (resultFields) => ({ resultFields }), + confirmResetAllFields: () => true, + saveResultSettings: () => true, }), reducers: () => ({ dataLoading: [ @@ -129,17 +137,6 @@ export const ResultSettingsLogic = kea true, }, ], - openModal: [ - OpenModal.None, - { - initializeResultFields: () => OpenModal.None, - closeModals: () => OpenModal.None, - resetAllFields: () => OpenModal.None, - openConfirmResetModal: () => OpenModal.ConfirmResetModal, - openConfirmSaveModal: () => OpenModal.ConfirmSaveModal, - saving: () => OpenModal.None, - }, - ], resultFields: [ {}, { @@ -308,35 +305,42 @@ export const ResultSettingsLogic = kea { - actions.saving(); + confirmResetAllFields: () => { + if (window.confirm(RESET_CONFIRMATION_MESSAGE)) { + actions.resetAllFields(); + } + }, + saveResultSettings: async () => { + if (window.confirm(SAVE_CONFIRMATION_MESSAGE)) { + actions.saving(); - const { http } = HttpLogic.values; - const { engineName } = EngineLogic.values; - const url = `/api/app_search/engines/${engineName}/result_settings`; + const { http } = HttpLogic.values; + const { engineName } = EngineLogic.values; + const url = `/api/app_search/engines/${engineName}/result_settings`; - actions.saving(); + actions.saving(); - let response; - try { - response = await http.put(url, { - body: JSON.stringify({ - result_fields: resultFields, - }), - }); - } catch (e) { - flashAPIErrors(e); - } + let response; + try { + response = await http.put(url, { + body: JSON.stringify({ + result_fields: values.reducedServerResultFields, + }), + }); + } catch (e) { + flashAPIErrors(e); + } - actions.initializeResultFields(response.result_fields, values.schema); - setSuccessMessage( - i18n.translate( - 'xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage', - { - defaultMessage: 'Result settings have been saved successfully.', - } - ) - ); + actions.initializeResultFields(response.result_fields, values.schema); + setSuccessMessage( + i18n.translate( + 'xpack.enterpriseSearch.appSearch.engine.resultSettings.saveSuccessMessage', + { + defaultMessage: 'Result settings have been saved successfully.', + } + ) + ); + } }, }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts index 18843112f46bf..1174f65523d99 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result_settings/types.ts @@ -7,11 +7,6 @@ import { FieldValue } from '../result/types'; -export enum OpenModal { - None, - ConfirmResetModal, - ConfirmSaveModal, -} export interface ServerFieldResultSetting { raw?: | { From 92b98e740f5daf97d94c018aba8d93bd8c51dba3 Mon Sep 17 00:00:00 2001 From: Luca Belluccini Date: Mon, 12 Apr 2021 18:52:04 +0100 Subject: [PATCH 31/79] [DOC] Painless lab enable/disable flag (#95392) * [DOC] Painless lab enable/disable flag * Update docs/settings/dev-settings.asciidoc * Update docs/settings/dev-settings.asciidoc Co-authored-by: Kaarina Tungseth Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- docs/settings/dev-settings.asciidoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/settings/dev-settings.asciidoc b/docs/settings/dev-settings.asciidoc index 62553293a7d03..810694f46b317 100644 --- a/docs/settings/dev-settings.asciidoc +++ b/docs/settings/dev-settings.asciidoc @@ -29,3 +29,14 @@ They are enabled by default. | Set to `true` to enable the <>. Defaults to `true`. |=== + +[float] +[[painless_lab-settings]] +==== Painless Lab settings + +[cols="2*<"] +|=== +| `xpack.painless_lab.enabled` + | When set to `true`, enables the <>. Defaults to `true`. + +|=== From e7f5d079636f10c8469ceda8c9a8daba01494106 Mon Sep 17 00:00:00 2001 From: Quynh Nguyen <43350163+qn895@users.noreply.github.com> Date: Mon, 12 Apr 2021 12:59:57 -0500 Subject: [PATCH 32/79] [ML] Add runtime support for anomaly charts & add composite validations (#96348) --- .../types/anomaly_detection_jobs/datafeed.ts | 14 +-- .../plugins/ml/common/util/datafeed_utils.ts | 7 -- x-pack/plugins/ml/common/util/job_utils.ts | 117 +++++++++++------- .../ml/common/util/object_utils.test.ts | 16 ++- x-pack/plugins/ml/common/util/object_utils.ts | 11 ++ .../components/job_actions/results.js | 12 +- .../new_job/common/job_creator/job_creator.ts | 8 +- .../anomaly_explorer_charts_service.ts | 6 +- .../results_service/result_service_rx.ts | 8 +- .../translations/translations/ja-JP.json | 4 +- .../translations/translations/zh-CN.json | 4 +- 11 files changed, 115 insertions(+), 92 deletions(-) diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts index 77d453b68edc5..5d7f3f934700b 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/datafeed.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { estypes } from '@elastic/elasticsearch'; +import type { estypes } from '@elastic/elasticsearch'; // import { IndexPatternTitle } from '../kibana'; // import { RuntimeMappings } from '../fields'; // import { JobId } from './job'; @@ -41,17 +41,7 @@ export type ChunkingConfig = estypes.ChunkingConfig; // time_span?: string; // } -export type Aggregation = Record< - string, - { - date_histogram: { - field: string; - fixed_interval: string; - }; - aggregations?: { [key: string]: any }; - aggs?: { [key: string]: any }; - } ->; +export type Aggregation = Record; export type IndicesOptions = estypes.IndicesOptions; // export interface IndicesOptions { diff --git a/x-pack/plugins/ml/common/util/datafeed_utils.ts b/x-pack/plugins/ml/common/util/datafeed_utils.ts index c0579ce947992..58038feddb98b 100644 --- a/x-pack/plugins/ml/common/util/datafeed_utils.ts +++ b/x-pack/plugins/ml/common/util/datafeed_utils.ts @@ -18,10 +18,3 @@ export const getDatafeedAggregations = ( ): Aggregation | undefined => { return getAggregations(datafeedConfig); }; - -export const getAggregationBucketsName = (aggregations: any): string | undefined => { - if (aggregations !== null && typeof aggregations === 'object') { - const keys = Object.keys(aggregations); - return keys.length > 0 ? keys[0] : undefined; - } -}; diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 10f5fb975ef5e..da340d4413849 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -8,9 +8,9 @@ import { each, isEmpty, isEqual, pick } from 'lodash'; import semverGte from 'semver/functions/gte'; import moment, { Duration } from 'moment'; +import type { estypes } from '@elastic/elasticsearch'; // @ts-ignore import numeral from '@elastic/numeral'; - import { i18n } from '@kbn/i18n'; import { ALLOWED_DATA_UNITS, JOB_ID_MAX_LENGTH } from '../constants/validation'; import { parseInterval } from './parse_interval'; @@ -22,13 +22,9 @@ import { MlServerLimits } from '../types/ml_server_info'; import { JobValidationMessage, JobValidationMessageId } from '../constants/messages'; import { ES_AGGREGATION, ML_JOB_AGGREGATION } from '../constants/aggregation_types'; import { MLCATEGORY } from '../constants/field_types'; -import { - getAggregationBucketsName, - getAggregations, - getDatafeedAggregations, -} from './datafeed_utils'; +import { getAggregations, getDatafeedAggregations } from './datafeed_utils'; import { findAggField } from './validation_utils'; -import { isPopulatedObject } from './object_utils'; +import { getFirstKeyInObject, isPopulatedObject } from './object_utils'; import { isDefined } from '../types/guards'; export interface ValidationResults { @@ -52,14 +48,6 @@ export function calculateDatafeedFrequencyDefaultSeconds(bucketSpanSeconds: numb return freq; } -export function hasRuntimeMappings(job: CombinedJob): boolean { - const hasDatafeed = isPopulatedObject(job.datafeed_config); - if (hasDatafeed) { - return isPopulatedObject(job.datafeed_config.runtime_mappings); - } - return false; -} - export function isTimeSeriesViewJob(job: CombinedJob): boolean { return getSingleMetricViewerJobErrorMessage(job) === undefined; } @@ -85,6 +73,34 @@ export function isMappableJob(job: CombinedJob, detectorIndex: number): boolean return isMappable; } +/** + * Validates that composite definition only have sources that are only terms and date_histogram + * if composite is defined. + * @param buckets + */ +export function hasValidComposite(buckets: estypes.AggregationContainer) { + if ( + isPopulatedObject(buckets, ['composite']) && + isPopulatedObject(buckets.composite, ['sources']) && + Array.isArray(buckets.composite.sources) + ) { + const sources = buckets.composite.sources; + return !sources.some((source) => { + const sourceName = getFirstKeyInObject(source); + if (sourceName !== undefined && isPopulatedObject(source[sourceName])) { + const sourceTypes = Object.keys(source[sourceName]); + return ( + sourceTypes.length === 1 && + sourceTypes[0] !== 'date_histogram' && + sourceTypes[0] !== 'terms' + ); + } + return false; + }); + } + return true; +} + // Returns a flag to indicate whether the source data can be plotted in a time // series chart for the specified detector. export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex: number): boolean { @@ -105,42 +121,42 @@ export function isSourceDataChartableForDetector(job: CombinedJob, detectorIndex dtr.partition_field_name !== MLCATEGORY && dtr.over_field_name !== MLCATEGORY; - // If the datafeed uses script fields, we can only plot the time series if - // model plot is enabled. Without model plot it will be very difficult or impossible - // to invert to a reverse search of the underlying metric data. - if ( - isSourceDataChartable === true && - job.datafeed_config?.script_fields !== null && - typeof job.datafeed_config?.script_fields === 'object' - ) { + const hasDatafeed = isPopulatedObject(job.datafeed_config); + + if (isSourceDataChartable && hasDatafeed) { // Perform extra check to see if the detector is using a scripted field. - const scriptFields = Object.keys(job.datafeed_config.script_fields); - isSourceDataChartable = - scriptFields.indexOf(dtr.partition_field_name!) === -1 && - scriptFields.indexOf(dtr.by_field_name!) === -1 && - scriptFields.indexOf(dtr.over_field_name!) === -1; - } + if (isPopulatedObject(job.datafeed_config.script_fields)) { + // If the datafeed uses script fields, we can only plot the time series if + // model plot is enabled. Without model plot it will be very difficult or impossible + // to invert to a reverse search of the underlying metric data. + + const scriptFields = Object.keys(job.datafeed_config.script_fields); + return ( + scriptFields.indexOf(dtr.partition_field_name!) === -1 && + scriptFields.indexOf(dtr.by_field_name!) === -1 && + scriptFields.indexOf(dtr.over_field_name!) === -1 + ); + } - const hasDatafeed = isPopulatedObject(job.datafeed_config); - if (hasDatafeed) { // We cannot plot the source data for some specific aggregation configurations const aggs = getDatafeedAggregations(job.datafeed_config); - if (aggs !== undefined) { - const aggBucketsName = getAggregationBucketsName(aggs); + if (isPopulatedObject(aggs)) { + const aggBucketsName = getFirstKeyInObject(aggs); if (aggBucketsName !== undefined) { - // if fieldName is a aggregated field under nested terms using bucket_script - const aggregations = getAggregations<{ [key: string]: any }>(aggs[aggBucketsName]) ?? {}; + // if fieldName is an aggregated field under nested terms using bucket_script + const aggregations = + getAggregations(aggs[aggBucketsName]) ?? {}; const foundField = findAggField(aggregations, dtr.field_name, false); if (foundField?.bucket_script !== undefined) { return false; } + + // composite sources should be terms and date_histogram only for now + return hasValidComposite(aggregations); } } - // We also cannot plot the source data if they datafeed uses any field defined by runtime_mappings - if (hasRuntimeMappings(job)) { - return false; - } + return true; } } @@ -180,11 +196,22 @@ export function isModelPlotChartableForDetector(job: Job, detectorIndex: number) // Returns a reason to indicate why the job configuration is not supported // if the result is undefined, that means the single metric job should be viewable export function getSingleMetricViewerJobErrorMessage(job: CombinedJob): string | undefined { - // if job has runtime mappings with no model plot - if (hasRuntimeMappings(job) && !job.model_plot_config?.enabled) { - return i18n.translate('xpack.ml.timeSeriesJob.jobWithRunTimeMessage', { - defaultMessage: 'the datafeed contains runtime fields and model plot is disabled', - }); + // if job has at least one composite source that is not terms or date_histogram + const aggs = getDatafeedAggregations(job.datafeed_config); + if (isPopulatedObject(aggs)) { + const aggBucketsName = getFirstKeyInObject(aggs); + if (aggBucketsName !== undefined && aggs[aggBucketsName] !== undefined) { + // if fieldName is an aggregated field under nested terms using bucket_script + + if (!hasValidComposite(aggs[aggBucketsName])) { + return i18n.translate( + 'xpack.ml.timeSeriesJob.jobWithUnsupportedCompositeAggregationMessage', + { + defaultMessage: 'Disabled because the datafeed contains unsupported composite sources.', + } + ); + } + } } // only allow jobs with at least one detector whose function corresponds to // an ES aggregation which can be viewed in the single metric view and which @@ -196,7 +223,7 @@ export function getSingleMetricViewerJobErrorMessage(job: CombinedJob): string | if (isChartableTimeSeriesViewJob === false) { return i18n.translate('xpack.ml.timeSeriesJob.notViewableTimeSeriesJobMessage', { - defaultMessage: 'not a viewable time series job', + defaultMessage: 'Disabled because not a viewable time series job.', }); } } diff --git a/x-pack/plugins/ml/common/util/object_utils.test.ts b/x-pack/plugins/ml/common/util/object_utils.test.ts index 8e4196ed4d826..d6d500cdb82c6 100644 --- a/x-pack/plugins/ml/common/util/object_utils.test.ts +++ b/x-pack/plugins/ml/common/util/object_utils.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { isPopulatedObject } from './object_utils'; +import { getFirstKeyInObject, isPopulatedObject } from './object_utils'; describe('object_utils', () => { describe('isPopulatedObject()', () => { @@ -47,4 +47,18 @@ describe('object_utils', () => { ).toBe(false); }); }); + + describe('getFirstKeyInObject()', () => { + it('gets the first key in object', () => { + expect(getFirstKeyInObject({ attribute1: 'value', attribute2: 'value2' })).toBe('attribute1'); + }); + + it('returns undefined with invalid argument', () => { + expect(getFirstKeyInObject(undefined)).toBe(undefined); + expect(getFirstKeyInObject(null)).toBe(undefined); + expect(getFirstKeyInObject({})).toBe(undefined); + expect(getFirstKeyInObject('value')).toBe(undefined); + expect(getFirstKeyInObject(5)).toBe(undefined); + }); + }); }); diff --git a/x-pack/plugins/ml/common/util/object_utils.ts b/x-pack/plugins/ml/common/util/object_utils.ts index 537ee9202b4de..cd62ca006725e 100644 --- a/x-pack/plugins/ml/common/util/object_utils.ts +++ b/x-pack/plugins/ml/common/util/object_utils.ts @@ -34,3 +34,14 @@ export const isPopulatedObject = ( requiredAttributes.every((d) => ({}.hasOwnProperty.call(arg, d)))) ); }; + +/** + * Get the first key in the object + * getFirstKeyInObject({ firstKey: {}, secondKey: {}}) -> firstKey + */ +export const getFirstKeyInObject = (arg: unknown): string | undefined => { + if (isPopulatedObject(arg)) { + const keys = Object.keys(arg); + return keys.length > 0 ? keys[0] : undefined; + } +}; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js index 251b1b24087fa..f8195f5747f7e 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_actions/results.js @@ -39,16 +39,6 @@ export function ResultLinks({ jobs }) { const singleMetricDisabledMessage = jobs.length === 1 && jobs[0].isNotSingleMetricViewerJobMessage; - const singleMetricDisabledMessageText = - singleMetricDisabledMessage !== undefined - ? i18n.translate('xpack.ml.jobsList.resultActions.singleMetricDisabledMessageText', { - defaultMessage: 'Disabled because {reason}.', - values: { - reason: singleMetricDisabledMessage, - }, - }) - : undefined; - const jobActionsDisabled = jobs.length === 1 && jobs[0].deleting === true; const { createLinkWithUserDefaults } = useCreateADLinks(); const timeSeriesExplorerLink = useMemo( @@ -62,7 +52,7 @@ export function ResultLinks({ jobs }) { {singleMetricVisible && ( 0 && records.length > 0) { + if (records.length > 0) { const filterField = records[0].by_field_value || records[0].over_field_value; - chartData = eventDistribution.filter((d: { entity: any }) => d.entity !== filterField); + if (eventDistribution.length > 0) { + chartData = eventDistribution.filter((d: { entity: any }) => d.entity !== filterField); + } map(metricData, (value, time) => { // The filtering for rare/event_distribution charts needs to be handled // differently because of how the source data is structured. diff --git a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts index caa0e20c3230d..c31194b58d589 100644 --- a/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -27,6 +27,7 @@ import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; import { isPopulatedObject } from '../../../../common/util/object_utils'; import { InfluencersFilterQuery } from '../../../../common/types/es_client'; import { RecordForInfluencer } from './results_service'; +import { isRuntimeMappings } from '../../../../common'; interface ResultResponse { success: boolean; @@ -140,9 +141,7 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) { }, }, size: 0, - _source: { - excludes: [], - }, + _source: false, aggs: { byTime: { date_histogram: { @@ -152,6 +151,9 @@ export function resultsServiceRxProvider(mlApiServices: MlApiServices) { }, }, }, + ...(isRuntimeMappings(datafeedConfig?.runtime_mappings) + ? { runtime_mappings: datafeedConfig?.runtime_mappings } + : {}), }; if (shouldCriteria.length > 0) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8b125394eb612..527f32828979a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -14207,7 +14207,6 @@ "xpack.ml.jobsList.refreshButtonLabel": "更新", "xpack.ml.jobsList.resultActions.openJobsInAnomalyExplorerText": "{jobsCount, plural, one {{jobId}} other {# 件のジョブ}} を異常エクスプローラーで開く", "xpack.ml.jobsList.resultActions.openJobsInSingleMetricViewerText": "シングルメトリックビューアーで {jobsCount, plural, one {{jobId}} other {# 件のジョブ}} を開く", - "xpack.ml.jobsList.resultActions.singleMetricDisabledMessageText": "{reason}のため無効です。", "xpack.ml.jobsList.selectRowForJobMessage": "ジョブID {jobId} の行を選択", "xpack.ml.jobsList.showDetailsColumn.screenReaderDescription": "このカラムには各ジョブの詳細を示すクリック可能なコントロールが含まれます", "xpack.ml.jobsList.spacesLabel": "スペース", @@ -15074,7 +15073,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.zoomLabel": "ズーム:", "xpack.ml.timeSeriesExplorer.tryWideningTheTimeSelectionDescription": "時間範囲を広げるか、さらに過去に遡ってみてください。", "xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage": "このダッシュボードでは 1 度に 1 つのジョブしか表示できません", - "xpack.ml.timeSeriesJob.jobWithRunTimeMessage": "データフィードにはランタイムフィールドが含まれ、モデルプロットが無効です", "xpack.ml.timeSeriesJob.notViewableTimeSeriesJobMessage": "表示可能な時系列ジョブではありません", "xpack.ml.timeSeriesJob.sourceDataModelPlotNotChartableMessage": "この検出器ではソースデータとモデルプロットの両方をグラフ化できません", "xpack.ml.timeSeriesJob.sourceDataNotChartableWithDisabledModelPlotMessage": "この検出器ではソースデータを表示できません。モデルプロットが無効です", @@ -23570,4 +23568,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "フィールドを選択してください。", "xpack.watcher.watcherDescription": "アラートの作成、管理、監視によりデータへの変更を検知します。" } -} \ 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 f79a095277bd7..f8c8ee753942c 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -14404,7 +14404,6 @@ "xpack.ml.jobsList.refreshButtonLabel": "刷新", "xpack.ml.jobsList.resultActions.openJobsInAnomalyExplorerText": "在 Anomaly Explorer 中打开 {jobsCount, plural, one {{jobId}} other {# 个作业}}", "xpack.ml.jobsList.resultActions.openJobsInSingleMetricViewerText": "在 Single Metric Viewer 中打开 {jobsCount, plural, one {{jobId}} other {# 个作业}}", - "xpack.ml.jobsList.resultActions.singleMetricDisabledMessageText": "由于{reason},已禁用。", "xpack.ml.jobsList.selectRowForJobMessage": "选择作业 ID {jobId} 的行", "xpack.ml.jobsList.showDetailsColumn.screenReaderDescription": "此列包含可单击控件,用于显示每个作业的更多详情", "xpack.ml.jobsList.spacesLabel": "工作区", @@ -15292,7 +15291,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.zoomLabel": "缩放:", "xpack.ml.timeSeriesExplorer.tryWideningTheTimeSelectionDescription": "请尝试扩大时间选择范围或进一步向后追溯。", "xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage": "在此仪表板中,一次仅可以查看一个作业", - "xpack.ml.timeSeriesJob.jobWithRunTimeMessage": "数据馈送包含运行时字段,模型绘图已禁用", "xpack.ml.timeSeriesJob.notViewableTimeSeriesJobMessage": "不是可查看的时间序列作业", "xpack.ml.timeSeriesJob.sourceDataModelPlotNotChartableMessage": "此检测器的源数据和模型绘图均无法绘制", "xpack.ml.timeSeriesJob.sourceDataNotChartableWithDisabledModelPlotMessage": "此检测器的源数据无法查看,且模型绘图处于禁用状态", @@ -23939,4 +23937,4 @@ "xpack.watcher.watchEdit.thresholdWatchExpression.aggType.fieldIsRequiredValidationMessage": "此字段必填。", "xpack.watcher.watcherDescription": "通过创建、管理和监测警报来检测数据中的更改。" } -} \ No newline at end of file +} From cb3c4e3a212255a9e9b8c89e784e0e452b661233 Mon Sep 17 00:00:00 2001 From: John Schulz Date: Mon, 12 Apr 2021 14:05:39 -0400 Subject: [PATCH 33/79] [Fleet] support force flag to add/remove package_policies (#96713) ## Summary Can now pass a `force=true` parameter to add & remove integrations on hosted policies as originally intended [1] & [2] * Add `force` param for `POST` `/api/fleet/package_policies` & `/api/fleet/package_policies/delete` to a policy. Update tests to confirm * Not strictly required, but "while I was in there" * Updated a few places to throw `IngestManagerError` vs `Error` for `400` response vs `500`. Updated tests. * removed a few unnecessary `await`s of sync function [1] https://github.com/elastic/kibana/issues/92426#issuecomment-785092670 [2] https://github.com/elastic/kibana/issues/90445 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/routes/package_policy/handlers.ts | 12 +- .../fleet/server/services/agent_policy.ts | 10 +- .../fleet/server/services/package_policy.ts | 14 +- .../server/types/models/package_policy.ts | 1 + .../server/types/rest_spec/package_policy.ts | 1 + .../apis/package_policy/create.ts | 422 +++++++++--------- .../apis/package_policy/delete.ts | 14 +- 7 files changed, 247 insertions(+), 227 deletions(-) diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index 5e8abd5966e3a..4427ba714ad6a 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -79,11 +79,12 @@ export const createPackagePolicyHandler: RequestHandler< > = async (context, request, response) => { const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; - const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; + const { force, ...newPolicy } = request.body; try { const newData = await packagePolicyService.runExternalCallbacks( 'packagePolicyCreate', - { ...request.body }, + newPolicy, context, request ); @@ -91,6 +92,7 @@ export const createPackagePolicyHandler: RequestHandler< // Create package policy const packagePolicy = await packagePolicyService.create(soClient, esClient, newData, { user, + force, }); const body: CreatePackagePolicyResponse = { item: packagePolicy }; return response.ok({ @@ -114,7 +116,7 @@ export const updatePackagePolicyHandler: RequestHandler< > = async (context, request, response) => { const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; - const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; const packagePolicy = await packagePolicyService.get(soClient, request.params.packagePolicyId); if (!packagePolicy) { @@ -155,13 +157,13 @@ export const deletePackagePolicyHandler: RequestHandler< > = async (context, request, response) => { const soClient = context.core.savedObjects.client; const esClient = context.core.elasticsearch.client.asCurrentUser; - const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined; + const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined; try { const body: DeletePackagePoliciesResponse = await packagePolicyService.delete( soClient, esClient, request.body.packagePolicyIds, - { user } + { user, force: request.body.force } ); return response.ok({ body, diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index be61a70154b11..7f793a41ab985 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -466,7 +466,9 @@ class AgentPolicyService { esClient: ElasticsearchClient, id: string, packagePolicyIds: string[], - options: { user?: AuthenticatedUser; bumpRevision: boolean } = { bumpRevision: true } + options: { user?: AuthenticatedUser; bumpRevision: boolean; force?: boolean } = { + bumpRevision: true, + } ): Promise { const oldAgentPolicy = await this.get(soClient, id, false); @@ -474,7 +476,7 @@ class AgentPolicyService { throw new Error('Agent policy not found'); } - if (oldAgentPolicy.is_managed) { + if (oldAgentPolicy.is_managed && !options?.force) { throw new IngestManagerError(`Cannot update integrations of managed policy ${id}`); } @@ -497,7 +499,7 @@ class AgentPolicyService { esClient: ElasticsearchClient, id: string, packagePolicyIds: string[], - options?: { user?: AuthenticatedUser } + options?: { user?: AuthenticatedUser; force?: boolean } ): Promise { const oldAgentPolicy = await this.get(soClient, id, false); @@ -505,7 +507,7 @@ class AgentPolicyService { throw new Error('Agent policy not found'); } - if (oldAgentPolicy.is_managed) { + if (oldAgentPolicy.is_managed && !options?.force) { throw new IngestManagerError(`Cannot remove integrations of managed policy ${id}`); } diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 210c9128b1ec7..7d12aad6f32b5 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -60,14 +60,14 @@ class PackagePolicyService { soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, packagePolicy: NewPackagePolicy, - options?: { id?: string; user?: AuthenticatedUser; bumpRevision?: boolean } + options?: { id?: string; user?: AuthenticatedUser; bumpRevision?: boolean; force?: boolean } ): Promise { // Check that its agent policy does not have a package policy with the same name const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id); if (!parentAgentPolicy) { throw new Error('Agent policy not found'); } - if (parentAgentPolicy.is_managed) { + if (parentAgentPolicy.is_managed && !options?.force) { throw new IngestManagerError( `Cannot add integrations to managed policy ${parentAgentPolicy.id}` ); @@ -77,7 +77,9 @@ class PackagePolicyService { (siblingPackagePolicy) => siblingPackagePolicy.name === packagePolicy.name ) ) { - throw new Error('There is already a package with the same name on this agent policy'); + throw new IngestManagerError( + 'There is already a package with the same name on this agent policy' + ); } // Add ids to stream @@ -106,7 +108,7 @@ class PackagePolicyService { if (isPackageLimited(pkgInfo)) { const agentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id, true); if (agentPolicy && doesAgentPolicyAlreadyIncludePackage(agentPolicy, pkgInfo.name)) { - throw new Error( + throw new IngestManagerError( `Unable to create package policy. Package '${pkgInfo.name}' already exists on this agent policy.` ); } @@ -140,6 +142,7 @@ class PackagePolicyService { { user: options?.user, bumpRevision: options?.bumpRevision ?? true, + force: options?.force, } ); @@ -367,7 +370,7 @@ class PackagePolicyService { soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, ids: string[], - options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean } + options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean; force?: boolean } ): Promise { const result: DeletePackagePoliciesResponse = []; @@ -385,6 +388,7 @@ class PackagePolicyService { [packagePolicy.id], { user: options?.user, + force: options?.force, } ); } diff --git a/x-pack/plugins/fleet/server/types/models/package_policy.ts b/x-pack/plugins/fleet/server/types/models/package_policy.ts index 6248b375f8edb..1f39b3135cb3f 100644 --- a/x-pack/plugins/fleet/server/types/models/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/models/package_policy.ts @@ -78,6 +78,7 @@ const PackagePolicyBaseSchema = { export const NewPackagePolicySchema = schema.object({ ...PackagePolicyBaseSchema, + force: schema.maybe(schema.boolean()), }); export const UpdatePackagePolicySchema = schema.object({ diff --git a/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts index 3c6f54177096e..6086d1f0e00fb 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/package_policy.ts @@ -33,5 +33,6 @@ export const UpdatePackagePolicyRequestSchema = { export const DeletePackagePoliciesRequestSchema = { body: schema.object({ packagePolicyIds: schema.arrayOf(schema.string()), + force: schema.maybe(schema.boolean()), }), }; diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts index 3764bdbc20d03..e2e1cc2f584bb 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts @@ -6,19 +6,18 @@ */ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; -import { warnAndSkipTest } from '../../helpers'; +import { skipIfNoDockerRegistry } from '../../helpers'; -export default function ({ getService }: FtrProviderContext) { - const log = getService('log'); +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; const supertest = getService('supertest'); - const dockerServers = getService('dockerServers'); - const server = dockerServers.get('registry'); // use function () {} and not () => {} here // because `this` has to point to the Mocha context // see https://mochajs.org/#arrow-functions describe('Package Policy - create', async function () { + skipIfNoDockerRegistry(providerContext); let agentPolicyId: string; before(async () => { await getService('esArchiver').load('empty_kibana'); @@ -47,230 +46,229 @@ export default function ({ getService }: FtrProviderContext) { .send({ agentPolicyId }); }); - it('should fail for managed agent policies', async function () { - if (server.enabled) { - // get a managed policy - const { - body: { item: managedPolicy }, - } = await supertest - .post(`/api/fleet/agent_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: `Managed policy from ${Date.now()}`, - namespace: 'default', - is_managed: true, - }); + it('can only add to managed agent policies using the force parameter', async function () { + // get a managed policy + const { + body: { item: managedPolicy }, + } = await supertest + .post(`/api/fleet/agent_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: `Managed policy from ${Date.now()}`, + namespace: 'default', + is_managed: true, + }); - // try to add an integration to the managed policy - const { body } = await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-1', - description: '', - namespace: 'default', - policy_id: managedPolicy.id, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(400); + // try to add an integration to the managed policy + const { body: responseWithoutForce } = await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'default', + policy_id: managedPolicy.id, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); - expect(body.statusCode).to.be(400); - expect(body.message).to.contain('Cannot add integrations to managed policy'); + expect(responseWithoutForce.statusCode).to.be(400); + expect(responseWithoutForce.message).to.contain('Cannot add integrations to managed policy'); - // delete policy we just made - await supertest.post(`/api/fleet/agent_policies/delete`).set('kbn-xsrf', 'xxxx').send({ - agentPolicyId: managedPolicy.id, - }); - } else { - warnAndSkipTest(this, log); - } + // try same request with `force: true` + const { body: responseWithForce } = await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + force: true, + name: 'filetest-1', + description: '', + namespace: 'default', + policy_id: managedPolicy.id, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(200); + + expect(responseWithForce.item.name).to.eql('filetest-1'); + + // delete policy we just made + await supertest.post(`/api/fleet/agent_policies/delete`).set('kbn-xsrf', 'xxxx').send({ + agentPolicyId: managedPolicy.id, + }); }); it('should work with valid values', async function () { - if (server.enabled) { - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(200); - } else { - warnAndSkipTest(this, log); - } + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(200); }); it('should return a 400 with an empty namespace', async function () { - if (server.enabled) { - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-1', - description: '', - namespace: '', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(400); - } else { - warnAndSkipTest(this, log); - } + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: '', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); }); it('should return a 400 with an invalid namespace', async function () { - if (server.enabled) { - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-1', - description: '', - namespace: 'InvalidNamespace', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(400); - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'filetest-1', - description: '', - namespace: - 'testlength😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(400); - } else { - warnAndSkipTest(this, log); - } + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: 'InvalidNamespace', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'filetest-1', + description: '', + namespace: + 'testlength😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀😀', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); }); it('should not allow multiple limited packages on the same agent policy', async function () { - if (server.enabled) { - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'endpoint-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'endpoint', - title: 'Endpoint', - version: '0.13.0', - }, - }) - .expect(200); - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'endpoint-2', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'endpoint', - title: 'Endpoint', - version: '0.13.0', - }, - }) - .expect(500); - } else { - warnAndSkipTest(this, log); - } + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'endpoint-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'endpoint', + title: 'Endpoint', + version: '0.13.0', + }, + }) + .expect(200); + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'endpoint-2', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'endpoint', + title: 'Endpoint', + version: '0.13.0', + }, + }) + .expect(400); }); - it('should return a 500 if there is another package policy with the same name', async function () { - if (server.enabled) { - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'same-name-test-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(200); - await supertest - .post(`/api/fleet/package_policies`) - .set('kbn-xsrf', 'xxxx') - .send({ - name: 'same-name-test-1', - description: '', - namespace: 'default', - policy_id: agentPolicyId, - enabled: true, - output_id: '', - inputs: [], - package: { - name: 'filetest', - title: 'For File Tests', - version: '0.1.0', - }, - }) - .expect(500); - } else { - warnAndSkipTest(this, log); - } + it('should return a 400 if there is another package policy with the same name', async function () { + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'same-name-test-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(200); + await supertest + .post(`/api/fleet/package_policies`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'same-name-test-1', + description: '', + namespace: 'default', + policy_id: agentPolicyId, + enabled: true, + output_id: '', + inputs: [], + package: { + name: 'filetest', + title: 'For File Tests', + version: '0.1.0', + }, + }) + .expect(400); }); }); } diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts b/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts index 85e6f5ab92b74..15aba758c85d0 100644 --- a/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts +++ b/x-pack/test/fleet_api_integration/apis/package_policy/delete.ts @@ -80,7 +80,7 @@ export default function (providerContext: FtrProviderContext) { await supertest .post(`/api/fleet/package_policies/delete`) .set('kbn-xsrf', 'xxxx') - .send({ packagePolicyIds: [packagePolicy.id] }); + .send({ force: true, packagePolicyIds: [packagePolicy.id] }); }); after(async () => { await getService('esArchiver').unload('empty_kibana'); @@ -112,6 +112,18 @@ export default function (providerContext: FtrProviderContext) { expect(results[0].success).to.be(false); expect(results[0].body.message).to.contain('Cannot remove integrations of managed policy'); + // same, but with force + const { body: resultsWithForce } = await supertest + .post(`/api/fleet/package_policies/delete`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true, packagePolicyIds: [packagePolicy.id] }) + .expect(200); + + // delete always succeeds (returns 200) with Array<{success: boolean}> + expect(Array.isArray(resultsWithForce)); + expect(resultsWithForce.length).to.be(1); + expect(resultsWithForce[0].success).to.be(true); + // revert existing policy to unmanaged await supertest .put(`/api/fleet/agent_policies/${agentPolicy.id}`) From c2b17696879171858a028bdf4ddfcac6faaf11d9 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 12 Apr 2021 20:38:47 +0200 Subject: [PATCH 34/79] Migration v2 waits for yellow cluster (#96788) * migrator waits for source index to be yellow otherwise the next request to Elasticsearch can fail * unskip integration tests that failed due to a red cluster * log how much the every step lasts * use Date.now instead of performance.now migration cannot finish in ms * update tests * clean log file before running tests * fix wrong type * add an integration test for waitForIndexStatusYellow --- .../migrationsv2/actions/index.ts | 4 +- .../integration_tests/actions.test.ts | 46 ++++ .../integration_tests/migration.test.ts | 23 +- .../migration_7.7.2_xpack_100k.test.ts | 18 +- .../migrations_state_action_machine.test.ts | 13 +- .../migrations_state_action_machine.ts | 23 +- .../saved_objects/migrationsv2/model.test.ts | 228 +++++++----------- .../saved_objects/migrationsv2/model.ts | 41 ++-- .../server/saved_objects/migrationsv2/next.ts | 5 +- .../saved_objects/migrationsv2/types.ts | 8 + 10 files changed, 225 insertions(+), 184 deletions(-) diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index d759c0c9be20e..9d6afbd3b0d87 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -185,10 +185,10 @@ export const removeWriteBlock = ( * yellow at any point in the future. So ultimately data-redundancy is up to * users to maintain. */ -const waitForIndexStatusYellow = ( +export const waitForIndexStatusYellow = ( client: ElasticsearchClient, index: string, - timeout: string + timeout = DEFAULT_TIMEOUT ): TaskEither.TaskEither => () => { return client.cluster .health({ index, wait_for_status: 'yellow', timeout }) diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 3ed3ace416990..21c05d22b0581 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -30,6 +30,7 @@ import { UpdateAndPickupMappingsResponse, verifyReindex, removeWriteBlock, + waitForIndexStatusYellow, } from '../actions'; import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; @@ -207,6 +208,51 @@ describe('migration actions', () => { }); }); + describe('waitForIndexStatusYellow', () => { + afterAll(async () => { + await client.indices.delete({ index: 'red_then_yellow_index' }); + }); + it('resolves right after waiting for an index status to be yellow if the index already existed', async () => { + // Create a red index + await client.indices.create( + { + index: 'red_then_yellow_index', + timeout: '5s', + body: { + mappings: { properties: {} }, + settings: { + // Allocate 1 replica so that this index stays yellow + number_of_replicas: '1', + // Disable all shard allocation so that the index status is red + index: { routing: { allocation: { enable: 'none' } } }, + }, + }, + }, + { maxRetries: 0 /** handle retry ourselves for now */ } + ); + + // Start tracking the index status + const indexStatusPromise = waitForIndexStatusYellow(client, 'red_then_yellow_index')(); + + const redStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); + expect(redStatusResponse.body.status).toBe('red'); + + client.indices.putSettings({ + index: 'red_then_yellow_index', + body: { + // Enable all shard allocation so that the index status turns yellow + index: { routing: { allocation: { enable: 'all' } } }, + }, + }); + + await indexStatusPromise; + // Assert that the promise didn't resolve before the index became yellow + + const yellowStatusResponse = await client.cluster.health({ index: 'red_then_yellow_index' }); + expect(yellowStatusResponse.body.status).toBe('yellow'); + }); + }); + describe('cloneIndex', () => { afterAll(async () => { try { diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts index 4d41a147bc0ef..1f8c3a535a902 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration.test.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -import { join } from 'path'; +import Path from 'path'; +import Fs from 'fs'; +import Util from 'util'; import Semver from 'semver'; import { REPO_ROOT } from '@kbn/dev-utils'; import { Env } from '@kbn/config'; @@ -19,8 +21,15 @@ import { Root } from '../../../root'; const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; -// FLAKY: https://github.com/elastic/kibana/issues/91107 -describe.skip('migration v2', () => { +const logFilePath = Path.join(__dirname, 'migration_test_kibana.log'); + +const asyncUnlink = Util.promisify(Fs.unlink); +async function removeLogFile() { + // ignore errors if it doesn't exist + await asyncUnlink(logFilePath).catch(() => void 0); +} + +describe('migration v2', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; let coreStart: InternalCoreStart; @@ -47,7 +56,7 @@ describe.skip('migration v2', () => { appenders: { file: { type: 'file', - fileName: join(__dirname, 'migration_test_kibana.log'), + fileName: logFilePath, layout: { type: 'json', }, @@ -122,9 +131,10 @@ describe.skip('migration v2', () => { const migratedIndex = `.kibana_${kibanaVersion}_001`; beforeAll(async () => { + await removeLogFile(); await startServers({ oss: false, - dataArchive: join(__dirname, 'archives', '7.3.0_xpack_sample_saved_objects.zip'), + dataArchive: Path.join(__dirname, 'archives', '7.3.0_xpack_sample_saved_objects.zip'), }); }); @@ -179,9 +189,10 @@ describe.skip('migration v2', () => { const migratedIndex = `.kibana_${kibanaVersion}_001`; beforeAll(async () => { + await removeLogFile(); await startServers({ oss: true, - dataArchive: join(__dirname, 'archives', '8.0.0_oss_sample_saved_objects.zip'), + dataArchive: Path.join(__dirname, 'archives', '8.0.0_oss_sample_saved_objects.zip'), }); }); diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts index c26d4593bede1..0e51c886f7f30 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts @@ -6,7 +6,9 @@ * Side Public License, v 1. */ -import { join } from 'path'; +import Path from 'path'; +import Fs from 'fs'; +import Util from 'util'; import { REPO_ROOT } from '@kbn/dev-utils'; import { Env } from '@kbn/config'; import { getEnvOptions } from '@kbn/config/target/mocks'; @@ -16,8 +18,15 @@ import { InternalCoreStart } from '../../../internal_types'; import { Root } from '../../../root'; const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; +const logFilePath = Path.join(__dirname, 'migration_test_kibana.log'); -describe.skip('migration from 7.7.2-xpack with 100k objects', () => { +const asyncUnlink = Util.promisify(Fs.unlink); +async function removeLogFile() { + // ignore errors if it doesn't exist + await asyncUnlink(logFilePath).catch(() => void 0); +} + +describe('migration from 7.7.2-xpack with 100k objects', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; let coreStart: InternalCoreStart; @@ -48,7 +57,7 @@ describe.skip('migration from 7.7.2-xpack with 100k objects', () => { appenders: { file: { type: 'file', - fileName: join(__dirname, 'migration_test_kibana.log'), + fileName: logFilePath, layout: { type: 'json', }, @@ -93,9 +102,10 @@ describe.skip('migration from 7.7.2-xpack with 100k objects', () => { const migratedIndex = `.kibana_${kibanaVersion}_001`; beforeAll(async () => { + await removeLogFile(); await startServers({ oss: false, - dataArchive: join(__dirname, 'archives', '7.7.2_xpack_100k_obj.zip'), + dataArchive: Path.join(__dirname, 'archives', '7.7.2_xpack_100k_obj.zip'), }); }); diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index 2c2cd0032abfd..4d93abcc4018f 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -16,6 +16,11 @@ import { ResponseError } from '@elastic/elasticsearch/lib/errors'; import { elasticsearchClientMock } from '../../elasticsearch/client/mocks'; describe('migrationsStateActionMachine', () => { + beforeAll(() => { + jest + .spyOn(global.Date, 'now') + .mockImplementation(() => new Date('2021-04-12T16:00:00.000Z').valueOf()); + }); beforeEach(() => { jest.clearAllMocks(); }); @@ -112,25 +117,25 @@ describe('migrationsStateActionMachine', () => { "[.my-so-index] Log from LEGACY_REINDEX control state", ], Array [ - "[.my-so-index] INIT -> LEGACY_REINDEX", + "[.my-so-index] INIT -> LEGACY_REINDEX. took: 0ms.", ], Array [ "[.my-so-index] Log from LEGACY_DELETE control state", ], Array [ - "[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE", + "[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE. took: 0ms.", ], Array [ "[.my-so-index] Log from LEGACY_DELETE control state", ], Array [ - "[.my-so-index] LEGACY_DELETE -> LEGACY_DELETE", + "[.my-so-index] LEGACY_DELETE -> LEGACY_DELETE. took: 0ms.", ], Array [ "[.my-so-index] Log from DONE control state", ], Array [ - "[.my-so-index] LEGACY_DELETE -> DONE", + "[.my-so-index] LEGACY_DELETE -> DONE. took: 0ms.", ], ], "log": Array [], diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts index dddc66d68ad20..e35e21421ac1f 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts @@ -8,7 +8,6 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import * as Option from 'fp-ts/lib/Option'; -import { performance } from 'perf_hooks'; import { Logger, LogMeta } from '../../logging'; import { CorruptSavedObjectError } from '../migrations/core/migrate_raw_docs'; import { Model, Next, stateActionMachine } from './state_action_machine'; @@ -32,7 +31,8 @@ const logStateTransition = ( logger: Logger, logMessagePrefix: string, oldState: State, - newState: State + newState: State, + tookMs: number ) => { if (newState.logs.length > oldState.logs.length) { newState.logs @@ -40,7 +40,9 @@ const logStateTransition = ( .forEach((log) => logger[log.level](logMessagePrefix + log.message)); } - logger.info(logMessagePrefix + `${oldState.controlState} -> ${newState.controlState}`); + logger.info( + logMessagePrefix + `${oldState.controlState} -> ${newState.controlState}. took: ${tookMs}ms.` + ); }; const logActionResponse = ( @@ -85,11 +87,12 @@ export async function migrationStateActionMachine({ model: Model; }) { const executionLog: ExecutionLog = []; - const starteTime = performance.now(); + const startTime = Date.now(); // Since saved object index names usually start with a `.` and can be // configured by users to include several `.`'s we can't use a logger tag to // indicate which messages come from which index upgrade. const logMessagePrefix = `[${initialState.indexPrefix}] `; + let prevTimestamp = startTime; try { const finalState = await stateActionMachine( initialState, @@ -116,12 +119,20 @@ export async function migrationStateActionMachine({ controlState: newState.controlState, prevControlState: state.controlState, }); - logStateTransition(logger, logMessagePrefix, state, redactedNewState as State); + const now = Date.now(); + logStateTransition( + logger, + logMessagePrefix, + state, + redactedNewState as State, + now - prevTimestamp + ); + prevTimestamp = now; return newState; } ); - const elapsedMs = performance.now() - starteTime; + const elapsedMs = Date.now() - startTime; if (finalState.controlState === 'DONE') { logger.info(logMessagePrefix + `Migration completed after ${Math.round(elapsedMs)}ms`); if (finalState.sourceIndex != null && Option.isSome(finalState.sourceIndex)) { diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 4fd9b7cbb3df4..8aad62f13b8fe 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; -import { +import type { FatalState, State, LegacySetWriteBlockState, @@ -30,6 +30,7 @@ import { CreateNewTargetState, CloneTempToSource, SetTempWriteBlock, + WaitForYellowSourceState, } from './types'; import { SavedObjectsRawDoc } from '..'; import { AliasAction, RetryableEsClientError } from './actions'; @@ -265,7 +266,7 @@ describe('migrations v2 model', () => { `"The .kibana alias is pointing to a newer version of Kibana: v7.12.0"` ); }); - test('INIT -> SET_SOURCE_WRITE_BLOCK when .kibana points to an index with an invalid version', () => { + test('INIT -> WAIT_FOR_YELLOW_SOURCE when .kibana points to an index with an invalid version', () => { // If users tamper with our index version naming scheme we can no // longer accurately detect a newer version. Older Kibana versions // will have indices like `.kibana_10` and users might choose an @@ -290,39 +291,13 @@ describe('migrations v2 model', () => { }); const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('SET_SOURCE_WRITE_BLOCK'); + expect(newState.controlState).toEqual('WAIT_FOR_YELLOW_SOURCE'); expect(newState).toMatchObject({ - controlState: 'SET_SOURCE_WRITE_BLOCK', - sourceIndex: Option.some('.kibana_7.invalid.0_001'), - targetIndex: '.kibana_7.11.0_001', + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: '.kibana_7.invalid.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); }); - test('INIT -> SET_SOURCE_WRITE_BLOCK when migrating from a v2 migrations index (>= 7.11.0)', () => { + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_7.11.0_001': { aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, @@ -348,39 +323,13 @@ describe('migrations v2 model', () => { ); expect(newState).toMatchObject({ - controlState: 'SET_SOURCE_WRITE_BLOCK', - sourceIndex: Option.some('.kibana_7.11.0_001'), - targetIndex: '.kibana_7.12.0_001', + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: '.kibana_7.11.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - test('INIT -> SET_SOURCE_WRITE_BLOCK when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a v1 migrations index (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_3': { aliases: { @@ -393,35 +342,9 @@ describe('migrations v2 model', () => { const newState = model(initState, res); expect(newState).toMatchObject({ - controlState: 'SET_SOURCE_WRITE_BLOCK', - sourceIndex: Option.some('.kibana_3'), - targetIndex: '.kibana_7.11.0_001', + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: '.kibana_3', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); @@ -468,7 +391,7 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - test('INIT -> SET_SOURCE_WRITE_BLOCK when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index name (>= 6.5 < 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ 'my-saved-objects_3': { aliases: { @@ -490,39 +413,13 @@ describe('migrations v2 model', () => { ); expect(newState).toMatchObject({ - controlState: 'SET_SOURCE_WRITE_BLOCK', - sourceIndex: Option.some('my-saved-objects_3'), - targetIndex: 'my-saved-objects_7.11.0_001', + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: 'my-saved-objects_3', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - test('INIT -> SET_SOURCE_WRITE_BLOCK when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { + test('INIT -> WAIT_FOR_YELLOW_SOURCE when migrating from a custom kibana.index v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ 'my-saved-objects_7.11.0': { aliases: { @@ -545,35 +442,9 @@ describe('migrations v2 model', () => { ); expect(newState).toMatchObject({ - controlState: 'SET_SOURCE_WRITE_BLOCK', - sourceIndex: Option.some('my-saved-objects_7.11.0'), - targetIndex: 'my-saved-objects_7.12.0_001', + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: 'my-saved-objects_7.11.0', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); @@ -761,6 +632,69 @@ describe('migrations v2 model', () => { expect(newState.retryDelay).toEqual(0); }); }); + + describe('WAIT_FOR_YELLOW_SOURCE', () => { + const mappingsWithUnknownType = { + properties: { + disabled_saved_object_type: { + properties: { + value: { type: 'keyword' }, + }, + }, + }, + _meta: { + migrationMappingPropertyHashes: { + disabled_saved_object_type: '7997cf5a56cc02bdc9c93361bde732b0', + }, + }, + }; + + const waitForYellowSourceState: WaitForYellowSourceState = { + ...baseState, + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: '.kibana_3', + sourceIndexMappings: mappingsWithUnknownType, + }; + + test('WAIT_FOR_YELLOW_SOURCE -> SET_SOURCE_WRITE_BLOCK if action succeeds', () => { + const res: ResponseType<'WAIT_FOR_YELLOW_SOURCE'> = Either.right({}); + const newState = model(waitForYellowSourceState, res); + expect(newState.controlState).toEqual('SET_SOURCE_WRITE_BLOCK'); + + expect(newState).toMatchObject({ + controlState: 'SET_SOURCE_WRITE_BLOCK', + sourceIndex: Option.some('.kibana_3'), + targetIndex: '.kibana_7.11.0_001', + }); + + // This snapshot asserts that we disable the unknown saved object + // type. Because it's mappings are disabled, we also don't copy the + // `_meta.migrationMappingPropertyHashes` for the disabled type. + expect(newState.targetIndexMappings).toMatchInlineSnapshot(` + Object { + "_meta": Object { + "migrationMappingPropertyHashes": Object { + "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", + }, + }, + "properties": Object { + "disabled_saved_object_type": Object { + "dynamic": false, + "properties": Object {}, + }, + "new_saved_object_type": Object { + "properties": Object { + "value": Object { + "type": "text", + }, + }, + }, + }, + } + `); + }); + }); + describe('SET_SOURCE_WRITE_BLOCK', () => { const setWriteBlockState: SetSourceWriteBlockState = { ...baseState, diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index 2353452a6a51b..ee78692a7044f 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -222,22 +222,11 @@ export const model = (currentState: State, resW: ResponseType): ) { // The source index is the index the `.kibana` alias points to const source = aliases[stateP.currentAlias]; - const target = stateP.versionIndex; return { ...stateP, - controlState: 'SET_SOURCE_WRITE_BLOCK', - sourceIndex: Option.some(source) as Option.Some, - targetIndex: target, - targetIndexMappings: disableUnknownTypeMappingFields( - stateP.targetIndexMappings, - indices[source].mappings - ), - versionIndexReadyActions: Option.some([ - { remove: { index: source, alias: stateP.currentAlias, must_exist: true } }, - { add: { index: target, alias: stateP.currentAlias } }, - { add: { index: target, alias: stateP.versionAlias } }, - { remove_index: { index: stateP.tempIndex } }, - ]), + controlState: 'WAIT_FOR_YELLOW_SOURCE', + sourceIndex: source, + sourceIndexMappings: indices[source].mappings, }; } else if (indices[stateP.legacyIndex] != null) { // Migrate from a legacy index @@ -432,6 +421,30 @@ export const model = (currentState: State, resW: ResponseType): } else { throwBadResponse(stateP, res); } + } else if (stateP.controlState === 'WAIT_FOR_YELLOW_SOURCE') { + const res = resW as ExcludeRetryableEsError>; + if (Either.isRight(res)) { + const source = stateP.sourceIndex; + const target = stateP.versionIndex; + return { + ...stateP, + controlState: 'SET_SOURCE_WRITE_BLOCK', + sourceIndex: Option.some(source) as Option.Some, + targetIndex: target, + targetIndexMappings: disableUnknownTypeMappingFields( + stateP.targetIndexMappings, + stateP.sourceIndexMappings + ), + versionIndexReadyActions: Option.some([ + { remove: { index: source, alias: stateP.currentAlias, must_exist: true } }, + { add: { index: target, alias: stateP.currentAlias } }, + { add: { index: target, alias: stateP.versionAlias } }, + { remove_index: { index: stateP.tempIndex } }, + ]), + }; + } else { + return throwBadResponse(stateP, res); + } } else if (stateP.controlState === 'SET_SOURCE_WRITE_BLOCK') { const res = resW as ExcludeRetryableEsError>; if (Either.isRight(res)) { diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 67b2004a4b31a..5cbda741a0ce5 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -10,7 +10,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { UnwrapPromise } from '@kbn/utility-types'; import { pipe } from 'fp-ts/lib/pipeable'; -import { +import type { AllActionStates, ReindexSourceToTempState, MarkVersionIndexReady, @@ -32,6 +32,7 @@ import { CreateNewTargetState, CloneTempToSource, SetTempWriteBlock, + WaitForYellowSourceState, } from './types'; import * as Actions from './actions'; import { ElasticsearchClient } from '../../elasticsearch'; @@ -54,6 +55,8 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra return { INIT: (state: InitState) => Actions.fetchIndices(client, [state.currentAlias, state.versionAlias]), + WAIT_FOR_YELLOW_SOURCE: (state: WaitForYellowSourceState) => + Actions.waitForIndexStatusYellow(client, state.sourceIndex), SET_SOURCE_WRITE_BLOCK: (state: SetSourceWriteBlockState) => Actions.setWriteBlock(client, state.sourceIndex.value), CREATE_NEW_TARGET: (state: CreateNewTargetState) => diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts index cc4aa18171843..e9b351c0152fc 100644 --- a/src/core/server/saved_objects/migrationsv2/types.ts +++ b/src/core/server/saved_objects/migrationsv2/types.ts @@ -128,6 +128,13 @@ export type FatalState = BaseState & { readonly reason: string; }; +export interface WaitForYellowSourceState extends BaseState { + /** Wait for the source index to be yellow before requesting it. */ + readonly controlState: 'WAIT_FOR_YELLOW_SOURCE'; + readonly sourceIndex: string; + readonly sourceIndexMappings: IndexMapping; +} + export type SetSourceWriteBlockState = PostInitState & { /** Set a write block on the source index to prevent any further writes */ readonly controlState: 'SET_SOURCE_WRITE_BLOCK'; @@ -290,6 +297,7 @@ export type State = | FatalState | InitState | DoneState + | WaitForYellowSourceState | SetSourceWriteBlockState | CreateNewTargetState | CreateReindexTempState From 171f39821a063587a2db1f27b84cd4b05b857d26 Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 12 Apr 2021 12:12:33 -0700 Subject: [PATCH 35/79] [App Search] Results follow-up (#96709) * CSS cleanup * Refactor ResultActions component + DRY out link behavior - Create new separate ResultActions component - Pass actions array through to header and have haeder in charge of conditional visibility / FlexItem wrapper (this matches the other header items) - shouldLinkToDetailPage: instead of generating custom JSX, just have it be a standard action and append it to the actions array Link behavior: - ResultHeaderItem - switch to EuiLinkTo, no need for extra wrapper - ResultHeader - DRY out unnecessary extra path generation - instead pass down a conditional documentLink instead of a bool * PR feedback: Fix test name * PR feedback: unshift Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../app_search/components/result/result.scss | 31 +------ .../components/result/result.test.tsx | 83 ++++++++----------- .../app_search/components/result/result.tsx | 62 +++++--------- .../components/result/result_actions.test.tsx | 55 ++++++++++++ .../components/result/result_actions.tsx | 34 ++++++++ .../components/result/result_header.scss | 25 +----- .../components/result/result_header.test.tsx | 68 +++++++-------- .../components/result/result_header.tsx | 35 ++++---- .../components/result/result_header_item.scss | 10 +-- .../result/result_header_item.test.tsx | 4 +- .../components/result/result_header_item.tsx | 10 +-- 11 files changed, 208 insertions(+), 209 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss index 3132894ddc7a1..93bace1d77775 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss @@ -1,10 +1,10 @@ .appSearchResult { display: grid; - grid-template-columns: auto 1fr auto; - grid-template-rows: auto 1fr auto; + grid-template-columns: auto 1fr; + grid-template-rows: auto 1fr; grid-template-areas: - 'drag content actions' - 'drag toggle actions'; + 'drag content' + 'drag toggle'; overflow: hidden; // Prevents child background-colors from clipping outside of panel border-radius border: $euiBorderThin; // TODO: Remove after EUI version is bumped beyond 31.8.0 @@ -35,29 +35,6 @@ } } - &__actionButtons { - grid-area: actions; - display: flex; - flex-wrap: no-wrap; - } - - &__actionButton { - display: flex; - justify-content: center; - align-items: center; - width: $euiSize * 2; - border-left: $euiBorderThin; - - &:first-child { - border-left: none; - } - - &:hover, - &:focus { - background-color: $euiPageBackgroundColor; - } - } - &__dragHandle { grid-area: drag; display: flex; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index 3e83717bf9355..ba9944744e5c7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -5,12 +5,14 @@ * 2.0. */ +import { mockKibanaValues } from '../../../__mocks__'; + import React from 'react'; import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd'; import { shallow, ShallowWrapper } from 'enzyme'; -import { EuiButtonIcon, EuiPanel, EuiButtonIconColor } from '@elastic/eui'; +import { EuiPanel } from '@elastic/eui'; import { SchemaTypes } from '../../../shared/types'; @@ -63,18 +65,28 @@ describe('Result', () => { ]); }); - it('renders a header', () => { - const wrapper = shallow(); - const header = wrapper.find(ResultHeader); - expect(header.exists()).toBe(true); - expect(header.prop('isMetaEngine')).toBe(true); // passed through from props - expect(header.prop('showScore')).toBe(true); // passed through from props - expect(header.prop('shouldLinkToDetailPage')).toBe(false); // passed through from props - expect(header.prop('resultMeta')).toEqual({ - id: '1', - score: 100, - engine: 'my-engine', - }); // passed through from meta in result prop + describe('header', () => { + it('renders a header', () => { + const wrapper = shallow(); + const header = wrapper.find(ResultHeader); + + expect(header.exists()).toBe(true); + expect(header.prop('isMetaEngine')).toBe(true); // passed through from props + expect(header.prop('showScore')).toBe(true); // passed through from props + expect(header.prop('resultMeta')).toEqual({ + id: '1', + score: 100, + engine: 'my-engine', + }); // passed through from meta in result prop + expect(header.prop('documentLink')).toBe(undefined); // based on shouldLinkToDetailPage prop + }); + + it('passes documentLink when shouldLinkToDetailPage is true', () => { + const wrapper = shallow(); + const header = wrapper.find(ResultHeader); + + expect(header.prop('documentLink')).toBe('/engines/my-engine/documents/1'); + }); }); describe('actions', () => { @@ -83,53 +95,30 @@ describe('Result', () => { title: 'Hide', onClick: jest.fn(), iconType: 'eyeClosed', - iconColor: 'danger' as EuiButtonIconColor, }, { title: 'Bookmark', onClick: jest.fn(), iconType: 'starFilled', - iconColor: undefined, }, ]; - it('will render an action button in the header for each action passed', () => { + it('passes actions to the header', () => { const wrapper = shallow(); - const header = wrapper.find(ResultHeader); - const renderedActions = shallow(header.prop('actions') as any); - const buttons = renderedActions.find(EuiButtonIcon); - expect(buttons).toHaveLength(2); - - expect(buttons.first().prop('iconType')).toEqual('eyeClosed'); - expect(buttons.first().prop('color')).toEqual('danger'); - buttons.first().simulate('click'); - expect(actions[0].onClick).toHaveBeenCalled(); - - expect(buttons.last().prop('iconType')).toEqual('starFilled'); - // Note that no iconColor was passed so it was defaulted to primary - expect(buttons.last().prop('color')).toEqual('primary'); - buttons.last().simulate('click'); - expect(actions[1].onClick).toHaveBeenCalled(); + expect(wrapper.find(ResultHeader).prop('actions')).toEqual(actions); }); - it('will render a document detail link as the first action if shouldLinkToDetailPage is passed', () => { + it('adds a link action to the start of the actions array if shouldLinkToDetailPage is passed', () => { const wrapper = shallow(); - const header = wrapper.find(ResultHeader); - const renderedActions = shallow(header.prop('actions') as any); - const buttons = renderedActions.find(EuiButtonIcon); - // In addition to the 2 actions passed, we also have a link action - expect(buttons).toHaveLength(3); + const passedActions = wrapper.find(ResultHeader).prop('actions'); + expect(passedActions.length).toEqual(3); // In addition to the 2 actions passed, we also have a link action - expect(buttons.first().prop('data-test-subj')).toEqual('DocumentDetailLink'); - }); + const linkAction = passedActions[0]; + expect(linkAction.title).toEqual('Visit document details'); - it('will not render anything if no actions are passed and shouldLinkToDetailPage is false', () => { - const wrapper = shallow(); - const header = wrapper.find(ResultHeader); - const renderedActions = shallow(header.prop('actions') as any); - const buttons = renderedActions.find(EuiButtonIcon); - expect(buttons).toHaveLength(0); + linkAction.onClick(); + expect(mockKibanaValues.navigateToUrl).toHaveBeenCalledWith('/engines/my-engine/documents/1'); }); }); @@ -148,9 +137,7 @@ describe('Result', () => { }); it('will render field details with type highlights if schemaForTypeHighlights has been provided', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find(ResultField).map((rf) => rf.prop('type'))).toEqual([ 'text', 'text', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 71d9f39d802d5..d9c16a877dc59 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -10,12 +10,11 @@ import { DraggableProvidedDragHandleProps } from 'react-beautiful-dnd'; import './result.scss'; -import { EuiButtonIcon, EuiPanel, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui'; +import { EuiPanel, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; - +import { KibanaLogic } from '../../../shared/kibana'; import { Schema } from '../../../shared/types'; import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../routes'; @@ -56,48 +55,27 @@ export const Result: React.FC = ({ [result] ); const numResults = resultFields.length; - const documentLink = generateEncodedPath(ENGINE_DOCUMENT_DETAIL_PATH, { - engineName: resultMeta.engine, - documentId: resultMeta.id, - }); const typeForField = (fieldName: string) => { if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; }; - const ResultActions = () => { - if (!shouldLinkToDetailPage && !actions.length) return null; - return ( - - - {shouldLinkToDetailPage && ( - - - - - - )} - {actions.map(({ onClick, title, iconType, iconColor }) => ( - - - - ))} - - - ); - }; + const documentLink = shouldLinkToDetailPage + ? generateEncodedPath(ENGINE_DOCUMENT_DETAIL_PATH, { + engineName: resultMeta.engine, + documentId: resultMeta.id, + }) + : undefined; + if (shouldLinkToDetailPage && documentLink) { + const linkAction = { + onClick: () => KibanaLogic.values.navigateToUrl(documentLink), + title: i18n.translate('xpack.enterpriseSearch.appSearch.result.documentDetailLink', { + defaultMessage: 'Visit document details', + }), + iconType: 'eye', + }; + actions = [linkAction, ...actions]; + } return ( = ({ resultMeta={resultMeta} showScore={!!showScore} isMetaEngine={isMetaEngine} - shouldLinkToDetailPage={shouldLinkToDetailPage} - actions={} + documentLink={documentLink} + actions={actions} /> {resultFields .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx new file mode 100644 index 0000000000000..4aae1e07f0f8c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { shallow } from 'enzyme'; + +import { EuiButtonIcon, EuiButtonIconColor } from '@elastic/eui'; + +import { ResultActions } from './result_actions'; + +describe('ResultActions', () => { + const actions = [ + { + title: 'Hide', + onClick: jest.fn(), + iconType: 'eyeClosed', + iconColor: 'danger' as EuiButtonIconColor, + }, + { + title: 'Bookmark', + onClick: jest.fn(), + iconType: 'starFilled', + iconColor: undefined, + }, + ]; + + const wrapper = shallow(); + const buttons = wrapper.find(EuiButtonIcon); + + it('renders an action button for each action passed', () => { + expect(buttons).toHaveLength(2); + }); + + it('passes icon props correctly', () => { + expect(buttons.first().prop('iconType')).toEqual('eyeClosed'); + expect(buttons.first().prop('color')).toEqual('danger'); + + expect(buttons.last().prop('iconType')).toEqual('starFilled'); + // Note that no iconColor was passed so it was defaulted to primary + expect(buttons.last().prop('color')).toEqual('primary'); + }); + + it('passes click events', () => { + buttons.first().simulate('click'); + expect(actions[0].onClick).toHaveBeenCalled(); + + buttons.last().simulate('click'); + expect(actions[1].onClick).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx new file mode 100644 index 0000000000000..52fbee90fe31a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_actions.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 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 { EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { ResultAction } from './types'; + +interface Props { + actions: ResultAction[]; +} + +export const ResultActions: React.FC = ({ actions }) => { + return ( + + {actions.map(({ onClick, title, iconType, iconColor }) => ( + + + + ))} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss index cd1042998dd34..ebae11ee8ad33 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.scss @@ -1,26 +1,3 @@ .appSearchResultHeader { - display: flex; - margin-bottom: $euiSizeS; - - @include euiBreakpoint('xs') { - flex-direction: column; - } - - &__column { - display: flex; - flex-wrap: wrap; - - @include euiBreakpoint('xs') { - flex-direction: column; - } - - & + &, - .appSearchResultHeaderItem + .appSearchResultHeaderItem { - margin-left: $euiSizeL; - - @include euiBreakpoint('xs') { - margin-left: 0; - } - } - } + margin-bottom: $euiSizeM; } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx index 80cff9b96a3ca..cdd43c3efd97a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; +import { ResultActions } from './result_actions'; import { ResultHeader } from './result_header'; describe('ResultHeader', () => { @@ -17,30 +18,27 @@ describe('ResultHeader', () => { score: 100, engine: 'my-engine', }; + const props = { + showScore: false, + isMetaEngine: false, + resultMeta, + actions: [], + }; it('renders', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.isEmptyRender()).toBe(false); }); it('always renders an id', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find('[data-test-subj="ResultId"]').prop('value')).toEqual('1'); expect(wrapper.find('[data-test-subj="ResultId"]').prop('href')).toBeUndefined(); }); - it('renders id as a link if shouldLinkToDetailPage is true', () => { + it('renders id as a link if a documentLink has been passed', () => { const wrapper = shallow( - + ); expect(wrapper.find('[data-test-subj="ResultId"]').prop('value')).toEqual('1'); expect(wrapper.find('[data-test-subj="ResultId"]').prop('href')).toEqual( @@ -50,47 +48,39 @@ describe('ResultHeader', () => { describe('score', () => { it('renders score if showScore is true ', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find('[data-test-subj="ResultScore"]').prop('value')).toEqual(100); }); it('does not render score if showScore is false', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find('[data-test-subj="ResultScore"]').exists()).toBe(false); }); }); describe('engine', () => { it('renders engine name if this is a meta engine', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find('[data-test-subj="ResultEngine"]').prop('value')).toBe('my-engine'); }); it('does not render an engine if this is not a meta engine', () => { - const wrapper = shallow( - - ); + const wrapper = shallow(); expect(wrapper.find('[data-test-subj="ResultEngine"]').exists()).toBe(false); }); }); + + describe('actions', () => { + const actions = [{ title: 'View document', onClick: () => {}, iconType: 'eye' }]; + + it('renders ResultActions if actions have been passed', () => { + const wrapper = shallow(); + expect(wrapper.find(ResultActions).exists()).toBe(true); + }); + + it('does not render ResultActions if no actions are passed', () => { + const wrapper = shallow(); + expect(wrapper.find(ResultActions).exists()).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx index 93a684b1968a2..f577b481b39cf 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header.tsx @@ -9,11 +9,9 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../routes'; -import { generateEncodedPath } from '../../utils/encode_path_params'; - +import { ResultActions } from './result_actions'; import { ResultHeaderItem } from './result_header_item'; -import { ResultMeta } from './types'; +import { ResultMeta, ResultAction } from './types'; import './result_header.scss'; @@ -21,8 +19,8 @@ interface Props { showScore: boolean; isMetaEngine: boolean; resultMeta: ResultMeta; - actions?: React.ReactNode; - shouldLinkToDetailPage?: boolean; + actions: ResultAction[]; + documentLink?: string; } export const ResultHeader: React.FC = ({ @@ -30,19 +28,20 @@ export const ResultHeader: React.FC = ({ resultMeta, isMetaEngine, actions, - shouldLinkToDetailPage = false, + documentLink, }) => { - const documentLink = generateEncodedPath(ENGINE_DOCUMENT_DETAIL_PATH, { - engineName: resultMeta.engine, - documentId: resultMeta.id, - }); - return ( -
- +
+ = ({ /> )} - {actions} + {actions.length > 0 && ( + + + + )}
); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss index df3e2ec241106..94367ae634b7c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.scss @@ -1,12 +1,12 @@ -.euiFlexItem:not(:first-child):not(:last-child) .appSearchResultHeaderItem { - padding-right: .75rem; - box-shadow: inset -1px 0 0 0 $euiBorderColor; -} - .appSearchResultHeaderItem { @include euiCodeFont; &__score { color: $euiColorSuccessText; } + + .euiFlexItem:not(:first-child):not(:last-child) & { + padding-right: $euiSizeS; + box-shadow: inset (-$euiBorderWidthThin) 0 0 0 $euiBorderColor; + } } diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx index e0407b4db7f25..d45eb8856d118 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.test.tsx @@ -69,7 +69,7 @@ describe('ResultHeaderItem', () => { const wrapper = shallow( ); - expect(wrapper.find('ReactRouterHelper').exists()).toBe(true); - expect(wrapper.find('ReactRouterHelper').prop('to')).toBe('http://www.example.com'); + expect(wrapper.find('EuiLinkTo').exists()).toBe(true); + expect(wrapper.find('EuiLinkTo').prop('to')).toBe('http://www.example.com'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx index 545b85c17a529..cf3b385fd9257 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result_header_item.tsx @@ -9,7 +9,7 @@ import React from 'react'; import './result_header_item.scss'; -import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { EuiLinkTo } from '../../../shared/react_router_helpers/eui_components'; import { TruncatedContent } from '../../../shared/truncate'; @@ -48,11 +48,9 @@ export const ResultHeaderItem: React.FC = ({ field, type, value, href })   {href ? ( - -
- - - + + + ) : ( )} From c4b3dfddcdd9b280434b8c135e0ccad806753fbe Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 12 Apr 2021 21:23:22 +0200 Subject: [PATCH 36/79] [Search Sessions] Implement cancel on search session monitoring task, fetch and process sessions page by page (#96321) --- x-pack/plugins/data_enhanced/config.ts | 7 + .../session/check_running_sessions.test.ts | 137 +++++++++++- .../search/session/check_running_sessions.ts | 205 +++++++++--------- .../server/search/session/monitoring_task.ts | 16 +- .../search/session/session_service.test.ts | 2 + 5 files changed, 262 insertions(+), 105 deletions(-) diff --git a/x-pack/plugins/data_enhanced/config.ts b/x-pack/plugins/data_enhanced/config.ts index 8cbf930fe87bd..c895e586a6931 100644 --- a/x-pack/plugins/data_enhanced/config.ts +++ b/x-pack/plugins/data_enhanced/config.ts @@ -23,6 +23,13 @@ export const configSchema = schema.object({ * trackingInterval controls how often we track search session objects progress */ trackingInterval: schema.duration({ defaultValue: '10s' }), + + /** + * monitoringTaskTimeout controls for how long task manager waits for search session monitoring task to complete before considering it timed out, + * If tasks timeouts it receives cancel signal and next task starts in "trackingInterval" time + */ + monitoringTaskTimeout: schema.duration({ defaultValue: '5m' }), + /** * notTouchedTimeout controls how long do we store unpersisted search session results, * after the last search in the session has completed diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index 2611f6c9da19f..eba463662e26d 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -5,7 +5,10 @@ * 2.0. */ -import { checkRunningSessions } from './check_running_sessions'; +import { + checkRunningSessions as checkRunningSessions$, + CheckRunningSessionsDeps, +} from './check_running_sessions'; import { SearchSessionStatus, SearchSessionSavedObjectAttributes, @@ -20,6 +23,13 @@ import { SavedObjectsDeleteOptions, SavedObjectsClientContract, } from '../../../../../../src/core/server'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +jest.useFakeTimers(); + +const checkRunningSessions = (deps: CheckRunningSessionsDeps, config: SearchSessionsConfig) => + checkRunningSessions$(deps, config).toPromise(); describe('getSearchStatus', () => { let mockClient: any; @@ -32,6 +42,7 @@ describe('getSearchStatus', () => { maxUpdateRetries: 3, defaultExpiration: moment.duration(7, 'd'), trackingInterval: moment.duration(10, 's'), + monitoringTaskTimeout: moment.duration(5, 'm'), management: {} as any, }; const mockLogger: any = { @@ -41,11 +52,13 @@ describe('getSearchStatus', () => { }; const emptySO = { - persisted: false, - status: SearchSessionStatus.IN_PROGRESS, - created: moment().subtract(moment.duration(3, 'm')), - touched: moment().subtract(moment.duration(10, 's')), - idMapping: {}, + attributes: { + persisted: false, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(10, 's')), + idMapping: {}, + }, }; beforeEach(() => { @@ -171,6 +184,118 @@ describe('getSearchStatus', () => { expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); }); + + test('fetching is abortable', async () => { + let i = 0; + const abort$ = new Subject(); + savedObjectsClient.find.mockImplementation(() => { + return new Promise((resolve) => { + if (++i === 2) { + abort$.next(); + } + resolve({ + saved_objects: i <= 5 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [], + total: 25, + page: i, + } as any); + }); + }); + + await checkRunningSessions$( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ) + .pipe(takeUntil(abort$)) + .toPromise(); + + jest.runAllTimers(); + + // if not for `abort$` then this would be called 6 times! + expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); + }); + + test('sorting is by "touched"', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + saved_objects: [], + total: 0, + } as any); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.find).toHaveBeenCalledWith( + expect.objectContaining({ sortField: 'touched', sortOrder: 'asc' }) + ); + }); + + test('sessions fetched in the beginning are processed even if sessions in the end fail', async () => { + let i = 0; + savedObjectsClient.find.mockImplementation(() => { + return new Promise((resolve, reject) => { + if (++i === 2) { + reject(new Error('Fake find error...')); + } + resolve({ + saved_objects: + i <= 5 + ? [ + i === 1 + ? { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(2, 'm')), + idMapping: { + 'map-key': { + strategy: ENHANCED_ES_SEARCH_STRATEGY, + id: 'async-id', + }, + }, + }, + } + : emptySO, + emptySO, + emptySO, + emptySO, + emptySO, + ] + : [], + total: 25, + page: i, + } as any); + }); + }); + + await checkRunningSessions$( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ).toPromise(); + + jest.runAllTimers(); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); + + // by checking that delete was called we validate that sessions from session that were successfully fetched were processed + expect(mockClient.asyncSearch.delete).toBeCalled(); + const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; + expect(id).toBe('async-id'); + }); }); describe('delete', () => { diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 60c7283320d0c..bb1e9643cd0d5 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -13,8 +13,8 @@ import { SavedObjectsUpdateResponse, } from 'kibana/server'; import moment from 'moment'; -import { EMPTY, from } from 'rxjs'; -import { expand, concatMap } from 'rxjs/operators'; +import { EMPTY, from, Observable } from 'rxjs'; +import { catchError, concatMap } from 'rxjs/operators'; import { nodeBuilder } from '../../../../../../src/plugins/data/common'; import { ENHANCED_ES_SEARCH_STRATEGY, @@ -120,6 +120,9 @@ function getSavedSearchSessionsPage$( perPage: config.pageSize, type: SEARCH_SESSION_TYPE, namespaces: ['*'], + // process older sessions first + sortField: 'touched', + sortOrder: 'asc', filter: nodeBuilder.or([ nodeBuilder.and([ nodeBuilder.is( @@ -134,113 +137,121 @@ function getSavedSearchSessionsPage$( ); } -function getAllSavedSearchSessions$(deps: CheckRunningSessionsDeps, config: SearchSessionsConfig) { - return getSavedSearchSessionsPage$(deps, config, 1).pipe( - expand((result) => { - if (!result || !result.saved_objects || result.saved_objects.length < config.pageSize) - return EMPTY; - else { - return getSavedSearchSessionsPage$(deps, config, result.page + 1); - } - }) - ); -} - -export async function checkRunningSessions( +function checkRunningSessionsPage( deps: CheckRunningSessionsDeps, - config: SearchSessionsConfig -): Promise { + config: SearchSessionsConfig, + page: number +) { const { logger, client, savedObjectsClient } = deps; - try { - await getAllSavedSearchSessions$(deps, config) - .pipe( - concatMap(async (runningSearchSessionsResponse) => { - if (!runningSearchSessionsResponse.total) return; - - logger.debug(`Found ${runningSearchSessionsResponse.total} running sessions`); - - const updatedSessions = new Array< - SavedObjectsFindResult - >(); - - await Promise.all( - runningSearchSessionsResponse.saved_objects.map(async (session) => { - const updated = await updateSessionStatus(session, client, logger); - let deleted = false; - - if (!session.attributes.persisted) { - if (isSessionStale(session, config, logger)) { - // delete saved object to free up memory - // TODO: there's a potential rare edge case of deleting an object and then receiving a new trackId for that same session! - // Maybe we want to change state to deleted and cleanup later? - logger.debug(`Deleting stale session | ${session.id}`); + return getSavedSearchSessionsPage$(deps, config, page).pipe( + concatMap(async (runningSearchSessionsResponse) => { + if (!runningSearchSessionsResponse.total) return; + + logger.debug( + `Found ${runningSearchSessionsResponse.total} running sessions, processing ${runningSearchSessionsResponse.saved_objects.length} sessions from page ${page}` + ); + + const updatedSessions = new Array< + SavedObjectsFindResult + >(); + + await Promise.all( + runningSearchSessionsResponse.saved_objects.map(async (session) => { + const updated = await updateSessionStatus(session, client, logger); + let deleted = false; + + if (!session.attributes.persisted) { + if (isSessionStale(session, config, logger)) { + // delete saved object to free up memory + // TODO: there's a potential rare edge case of deleting an object and then receiving a new trackId for that same session! + // Maybe we want to change state to deleted and cleanup later? + logger.debug(`Deleting stale session | ${session.id}`); + try { + await savedObjectsClient.delete(SEARCH_SESSION_TYPE, session.id, { + namespace: session.namespaces?.[0], + }); + deleted = true; + } catch (e) { + logger.error( + `Error while deleting stale search session ${session.id}: ${e.message}` + ); + } + + // Send a delete request for each async search to ES + Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { + const searchInfo = session.attributes.idMapping[searchKey]; + if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) { try { - await savedObjectsClient.delete(SEARCH_SESSION_TYPE, session.id, { - namespace: session.namespaces?.[0], - }); - deleted = true; + await client.asyncSearch.delete({ id: searchInfo.id }); } catch (e) { logger.error( - `Error while deleting stale search session ${session.id}: ${e.message}` + `Error while deleting async_search ${searchInfo.id}: ${e.message}` ); } - - // Send a delete request for each async search to ES - Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { - const searchInfo = session.attributes.idMapping[searchKey]; - if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) { - try { - await client.asyncSearch.delete({ id: searchInfo.id }); - } catch (e) { - logger.error( - `Error while deleting async_search ${searchInfo.id}: ${e.message}` - ); - } - } - }); } - } + }); + } + } - if (updated && !deleted) { - updatedSessions.push(session); - } - }) - ); - - // Do a bulk update - if (updatedSessions.length) { - // If there's an error, we'll try again in the next iteration, so there's no need to check the output. - const updatedResponse = await savedObjectsClient.bulkUpdate( - updatedSessions.map((session) => ({ - ...session, - namespace: session.namespaces?.[0], - })) - ); + if (updated && !deleted) { + updatedSessions.push(session); + } + }) + ); - const success: Array< - SavedObjectsUpdateResponse - > = []; - const fail: Array> = []; + // Do a bulk update + if (updatedSessions.length) { + // If there's an error, we'll try again in the next iteration, so there's no need to check the output. + const updatedResponse = await savedObjectsClient.bulkUpdate( + updatedSessions.map((session) => ({ + ...session, + namespace: session.namespaces?.[0], + })) + ); - updatedResponse.saved_objects.forEach((savedObjectResponse) => { - if ('error' in savedObjectResponse) { - fail.push(savedObjectResponse); - logger.error( - `Error while updating search session ${savedObjectResponse?.id}: ${savedObjectResponse.error?.message}` - ); - } else { - success.push(savedObjectResponse); - } - }); + const success: Array> = []; + const fail: Array> = []; - logger.debug( - `Updating search sessions: success: ${success.length}, fail: ${fail.length}` + updatedResponse.saved_objects.forEach((savedObjectResponse) => { + if ('error' in savedObjectResponse) { + fail.push(savedObjectResponse); + logger.error( + `Error while updating search session ${savedObjectResponse?.id}: ${savedObjectResponse.error?.message}` ); + } else { + success.push(savedObjectResponse); } - }) - ) - .toPromise(); - } catch (err) { - logger.error(err); - } + }); + + logger.debug(`Updating search sessions: success: ${success.length}, fail: ${fail.length}`); + } + + return runningSearchSessionsResponse; + }) + ); +} + +export function checkRunningSessions(deps: CheckRunningSessionsDeps, config: SearchSessionsConfig) { + const { logger } = deps; + + const checkRunningSessionsByPage = (nextPage = 1): Observable => + checkRunningSessionsPage(deps, config, nextPage).pipe( + concatMap((result) => { + if (!result || !result.saved_objects || result.saved_objects.length < config.pageSize) { + return EMPTY; + } else { + // TODO: while processing previous page session list might have been changed and we might skip a session, + // because it would appear now on a different "page". + // This isn't critical, as we would pick it up on a next task iteration, but maybe we could improve this somehow + return checkRunningSessionsByPage(result.page + 1); + } + }) + ); + + return checkRunningSessionsByPage().pipe( + catchError((e) => { + logger.error(`Error while processing search sessions: ${e?.message}`); + return EMPTY; + }) + ); } diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index 101ccb14edf67..c0dc69dfc307b 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -6,10 +6,13 @@ */ import { Duration } from 'moment'; +import { filter, takeUntil } from 'rxjs/operators'; +import { BehaviorSubject } from 'rxjs'; import { TaskManagerSetupContract, TaskManagerStartContract, RunContext, + TaskRunCreatorFunction, } from '../../../../task_manager/server'; import { checkRunningSessions } from './check_running_sessions'; import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; @@ -29,8 +32,9 @@ interface SearchSessionTaskDeps { function searchSessionRunner( core: CoreSetup, { logger, config }: SearchSessionTaskDeps -) { +): TaskRunCreatorFunction { return ({ taskInstance }: RunContext) => { + const aborted$ = new BehaviorSubject(false); return { async run() { const sessionConfig = config.search.sessions; @@ -39,6 +43,8 @@ function searchSessionRunner( logger.debug('Search sessions are disabled. Skipping task.'); return; } + if (aborted$.getValue()) return; + const internalRepo = coreStart.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]); const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); await checkRunningSessions( @@ -48,12 +54,17 @@ function searchSessionRunner( logger, }, sessionConfig - ); + ) + .pipe(takeUntil(aborted$.pipe(filter((aborted) => aborted)))) + .toPromise(); return { state: {}, }; }, + cancel: async () => { + aborted$.next(true); + }, }; }; } @@ -66,6 +77,7 @@ export function registerSearchSessionsTask( [SEARCH_SESSIONS_TASK_TYPE]: { title: 'Search Sessions Monitor', createTaskRunner: searchSessionRunner(core, deps), + timeout: `${deps.config.search.sessions.monitoringTaskTimeout.asSeconds()}s`, }, }); } diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 9344ab973c636..f1f8805a28884 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -75,6 +75,7 @@ describe('SearchSessionService', () => { notTouchedTimeout: moment.duration(2, 'm'), maxUpdateRetries: MAX_UPDATE_RETRIES, defaultExpiration: moment.duration(7, 'd'), + monitoringTaskTimeout: moment.duration(5, 'm'), trackingInterval: moment.duration(10, 's'), management: {} as any, }, @@ -153,6 +154,7 @@ describe('SearchSessionService', () => { maxUpdateRetries: MAX_UPDATE_RETRIES, defaultExpiration: moment.duration(7, 'd'), trackingInterval: moment.duration(10, 's'), + monitoringTaskTimeout: moment.duration(5, 'm'), management: {} as any, }, }, From cf2c62edf885165721228a6b6c417a5ddd60a330 Mon Sep 17 00:00:00 2001 From: Rashmi Kulkarni Date: Mon, 12 Apr 2021 12:23:46 -0700 Subject: [PATCH 37/79] ccs_discover additional tests (#96669) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../apps/ccs/{ccs.js => ccs_discover.js} | 35 +++++++++++++++++-- .../apps/ccs/index.js | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) rename x-pack/test/stack_functional_integration/apps/ccs/{ccs.js => ccs_discover.js} (77%) diff --git a/x-pack/test/stack_functional_integration/apps/ccs/ccs.js b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js similarity index 77% rename from x-pack/test/stack_functional_integration/apps/ccs/ccs.js rename to x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js index c335680fbc6f9..588ff9a6e9f92 100644 --- a/x-pack/test/stack_functional_integration/apps/ccs/ccs.js +++ b/x-pack/test/stack_functional_integration/apps/ccs/ccs_discover.js @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; export default ({ getService, getPageObjects }) => { - describe('Cross cluster search test', async () => { + describe('Cross cluster search test in discover', async () => { const PageObjects = getPageObjects([ 'common', 'settings', @@ -22,10 +22,12 @@ export default ({ getService, getPageObjects }) => { const browser = getService('browser'); const appsMenu = getService('appsMenu'); const kibanaServer = getService('kibanaServer'); + const queryBar = getService('queryBar'); + const filterBar = getService('filterBar'); before(async () => { await browser.setWindowSize(1200, 800); - // pincking relative time in timepicker isn't working. This is also faster. + // picking relative time in timepicker isn't working. This is also faster. // It's the default set, plus new "makelogs" +/- 3 days from now await kibanaServer.uiSettings.replace({ 'timepicker:quickRanges': `[ @@ -172,5 +174,34 @@ export default ({ getService, getPageObjects }) => { expect(hitCount).to.be('28,010'); }); }); + + it('should reload the saved search with persisted query to show the initial hit count', async function () { + await PageObjects.discover.selectIndexPattern('data:makelogs工程-*,local:makelogs工程-*'); + // apply query some changes + await queryBar.setQuery('success'); + await queryBar.submitQuery(); + await retry.try(async () => { + const hitCountNumber = await PageObjects.discover.getHitCount(); + const hitCount = parseInt(hitCountNumber.replace(/\,/g, '')); + log.debug('### hit count = ' + hitCount); + expect(hitCount).to.be.greaterThan(25000); + expect(hitCount).to.be.lessThan(28000); + }); + }); + + it('should add a phrases filter', async function () { + await PageObjects.discover.selectIndexPattern('data:makelogs工程-*,local:makelogs工程-*'); + const hitCountNumber = await PageObjects.discover.getHitCount(); + const originalHitCount = parseInt(hitCountNumber.replace(/\,/g, '')); + await filterBar.addFilter('extension.keyword', 'is', 'jpg'); + expect(await filterBar.hasFilter('extension.keyword', 'jpg')).to.be(true); + await retry.try(async () => { + const hitCountNumber = await PageObjects.discover.getHitCount(); + const hitCount = parseInt(hitCountNumber.replace(/\,/g, '')); + log.debug('### hit count = ' + hitCount); + expect(hitCount).to.be.greaterThan(15000); + expect(hitCount).to.be.lessThan(originalHitCount); + }); + }); }); }; diff --git a/x-pack/test/stack_functional_integration/apps/ccs/index.js b/x-pack/test/stack_functional_integration/apps/ccs/index.js index dd87414c2b9f0..ac82ca0dfda65 100644 --- a/x-pack/test/stack_functional_integration/apps/ccs/index.js +++ b/x-pack/test/stack_functional_integration/apps/ccs/index.js @@ -7,6 +7,6 @@ export default function ({ loadTestFile }) { describe('ccs test', function () { - loadTestFile(require.resolve('./ccs')); + loadTestFile(require.resolve('./ccs_discover')); }); } From 31c1a0838481fcadeffdce5bbe50c4affa1cab28 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 12 Apr 2021 14:28:12 -0500 Subject: [PATCH 38/79] [Workplace Search] Design polish: Configure and connect source (#96851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update ‘How to add’ view * Update config completed view * Update add source connect page * Remove padding on how to add card Original had no padding. --- .../components/add_source/add_source.scss | 10 - .../add_source/config_completed.tsx | 199 +++++++++-------- .../add_source/configuration_intro.tsx | 205 +++++++++--------- .../add_source/connect_instance.tsx | 8 +- .../components/add_source/source_features.tsx | 2 +- 5 files changed, 216 insertions(+), 208 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss index fbc10b5e8ed0f..fe772000f78f7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.scss @@ -43,16 +43,6 @@ } } - &__outer-box { - border: 1px solid #DBE2EB; - padding-right: 16px; - border-radius: 6px; - overflow: hidden; - background-color: #FFFFFF; - box-shadow: 0 2px 2px -1px rgba(152, 162, 179, .3), - 0 1px 5px -2px rgba(152, 162, 179, .3); - } - &__intro-image { background-color: #22272E; display: flex; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx index 8edef425f414c..965d71abd5101 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/config_completed.tsx @@ -13,6 +13,7 @@ import { EuiFlexItem, EuiIcon, EuiLink, + EuiPanel, EuiSpacer, EuiText, EuiTextAlign, @@ -51,116 +52,122 @@ export const ConfigCompleted: React.FC = ({ <> {header} - - - - - - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.heading', - { - defaultMessage: '{name} Configured', - values: { name }, - } - )} -

-
-
- - - {!accountContextOnly ? ( -

+ + + + + + + + + + +

{i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.orgCanConnect.message', + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.heading', { - defaultMessage: '{name} can now be connected to Workplace Search', + defaultMessage: '{name} Configured', values: { name }, } )} -

- ) : ( - -

+

+
+
+ + + {!accountContextOnly ? ( +

{i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.personalConnectLink.message', + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.orgCanConnect.message', { - defaultMessage: - 'Users can now link their {name} accounts from their personal dashboards.', + defaultMessage: '{name} can now be connected to Workplace Search', values: { name }, } )}

- {!privateSourcesEnabled && ( -

- - enable private source connection - - ), - }} - /> + ) : ( + +

+ {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.personalConnectLink.message', + { + defaultMessage: + 'Users can now link their {name} accounts from their personal dashboards.', + values: { name }, + } + )}

- )} -

- - {CONFIG_COMPLETED_PRIVATE_SOURCES_DOCS_LINK} - -

-
- )} - - -
-
-
-
- - - - - {CONFIG_COMPLETED_CONFIGURE_NEW_BUTTON} - - - {!accountContextOnly && ( + {!privateSourcesEnabled && ( +

+ + enable private source connection + + ), + }} + /> +

+ )} +

+ + {CONFIG_COMPLETED_PRIVATE_SOURCES_DOCS_LINK} + +

+ + )} + + + +
+ + + + - - {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.connect.button', - { - defaultMessage: 'Connect {name}', - values: { name }, - } - )} - + {CONFIG_COMPLETED_CONFIGURE_NEW_BUTTON} + - )} - + {!accountContextOnly && ( + + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configCompleted.connect.button', + { + defaultMessage: 'Connect {name}', + values: { name }, + } + )} + + + )} + + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx index 8a1cdf0b84274..23bd34cfeb944 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configuration_intro.tsx @@ -13,6 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiPanel, EuiSpacer, EuiText, EuiTitle, @@ -52,105 +53,115 @@ export const ConfigurationIntro: React.FC = ({ direction="row" responsive={false} > - - - -
- {CONFIG_INTRO_ALT_TEXT} -
-
- - - - - -

- {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.title', - { - defaultMessage: 'How to add {name}', - values: { name }, - } - )} -

-
- - -

{CONFIG_INTRO_STEPS_TEXT}

-
- -
- - - -
- -

{CONFIG_INTRO_STEP1_HEADING}

-
-
-
- - -

- One-Time Action, - }} - /> -

-

{CONFIG_INTRO_STEP1_TEXT}

-
-
-
-
- - - -
- -

{CONFIG_INTRO_STEP2_HEADING}

+ + + + +
+ {CONFIG_INTRO_ALT_TEXT} +
+
+ + + + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.steps.title', + { + defaultMessage: 'How to add {name}', + values: { name }, + } + )} +

+
+ + +

{CONFIG_INTRO_STEPS_TEXT}

+
+ +
+ + + +
+ +

{CONFIG_INTRO_STEP1_HEADING}

+
+
+
+ + +

+ One-Time Action, + }} + /> +

+

{CONFIG_INTRO_STEP1_TEXT}

-
-
- - -

{CONFIG_INTRO_STEP2_TITLE}

-

{CONFIG_INTRO_STEP2_TEXT}

-
-
-
-
- - - - +
+
+ + - {i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button', - { - defaultMessage: 'Configure {name}', - values: { name }, - } - )} - - - - -
-
- + +
+ +

{CONFIG_INTRO_STEP2_HEADING}

+
+
+
+ + +

{CONFIG_INTRO_STEP2_TITLE}

+

{CONFIG_INTRO_STEP2_TEXT}

+
+
+ + + + + + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSource.configIntro.configure.button', + { + defaultMessage: 'Configure {name}', + values: { name }, + } + )} + + + + + + + + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index a34641784b162..fd45d779e6f2a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -160,7 +160,7 @@ export const ConnectInstance: React.FC = ({ const permissionField = ( <> - +

{CONNECT_DOC_PERMISSIONS_TITLE} @@ -272,12 +272,12 @@ export const ConnectInstance: React.FC = ({ responsive={false} > - - + + {header} - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx index ad16260b1de7c..7a66efe4ba5f4 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/source_features.tsx @@ -187,7 +187,7 @@ export const SourceFeatures: React.FC = ({ features, objTy {includedFeatures.map((featureId, i) => ( - + From 8bf9e8694248e4c1295c1f39fd79eac3b085ff69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Mon, 12 Apr 2021 15:31:39 -0400 Subject: [PATCH 39/79] [Security-Solution] Adds Threat Summary and Threat Details tabs to Alert Side Panel (#909) (#95604) [Security Solution] Adds Threat Summary and Threat Info views to Alert Side Panel (elastic/security-team/909) --- .../utils/field_formatters.test.ts} | 6 +- .../utils/field_formatters.ts} | 8 +- .../utils/mock_event_details.ts} | 0 .../helpers => common/utils}/to_array.ts | 0 .../event_details/__mocks__/index.ts | 12 + ...w.test.tsx => alert_summary_view.test.tsx} | 10 +- .../event_details/alert_summary_view.tsx | 200 +++++++++++++++ .../event_details/event_details.test.tsx | 31 ++- .../event_details/event_details.tsx | 116 +++++++-- .../components/event_details/helpers.tsx | 64 +++++ .../components/event_details/summary_view.tsx | 241 ++---------------- .../threat_details_view.test.tsx | 44 ++++ .../event_details/threat_details_view.tsx | 89 +++++++ .../threat_summary_view.test.tsx | 44 ++++ .../event_details/threat_summary_view.tsx | 89 +++++++ .../components/event_details/translations.ts | 8 + .../__snapshots__/index.test.tsx.snap | 6 +- .../event_details/expandable_event.tsx | 14 +- .../side_panel/event_details/index.tsx | 2 +- .../body/renderers/formatted_field.tsx | 2 +- .../helpers/format_response_object_values.ts | 2 +- .../factory/hosts/all/helpers.ts | 3 +- .../factory/hosts/authentications/helpers.ts | 2 +- .../factory/hosts/details/helpers.ts | 2 +- .../hosts/uncommon_processes/helpers.ts | 2 +- .../factory/network/details/helpers.ts | 2 +- .../factory/events/all/helpers.test.ts | 2 +- .../timeline/factory/events/all/helpers.ts | 7 +- .../timeline/factory/events/details/index.ts | 6 +- 29 files changed, 731 insertions(+), 283 deletions(-) rename x-pack/plugins/security_solution/{server/search_strategy/timeline/factory/events/details/helpers.test.ts => common/utils/field_formatters.test.ts} (97%) rename x-pack/plugins/security_solution/{server/search_strategy/timeline/factory/events/details/helpers.ts => common/utils/field_formatters.ts} (96%) rename x-pack/plugins/security_solution/{server/search_strategy/timeline/factory/events/mocks.ts => common/utils/mock_event_details.ts} (100%) rename x-pack/plugins/security_solution/{server/search_strategy/helpers => common/utils}/to_array.ts (100%) rename x-pack/plugins/security_solution/public/common/components/event_details/{summary_view.test.tsx => alert_summary_view.test.tsx} (90%) create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.test.ts b/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts similarity index 97% rename from x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.test.ts rename to x-pack/plugins/security_solution/common/utils/field_formatters.test.ts index dc3efc6909c63..b724c0f672b50 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.test.ts +++ b/x-pack/plugins/security_solution/common/utils/field_formatters.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { EventHit, EventSource } from '../../../../../../common/search_strategy'; -import { getDataFromFieldsHits, getDataFromSourceHits, getDataSafety } from './helpers'; -import { eventDetailsFormattedFields, eventHit } from '../mocks'; +import { EventHit, EventSource } from '../search_strategy'; +import { getDataFromFieldsHits, getDataFromSourceHits, getDataSafety } from './field_formatters'; +import { eventDetailsFormattedFields, eventHit } from './mock_event_details'; describe('Events Details Helpers', () => { const fields: EventHit['fields'] = eventHit.fields; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts b/x-pack/plugins/security_solution/common/utils/field_formatters.ts similarity index 96% rename from x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts rename to x-pack/plugins/security_solution/common/utils/field_formatters.ts index 2fc729729e435..b436f8e616122 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/helpers.ts +++ b/x-pack/plugins/security_solution/common/utils/field_formatters.ts @@ -7,12 +7,8 @@ import { get, isEmpty, isNumber, isObject, isString } from 'lodash/fp'; -import { - EventHit, - EventSource, - TimelineEventsDetailsItem, -} from '../../../../../../common/search_strategy'; -import { toObjectArrayOfStrings, toStringArray } from '../../../../helpers/to_array'; +import { EventHit, EventSource, TimelineEventsDetailsItem } from '../search_strategy'; +import { toObjectArrayOfStrings, toStringArray } from './to_array'; export const baseCategoryFields = ['@timestamp', 'labels', 'message', 'tags']; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/mocks.ts b/x-pack/plugins/security_solution/common/utils/mock_event_details.ts similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/mocks.ts rename to x-pack/plugins/security_solution/common/utils/mock_event_details.ts diff --git a/x-pack/plugins/security_solution/server/search_strategy/helpers/to_array.ts b/x-pack/plugins/security_solution/common/utils/to_array.ts similarity index 100% rename from x-pack/plugins/security_solution/server/search_strategy/helpers/to_array.ts rename to x-pack/plugins/security_solution/common/utils/to_array.ts diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts index ba0567c40eb92..3edd6e6fda14b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/__mocks__/index.ts @@ -655,4 +655,16 @@ export const mockAlertDetailsData = [ values: ['7.10.0'], originalValue: ['7.10.0'], }, + { + category: 'threat', + field: 'threat.indicator', + values: [`{"first_seen":"2021-03-25T18:17:00.000Z"}`], + originalValue: [`{"first_seen":"2021-03-25T18:17:00.000Z"}`], + }, + { + category: 'threat', + field: 'threat.indicator.matched', + values: `["file", "url"]`, + originalValue: ['file', 'url'], + }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx similarity index 90% rename from x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx rename to x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx index c19a3952220cf..b8f29996d603b 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { waitFor } from '@testing-library/react'; -import { SummaryViewComponent } from './summary_view'; +import { AlertSummaryView } from './alert_summary_view'; import { mockAlertDetailsData } from './__mocks__'; import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; @@ -30,7 +30,7 @@ const props = { timelineId: 'detections-page', }; -describe('SummaryViewComponent', () => { +describe('AlertSummaryView', () => { const mount = useMountAppended(); beforeEach(() => { @@ -44,7 +44,7 @@ describe('SummaryViewComponent', () => { test('render correct items', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="summary-view"]').exists()).toEqual(true); @@ -53,7 +53,7 @@ describe('SummaryViewComponent', () => { test('render investigation guide', async () => { const wrapper = mount( - + ); await waitFor(() => { @@ -69,7 +69,7 @@ describe('SummaryViewComponent', () => { }); const wrapper = mount( - + ); await waitFor(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx new file mode 100644 index 0000000000000..091049b967f02 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -0,0 +1,200 @@ +/* + * Copyright 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 { + EuiBasicTableColumn, + EuiDescriptionList, + EuiDescriptionListDescription, + EuiDescriptionListTitle, +} from '@elastic/eui'; +import { get, getOr } from 'lodash/fp'; +import React, { useMemo } from 'react'; +import styled from 'styled-components'; +import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { BrowserFields } from '../../../../common/search_strategy/index_fields'; +import { + ALERTS_HEADERS_RISK_SCORE, + ALERTS_HEADERS_RULE, + ALERTS_HEADERS_SEVERITY, + ALERTS_HEADERS_THRESHOLD_CARDINALITY, + ALERTS_HEADERS_THRESHOLD_COUNT, + ALERTS_HEADERS_THRESHOLD_TERMS, +} from '../../../detections/components/alerts_table/translations'; +import { + IP_FIELD_TYPE, + SIGNAL_RULE_NAME_FIELD_NAME, +} from '../../../timelines/components/timeline/body/renderers/constants'; +import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../network/components/ip'; +import { SummaryView } from './summary_view'; +import { AlertSummaryRow, getSummaryColumns, SummaryRow } from './helpers'; +import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; +import * as i18n from './translations'; +import { LineClamp } from '../line_clamp'; + +const StyledEuiDescriptionList = styled(EuiDescriptionList)` + padding: 24px 4px 4px; +`; + +const fields = [ + { id: 'signal.status' }, + { id: '@timestamp' }, + { + id: SIGNAL_RULE_NAME_FIELD_NAME, + linkField: 'signal.rule.id', + label: ALERTS_HEADERS_RULE, + }, + { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, + { id: 'signal.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE }, + { id: 'host.name' }, + { id: 'user.name' }, + { id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, + { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, + { id: 'signal.threshold_result.count', label: ALERTS_HEADERS_THRESHOLD_COUNT }, + { id: 'signal.threshold_result.terms', label: ALERTS_HEADERS_THRESHOLD_TERMS }, + { id: 'signal.threshold_result.cardinality', label: ALERTS_HEADERS_THRESHOLD_CARDINALITY }, +]; + +const getDescription = ({ + contextId, + eventId, + fieldName, + value, + fieldType = '', + linkValue, +}: AlertSummaryRow['description']) => ( + +); + +const getSummaryRows = ({ + data, + browserFields, + timelineId, + eventId, +}: { + data: TimelineEventsDetailsItem[]; + browserFields: BrowserFields; + timelineId: string; + eventId: string; +}) => { + return data != null + ? fields.reduce((acc, item) => { + const field = data.find((d) => d.field === item.id); + if (!field) { + return acc; + } + const linkValueField = + item.linkField != null && data.find((d) => d.field === item.linkField); + const linkValue = getOr(null, 'originalValue.0', linkValueField); + const value = getOr(null, 'originalValue.0', field); + const category = field.category; + const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; + const description = { + contextId: timelineId, + eventId, + fieldName: item.id, + value, + fieldType: item.fieldType ?? fieldType, + linkValue: linkValue ?? undefined, + }; + + if (item.id === 'signal.threshold_result.terms') { + try { + const terms = getOr(null, 'originalValue', field); + const parsedValue = terms.map((term: string) => JSON.parse(term)); + const thresholdTerms = (parsedValue ?? []).map( + (entry: { field: string; value: string }) => { + return { + title: `${entry.field} [threshold]`, + description: { + ...description, + value: entry.value, + }, + }; + } + ); + return [...acc, ...thresholdTerms]; + } catch (err) { + return acc; + } + } + + if (item.id === 'signal.threshold_result.cardinality') { + try { + const parsedValue = JSON.parse(value); + return [ + ...acc, + { + title: ALERTS_HEADERS_THRESHOLD_CARDINALITY, + description: { + ...description, + value: `count(${parsedValue.field}) == ${parsedValue.value}`, + }, + }, + ]; + } catch (err) { + return acc; + } + } + + return [ + ...acc, + { + title: item.label ?? item.id, + description, + }, + ]; + }, []) + : []; +}; + +const summaryColumns: Array> = getSummaryColumns(getDescription); + +const AlertSummaryViewComponent: React.FC<{ + browserFields: BrowserFields; + data: TimelineEventsDetailsItem[]; + eventId: string; + timelineId: string; +}> = ({ browserFields, data, eventId, timelineId }) => { + const summaryRows = useMemo(() => getSummaryRows({ browserFields, data, eventId, timelineId }), [ + browserFields, + data, + eventId, + timelineId, + ]); + + const ruleId = useMemo(() => { + const item = data.find((d) => d.field === 'signal.rule.id'); + return Array.isArray(item?.originalValue) + ? item?.originalValue[0] + : item?.originalValue ?? null; + }, [data]); + const { rule: maybeRule } = useRuleAsync(ruleId); + + return ( + <> + + {maybeRule?.note && ( + + {i18n.INVESTIGATION_GUIDE} + + + + + )} + + ); +}; + +export const AlertSummaryView = React.memo(AlertSummaryViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx index 164543a4b84d5..e799df0fdd10d 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.test.tsx @@ -13,7 +13,7 @@ import '../../mock/match_media'; import '../../mock/react_beautiful_dnd'; import { mockDetailItemData, mockDetailItemDataId, TestProviders } from '../../mock'; -import { EventDetails, EventsViewType } from './event_details'; +import { EventDetails, EventsViewType, EventView, ThreatView } from './event_details'; import { mockBrowserFields } from '../../containers/source/mock'; import { useMountAppended } from '../../utils/use_mount_appended'; import { mockAlertDetailsData } from './__mocks__'; @@ -28,10 +28,12 @@ describe('EventDetails', () => { data: mockDetailItemData, id: mockDetailItemDataId, isAlert: false, - onViewSelected: jest.fn(), + onEventViewSelected: jest.fn(), + onThreatViewSelected: jest.fn(), timelineTabType: TimelineTabs.query, timelineId: 'test', - view: EventsViewType.summaryView, + eventView: EventsViewType.summaryView as EventView, + threatView: EventsViewType.threatSummaryView as ThreatView, }; const alertsProps = { @@ -97,4 +99,27 @@ describe('EventDetails', () => { ).toEqual('Summary'); }); }); + + describe('threat tabs', () => { + ['Threat Summary', 'Threat Details'].forEach((tab) => { + test(`it renders the ${tab} tab`, () => { + expect( + alertsWrapper + .find('[data-test-subj="threatDetails"]') + .find('[role="tablist"]') + .containsMatchingElement({tab}) + ).toBeTruthy(); + }); + }); + + test('the Summary tab is selected by default', () => { + expect( + alertsWrapper + .find('[data-test-subj="threatDetails"]') + .find('.euiTab-isSelected') + .first() + .text() + ).toEqual('Threat Summary'); + }); + }); }); 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 4979d70ce2d7b..0e4cf7f4ae2fe 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 @@ -14,14 +14,23 @@ import { TimelineEventsDetailsItem } from '../../../../common/search_strategy/ti import { EventFieldsBrowser } from './event_fields_browser'; import { JsonView } from './json_view'; import * as i18n from './translations'; -import { SummaryView } from './summary_view'; +import { AlertSummaryView } from './alert_summary_view'; +import { ThreatSummaryView } from './threat_summary_view'; +import { ThreatDetailsView } from './threat_details_view'; import { TimelineTabs } from '../../../../common/types/timeline'; +import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants'; -export type View = EventsViewType.tableView | EventsViewType.jsonView | EventsViewType.summaryView; +export type EventView = + | EventsViewType.tableView + | EventsViewType.jsonView + | EventsViewType.summaryView; +export type ThreatView = EventsViewType.threatSummaryView | EventsViewType.threatDetailsView; export enum EventsViewType { tableView = 'table-view', jsonView = 'json-view', summaryView = 'summary-view', + threatSummaryView = 'threat-summary-view', + threatDetailsView = 'threat-details-view', } interface Props { @@ -29,8 +38,10 @@ interface Props { data: TimelineEventsDetailsItem[]; id: string; isAlert: boolean; - view: EventsViewType; - onViewSelected: (selected: EventsViewType) => void; + eventView: EventView; + threatView: ThreatView; + onEventViewSelected: (selected: EventView) => void; + onThreatViewSelected: (selected: ThreatView) => void; timelineTabType: TimelineTabs | 'flyout'; timelineId: string; } @@ -45,7 +56,16 @@ const StyledEuiTabbedContent = styled(EuiTabbedContent)` display: flex; flex: 1; flex-direction: column; - overflow: hidden; + overflow: scroll; + ::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; + } + ::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, 0.5); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); + } } `; @@ -57,14 +77,19 @@ const TabContentWrapper = styled.div` const EventDetailsComponent: React.FC = ({ browserFields, data, + eventView, id, - view, - onViewSelected, - timelineTabType, - timelineId, isAlert, + onEventViewSelected, + onThreatViewSelected, + threatView, + timelineId, + timelineTabType, }) => { - const handleTabClick = useCallback((e) => onViewSelected(e.id), [onViewSelected]); + const handleEventTabClick = useCallback((e) => onEventViewSelected(e.id), [onEventViewSelected]); + const handleThreatTabClick = useCallback((e) => onThreatViewSelected(e.id), [ + onThreatViewSelected, + ]); const alerts = useMemo( () => [ @@ -74,11 +99,13 @@ const EventDetailsComponent: React.FC = ({ content: ( <> - ), @@ -122,15 +149,60 @@ const EventDetailsComponent: React.FC = ({ [alerts, browserFields, data, id, isAlert, timelineId, timelineTabType] ); - const selectedTab = useMemo(() => tabs.find((t) => t.id === view) ?? tabs[0], [tabs, view]); + const selectedEventTab = useMemo(() => tabs.find((t) => t.id === eventView) ?? tabs[0], [ + tabs, + eventView, + ]); + + const isThreatPresent: boolean = useMemo( + () => + selectedEventTab.id === tabs[0].id && + isAlert && + data.some((item) => item.field === INDICATOR_DESTINATION_PATH), + [tabs, selectedEventTab, isAlert, data] + ); + + const threatTabs: EuiTabbedContentTab[] = useMemo(() => { + return isAlert && isThreatPresent + ? [ + { + id: EventsViewType.threatSummaryView, + name: i18n.THREAT_SUMMARY, + content: , + }, + { + id: EventsViewType.threatDetailsView, + name: i18n.THREAT_DETAILS, + content: , + }, + ] + : []; + }, [data, id, isAlert, timelineId, isThreatPresent]); + + const selectedThreatTab = useMemo( + () => threatTabs.find((t) => t.id === threatView) ?? threatTabs[0], + [threatTabs, threatView] + ); return ( - + <> + + {isThreatPresent && ( + + )} + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx index 00e2ee276f181..67e67584849cc 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/helpers.tsx @@ -7,6 +7,8 @@ import { get, getOr, isEmpty, uniqBy } from 'lodash/fp'; +import React from 'react'; +import { EuiBasicTableColumn, EuiTitle } from '@elastic/eui'; import { elementOrChildrenHasFocus, getFocusedDataColindexCell, @@ -51,6 +53,38 @@ export interface Item { values: ToStringArray; } +export interface AlertSummaryRow { + title: string; + description: { + contextId: string; + eventId: string; + fieldName: string; + value: string; + fieldType: string; + linkValue: string | undefined; + }; +} + +export interface ThreatSummaryRow { + title: string; + description: { + contextId: string; + eventId: string; + fieldName: string; + values: string[]; + }; +} + +export interface ThreatDetailsRow { + title: string; + description: { + fieldName: string; + value: string; + }; +} + +export type SummaryRow = AlertSummaryRow | ThreatSummaryRow | ThreatDetailsRow; + export const getColumnHeaderFromBrowserField = ({ browserField, width = DEFAULT_COLUMN_MIN_WIDTH, @@ -172,3 +206,33 @@ export const onEventDetailsTabKeyPressed = ({ }); } }; + +const getTitle = (title: string) => ( + +
{title}
+
+); +getTitle.displayName = 'getTitle'; + +export const getSummaryColumns = ( + DescriptionComponent: + | React.FC + | React.FC + | React.FC +): Array> => { + return [ + { + field: 'title', + truncateText: false, + render: getTitle, + width: '120px', + name: '', + }, + { + field: 'description', + truncateText: false, + render: DescriptionComponent, + name: '', + }, + ]; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 8e07910c1c071..3b2c55e9a6b67 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -5,69 +5,11 @@ * 2.0. */ -import { get, getOr } from 'lodash/fp'; -import { - EuiTitle, - EuiDescriptionList, - EuiDescriptionListTitle, - EuiDescriptionListDescription, - EuiInMemoryTable, - EuiBasicTableColumn, -} from '@elastic/eui'; -import React, { useMemo } from 'react'; +import { EuiInMemoryTable, EuiBasicTableColumn } from '@elastic/eui'; +import React from 'react'; import styled from 'styled-components'; -import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; -import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; -import * as i18n from './translations'; -import { BrowserFields } from '../../../../common/search_strategy/index_fields'; -import { - ALERTS_HEADERS_RISK_SCORE, - ALERTS_HEADERS_RULE, - ALERTS_HEADERS_SEVERITY, - ALERTS_HEADERS_THRESHOLD_COUNT, - ALERTS_HEADERS_THRESHOLD_TERMS, - ALERTS_HEADERS_THRESHOLD_CARDINALITY, -} from '../../../detections/components/alerts_table/translations'; -import { - IP_FIELD_TYPE, - SIGNAL_RULE_NAME_FIELD_NAME, -} from '../../../timelines/components/timeline/body/renderers/constants'; -import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../../../network/components/ip'; -import { LineClamp } from '../line_clamp'; -import { useRuleAsync } from '../../../detections/containers/detection_engine/rules/use_rule_async'; - -interface SummaryRow { - title: string; - description: { - contextId: string; - eventId: string; - fieldName: string; - value: string; - fieldType: string; - linkValue: string | undefined; - }; -} -type Summary = SummaryRow[]; - -const fields = [ - { id: 'signal.status' }, - { id: '@timestamp' }, - { - id: SIGNAL_RULE_NAME_FIELD_NAME, - linkField: 'signal.rule.id', - label: ALERTS_HEADERS_RULE, - }, - { id: 'signal.rule.severity', label: ALERTS_HEADERS_SEVERITY }, - { id: 'signal.rule.risk_score', label: ALERTS_HEADERS_RISK_SCORE }, - { id: 'host.name' }, - { id: 'user.name' }, - { id: SOURCE_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, - { id: DESTINATION_IP_FIELD_NAME, fieldType: IP_FIELD_TYPE }, - { id: 'signal.threshold_result.count', label: ALERTS_HEADERS_THRESHOLD_COUNT }, - { id: 'signal.threshold_result.terms', label: ALERTS_HEADERS_THRESHOLD_TERMS }, - { id: 'signal.threshold_result.cardinality', label: ALERTS_HEADERS_THRESHOLD_CARDINALITY }, -]; +import { SummaryRow } from './helpers'; // eslint-disable-next-line @typescript-eslint/no-explicit-any const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` @@ -77,173 +19,26 @@ const StyledEuiInMemoryTable = styled(EuiInMemoryTable as any)` .euiTableRowCell { border: none; } -`; -const StyledEuiDescriptionList = styled(EuiDescriptionList)` - padding: 24px 4px 4px; + .euiTableCellContent { + display: flex; + flex-direction: column; + align-items: flex-start; + } `; -const getTitle = (title: SummaryRow['title']) => ( - -
{title}
-
-); - -getTitle.displayName = 'getTitle'; - -const getDescription = ({ - contextId, - eventId, - fieldName, - value, - fieldType = '', - linkValue, -}: SummaryRow['description']) => ( - -); - -const getSummary = ({ - data, - browserFields, - timelineId, - eventId, -}: { - data: TimelineEventsDetailsItem[]; - browserFields: BrowserFields; - timelineId: string; - eventId: string; -}) => { - return data != null - ? fields.reduce((acc, item) => { - const field = data.find((d) => d.field === item.id); - if (!field) { - return acc; - } - const linkValueField = - item.linkField != null && data.find((d) => d.field === item.linkField); - const linkValue = getOr(null, 'originalValue.0', linkValueField); - const value = getOr(null, 'originalValue.0', field); - const category = field.category; - const fieldType = get(`${category}.fields.${field.field}.type`, browserFields) as string; - const description = { - contextId: timelineId, - eventId, - fieldName: item.id, - value, - fieldType: item.fieldType ?? fieldType, - linkValue: linkValue ?? undefined, - }; - - if (item.id === 'signal.threshold_result.terms') { - try { - const terms = getOr(null, 'originalValue', field); - const parsedValue = terms.map((term: string) => JSON.parse(term)); - const thresholdTerms = (parsedValue ?? []).map( - (entry: { field: string; value: string }) => { - return { - title: `${entry.field} [threshold]`, - description: { - ...description, - value: entry.value, - }, - }; - } - ); - return [...acc, ...thresholdTerms]; - } catch (err) { - return acc; - } - } - - if (item.id === 'signal.threshold_result.cardinality') { - try { - const parsedValue = JSON.parse(value); - return [ - ...acc, - { - title: ALERTS_HEADERS_THRESHOLD_CARDINALITY, - description: { - ...description, - value: `count(${parsedValue.field}) == ${parsedValue.value}`, - }, - }, - ]; - } catch (err) { - return acc; - } - } - - return [ - ...acc, - { - title: item.label ?? item.id, - description, - }, - ]; - }, []) - : []; -}; - -const summaryColumns: Array> = [ - { - field: 'title', - truncateText: false, - render: getTitle, - width: '120px', - name: '', - }, - { - field: 'description', - truncateText: false, - render: getDescription, - name: '', - }, -]; - export const SummaryViewComponent: React.FC<{ - browserFields: BrowserFields; - data: TimelineEventsDetailsItem[]; - eventId: string; - timelineId: string; -}> = ({ data, eventId, timelineId, browserFields }) => { - const ruleId = useMemo(() => { - const item = data.find((d) => d.field === 'signal.rule.id'); - return Array.isArray(item?.originalValue) - ? item?.originalValue[0] - : item?.originalValue ?? null; - }, [data]); - const { rule: maybeRule } = useRuleAsync(ruleId); - const summaryList = useMemo(() => getSummary({ browserFields, data, eventId, timelineId }), [ - browserFields, - data, - eventId, - timelineId, - ]); - + summaryColumns: Array>; + summaryRows: SummaryRow[]; + dataTestSubj?: string; +}> = ({ summaryColumns, summaryRows, dataTestSubj = 'summary-view' }) => { return ( - <> - - {maybeRule?.note && ( - - {i18n.INVESTIGATION_GUIDE} - - - - - )} - + ); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx new file mode 100644 index 0000000000000..81bffe9b66638 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { ThreatDetailsView } from './threat_details_view'; +import { mockAlertDetailsData } from './__mocks__'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; + +import { TestProviders } from '../../mock'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { + return { + useRuleAsync: jest.fn(), + }; +}); + +const props = { + data: mockAlertDetailsData as TimelineEventsDetailsItem[], + eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', + timelineId: 'detections-page', +}; + +describe('ThreatDetailsView', () => { + const mount = useMountAppended(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('render correct items', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="threat-details-view-0"]').exists()).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx new file mode 100644 index 0000000000000..0889986237442 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/threat_details_view.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 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 { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiToolTip, +} from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { SummaryView } from './summary_view'; +import { getSummaryColumns, SummaryRow, ThreatDetailsRow } from './helpers'; +import { getDataFromSourceHits } from '../../../../common/utils/field_formatters'; +import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants'; + +const ThreatDetailsDescription: React.FC = ({ + fieldName, + value, +}) => ( + + + {fieldName} + + + } + > + {value} + +); + +const getSummaryRowsArray = ({ + data, +}: { + data: TimelineEventsDetailsItem[]; +}): ThreatDetailsRow[][] => { + if (!data) return [[]]; + const threatInfo = data.find( + ({ field, originalValue }) => field === INDICATOR_DESTINATION_PATH && originalValue + ); + if (!threatInfo) return [[]]; + const { originalValue } = threatInfo; + const values = Array.isArray(originalValue) ? originalValue : [originalValue]; + return values.map((value) => + getDataFromSourceHits(JSON.parse(value)).map((threatInfoItem) => ({ + title: threatInfoItem.field.replace(`${INDICATOR_DESTINATION_PATH}.`, ''), + description: { fieldName: threatInfoItem.field, value: threatInfoItem.originalValue }, + })) + ); +}; + +const summaryColumns: Array> = getSummaryColumns( + ThreatDetailsDescription +); + +const ThreatDetailsViewComponent: React.FC<{ + data: TimelineEventsDetailsItem[]; +}> = ({ data }) => { + const summaryRowsArray = useMemo(() => getSummaryRowsArray({ data }), [data]); + return ( + <> + {summaryRowsArray.map((summaryRows, index, arr) => { + const key = summaryRows.find((threat) => threat.title === 'matched.id')?.description + .value[0]; + return ( +
+ + {index < arr.length - 1 && } +
+ ); + })} + + ); +}; + +export const ThreatDetailsView = React.memo(ThreatDetailsViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx new file mode 100644 index 0000000000000..756fc7d32b371 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; + +import { ThreatSummaryView } from './threat_summary_view'; +import { mockAlertDetailsData } from './__mocks__'; +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; + +import { TestProviders } from '../../mock'; +import { useMountAppended } from '../../utils/use_mount_appended'; + +jest.mock('../../../detections/containers/detection_engine/rules/use_rule_async', () => { + return { + useRuleAsync: jest.fn(), + }; +}); + +const props = { + data: mockAlertDetailsData as TimelineEventsDetailsItem[], + eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31', + timelineId: 'detections-page', +}; + +describe('ThreatSummaryView', () => { + const mount = useMountAppended(); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('render correct items', () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="threat-summary-view"]').exists()).toEqual(true); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx new file mode 100644 index 0000000000000..96ae2071c449b --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/event_details/threat_summary_view.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 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 { EuiBasicTableColumn } from '@elastic/eui'; +import React, { useMemo } from 'react'; + +import { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; +import { FormattedFieldValue } from '../../../timelines/components/timeline/body/renderers/formatted_field'; +import { BrowserFields } from '../../../../common/search_strategy/index_fields'; +import { SummaryView } from './summary_view'; +import { getSummaryColumns, SummaryRow, ThreatSummaryRow } from './helpers'; +import { INDICATOR_DESTINATION_PATH } from '../../../../common/constants'; + +const getDescription = ({ + contextId, + eventId, + fieldName, + values, +}: ThreatSummaryRow['description']): JSX.Element => ( + <> + {values.map((value: string) => ( + + ))} + +); + +const getSummaryRows = ({ + data, + timelineId: contextId, + eventId, +}: { + data: TimelineEventsDetailsItem[]; + browserFields?: BrowserFields; + timelineId: string; + eventId: string; +}) => { + if (!data) return []; + return data.reduce((acc, { field, originalValue }) => { + if (field.startsWith(`${INDICATOR_DESTINATION_PATH}.`) && originalValue) { + return [ + ...acc, + { + title: field.replace(`${INDICATOR_DESTINATION_PATH}.`, ''), + description: { + values: Array.isArray(originalValue) ? originalValue : [originalValue], + contextId, + eventId, + fieldName: field, + }, + }, + ]; + } + return acc; + }, []); +}; + +const summaryColumns: Array> = getSummaryColumns(getDescription); + +const ThreatSummaryViewComponent: React.FC<{ + data: TimelineEventsDetailsItem[]; + eventId: string; + timelineId: string; +}> = ({ data, eventId, timelineId }) => { + const summaryRows = useMemo(() => getSummaryRows({ data, eventId, timelineId }), [ + data, + eventId, + timelineId, + ]); + + return ( + + ); +}; + +export const ThreatSummaryView = React.memo(ThreatSummaryViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts index 3a599b174251a..73a2e0d57307c 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/event_details/translations.ts @@ -11,6 +11,14 @@ export const SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.summa defaultMessage: 'Summary', }); +export const THREAT_SUMMARY = i18n.translate('xpack.securitySolution.alertDetails.threatSummary', { + defaultMessage: 'Threat Summary', +}); + +export const THREAT_DETAILS = i18n.translate('xpack.securitySolution.alertDetails.threatDetails', { + defaultMessage: 'Threat Details', +}); + export const INVESTIGATION_GUIDE = i18n.translate( 'xpack.securitySolution.alertDetails.summary.investigationGuide', { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap index 87392bce3ee63..50970304953ca 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap @@ -262,7 +262,7 @@ Array [ -ms-flex: 1; flex: 1; overflow: hidden; - padding: 4px 16px 64px; + padding: 4px 16px 50px; } .c0 { @@ -537,7 +537,7 @@ Array [ -ms-flex: 1; flex: 1; overflow: hidden; - padding: 4px 16px 64px; + padding: 4px 16px 50px; } .c0 { @@ -806,7 +806,7 @@ Array [ -ms-flex: 1; flex: 1; overflow: hidden; - padding: 4px 16px 64px; + padding: 4px 16px 50px; } .c0 { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx index 435a210b9d260..86175c0e06ad2 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/expandable_event.tsx @@ -26,7 +26,8 @@ import { BrowserFields } from '../../../../common/containers/source'; import { EventDetails, EventsViewType, - View, + EventView, + ThreatView, } from '../../../../common/components/event_details/event_details'; import { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline'; import { LineClamp } from '../../../../common/components/line_clamp'; @@ -87,7 +88,8 @@ ExpandableEventTitle.displayName = 'ExpandableEventTitle'; export const ExpandableEvent = React.memo( ({ browserFields, event, timelineId, timelineTabType, isAlert, loading, detailsData }) => { - const [view, setView] = useState(EventsViewType.summaryView); + const [eventView, setEventView] = useState(EventsViewType.summaryView); + const [threatView, setThreatView] = useState(EventsViewType.threatSummaryView); const message = useMemo(() => { if (detailsData) { @@ -131,10 +133,12 @@ export const ExpandableEvent = React.memo( data={detailsData!} id={event.eventId!} isAlert={isAlert} - onViewSelected={setView} - timelineTabType={timelineTabType} + onThreatViewSelected={setThreatView} + onEventViewSelected={setEventView} + threatView={threatView} timelineId={timelineId} - view={view} + timelineTabType={timelineTabType} + eventView={eventView} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx index 6f4778f36466b..9a4684193b997 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/index.tsx @@ -25,7 +25,7 @@ const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` .euiFlyoutBody__overflowContent { flex: 1; overflow: hidden; - padding: ${({ theme }) => `${theme.eui.paddingSizes.xs} ${theme.eui.paddingSizes.m} 64px`}; + padding: ${({ theme }) => `${theme.eui.paddingSizes.xs} ${theme.eui.paddingSizes.m} 50px`}; } } `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx index 3032f556251f3..e227c87b99870 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/formatted_field.tsx @@ -44,7 +44,7 @@ const FormattedFieldValueComponent: React.FC<{ isObjectArray?: boolean; fieldFormat?: string; fieldName: string; - fieldType: string; + fieldType?: string; truncate?: boolean; value: string | number | undefined | null; linkValue?: string | null | undefined; diff --git a/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts b/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts index 4dab0ebc43149..0b418c0da410c 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/helpers/format_response_object_values.ts @@ -7,7 +7,7 @@ import { mapValues, isObject, isArray } from 'lodash/fp'; -import { toArray } from './to_array'; +import { toArray } from '../../../common/utils/to_array'; export const mapObjectValuesToStringArray = (object: object): object => mapValues((o) => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts index 3f4eb5721164b..bed4a040f92b0 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/all/helpers.ts @@ -14,8 +14,7 @@ import { HostsEdges, HostValue, } from '../../../../../../common/search_strategy/security_solution/hosts'; - -import { toObjectArrayOfStrings } from '../../../../helpers/to_array'; +import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; export const HOSTS_FIELDS: readonly string[] = [ '_id', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts index aeaefe690cbde..807b78cb9cdd2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/authentications/helpers.ts @@ -8,7 +8,7 @@ import { get, getOr, isEmpty } from 'lodash/fp'; import { set } from '@elastic/safer-lodash-set/fp'; import { mergeFieldsWithHit } from '../../../../../utils/build_query'; -import { toObjectArrayOfStrings } from '../../../../helpers/to_array'; +import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; import { AuthenticationsEdges, AuthenticationHit, diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts index d36af61957690..00ed5c0c0dc01 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/details/helpers.ts @@ -8,6 +8,7 @@ import { set } from '@elastic/safer-lodash-set/fp'; import { get, has, head } from 'lodash/fp'; import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields'; +import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; import { Direction } from '../../../../../../common/search_strategy/common'; import { AggregationRequest, @@ -16,7 +17,6 @@ import { HostItem, HostValue, } from '../../../../../../common/search_strategy/security_solution/hosts'; -import { toObjectArrayOfStrings } from '../../../../helpers/to_array'; export const HOST_FIELDS = [ '_id', diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts index fe202b48540d7..1c1e2111f3771 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/hosts/uncommon_processes/helpers.ts @@ -14,7 +14,7 @@ import { HostsUncommonProcessesEdges, HostsUncommonProcessHit, } from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes'; -import { toObjectArrayOfStrings } from '../../../../helpers/to_array'; +import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; import { HostHits } from '../../../../../../common/search_strategy'; export const uncommonProcessesFields = [ diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts index 8fc7ae0304a35..cc1bfdff8e096 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/factory/network/details/helpers.ts @@ -13,7 +13,7 @@ import { NetworkDetailsHostHit, NetworkHit, } from '../../../../../../common/search_strategy/security_solution/network'; -import { toObjectArrayOfStrings } from '../../../../helpers/to_array'; +import { toObjectArrayOfStrings } from '../../../../../../common/utils/to_array'; export const getNetworkDetailsAgg = (type: string, networkHit: NetworkHit | {}) => { const firstSeen = getOr(null, `firstSeen.value_as_string`, networkHit); diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts index 61af6a7664faa..405ddba137dae 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts @@ -8,7 +8,7 @@ import { EventHit } from '../../../../../../common/search_strategy'; import { TIMELINE_EVENTS_FIELDS } from './constants'; import { formatTimelineData } from './helpers'; -import { eventHit } from '../mocks'; +import { eventHit } from '../../../../../../common/utils/mock_event_details'; describe('#formatTimelineData', () => { it('happy path', async () => { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts index e5bb8cb7e14b7..2c18fb2840865 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts @@ -11,8 +11,11 @@ import { TimelineEdges, TimelineNonEcsData, } from '../../../../../../common/search_strategy'; -import { toStringArray } from '../../../../helpers/to_array'; -import { getDataSafety, getDataFromFieldsHits } from '../details/helpers'; +import { toStringArray } from '../../../../../../common/utils/to_array'; +import { + getDataFromFieldsHits, + getDataSafety, +} from '../../../../../../common/utils/field_formatters'; const getTimestamp = (hit: EventHit): string => { if (hit.fields && hit.fields['@timestamp']) { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts index 0107ba44baec7..a4d6eebfb71b8 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts @@ -19,7 +19,11 @@ import { import { inspectStringifyObject } from '../../../../../utils/build_query'; import { SecuritySolutionTimelineFactory } from '../../types'; import { buildTimelineDetailsQuery } from './query.events_details.dsl'; -import { getDataFromFieldsHits, getDataFromSourceHits, getDataSafety } from './helpers'; +import { + getDataFromFieldsHits, + getDataFromSourceHits, + getDataSafety, +} from '../../../../../../common/utils/field_formatters'; export const timelineEventsDetails: SecuritySolutionTimelineFactory = { buildDsl: (options: TimelineEventsDetailsRequestOptions) => { From fb24006545b05fb1ba57d10610af1defc7067d4e Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 12 Apr 2021 20:54:03 +0100 Subject: [PATCH 40/79] skip flaky suite (#96788) --- .../integration_tests/migration_7.7.2_xpack_100k.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts index 0e51c886f7f30..7f3ee03f1437d 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/migration_7.7.2_xpack_100k.test.ts @@ -26,7 +26,8 @@ async function removeLogFile() { await asyncUnlink(logFilePath).catch(() => void 0); } -describe('migration from 7.7.2-xpack with 100k objects', () => { +// FAILING: https://github.com/elastic/kibana/pull/96788 +describe.skip('migration from 7.7.2-xpack with 100k objects', () => { let esServer: kbnTestServer.TestElasticsearchUtils; let root: Root; let coreStart: InternalCoreStart; From 3f131f59662df8c40cbaecc6b6b740c90b8410b8 Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Mon, 12 Apr 2021 21:19:26 +0100 Subject: [PATCH 41/79] skip flaky suite (#89550) --- test/functional/apps/discover/_discover.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index bf90d90cc828c..0c12f32f6e717 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -182,7 +182,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('query #2, which has an empty time range', () => { + // FLAKY: https://github.com/elastic/kibana/issues/89550 + describe.skip('query #2, which has an empty time range', () => { const fromTime = 'Jun 11, 1999 @ 09:22:11.000'; const toTime = 'Jun 12, 1999 @ 11:21:04.000'; From 5f16bcc15595c4c7b728c2ff2e7d1e7e14d1b581 Mon Sep 17 00:00:00 2001 From: Lisa Cawley Date: Mon, 12 Apr 2021 13:31:17 -0700 Subject: [PATCH 42/79] [docs] minor typo in word (#96684) (#96866) Co-authored-by: Peter Dyson --- docs/user/monitoring/kibana-alerts.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/monitoring/kibana-alerts.asciidoc b/docs/user/monitoring/kibana-alerts.asciidoc index 04f4e986ca289..bbc9c41c6ca5a 100644 --- a/docs/user/monitoring/kibana-alerts.asciidoc +++ b/docs/user/monitoring/kibana-alerts.asciidoc @@ -84,7 +84,7 @@ by running checks on a schedule time of 1 minute with a re-notify interval of 6 This alert is triggered if a large (primary) shard size is found on any of the specified index patterns. The trigger condition is met if an index's shard size is 55gb or higher in the last 5 minutes. The alert is grouped across all indices that match -the default patter of `*` by running checks on a schedule time of 1 minute with a re-notify +the default pattern of `*` by running checks on a schedule time of 1 minute with a re-notify interval of 12 hours. [discrete] From 465734ae99ba72a05faf4fe50b9bfa2c83d8de99 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Mon, 12 Apr 2021 16:34:41 -0400 Subject: [PATCH 43/79] [Maps] Enable distance filtering on geo_shape (#96832) --- .../tools_control/tools_control.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx index 7ffd2a608c43a..1d2354ba3154a 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx @@ -132,17 +132,11 @@ export class ToolsControl extends Component { name: DRAW_BOUNDS_LABEL, panel: 2, }, - ]; - - const hasGeoPoints = this.props.geoFields.some(({ geoFieldType }) => { - return geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT; - }); - if (hasGeoPoints) { - tools.push({ + { name: DRAW_DISTANCE_LABEL, panel: 3, - }); - } + }, + ]; return [ { @@ -199,9 +193,7 @@ export class ToolsControl extends Component { { - return geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT; - })} + geoFields={this.props.geoFields} getFilterActions={this.props.getFilterActions} getActionContext={this.props.getActionContext} onSubmit={this._initiateDistanceDraw} From e7ecad7c3b158974c0ccfd4dbd740b65e70f53f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ece=20=C3=96zalp?= Date: Mon, 12 Apr 2021 17:10:36 -0400 Subject: [PATCH 44/79] [CTI] Filters alerts table by presence of threat (elastic/security-team#907) (#96096) [CTI] Filters alerts table by presence of threat (elastic/security-team#907) --- .../alerts_utility_bar/index.test.tsx | 271 +++++++++++++++--- .../alerts_table/alerts_utility_bar/index.tsx | 41 ++- .../alerts_utility_bar/translations.ts | 7 + .../alerts_table/default_config.test.tsx | 29 +- .../alerts_table/default_config.tsx | 66 +++-- .../components/alerts_table/index.test.tsx | 2 + .../components/alerts_table/index.tsx | 36 ++- .../detection_engine/detection_engine.tsx | 30 +- .../detection_engine/rules/details/index.tsx | 16 +- 9 files changed, 406 insertions(+), 92 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx index 6f83c075f0a9a..4ca2980dc74e5 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.test.tsx @@ -17,17 +17,19 @@ describe('AlertsUtilityBar', () => { test('renders correctly', () => { const wrapper = shallow( ); @@ -41,17 +43,19 @@ describe('AlertsUtilityBar', () => { const wrapper = mount( @@ -72,22 +76,61 @@ describe('AlertsUtilityBar', () => { ).toEqual(false); }); + test('does not show the showOnlyThreatIndicatorAlerts checked if the showThreatMatchOnly is false', () => { + const wrapper = mount( + + + + ); + // click the filters button to popup the checkbox to make it visible + wrapper + .find('[data-test-subj="additionalFilters"] button') + .first() + .simulate('click') + .update(); + + // The check box should be false + expect( + wrapper + .find('[data-test-subj="showOnlyThreatIndicatorAlertsCheckbox"] input') + .first() + .prop('checked') + ).toEqual(false); + }); + test('does show the showBuildingBlockAlerts checked if the showBuildingBlockAlerts is true', () => { const onShowBuildingBlockAlertsChanged = jest.fn(); const wrapper = mount( @@ -108,22 +151,61 @@ describe('AlertsUtilityBar', () => { ).toEqual(true); }); + test('does show the showOnlyThreatIndicatorAlerts checked if the showOnlyThreatIndicatorAlerts is true', () => { + const wrapper = mount( + + + + ); + // click the filters button to popup the checkbox to make it visible + wrapper + .find('[data-test-subj="additionalFilters"] button') + .first() + .simulate('click') + .update(); + + // The check box should be true + expect( + wrapper + .find('[data-test-subj="showOnlyThreatIndicatorAlertsCheckbox"] input') + .first() + .prop('checked') + ).toEqual(true); + }); + test('calls the onShowBuildingBlockAlertsChanged when the check box is clicked', () => { const onShowBuildingBlockAlertsChanged = jest.fn(); const wrapper = mount( @@ -145,21 +227,62 @@ describe('AlertsUtilityBar', () => { expect(onShowBuildingBlockAlertsChanged).toHaveBeenCalled(); }); + test('calls the onShowOnlyThreatIndicatorAlertsChanged when the check box is clicked', () => { + const onShowOnlyThreatIndicatorAlertsChanged = jest.fn(); + const wrapper = mount( + + + + ); + // click the filters button to popup the checkbox to make it visible + wrapper + .find('[data-test-subj="additionalFilters"] button') + .first() + .simulate('click') + .update(); + + // check the box + wrapper + .find('[data-test-subj="showOnlyThreatIndicatorAlertsCheckbox"] input') + .first() + .simulate('change', { target: { checked: true } }); + + // Make sure our callback is called + expect(onShowOnlyThreatIndicatorAlertsChanged).toHaveBeenCalled(); + }); + test('can update showBuildingBlockAlerts from false to true', () => { const Proxy = (props: AlertsUtilityBarProps) => ( @@ -167,17 +290,19 @@ describe('AlertsUtilityBar', () => { const wrapper = mount( ); @@ -214,5 +339,79 @@ describe('AlertsUtilityBar', () => { .prop('checked') ).toEqual(true); }); + + test('can update showOnlyThreatIndicatorAlerts from false to true', () => { + const Proxy = (props: AlertsUtilityBarProps) => ( + + + + ); + + const wrapper = mount( + + ); + // click the filters button to popup the checkbox to make it visible + wrapper + .find('[data-test-subj="additionalFilters"] button') + .first() + .simulate('click') + .update(); + + // The check box should false now since we initially set the showBuildingBlockAlerts to false + expect( + wrapper + .find('[data-test-subj="showOnlyThreatIndicatorAlertsCheckbox"] input') + .first() + .prop('checked') + ).toEqual(false); + + wrapper.setProps({ showOnlyThreatIndicatorAlerts: true }); + wrapper.update(); + + // click the filters button to popup the checkbox to make it visible + wrapper + .find('[data-test-subj="additionalFilters"] button') + .first() + .simulate('click') + .update(); + + // The check box should be true now since we changed the showBuildingBlockAlerts from false to true + expect( + wrapper + .find('[data-test-subj="showOnlyThreatIndicatorAlertsCheckbox"] input') + .first() + .prop('checked') + ).toEqual(true); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx index ec2f84ba3e12d..bda8c85ddb315 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/index.tsx @@ -30,16 +30,18 @@ import { UpdateAlertsStatus } from '../types'; import { FILTER_CLOSED, FILTER_IN_PROGRESS, FILTER_OPEN } from '../alerts_filter_group'; export interface AlertsUtilityBarProps { - hasIndexWrite: boolean; - hasIndexMaintenance: boolean; areEventsLoading: boolean; clearSelection: () => void; currentFilter: Status; + hasIndexMaintenance: boolean; + hasIndexWrite: boolean; + onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void; + onShowOnlyThreatIndicatorAlertsChanged: (showOnlyThreatIndicatorAlerts: boolean) => void; selectAll: () => void; selectedEventIds: Readonly>; showBuildingBlockAlerts: boolean; - onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void; showClearSelection: boolean; + showOnlyThreatIndicatorAlerts: boolean; totalCount: number; updateAlertsStatus: UpdateAlertsStatus; } @@ -56,21 +58,22 @@ const BuildingBlockContainer = styled(EuiFlexItem)` rgba(245, 167, 0, 0.05) 2px, rgba(245, 167, 0, 0.05) 10px ); - padding: ${({ theme }) => `${theme.eui.paddingSizes.xs}`}; `; const AlertsUtilityBarComponent: React.FC = ({ - hasIndexWrite, - hasIndexMaintenance, areEventsLoading, clearSelection, - totalCount, - selectedEventIds, currentFilter, + hasIndexMaintenance, + hasIndexWrite, + onShowBuildingBlockAlertsChanged, + onShowOnlyThreatIndicatorAlertsChanged, selectAll, + selectedEventIds, showBuildingBlockAlerts, - onShowBuildingBlockAlertsChanged, showClearSelection, + showOnlyThreatIndicatorAlerts, + totalCount, updateAlertsStatus, }) => { const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); @@ -144,7 +147,7 @@ const AlertsUtilityBarComponent: React.FC = ({ ); const UtilityBarAdditionalFiltersContent = (closePopover: () => void) => ( - + = ({ label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK} /> + + ) => { + closePopover(); + onShowOnlyThreatIndicatorAlertsChanged(e.target.checked); + }} + checked={showOnlyThreatIndicatorAlerts} + color="text" + data-test-subj="showOnlyThreatIndicatorAlertsCheckbox" + label={i18n.ADDITIONAL_FILTERS_ACTIONS_SHOW_ONLY_THREAT_INDICATOR_ALERTS} + /> + ); @@ -240,5 +257,7 @@ export const AlertsUtilityBar = React.memo( prevProps.selectedEventIds === nextProps.selectedEventIds && prevProps.totalCount === nextProps.totalCount && prevProps.showClearSelection === nextProps.showClearSelection && - prevProps.showBuildingBlockAlerts === nextProps.showBuildingBlockAlerts + prevProps.showBuildingBlockAlerts === nextProps.showBuildingBlockAlerts && + prevProps.onShowOnlyThreatIndicatorAlertsChanged === + nextProps.onShowOnlyThreatIndicatorAlertsChanged ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/translations.ts b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/translations.ts index 9307e8b1cd5f7..c52e443c50753 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/alerts_utility_bar/translations.ts @@ -42,6 +42,13 @@ export const ADDITIONAL_FILTERS_ACTIONS_SHOW_BUILDING_BLOCK = i18n.translate( } ); +export const ADDITIONAL_FILTERS_ACTIONS_SHOW_ONLY_THREAT_INDICATOR_ALERTS = i18n.translate( + 'xpack.securitySolution.detectionEngine.alerts.utilityBar.additionalFiltersActions.showOnlyThreatIndicatorAlerts', + { + defaultMessage: 'Show only threat indicator alerts', + } +); + export const CLEAR_SELECTION = i18n.translate( 'xpack.securitySolution.detectionEngine.alerts.utilityBar.clearSelectionTitle', { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx index 26bc8f213ca46..79c2a45273c33 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.test.tsx @@ -6,7 +6,7 @@ */ import { Filter } from '../../../../../../../src/plugins/data/common/es_query'; -import { buildAlertsRuleIdFilter } from './default_config'; +import { buildAlertsRuleIdFilter, buildThreatMatchFilter } from './default_config'; jest.mock('./actions'); @@ -34,7 +34,34 @@ describe('alerts default_config', () => { expect(filters).toHaveLength(1); expect(filters[0]).toEqual(expectedFilter); }); + + describe('buildThreatMatchFilter', () => { + test('given a showOnlyThreatIndicatorAlerts=true this will return an array with a single filter', () => { + const filters: Filter[] = buildThreatMatchFilter(true); + const expectedFilter: Filter = { + meta: { + alias: null, + disabled: false, + negate: false, + key: 'signal.rule.threat_mapping', + type: 'exists', + value: 'exists', + }, + // @ts-expect-error TODO: Rework parent typings to support ExistsFilter[] + exists: { + field: 'signal.rule.threat_mapping', + }, + }; + expect(filters).toHaveLength(1); + expect(filters[0]).toEqual(expectedFilter); + }); + test('given a showOnlyThreatIndicatorAlerts=false this will return an empty filter', () => { + const filters: Filter[] = buildThreatMatchFilter(false); + expect(filters).toHaveLength(0); + }); + }); }); + // TODO: move these tests to ../timelines/components/timeline/body/events/event_column_view.tsx // describe.skip('getAlertActions', () => { // let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 4fae2e69ac1f6..6a83039bf1ec8 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -39,28 +39,31 @@ export const buildAlertStatusFilter = (status: Status): Filter[] => [ }, ]; -export const buildAlertsRuleIdFilter = (ruleId: string): Filter[] => [ - { - meta: { - alias: null, - negate: false, - disabled: false, - type: 'phrase', - key: 'signal.rule.id', - params: { - query: ruleId, - }, - }, - query: { - match_phrase: { - 'signal.rule.id': ruleId, - }, - }, - }, -]; +export const buildAlertsRuleIdFilter = (ruleId: string | null): Filter[] => + ruleId + ? [ + { + meta: { + alias: null, + negate: false, + disabled: false, + type: 'phrase', + key: 'signal.rule.id', + params: { + query: ruleId, + }, + }, + query: { + match_phrase: { + 'signal.rule.id': ruleId, + }, + }, + }, + ] + : []; -export const buildShowBuildingBlockFilter = (showBuildingBlockAlerts: boolean): Filter[] => [ - ...(showBuildingBlockAlerts +export const buildShowBuildingBlockFilter = (showBuildingBlockAlerts: boolean): Filter[] => + showBuildingBlockAlerts ? [] : [ { @@ -75,8 +78,25 @@ export const buildShowBuildingBlockFilter = (showBuildingBlockAlerts: boolean): // @ts-expect-error TODO: Rework parent typings to support ExistsFilter[] exists: { field: 'signal.rule.building_block_type' }, }, - ]), -]; + ]; + +export const buildThreatMatchFilter = (showOnlyThreatIndicatorAlerts: boolean): Filter[] => + showOnlyThreatIndicatorAlerts + ? [ + { + meta: { + alias: null, + disabled: false, + negate: false, + key: 'signal.rule.threat_mapping', + type: 'exists', + value: 'exists', + }, + // @ts-expect-error TODO: Rework parent typings to support ExistsFilter[] + exists: { field: 'signal.rule.threat_mapping' }, + }, + ] + : []; export const alertsHeaders: ColumnHeaderOptions[] = [ { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx index 5c659b7554ec2..be11aecfe47dd 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx @@ -40,6 +40,8 @@ describe('AlertsTableComponent', () => { clearEventsDeleted={jest.fn()} showBuildingBlockAlerts={false} onShowBuildingBlockAlertsChanged={jest.fn()} + showOnlyThreatIndicatorAlerts={false} + onShowOnlyThreatIndicatorAlertsChanged={jest.fn()} /> ); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx index cf6db52d0cece..2890eb912b84c 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx @@ -52,22 +52,23 @@ import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers'; interface OwnProps { - timelineId: TimelineIdLiteral; defaultFilters?: Filter[]; - hasIndexWrite: boolean; - hasIndexMaintenance: boolean; from: string; + hasIndexMaintenance: boolean; + hasIndexWrite: boolean; loading: boolean; onRuleChange?: () => void; - showBuildingBlockAlerts: boolean; onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void; + onShowOnlyThreatIndicatorAlertsChanged: (showOnlyThreatIndicatorAlerts: boolean) => void; + showBuildingBlockAlerts: boolean; + showOnlyThreatIndicatorAlerts: boolean; + timelineId: TimelineIdLiteral; to: string; } type AlertsTableComponentProps = OwnProps & PropsFromRedux; export const AlertsTableComponent: React.FC = ({ - timelineId, clearEventsDeleted, clearEventsLoading, clearSelected, @@ -75,17 +76,20 @@ export const AlertsTableComponent: React.FC = ({ from, globalFilters, globalQuery, - hasIndexWrite, hasIndexMaintenance, + hasIndexWrite, isSelectAllChecked, loading, loadingEventIds, onRuleChange, + onShowBuildingBlockAlertsChanged, + onShowOnlyThreatIndicatorAlertsChanged, selectedEventIds, setEventsDeleted, setEventsLoading, showBuildingBlockAlerts, - onShowBuildingBlockAlertsChanged, + showOnlyThreatIndicatorAlerts, + timelineId, to, }) => { const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); @@ -264,30 +268,34 @@ export const AlertsTableComponent: React.FC = ({ 0} clearSelection={clearSelectionCallback} - hasIndexWrite={hasIndexWrite} - hasIndexMaintenance={hasIndexMaintenance} currentFilter={filterGroup} + hasIndexMaintenance={hasIndexMaintenance} + hasIndexWrite={hasIndexWrite} + onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChanged} + onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsChanged} selectAll={selectAllOnAllPagesCallback} selectedEventIds={selectedEventIds} showBuildingBlockAlerts={showBuildingBlockAlerts} - onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChanged} showClearSelection={showClearSelectionAction} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} totalCount={totalCount} updateAlertsStatus={updateAlertsStatusCallback.bind(null, refetchQuery)} /> ); }, [ - hasIndexWrite, - hasIndexMaintenance, clearSelectionCallback, filterGroup, - showBuildingBlockAlerts, - onShowBuildingBlockAlertsChanged, + hasIndexMaintenance, + hasIndexWrite, loadingEventIds.length, + onShowBuildingBlockAlertsChanged, + onShowOnlyThreatIndicatorAlertsChanged, selectAllOnAllPagesCallback, selectedEventIds, + showBuildingBlockAlerts, showClearSelectionAction, + showOnlyThreatIndicatorAlerts, updateAlertsStatusCallback, ] ); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx index 8d2f07e19b36a..02e18d09710d7 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx @@ -50,7 +50,10 @@ import { } from '../../../timelines/components/timeline/helpers'; import { timelineSelectors } from '../../../timelines/store/timeline'; import { timelineDefaults } from '../../../timelines/store/timeline/defaults'; -import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config'; +import { + buildShowBuildingBlockFilter, + buildThreatMatchFilter, +} from '../../components/alerts_table/default_config'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; import { NeedAdminForUpdateRulesCallOut } from '../../components/callouts/need_admin_for_update_callout'; @@ -100,6 +103,7 @@ const DetectionEnginePageComponent = () => { const [lastAlerts] = useAlertInfo({}); const { formatUrl } = useFormatUrl(SecurityPageName.detections); const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); + const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const loading = userInfoLoading || listsConfigLoading; const updateDateRangeCallback = useCallback( @@ -128,14 +132,21 @@ const DetectionEnginePageComponent = () => { ); const alertsHistogramDefaultFilters = useMemo( - () => [...filters, ...buildShowBuildingBlockFilter(showBuildingBlockAlerts)], - [filters, showBuildingBlockAlerts] + () => [ + ...filters, + ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), + ], + [filters, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); // AlertsTable manages global filters itself, so not including `filters` const alertsTableDefaultFilters = useMemo( - () => buildShowBuildingBlockFilter(showBuildingBlockAlerts), - [showBuildingBlockAlerts] + () => [ + ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), + ], + [showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); const onShowBuildingBlockAlertsChangedCallback = useCallback( @@ -145,6 +156,13 @@ const DetectionEnginePageComponent = () => { [setShowBuildingBlockAlerts] ); + const onShowOnlyThreatIndicatorAlertsCallback = useCallback( + (newShowOnlyThreatIndicatorAlerts: boolean) => { + setShowOnlyThreatIndicatorAlerts(newShowOnlyThreatIndicatorAlerts); + }, + [setShowOnlyThreatIndicatorAlerts] + ); + const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); const onSkipFocusBeforeEventsTable = useCallback(() => { @@ -250,6 +268,8 @@ const DetectionEnginePageComponent = () => { defaultFilters={alertsTableDefaultFilters} showBuildingBlockAlerts={showBuildingBlockAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} + onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsCallback} to={to} /> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index dddf8ac1bb839..a8d3742bfd600 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -59,6 +59,7 @@ import { StepScheduleRule } from '../../../../components/rules/step_schedule_rul import { buildAlertsRuleIdFilter, buildShowBuildingBlockFilter, + buildThreatMatchFilter, } from '../../../../components/alerts_table/default_config'; import { ReadOnlyAlertsCallOut } from '../../../../components/callouts/read_only_alerts_callout'; import { ReadOnlyRulesCallOut } from '../../../../components/callouts/read_only_rules_callout'; @@ -208,6 +209,7 @@ const RuleDetailsPageComponent = () => { }; const [lastAlerts] = useAlertInfo({ ruleId }); const [showBuildingBlockAlerts, setShowBuildingBlockAlerts] = useState(false); + const [showOnlyThreatIndicatorAlerts, setShowOnlyThreatIndicatorAlerts] = useState(false); const mlCapabilities = useMlCapabilities(); const history = useHistory(); const { formatUrl } = useFormatUrl(SecurityPageName.detections); @@ -286,10 +288,11 @@ const RuleDetailsPageComponent = () => { const alertDefaultFilters = useMemo( () => [ - ...(ruleId != null ? buildAlertsRuleIdFilter(ruleId) : []), + ...buildAlertsRuleIdFilter(ruleId), ...buildShowBuildingBlockFilter(showBuildingBlockAlerts), + ...buildThreatMatchFilter(showOnlyThreatIndicatorAlerts), ], - [ruleId, showBuildingBlockAlerts] + [ruleId, showBuildingBlockAlerts, showOnlyThreatIndicatorAlerts] ); const alertMergedFilters = useMemo(() => [...alertDefaultFilters, ...filters], [ @@ -446,6 +449,13 @@ const RuleDetailsPageComponent = () => { [setShowBuildingBlockAlerts] ); + const onShowOnlyThreatIndicatorAlertsCallback = useCallback( + (newShowOnlyThreatIndicatorAlerts: boolean) => { + setShowOnlyThreatIndicatorAlerts(newShowOnlyThreatIndicatorAlerts); + }, + [setShowOnlyThreatIndicatorAlerts] + ); + const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections); const exceptionLists = useMemo((): { @@ -670,7 +680,9 @@ const RuleDetailsPageComponent = () => { from={from} loading={loading} showBuildingBlockAlerts={showBuildingBlockAlerts} + showOnlyThreatIndicatorAlerts={showOnlyThreatIndicatorAlerts} onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback} + onShowOnlyThreatIndicatorAlertsChanged={onShowOnlyThreatIndicatorAlertsCallback} onRuleChange={refreshRule} to={to} /> From 22b53029e52ed1b91edecff6caee8dda64f41fba Mon Sep 17 00:00:00 2001 From: Dominique Clarke Date: Mon, 12 Apr 2021 17:49:54 -0400 Subject: [PATCH 45/79] [Uptime] Feature/migrate synthetics to ecs fields (#96369) * update get_network_events to use ecs fields --- .../common/runtime_types/network_events.ts | 6 +- .../waterfall/data_formatting.test.ts | 7 +- .../waterfall_chart_container.test.tsx | 205 ++++++++++++++++++ .../waterfall/waterfall_chart_container.tsx | 47 +++- .../waterfall_chart_wrapper.test.tsx | 5 - .../components/middle_truncated_text.tsx | 2 +- .../public/state/reducers/network_events.ts | 12 +- .../lib/requests/get_network_events.test.ts | 189 ++++++++-------- .../server/lib/requests/get_network_events.ts | 74 +++---- 9 files changed, 389 insertions(+), 158 deletions(-) create mode 100644 x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx diff --git a/x-pack/plugins/uptime/common/runtime_types/network_events.ts b/x-pack/plugins/uptime/common/runtime_types/network_events.ts index 7b651b6a91951..e896a165916fc 100644 --- a/x-pack/plugins/uptime/common/runtime_types/network_events.ts +++ b/x-pack/plugins/uptime/common/runtime_types/network_events.ts @@ -21,8 +21,8 @@ const NetworkTimingsType = t.type({ }); const CertificateDataType = t.partial({ - validFrom: t.number, - validTo: t.number, + validFrom: t.string, + validTo: t.string, issuer: t.string, subjectName: t.string, }); @@ -41,7 +41,6 @@ const NetworkEventType = t.intersection([ method: t.string, status: t.number, mimeType: t.string, - requestStartTime: t.number, responseHeaders: t.record(t.string, t.string), requestHeaders: t.record(t.string, t.string), timings: NetworkTimingsType, @@ -55,6 +54,7 @@ export type NetworkEvent = t.TypeOf; export const SyntheticsNetworkEventsApiResponseType = t.type({ events: t.array(NetworkEventType), total: t.number, + isWaterfallSupported: t.boolean, }); export type SyntheticsNetworkEventsApiResponse = t.TypeOf< diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts index 9376a83f48b3d..23270b1a8dd5b 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/data_formatting.test.ts @@ -30,7 +30,6 @@ export const networkItems: NetworkItems = [ status: 200, mimeType: 'text/css', requestSentTime: 18098833.175, - requestStartTime: 18098835.439, loadEndTime: 18098957.145, timings: { connect: 81.10800000213203, @@ -53,8 +52,8 @@ export const networkItems: NetworkItems = [ }, certificates: { issuer: 'Sample Issuer', - validFrom: 1578441600000, - validTo: 1617883200000, + validFrom: '2021-02-22T18:35:26.000Z', + validTo: '2021-04-05T22:28:43.000Z', subjectName: '*.elastic.co', }, ip: '104.18.8.22', @@ -66,7 +65,6 @@ export const networkItems: NetworkItems = [ status: 200, mimeType: 'application/javascript', requestSentTime: 18098833.537, - requestStartTime: 18098837.233999997, loadEndTime: 18098977.648000002, timings: { blocked: 84.54599999822676, @@ -152,7 +150,6 @@ export const networkItemsWithUncommonMimeType: NetworkItems = [ status: 200, mimeType: 'application/x-javascript', requestSentTime: 18098833.537, - requestStartTime: 18098837.233999997, loadEndTime: 18098977.648000002, timings: { blocked: 84.54599999822676, diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx new file mode 100644 index 0000000000000..b35fdb6100826 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.test.tsx @@ -0,0 +1,205 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; +import { render } from '../../../../../lib/helper/rtl_helpers'; +import { WaterfallChartContainer } from './waterfall_chart_container'; + +const networkEvents = { + events: [ + { + timestamp: '2021-01-21T10:31:21.537Z', + method: 'GET', + url: + 'https://apv-static.minute.ly/videos/v-c2a526c7-450d-428e-1244649-a390-fb639ffead96-s45.746-54.421m.mp4', + status: 206, + mimeType: 'video/mp4', + requestSentTime: 241114127.474, + loadEndTime: 241116573.402, + timings: { + total: 2445.928000001004, + queueing: 1.7399999778717756, + blocked: 0.391999987186864, + receive: 2283.964000031119, + connect: 91.5709999972023, + wait: 28.795999998692423, + proxy: -1, + dns: 36.952000024029985, + send: 0.10000000474974513, + ssl: 64.28900000173599, + }, + }, + { + timestamp: '2021-01-21T10:31:22.174Z', + method: 'GET', + url: 'https://dpm.demdex.net/ibs:dpid=73426&dpuuid=31597189268188866891125449924942215949', + status: 200, + mimeType: 'image/gif', + requestSentTime: 241114749.202, + loadEndTime: 241114805.541, + timings: { + queueing: 1.2240000069141388, + receive: 2.218999987235293, + proxy: -1, + dns: -1, + send: 0.14200000441633165, + blocked: 1.033000007737428, + total: 56.33900000248104, + wait: 51.72099999617785, + ssl: -1, + connect: -1, + }, + }, + { + timestamp: '2021-01-21T10:31:21.679Z', + method: 'GET', + url: 'https://dapi.cms.mlbinfra.com/v2/content/en-us/sel-t119-homepage-mediawall', + status: 200, + mimeType: 'application/json', + requestSentTime: 241114268.04299998, + loadEndTime: 241114665.609, + timings: { + total: 397.5659999996424, + dns: 29.5429999823682, + wait: 221.6830000106711, + queueing: 2.1410000044852495, + connect: 106.95499999565072, + ssl: 69.06899999012239, + receive: 2.027999988058582, + blocked: 0.877000013133511, + send: 23.719999997410923, + proxy: -1, + }, + }, + { + timestamp: '2021-01-21T10:31:21.740Z', + method: 'GET', + url: 'https://platform.twitter.com/embed/embed.runtime.b313577971db9c857801.js', + status: 200, + mimeType: 'application/javascript', + requestSentTime: 241114303.84899998, + loadEndTime: 241114370.361, + timings: { + send: 1.357000001007691, + wait: 40.12299998430535, + receive: 16.78500001435168, + ssl: -1, + queueing: 2.5670000177342445, + total: 66.51200001942925, + connect: -1, + blocked: 5.680000002030283, + proxy: -1, + dns: -1, + }, + }, + { + timestamp: '2021-01-21T10:31:21.740Z', + method: 'GET', + url: 'https://platform.twitter.com/embed/embed.modules.7a266e7acfd42f2581a5.js', + status: 200, + mimeType: 'application/javascript', + requestSentTime: 241114305.939, + loadEndTime: 241114938.264, + timings: { + wait: 51.61500000394881, + dns: -1, + ssl: -1, + receive: 506.5750000067055, + proxy: -1, + connect: -1, + blocked: 69.51599998865277, + queueing: 4.453999979887158, + total: 632.324999984121, + send: 0.16500000492669642, + }, + }, + ], +}; + +const defaultState = { + networkEvents: { + test: { + '1': { + ...networkEvents, + total: 100, + isWaterfallSupported: true, + loading: false, + }, + }, + }, +}; + +describe('WaterfallChartContainer', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + it('does not display waterfall chart unavailable when isWaterfallSupported is true', () => { + render(, { + state: defaultState, + }); + expect(screen.queryByText('Waterfall chart unavailable')).not.toBeInTheDocument(); + }); + + it('displays waterfall chart unavailable when isWaterfallSupported is false', () => { + const state = { + networkEvents: { + test: { + '1': { + ...networkEvents, + total: 100, + isWaterfallSupported: false, + loading: false, + }, + }, + }, + }; + render(, { + state, + }); + expect(screen.getByText('Waterfall chart unavailable')).toBeInTheDocument(); + }); + + it('displays loading bar when loading', () => { + const state = { + networkEvents: { + test: { + '1': { + ...networkEvents, + total: 100, + isWaterfallSupported: false, + loading: true, + }, + }, + }, + }; + render(, { + state, + }); + expect(screen.getByLabelText('Waterfall chart loading')).toBeInTheDocument(); + }); + + it('displays no data available message when no events are available', () => { + const state = { + networkEvents: { + test: { + '1': { + events: [], + total: 0, + isWaterfallSupported: true, + loading: false, + }, + }, + }, + }; + render(, { + state, + }); + expect(screen.getByText('No waterfall data could be found for this step')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx index 43f822726a4fa..044353125e748 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_container.tsx @@ -5,8 +5,9 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingChart } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiText, EuiLoadingChart, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import React, { useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { getNetworkEvents } from '../../../../../state/actions/network_events'; @@ -39,18 +40,25 @@ export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex const _networkEvents = useSelector(networkEventsSelector); const networkEvents = _networkEvents[checkGroup ?? '']?.[stepIndex]; + const waterfallLoaded = networkEvents && !networkEvents.loading; + const isWaterfallSupported = networkEvents?.isWaterfallSupported; + const hasEvents = networkEvents?.events?.length > 0; return ( <> - {!networkEvents || - (networkEvents.loading && ( - - - - - - ))} - {networkEvents && !networkEvents.loading && networkEvents.events.length === 0 && ( + {!waterfallLoaded && ( + + + + + + )} + {waterfallLoaded && !hasEvents && ( @@ -59,12 +67,29 @@ export const WaterfallChartContainer: React.FC = ({ checkGroup, stepIndex )} - {networkEvents && !networkEvents.loading && networkEvents.events.length > 0 && ( + {waterfallLoaded && hasEvents && isWaterfallSupported && ( )} + {waterfallLoaded && hasEvents && !isWaterfallSupported && ( + + } + color="warning" + iconType="help" + > + + + )} ); }; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx index 47c18225f38d3..3a0a30980ab52 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/waterfall/waterfall_chart_wrapper.test.tsx @@ -204,7 +204,6 @@ const NETWORK_EVENTS = { status: 206, mimeType: 'video/mp4', requestSentTime: 241114127.474, - requestStartTime: 241114129.214, loadEndTime: 241116573.402, timings: { total: 2445.928000001004, @@ -226,7 +225,6 @@ const NETWORK_EVENTS = { status: 200, mimeType: 'image/gif', requestSentTime: 241114749.202, - requestStartTime: 241114750.426, loadEndTime: 241114805.541, timings: { queueing: 1.2240000069141388, @@ -248,7 +246,6 @@ const NETWORK_EVENTS = { status: 200, mimeType: 'application/json', requestSentTime: 241114268.04299998, - requestStartTime: 241114270.184, loadEndTime: 241114665.609, timings: { total: 397.5659999996424, @@ -270,7 +267,6 @@ const NETWORK_EVENTS = { status: 200, mimeType: 'application/javascript', requestSentTime: 241114303.84899998, - requestStartTime: 241114306.416, loadEndTime: 241114370.361, timings: { send: 1.357000001007691, @@ -292,7 +288,6 @@ const NETWORK_EVENTS = { status: 200, mimeType: 'application/javascript', requestSentTime: 241114305.939, - requestStartTime: 241114310.393, loadEndTime: 241114938.264, timings: { wait: 51.61500000394881, diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx index a0993d54bbd07..4881fdb6e6b85 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx @@ -60,7 +60,7 @@ const StyledButton = styled(EuiButtonEmpty)` } `; -export const getChunks = (text: string) => { +export const getChunks = (text: string = '') => { const END_CHARS = 12; const chars = text.split(''); const splitPoint = chars.length - END_CHARS > 0 ? chars.length - END_CHARS : null; diff --git a/x-pack/plugins/uptime/public/state/reducers/network_events.ts b/x-pack/plugins/uptime/public/state/reducers/network_events.ts index 60f111cb4fafe..56fef5947fb0e 100644 --- a/x-pack/plugins/uptime/public/state/reducers/network_events.ts +++ b/x-pack/plugins/uptime/public/state/reducers/network_events.ts @@ -22,6 +22,7 @@ export interface NetworkEventsState { total: number; loading: boolean; error?: Error; + isWaterfallSupported: boolean; }; }; } @@ -48,11 +49,13 @@ export const networkEventsReducer = handleActions( loading: true, events: [], total: 0, + isWaterfallSupported: true, } : { loading: true, events: [], total: 0, + isWaterfallSupported: true, }, } : { @@ -60,6 +63,7 @@ export const networkEventsReducer = handleActions( loading: true, events: [], total: 0, + isWaterfallSupported: true, }, }, }), @@ -67,7 +71,7 @@ export const networkEventsReducer = handleActions( [String(getNetworkEventsSuccess)]: ( state: NetworkEventsState, { - payload: { events, total, checkGroup, stepIndex }, + payload: { events, total, checkGroup, stepIndex, isWaterfallSupported }, }: Action ) => { return { @@ -80,11 +84,13 @@ export const networkEventsReducer = handleActions( loading: false, events, total, + isWaterfallSupported, } : { loading: false, events, total, + isWaterfallSupported, }, } : { @@ -92,6 +98,7 @@ export const networkEventsReducer = handleActions( loading: false, events, total, + isWaterfallSupported, }, }, }; @@ -111,12 +118,14 @@ export const networkEventsReducer = handleActions( events: [], total: 0, error, + isWaterfallSupported: true, } : { loading: false, events: [], total: 0, error, + isWaterfallSupported: true, }, } : { @@ -125,6 +134,7 @@ export const networkEventsReducer = handleActions( events: [], total: 0, error, + isWaterfallSupported: true, }, }, }), diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts index a9c29012141da..d806606abcb13 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts @@ -52,96 +52,6 @@ describe('getNetworkEvents', () => { type: 'Image', request_sent_time: 3287.154973, url: 'www.test.com', - request: { - initial_priority: 'Low', - referrer_policy: 'no-referrer-when-downgrade', - url: 'www.test.com', - method: 'GET', - headers: { - referer: 'www.test.com', - user_agent: - 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4324.0 Safari/537.36', - }, - mixed_content_type: 'none', - }, - response: { - from_service_worker: false, - security_details: { - protocol: 'TLS 1.2', - key_exchange: 'ECDHE_RSA', - valid_to: 1638230399, - certificate_transparency_compliance: 'unknown', - cipher: 'AES_128_GCM', - issuer: 'DigiCert TLS RSA SHA256 2020 CA1', - subject_name: 'syndication.twitter.com', - valid_from: 1606694400, - signed_certificate_timestamp_list: [], - key_exchange_group: 'P-256', - san_list: [ - 'syndication.twitter.com', - 'syndication.twimg.com', - 'cdn.syndication.twitter.com', - 'cdn.syndication.twimg.com', - 'syndication-o.twitter.com', - 'syndication-o.twimg.com', - ], - certificate_id: 0, - }, - security_state: 'secure', - connection_reused: true, - remote_port: 443, - timing: { - ssl_start: -1, - send_start: 0.214, - ssl_end: -1, - connect_start: -1, - connect_end: -1, - send_end: 0.402, - dns_start: -1, - request_time: 3287.155502, - push_end: 0, - worker_fetch_start: -1, - worker_ready: -1, - worker_start: -1, - proxy_end: -1, - push_start: 0, - worker_respond_with_settled: -1, - proxy_start: -1, - dns_end: -1, - receive_headers_end: 142.215, - }, - connection_id: 852, - remote_i_p_address: '104.244.42.200', - encoded_data_length: 337, - response_time: 1.60794279932414e12, - from_prefetch_cache: false, - mime_type: 'image/gif', - from_disk_cache: false, - url: 'www.test.com', - protocol: 'h2', - headers: { - x_frame_options: 'SAMEORIGIN', - cache_control: 'no-cache, no-store, must-revalidate, pre-check=0, post-check=0', - strict_transport_security: 'max-age=631138519', - x_twitter_response_tags: 'BouncerCompliant', - content_type: 'image/gif;charset=utf-8', - expires: 'Tue, 31 Mar 1981 05:00:00 GMT', - date: 'Mon, 14 Dec 2020 10:46:39 GMT', - x_transaction: '008fff3d00a1e64c', - x_connection_hash: 'cb6fe99b8676f4e4b827cc3e6512c90d', - last_modified: 'Mon, 14 Dec 2020 10:46:39 GMT', - x_content_type_options: 'nosniff', - content_encoding: 'gzip', - x_xss_protection: '0', - server: 'tsa_f', - x_response_time: '108', - pragma: 'no-cache', - content_length: '65', - status: '200 OK', - }, - status_text: '', - status: 200, - }, timings: { proxy: -1, connect: -1, @@ -158,6 +68,99 @@ describe('getNetworkEvents', () => { timestamp: 1607942799183375, }, }, + http: { + response: { + from_service_worker: false, + security_state: 'secure', + connection_reused: true, + remote_port: 443, + timing: { + ssl_start: -1, + send_start: 0.214, + ssl_end: -1, + connect_start: -1, + connect_end: -1, + send_end: 0.402, + dns_start: -1, + request_time: 3287.155502, + push_end: 0, + worker_fetch_start: -1, + worker_ready: -1, + worker_start: -1, + proxy_end: -1, + push_start: 0, + worker_respond_with_settled: -1, + proxy_start: -1, + dns_end: -1, + receive_headers_end: 142.215, + }, + connection_id: 852, + remote_i_p_address: '104.244.42.200', + encoded_data_length: 337, + response_time: 1.60794279932414e12, + from_prefetch_cache: false, + mime_type: 'image/gif', + from_disk_cache: false, + url: 'www.test.com', + protocol: 'h2', + headers: { + x_frame_options: 'SAMEORIGIN', + cache_control: 'no-cache, no-store, must-revalidate, pre-check=0, post-check=0', + strict_transport_security: 'max-age=631138519', + x_twitter_response_tags: 'BouncerCompliant', + content_type: 'image/gif;charset=utf-8', + expires: 'Tue, 31 Mar 1981 05:00:00 GMT', + date: 'Mon, 14 Dec 2020 10:46:39 GMT', + x_transaction: '008fff3d00a1e64c', + x_connection_hash: 'cb6fe99b8676f4e4b827cc3e6512c90d', + last_modified: 'Mon, 14 Dec 2020 10:46:39 GMT', + x_content_type_options: 'nosniff', + content_encoding: 'gzip', + x_xss_protection: '0', + server: 'tsa_f', + x_response_time: '108', + pragma: 'no-cache', + content_length: '65', + status: '200 OK', + }, + status_text: '', + status: 200, + }, + version: 2, + request: { + initial_priority: 'Low', + referrer_policy: 'no-referrer-when-downgrade', + url: 'www.test.com', + method: 'GET', + headers: { + referer: 'www.test.com', + user_agent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4324.0 Safari/537.36', + }, + mixed_content_type: 'none', + }, + }, + tls: { + server: { + x509: { + subject: { + common_name: 'syndication.twitter.com', + }, + issuer: { + common_name: 'DigiCert TLS RSA SHA256 2020 CA1', + }, + not_before: '2021-02-22T18:35:26.000Z', + not_after: '2021-04-05T22:28:43.000Z', + }, + }, + }, + url: { + port: 443, + path: '', + full: 'www.test.com', + scheme: 'http', + domain: 'www.test.com', + }, }, }, ]; @@ -243,8 +246,8 @@ describe('getNetworkEvents', () => { "certificates": Object { "issuer": "DigiCert TLS RSA SHA256 2020 CA1", "subjectName": "syndication.twitter.com", - "validFrom": 1606694400000, - "validTo": 1638230399000, + "validFrom": "2021-02-22T18:35:26.000Z", + "validTo": "2021-04-05T22:28:43.000Z", }, "ip": "104.244.42.200", "loadEndTime": 3287298.251, @@ -255,7 +258,6 @@ describe('getNetworkEvents', () => { "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4324.0 Safari/537.36", }, "requestSentTime": 3287154.973, - "requestStartTime": 3287155.502, "responseHeaders": Object { "cache_control": "no-cache, no-store, must-revalidate, pre-check=0, post-check=0", "content_encoding": "gzip", @@ -293,6 +295,7 @@ describe('getNetworkEvents', () => { "url": "www.test.com", }, ], + "isWaterfallSupported": true, "total": 1, } `); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts index 246b2001a9381..2741062cc4038 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts @@ -20,7 +20,7 @@ export const secondsToMillis = (seconds: number) => export const getNetworkEvents: UMElasticsearchQueryFn< GetNetworkEventsParams, - { events: NetworkEvent[]; total: number } + { events: NetworkEvent[]; total: number; isWaterfallSupported: boolean } > = async ({ uptimeEsClient, checkGroup, stepIndex }) => { const params = { track_total_hits: true, @@ -40,46 +40,42 @@ export const getNetworkEvents: UMElasticsearchQueryFn< }; const { body: result } = await uptimeEsClient.search({ body: params }); + let isWaterfallSupported = false; + const events = result.hits.hits.map((event: any) => { + if (event._source.http && event._source.url) { + isWaterfallSupported = true; + } + const requestSentTime = secondsToMillis(event._source.synthetics.payload.request_sent_time); + const loadEndTime = secondsToMillis(event._source.synthetics.payload.load_end_time); + const securityDetails = event._source.tls?.server?.x509; + + return { + timestamp: event._source['@timestamp'], + method: event._source.http?.request?.method, + url: event._source.url?.full, + status: event._source.http?.response?.status, + mimeType: event._source.http?.response?.mime_type, + requestSentTime, + loadEndTime, + timings: event._source.synthetics.payload.timings, + bytesDownloadedCompressed: event._source.http?.response?.encoded_data_length, + certificates: securityDetails + ? { + issuer: securityDetails.issuer?.common_name, + subjectName: securityDetails.subject.common_name, + validFrom: securityDetails.not_before, + validTo: securityDetails.not_after, + } + : undefined, + requestHeaders: event._source.http?.request?.headers, + responseHeaders: event._source.http?.response?.headers, + ip: event._source.http?.response?.remote_i_p_address, + }; + }); return { total: result.hits.total.value, - events: result.hits.hits.map((event: any) => { - const requestSentTime = secondsToMillis(event._source.synthetics.payload.request_sent_time); - const loadEndTime = secondsToMillis(event._source.synthetics.payload.load_end_time); - const requestStartTime = - event._source.synthetics.payload.response && - event._source.synthetics.payload.response.timing - ? secondsToMillis(event._source.synthetics.payload.response.timing.request_time) - : undefined; - const securityDetails = event._source.synthetics.payload.response?.security_details; - - return { - timestamp: event._source['@timestamp'], - method: event._source.synthetics.payload?.method, - url: event._source.synthetics.payload?.url, - status: event._source.synthetics.payload?.status, - mimeType: event._source.synthetics.payload?.response?.mime_type, - requestSentTime, - requestStartTime, - loadEndTime, - timings: event._source.synthetics.payload.timings, - bytesDownloadedCompressed: event._source.synthetics.payload.response?.encoded_data_length, - certificates: securityDetails - ? { - issuer: securityDetails.issuer, - subjectName: securityDetails.subject_name, - validFrom: securityDetails.valid_from - ? secondsToMillis(securityDetails.valid_from) - : undefined, - validTo: securityDetails.valid_to - ? secondsToMillis(securityDetails.valid_to) - : undefined, - } - : undefined, - requestHeaders: event._source.synthetics.payload.request?.headers, - responseHeaders: event._source.synthetics.payload.response?.headers, - ip: event._source.synthetics.payload.response?.remote_i_p_address, - }; - }), + events, + isWaterfallSupported, }; }; From 4d593bbc086a255db20f64a666d2f5566f982108 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Mon, 12 Apr 2021 17:27:23 -0500 Subject: [PATCH 46/79] [Workplace Search] Design polish: Groups, Security and Custom source (#96870) * Add missing i18n Oops * Change button color * Fix custom source created screen * Add better empty state to groups * Align toggle to right side of table * Update design for security page --- .../components/add_source/save_custom.tsx | 3 +- .../groups/components/group_overview.test.tsx | 15 +++------ .../groups/components/group_overview.tsx | 31 ++++++++++++++++--- .../workplace_search/views/groups/groups.tsx | 2 +- .../components/private_sources_table.tsx | 2 +- .../views/security/security.tsx | 9 ++++-- 6 files changed, 41 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index 1bf8239a6b399..9689ecfae4a94 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -62,9 +62,10 @@ export const SaveCustom: React.FC = ({ }) => ( <> {header} + - + diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx index e39d72a861b6f..8d5714fd05792 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.test.tsx @@ -12,18 +12,14 @@ import React from 'react'; import { shallow } from 'enzyme'; -import { EuiFieldText } from '@elastic/eui'; +import { EuiFieldText, EuiEmptyPrompt } from '@elastic/eui'; import { Loading } from '../../../../shared/loading'; import { ContentSection } from '../../../components/shared/content_section'; import { SourcesTable } from '../../../components/shared/sources_table'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; -import { - GroupOverview, - EMPTY_SOURCES_DESCRIPTION, - EMPTY_USERS_DESCRIPTION, -} from './group_overview'; +import { GroupOverview } from './group_overview'; const deleteGroup = jest.fn(); const showSharedSourcesModal = jest.fn(); @@ -92,7 +88,7 @@ describe('GroupOverview', () => { expect(updateGroupName).toHaveBeenCalled(); }); - it('renders empty state messages', () => { + it('renders empty state', () => { setMockValues({ ...mockValues, group: { @@ -103,10 +99,7 @@ describe('GroupOverview', () => { }); const wrapper = shallow(); - const sourcesSection = wrapper.find('[data-test-subj="GroupContentSourcesSection"]') as any; - const usersSection = wrapper.find('[data-test-subj="GroupUsersSection"]') as any; - expect(sourcesSection.prop('description')).toEqual(EMPTY_SOURCES_DESCRIPTION); - expect(usersSection.prop('description')).toEqual(EMPTY_USERS_DESCRIPTION); + expect(wrapper.find(EuiEmptyPrompt)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index 375ac7476f9b6..364ca0ba47256 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -12,10 +12,12 @@ import { useActions, useValues } from 'kea'; import { EuiButton, EuiConfirmModal, + EuiEmptyPrompt, EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiPanel, EuiSpacer, EuiHorizontalRule, } from '@elastic/eui'; @@ -24,6 +26,7 @@ import { i18n } from '@kbn/i18n'; import { Loading } from '../../../../shared/loading'; import { TruncatedContent } from '../../../../shared/truncate'; import { AppLogic } from '../../../app_logic'; +import noSharedSourcesIcon from '../../../assets/share_circle.svg'; import { ContentSection } from '../../../components/shared/content_section'; import { SourcesTable } from '../../../components/shared/sources_table'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; @@ -145,6 +148,12 @@ export const GroupOverview: React.FC = () => { values: { name }, } ); + const GROUP_SOURCES_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesTitle', + { + defaultMessage: 'Group content sources', + } + ); const GROUP_SOURCES_DESCRIPTION = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.groupSourcesDescription', { @@ -170,15 +179,29 @@ export const GroupOverview: React.FC = () => { const sourcesSection = ( - {hasContentSources && sourcesTable} + {sourcesTable} ); + const sourcesEmptyState = ( + <> + + {GROUP_SOURCES_TITLE}

} + body={

{EMPTY_SOURCES_DESCRIPTION}

} + actions={manageSourcesButton} + /> +
+ + + ); + const usersSection = !isFederatedAuth && ( { <> - {sourcesSection} + {hasContentSources ? sourcesSection : sourcesEmptyState} {usersSection} {nameSection} {canDeleteGroup && deleteSection} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx index b2bf0364b2d1f..b82e141bc810e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/groups.tsx @@ -60,7 +60,7 @@ export const Groups: React.FC = () => { messages[0].description = ( {i18n.translate('xpack.enterpriseSearch.workplaceSearch.groups.newGroup.action', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx index 312745ee7496c..68f2a2289c1f2 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/security/components/private_sources_table.tsx @@ -152,7 +152,7 @@ export const PrivateSourcesTable: React.FC = ({ {contentSources.map((source, i) => ( {source.name} - + { { messageText={SECURITY_UNSAVED_CHANGES_MESSAGE} /> {header} - {allSourcesToggle} - {!hasPlatinumLicense && platinumLicenseCallout} - {sourceTables} + + {allSourcesToggle} + {!hasPlatinumLicense && platinumLicenseCallout} + {sourceTables} + {confirmModalVisible && confirmModal} ); From 39f87f45600f0c1b6257a5b63ab9012c9f8e846c Mon Sep 17 00:00:00 2001 From: Ryland Herrick Date: Mon, 12 Apr 2021 17:52:42 -0500 Subject: [PATCH 47/79] [Security Solution][Timeline] Rebuild nested fields structure from fields response (#96187) * First pass at rebuilding nested object structure from fields response * Always requests TIMELINE_CTI_FIELDS as part of request This only works for one level of nesting; will be extending tests to allow for multiple levels momentarily. * Build objects from arbitrary levels of nesting This is a recursive implementation, but recursion depth is limited to the number of levels of nesting, with arguments reducing in size as we go (i.e. logarithmic) * Simplify parsing logic, perf improvements * Order short-circuiting conditions by cost, ascending * Simplify object building for non-nested objects from fields * The non-nested case is the same as the base recursive case, so always call our recursive function if building from .fields * Simplify getNestedParentPath * We can do a few simple string comparison rather than building up multiple strings/arrays * Don't call getNestedParentPath unnecessarily, only if we have a field * Simplify if branching By definition, nestedParentFieldName can never be equal to fieldName, which means there are only two branches here. * Declare/export a more accurate fields type Each top-level field value can be either an array of leaf values (unknown[]), or an array of nested fields. * Remove unnecessary condition If fieldName is null or undefined, there is no reason to search for it in dataFields. Looking through the git history this looks to be dead code as a result of refactoring, as opposed to a legitimate bugfix, so I'm removing it. * Fix failing tests * one was a test failure due to my modifying mock data * one may have been a legitimate bug where we don't handle a hit without a fields response; I need to follow up with Xavier to verify. Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../matrix_histogram/events/index.ts | 4 +- .../common/utils/mock_event_details.ts | 6 +- .../timeline/factory/events/all/constants.ts | 10 + .../factory/events/all/helpers.test.ts | 209 +++++++++++++++++- .../timeline/factory/events/all/helpers.ts | 65 ++++-- 5 files changed, 260 insertions(+), 34 deletions(-) diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts index 53cdc7239f69d..b2e0461b0b9b8 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/matrix_histogram/events/index.ts @@ -26,10 +26,12 @@ export interface EventsActionGroupData { doc_count: number; } +export type Fields = Record; + export interface EventHit extends SearchHit { sort: string[]; _source: EventSource; - fields: Record; + fields: Fields; aggregations: { // eslint-disable-next-line @typescript-eslint/no-explicit-any [agg: string]: any; diff --git a/x-pack/plugins/security_solution/common/utils/mock_event_details.ts b/x-pack/plugins/security_solution/common/utils/mock_event_details.ts index 13b7fe7051246..7dc257ebb3fef 100644 --- a/x-pack/plugins/security_solution/common/utils/mock_event_details.ts +++ b/x-pack/plugins/security_solution/common/utils/mock_event_details.ts @@ -40,7 +40,7 @@ export const eventHit = { 'source.geo.location': [{ coordinates: [118.7778, 32.0617], type: 'Point' }], 'threat.indicator': [ { - 'matched.field': ['matched_field'], + 'matched.field': ['matched_field', 'other_matched_field'], first_seen: ['2021-02-22T17:29:25.195Z'], provider: ['yourself'], type: ['custom'], @@ -259,8 +259,8 @@ export const eventDetailsFormattedFields = [ { category: 'threat', field: 'threat.indicator.matched.field', - values: ['matched_field', 'matched_field_2'], - originalValue: ['matched_field', 'matched_field_2'], + values: ['matched_field', 'other_matched_field', 'matched_field_2'], + originalValue: ['matched_field', 'other_matched_field', 'matched_field_2'], isObjectArray: false, }, { diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts index 15d0e2d5494b8..29b0df9e4bbf7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/constants.ts @@ -5,6 +5,15 @@ * 2.0. */ +export const TIMELINE_CTI_FIELDS = [ + 'threat.indicator.event.dataset', + 'threat.indicator.event.reference', + 'threat.indicator.matched.atomic', + 'threat.indicator.matched.field', + 'threat.indicator.matched.type', + 'threat.indicator.provider', +]; + export const TIMELINE_EVENTS_FIELDS = [ '@timestamp', 'signal.status', @@ -230,4 +239,5 @@ export const TIMELINE_EVENTS_FIELDS = [ 'zeek.ssl.established', 'zeek.ssl.resumed', 'zeek.ssl.version', + ...TIMELINE_CTI_FIELDS, ]; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts index 405ddba137dae..da19df32ac87a 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.test.ts @@ -5,10 +5,10 @@ * 2.0. */ +import { eventHit } from '../../../../../../common/utils/mock_event_details'; import { EventHit } from '../../../../../../common/search_strategy'; import { TIMELINE_EVENTS_FIELDS } from './constants'; -import { formatTimelineData } from './helpers'; -import { eventHit } from '../../../../../../common/utils/mock_event_details'; +import { buildObjectForFieldPath, formatTimelineData } from './helpers'; describe('#formatTimelineData', () => { it('happy path', async () => { @@ -42,12 +42,12 @@ describe('#formatTimelineData', () => { value: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'], }, { - field: 'source.geo.location', - value: [`{"lon":118.7778,"lat":32.0617}`], + field: 'threat.indicator.matched.field', + value: ['matched_field', 'other_matched_field', 'matched_field_2'], }, { - field: 'threat.indicator.matched.field', - value: ['matched_field', 'matched_field_2'], + field: 'source.geo.location', + value: [`{"lon":118.7778,"lat":32.0617}`], }, ], ecs: { @@ -94,6 +94,34 @@ describe('#formatTimelineData', () => { user: { name: ['jenkins'], }, + threat: { + indicator: [ + { + event: { + dataset: [], + reference: [], + }, + matched: { + atomic: ['matched_atomic'], + field: ['matched_field', 'other_matched_field'], + type: [], + }, + provider: ['yourself'], + }, + { + event: { + dataset: [], + reference: [], + }, + matched: { + atomic: ['matched_atomic_2'], + field: ['matched_field_2'], + type: [], + }, + provider: ['other_you'], + }, + ], + }, }, }, }); @@ -371,4 +399,173 @@ describe('#formatTimelineData', () => { }, }); }); + + describe('buildObjectForFieldPath', () => { + it('builds an object from a single non-nested field', () => { + expect(buildObjectForFieldPath('@timestamp', eventHit)).toEqual({ + '@timestamp': ['2020-11-17T14:48:08.922Z'], + }); + }); + + it('builds an object with no fields response', () => { + const { fields, ...fieldLessHit } = eventHit; + // @ts-expect-error fieldLessHit is intentionally missing fields + expect(buildObjectForFieldPath('@timestamp', fieldLessHit)).toEqual({ + '@timestamp': [], + }); + }); + + it('does not misinterpret non-nested fields with a common prefix', () => { + // @ts-expect-error hit is minimal + const hit: EventHit = { + fields: { + 'foo.bar': ['baz'], + 'foo.barBaz': ['foo'], + }, + }; + + expect(buildObjectForFieldPath('foo.barBaz', hit)).toEqual({ + foo: { barBaz: ['foo'] }, + }); + }); + + it('builds an array of objects from a nested field', () => { + // @ts-expect-error hit is minimal + const hit: EventHit = { + fields: { + foo: [{ bar: ['baz'] }], + }, + }; + expect(buildObjectForFieldPath('foo.bar', hit)).toEqual({ + foo: [{ bar: ['baz'] }], + }); + }); + + it('builds intermediate objects for nested fields', () => { + // @ts-expect-error nestedHit is minimal + const nestedHit: EventHit = { + fields: { + 'foo.bar': [ + { + baz: ['host.name'], + }, + ], + }, + }; + expect(buildObjectForFieldPath('foo.bar.baz', nestedHit)).toEqual({ + foo: { + bar: [ + { + baz: ['host.name'], + }, + ], + }, + }); + }); + + it('builds intermediate objects at multiple levels', () => { + expect(buildObjectForFieldPath('threat.indicator.matched.atomic', eventHit)).toEqual({ + threat: { + indicator: [ + { + matched: { + atomic: ['matched_atomic'], + }, + }, + { + matched: { + atomic: ['matched_atomic_2'], + }, + }, + ], + }, + }); + }); + + it('preserves multiple values for a single leaf', () => { + expect(buildObjectForFieldPath('threat.indicator.matched.field', eventHit)).toEqual({ + threat: { + indicator: [ + { + matched: { + field: ['matched_field', 'other_matched_field'], + }, + }, + { + matched: { + field: ['matched_field_2'], + }, + }, + ], + }, + }); + }); + + describe('multiple levels of nested fields', () => { + let nestedHit: EventHit; + + beforeEach(() => { + // @ts-expect-error nestedHit is minimal + nestedHit = { + fields: { + 'nested_1.foo': [ + { + 'nested_2.bar': [ + { leaf: ['leaf_value'], leaf_2: ['leaf_2_value'] }, + { leaf_2: ['leaf_2_value_2', 'leaf_2_value_3'] }, + ], + }, + { + 'nested_2.bar': [ + { leaf: ['leaf_value_2'], leaf_2: ['leaf_2_value_4'] }, + { leaf: ['leaf_value_3'], leaf_2: ['leaf_2_value_5'] }, + ], + }, + ], + }, + }; + }); + + it('includes objects without the field', () => { + expect(buildObjectForFieldPath('nested_1.foo.nested_2.bar.leaf', nestedHit)).toEqual({ + nested_1: { + foo: [ + { + nested_2: { + bar: [{ leaf: ['leaf_value'] }, { leaf: [] }], + }, + }, + { + nested_2: { + bar: [{ leaf: ['leaf_value_2'] }, { leaf: ['leaf_value_3'] }], + }, + }, + ], + }, + }); + }); + + it('groups multiple leaf values', () => { + expect(buildObjectForFieldPath('nested_1.foo.nested_2.bar.leaf_2', nestedHit)).toEqual({ + nested_1: { + foo: [ + { + nested_2: { + bar: [ + { leaf_2: ['leaf_2_value'] }, + { leaf_2: ['leaf_2_value_2', 'leaf_2_value_3'] }, + ], + }, + }, + { + nested_2: { + bar: [{ leaf_2: ['leaf_2_value_4'] }, { leaf_2: ['leaf_2_value_5'] }], + }, + }, + ], + }, + }); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts index 2c18fb2840865..6c20843058ff1 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/all/helpers.ts @@ -5,9 +5,12 @@ * 2.0. */ +import { set } from '@elastic/safer-lodash-set'; import { get, has, merge, uniq } from 'lodash/fp'; +import { Ecs } from '../../../../../../common/ecs'; import { EventHit, + Fields, TimelineEdges, TimelineNonEcsData, } from '../../../../../../common/search_strategy'; @@ -78,18 +81,13 @@ const getValuesFromFields = async ( [fieldName]: get(fieldName, hit._source), }; } else { - if (nestedParentFieldName == null || nestedParentFieldName === fieldName) { + if (nestedParentFieldName == null) { fieldToEval = { [fieldName]: hit.fields[fieldName], }; - } else if (nestedParentFieldName != null) { - fieldToEval = { - [nestedParentFieldName]: hit.fields[nestedParentFieldName], - }; } else { - // fallback, should never hit fieldToEval = { - [fieldName]: [], + [nestedParentFieldName]: hit.fields[nestedParentFieldName], }; } } @@ -102,6 +100,37 @@ const getValuesFromFields = async ( ); }; +const buildObjectRecursive = (fieldPath: string, fields: Fields): Partial => { + const nestedParentPath = getNestedParentPath(fieldPath, fields); + if (!nestedParentPath) { + return set({}, fieldPath, toStringArray(get(fieldPath, fields))); + } + + const subPath = fieldPath.replace(`${nestedParentPath}.`, ''); + const subFields = (get(nestedParentPath, fields) ?? []) as Fields[]; + return set( + {}, + nestedParentPath, + subFields.map((subField) => buildObjectRecursive(subPath, subField)) + ); +}; + +export const buildObjectForFieldPath = (fieldPath: string, hit: EventHit): Partial => { + if (has(fieldPath, hit._source)) { + const value = get(fieldPath, hit._source); + return set({}, fieldPath, toStringArray(value)); + } + + return buildObjectRecursive(fieldPath, hit.fields); +}; + +/** + * If a prefix of our full field path is present as a field, we know that our field is nested + */ +const getNestedParentPath = (fieldPath: string, fields: Fields | undefined): string | undefined => + fields && + Object.keys(fields).find((field) => field !== fieldPath && fieldPath.startsWith(`${field}.`)); + const mergeTimelineFieldsWithHit = async ( fieldName: string, flattenedFields: T, @@ -109,15 +138,12 @@ const mergeTimelineFieldsWithHit = async ( dataFields: readonly string[], ecsFields: readonly string[] ) => { - if (fieldName != null || dataFields.includes(fieldName)) { - const fieldNameAsArray = fieldName.split('.'); - const nestedParentFieldName = Object.keys(hit.fields ?? []).find((f) => { - return f === fieldNameAsArray.slice(0, f.split('.').length).join('.'); - }); + if (fieldName != null) { + const nestedParentPath = getNestedParentPath(fieldName, hit.fields); if ( + nestedParentPath != null || has(fieldName, hit._source) || has(fieldName, hit.fields) || - nestedParentFieldName != null || specialFields.includes(fieldName) ) { const objectWithProperty = { @@ -126,22 +152,13 @@ const mergeTimelineFieldsWithHit = async ( data: dataFields.includes(fieldName) ? [ ...get('node.data', flattenedFields), - ...(await getValuesFromFields(fieldName, hit, nestedParentFieldName)), + ...(await getValuesFromFields(fieldName, hit, nestedParentPath)), ] : get('node.data', flattenedFields), ecs: ecsFields.includes(fieldName) ? { ...get('node.ecs', flattenedFields), - // @ts-expect-error - ...fieldName.split('.').reduceRight( - // @ts-expect-error - (obj, next) => ({ [next]: obj }), - toStringArray( - has(fieldName, hit._source) - ? get(fieldName, hit._source) - : hit.fields[fieldName] - ) - ), + ...buildObjectForFieldPath(fieldName, hit), } : get('node.ecs', flattenedFields), }, From f4bc0d61a1d17926655abf78fd2e16e87cb47e7a Mon Sep 17 00:00:00 2001 From: Constance Date: Mon, 12 Apr 2021 15:55:04 -0700 Subject: [PATCH 48/79] Update create meta engine button to match create engine (#96884) --- .../app_search/components/engines/engines_overview.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx index 4d51012f2aa2a..d7e2309fd2a07 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_overview.tsx @@ -134,8 +134,9 @@ export const EnginesOverview: React.FC = () => { {canManageEngines && ( From c218ba83976e1c0fc4d43d674b12180249cb7788 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Mon, 12 Apr 2021 17:02:03 -0600 Subject: [PATCH 49/79] [Maps] only allow sorting on numeric fields for tracks (#96877) --- .../classes/sources/es_geo_line_source/geo_line_form.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx index bc743fe8d79b4..081272f40b344 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_line_source/geo_line_form.tsx @@ -64,7 +64,12 @@ export function GeoLineForm(props: Props) { onChange={onSortFieldChange} fields={props.indexPattern.fields.filter((field) => { const isSplitField = props.splitField ? field.name === props.splitField : false; - return !isSplitField && field.sortable && !indexPatterns.isNestedField(field); + return ( + !isSplitField && + field.sortable && + !indexPatterns.isNestedField(field) && + ['number', 'date'].includes(field.type) + ); })} isClearable={false} /> From e3f5249c88bb8427eff7bedb17bf8ec8c837628c Mon Sep 17 00:00:00 2001 From: Tiago Costa Date: Tue, 13 Apr 2021 01:24:19 +0100 Subject: [PATCH 50/79] chore(NA): @kbn/pm new commands to support development on Bazel packages (#96465) * chore(NA): add warnings both to run and watch commands about Bazel built packages * chore(NA): add new commands to build and watch bazel packages * docs(NA): add documentation about how to deal with bazel packages * chore(NA): addressed majority of the feedback received except for improved error logging * chore(NA): disable ibazel info notification. * chore(NA): remove iBazel notification * chore(NA): remove iBazel notification - kbn pm dist * chore(NA): move show_results option to kbn-pm only * chore(NA): patch build bazel command to include packages target list * chore(NA): add pretty logging for elastic-datemath * chore(NA): remove double error output from commands ran with Bazel * fix(NA): include simple error message to preserve subprocess failure state * docs(NA): missing docs about how to independentely watch non bazel packages Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .bazelrc.common | 14 +- WORKSPACE.bazel | 3 - docs/developer/getting-started/index.asciidoc | 5 +- .../monorepo-packages.asciidoc | 66 + packages/elastic-datemath/BUILD.bazel | 1 + packages/kbn-pm/dist/index.js | 1910 +++++++++-------- packages/kbn-pm/src/commands/bootstrap.ts | 2 +- packages/kbn-pm/src/commands/build_bazel.ts | 22 + packages/kbn-pm/src/commands/index.ts | 4 + packages/kbn-pm/src/commands/run.ts | 10 +- packages/kbn-pm/src/commands/watch.ts | 9 +- packages/kbn-pm/src/commands/watch_bazel.ts | 25 + packages/kbn-pm/src/utils/bazel/run.ts | 34 +- 13 files changed, 1184 insertions(+), 921 deletions(-) create mode 100644 docs/developer/getting-started/monorepo-packages.asciidoc create mode 100644 packages/kbn-pm/src/commands/build_bazel.ts create mode 100644 packages/kbn-pm/src/commands/watch_bazel.ts diff --git a/.bazelrc.common b/.bazelrc.common index 20a41c4cde9a0..115c0214b1a53 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -10,12 +10,13 @@ build --experimental_guard_against_concurrent_changes run --experimental_guard_against_concurrent_changes test --experimental_guard_against_concurrent_changes +query --experimental_guard_against_concurrent_changes ## Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) -build --disk_cache=~/.bazel-cache/disk-cache +common --disk_cache=~/.bazel-cache/disk-cache ## Bazel repo cache settings -build --repository_cache=~/.bazel-cache/repository-cache +common --repository_cache=~/.bazel-cache/repository-cache # Bazel will create symlinks from the workspace directory to output artifacts. # Build results will be placed in a directory called "bazel-bin" @@ -35,13 +36,16 @@ build --experimental_inprocess_symlink_creation # Incompatible flags to run with build --incompatible_no_implicit_file_export build --incompatible_restrict_string_escapes +query --incompatible_no_implicit_file_export +query --incompatible_restrict_string_escapes # Log configs ## different from default common --color=yes -build --show_task_finish -build --noshow_progress +common --noshow_progress +common --show_task_finish build --noshow_loading_progress +query --noshow_loading_progress build --show_result=0 # Specifies desired output mode for running tests. @@ -82,7 +86,7 @@ test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk # The following option will change the build output of certain rules such as terser and may not be desirable in all cases # It will also output both the repo cache and action cache to a folder inside the repo -build:debug --compilation_mode=dbg --show_result=1 +build:debug --compilation_mode=dbg --show_result=0 --noshow_loading_progress --noshow_progress --show_task_finish # Turn off legacy external runfiles # This prevents accidentally depending on this feature, which Bazel will remove. diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index e74c646eedeaf..bd4d8801b0d4e 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -52,9 +52,6 @@ node_repositories( # NOTE: FORCE_COLOR env var forces colors on non tty mode yarn_install( name = "npm", - environment = { - "FORCE_COLOR": "True", - }, package_json = "//:package.json", yarn_lock = "//:yarn.lock", data = [ diff --git a/docs/developer/getting-started/index.asciidoc b/docs/developer/getting-started/index.asciidoc index 5a16dac66c822..d5fe7ebf47038 100644 --- a/docs/developer/getting-started/index.asciidoc +++ b/docs/developer/getting-started/index.asciidoc @@ -66,7 +66,8 @@ yarn kbn bootstrap --force-install (You can also run `yarn kbn` to see the other available commands. For more info about this tool, see -{kib-repo}tree/{branch}/packages/kbn-pm[{kib-repo}tree/{branch}/packages/kbn-pm].) +{kib-repo}tree/{branch}/packages/kbn-pm[{kib-repo}tree/{branch}/packages/kbn-pm]. If you want more +information about how to actively develop over packages please read <>) When switching branches which use different versions of npm packages you may need to run: @@ -169,3 +170,5 @@ include::debugging.asciidoc[leveloffset=+1] include::building-kibana.asciidoc[leveloffset=+1] include::development-plugin-resources.asciidoc[leveloffset=+1] + +include::monorepo-packages.asciidoc[leveloffset=+1] diff --git a/docs/developer/getting-started/monorepo-packages.asciidoc b/docs/developer/getting-started/monorepo-packages.asciidoc new file mode 100644 index 0000000000000..a95b357570278 --- /dev/null +++ b/docs/developer/getting-started/monorepo-packages.asciidoc @@ -0,0 +1,66 @@ +[[monorepo-packages]] +== {kib} Monorepo Packages + +Currently {kib} works as a monorepo composed by a core, plugins and packages. +The latest are located in a folder called `packages` and are pieces of software that +composes a set of features that can be isolated and reused across the entire repository. +They are also supposed to be able to imported just like any other `node_module`. + +Previously we relied solely on `@kbn/pm` to manage the development tools of those packages, but we are +now in the middle of migrating those responsibilities into Bazel. Every package already migrated +will contain in its root folder a `BUILD.bazel` file and other `build` and `watching` strategies should be used. + +Remember that any time you need to make sure the monorepo is ready to be used just run: + +[source,bash] +---- +yarn kbn bootstrap +---- + +[discrete] +=== Building Non Bazel Packages + +Non Bazel packages can be built independently with + +[source,bash] +---- +yarn kbn run build -i PACKAGE_NAME +---- + +[discrete] +=== Watching Non Bazel Packages + +Non Bazel packages can be watched independently with + +[source,bash] +---- +yarn kbn watch -i PACKAGE_NAME +---- + +[discrete] +=== Building Bazel Packages + +Bazel packages are built as a whole for now. You can use: + +[source,bash] +---- +yarn kbn build-bazel +---- + +[discrete] +=== Watching Bazel Packages + +Bazel packages are watched as a whole for now. You can use: + +[source,bash] +---- +yarn kbn watch-bazel +---- + + +[discrete] +=== List of Already Migrated Packages to Bazel + +- @elastic/datemath + + diff --git a/packages/elastic-datemath/BUILD.bazel b/packages/elastic-datemath/BUILD.bazel index 6a80556d4eed5..6b9a725e91bd4 100644 --- a/packages/elastic-datemath/BUILD.bazel +++ b/packages/elastic-datemath/BUILD.bazel @@ -40,6 +40,7 @@ ts_config( ts_project( name = "tsc", + args = ['--pretty'], srcs = SRCS, deps = DEPS, declaration = True, diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 7c5d0390d9fba..af199fbbc27c2 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(563); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(565); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildBazelProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); @@ -108,7 +108,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(562); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(564); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -141,7 +141,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(514); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(516); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -8833,10 +8833,12 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(478); -/* harmony import */ var _reset__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(510); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(511); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(512); +/* harmony import */ var _build_bazel__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(478); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); +/* harmony import */ var _reset__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(511); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(512); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(513); +/* harmony import */ var _watch_bazel__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(515); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -8849,12 +8851,16 @@ __webpack_require__.r(__webpack_exports__); + + const commands = { bootstrap: _bootstrap__WEBPACK_IMPORTED_MODULE_0__["BootstrapCommand"], - clean: _clean__WEBPACK_IMPORTED_MODULE_1__["CleanCommand"], - reset: _reset__WEBPACK_IMPORTED_MODULE_2__["ResetCommand"], - run: _run__WEBPACK_IMPORTED_MODULE_3__["RunCommand"], - watch: _watch__WEBPACK_IMPORTED_MODULE_4__["WatchCommand"] + 'build-bazel': _build_bazel__WEBPACK_IMPORTED_MODULE_1__["BuildBazelCommand"], + clean: _clean__WEBPACK_IMPORTED_MODULE_2__["CleanCommand"], + reset: _reset__WEBPACK_IMPORTED_MODULE_3__["ResetCommand"], + run: _run__WEBPACK_IMPORTED_MODULE_4__["RunCommand"], + watch: _watch__WEBPACK_IMPORTED_MODULE_5__["WatchCommand"], + 'watch-bazel': _watch_bazel__WEBPACK_IMPORTED_MODULE_6__["WatchBazelCommand"] }; /***/ }), @@ -8933,7 +8939,7 @@ const BootstrapCommand = { await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['run', '@nodejs//:yarn'], runOffline); } - await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['build', '//packages:build'], runOffline); // Install monorepo npm dependencies outside of the Bazel managed ones + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["runBazel"])(['build', '//packages:build', '--show_result=1'], runOffline); // Install monorepo npm dependencies outside of the Bazel managed ones for (const batch of batchedNonBazelProjects) { for (const project of batch) { @@ -48141,6 +48147,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(376); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "runBazel", function() { return _run__WEBPACK_IMPORTED_MODULE_3__["runBazel"]; }); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "runIBazel", function() { return _run__WEBPACK_IMPORTED_MODULE_3__["runIBazel"]; }); + /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -48363,6 +48371,7 @@ async function installBazelTools(repoRootPath) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runBazel", function() { return runBazel; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runIBazel", function() { return runIBazel; }); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(113); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); @@ -48371,6 +48380,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(319); /* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); +/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(249); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -48390,7 +48400,9 @@ function _defineProperty(obj, key, value) { if (key in obj) { Object.definePrope -async function runBazel(bazelArgs, offline = false, runOpts = {}) { + + +async function runBazelCommandWithRunner(bazelCommandRunner, bazelArgs, offline = false, runOpts = {}) { // Force logs to pipe in order to control the output of them const bazelOpts = _objectSpread(_objectSpread({}, runOpts), {}, { stdio: 'pipe' @@ -48400,17 +48412,29 @@ async function runBazel(bazelArgs, offline = false, runOpts = {}) { bazelArgs.push('--config=offline'); } - const bazelProc = Object(_child_process__WEBPACK_IMPORTED_MODULE_4__["spawn"])('bazel', bazelArgs, bazelOpts); + const bazelProc = Object(_child_process__WEBPACK_IMPORTED_MODULE_4__["spawn"])(bazelCommandRunner, bazelArgs, bazelOpts); const bazelLogs$ = new rxjs__WEBPACK_IMPORTED_MODULE_1__["Subject"](); // Bazel outputs machine readable output into stdout and human readable output goes to stderr. // Therefore we need to get both. In order to get errors we need to parse the actual text line - const bazelLogSubscription = rxjs__WEBPACK_IMPORTED_MODULE_1__["merge"](Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stdout).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan('[bazel]')} ${line}`))), Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stderr).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan('[bazel]')} ${line}`)))).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end + const bazelLogSubscription = rxjs__WEBPACK_IMPORTED_MODULE_1__["merge"](Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stdout).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan(`[${bazelCommandRunner}]`)} ${line}`))), Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stderr).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan(`[${bazelCommandRunner}]`)} ${line}`)))).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end + + try { + await bazelProc; + } catch { + throw new _errors__WEBPACK_IMPORTED_MODULE_6__["CliError"](`The bazel command that was running failed to complete.`); + } - await bazelProc; await bazelLogs$.toPromise(); await bazelLogSubscription.unsubscribe(); } +async function runBazel(bazelArgs, offline = false, runOpts = {}) { + await runBazelCommandWithRunner('bazel', bazelArgs, offline, runOpts); +} +async function runIBazel(bazelArgs, offline = false, runOpts = {}) { + await runBazelCommandWithRunner('ibazel', bazelArgs, offline, runOpts); +} + /***/ }), /* 377 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { @@ -54550,6 +54574,36 @@ exports.observeReadable = observeReadable; /* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BuildBazelCommand", function() { return BuildBazelCommand; }); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const BuildBazelCommand = { + description: 'Runs a build in the Bazel built packages', + name: 'build-bazel', + + async run(projects, projectGraph, { + options + }) { + const runOffline = (options === null || options === void 0 ? void 0 : options.offline) === true; // Call bazel with the target to build all available packages + + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_0__["runBazel"])(['build', '//packages:build', '--show_result=1'], runOffline); + } + +}; + +/***/ }), +/* 479 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); @@ -54557,7 +54611,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(480); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); @@ -54660,20 +54714,20 @@ const CleanCommand = { }; /***/ }), -/* 479 */ +/* 480 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readline = __webpack_require__(480); -const chalk = __webpack_require__(481); -const cliCursor = __webpack_require__(488); -const cliSpinners = __webpack_require__(490); -const logSymbols = __webpack_require__(492); -const stripAnsi = __webpack_require__(502); -const wcwidth = __webpack_require__(504); -const isInteractive = __webpack_require__(508); -const MuteStream = __webpack_require__(509); +const readline = __webpack_require__(481); +const chalk = __webpack_require__(482); +const cliCursor = __webpack_require__(489); +const cliSpinners = __webpack_require__(491); +const logSymbols = __webpack_require__(493); +const stripAnsi = __webpack_require__(503); +const wcwidth = __webpack_require__(505); +const isInteractive = __webpack_require__(509); +const MuteStream = __webpack_require__(510); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); @@ -55026,23 +55080,23 @@ module.exports.promise = (action, options) => { /***/ }), -/* 480 */ +/* 481 */ /***/ (function(module, exports) { module.exports = require("readline"); /***/ }), -/* 481 */ +/* 482 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiStyles = __webpack_require__(482); +const ansiStyles = __webpack_require__(483); const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex -} = __webpack_require__(486); +} = __webpack_require__(487); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -55243,7 +55297,7 @@ const chalkTag = (chalk, ...strings) => { } if (template === undefined) { - template = __webpack_require__(487); + template = __webpack_require__(488); } return template(chalk, parts.join('')); @@ -55272,7 +55326,7 @@ module.exports = chalk; /***/ }), -/* 482 */ +/* 483 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55318,7 +55372,7 @@ const setLazyProperty = (object, property, get) => { let colorConvert; const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { if (colorConvert === undefined) { - colorConvert = __webpack_require__(483); + colorConvert = __webpack_require__(484); } const offset = isBackground ? 10 : 0; @@ -55443,11 +55497,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 483 */ +/* 484 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(484); -const route = __webpack_require__(485); +const conversions = __webpack_require__(485); +const route = __webpack_require__(486); const convert = {}; @@ -55530,7 +55584,7 @@ module.exports = convert; /***/ }), -/* 484 */ +/* 485 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ @@ -56375,10 +56429,10 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 485 */ +/* 486 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(484); +const conversions = __webpack_require__(485); /* This function routes a model to all other models. @@ -56478,7 +56532,7 @@ module.exports = function (fromModel) { /***/ }), -/* 486 */ +/* 487 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56524,7 +56578,7 @@ module.exports = { /***/ }), -/* 487 */ +/* 488 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56665,12 +56719,12 @@ module.exports = (chalk, temporary) => { /***/ }), -/* 488 */ +/* 489 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(489); +const restoreCursor = __webpack_require__(490); let isHidden = false; @@ -56707,7 +56761,7 @@ exports.toggle = (force, writableStream) => { /***/ }), -/* 489 */ +/* 490 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56723,13 +56777,13 @@ module.exports = onetime(() => { /***/ }), -/* 490 */ +/* 491 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const spinners = Object.assign({}, __webpack_require__(491)); +const spinners = Object.assign({}, __webpack_require__(492)); const spinnersList = Object.keys(spinners); @@ -56747,18 +56801,18 @@ module.exports.default = spinners; /***/ }), -/* 491 */ +/* 492 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]},\"aesthetic\":{\"interval\":80,\"frames\":[\"▰▱▱▱▱▱▱\",\"▰▰▱▱▱▱▱\",\"▰▰▰▱▱▱▱\",\"▰▰▰▰▱▱▱\",\"▰▰▰▰▰▱▱\",\"▰▰▰▰▰▰▱\",\"▰▰▰▰▰▰▰\",\"▰▱▱▱▱▱▱\"]}}"); /***/ }), -/* 492 */ +/* 493 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(493); +const chalk = __webpack_require__(494); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -56780,16 +56834,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 493 */ +/* 494 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(265); -const ansiStyles = __webpack_require__(494); -const stdoutColor = __webpack_require__(499).stdout; +const ansiStyles = __webpack_require__(495); +const stdoutColor = __webpack_require__(500).stdout; -const template = __webpack_require__(501); +const template = __webpack_require__(502); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -57015,12 +57069,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 494 */ +/* 495 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(495); +const colorConvert = __webpack_require__(496); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -57188,11 +57242,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 495 */ +/* 496 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(496); -var route = __webpack_require__(498); +var conversions = __webpack_require__(497); +var route = __webpack_require__(499); var convert = {}; @@ -57272,11 +57326,11 @@ module.exports = convert; /***/ }), -/* 496 */ +/* 497 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(497); +var cssKeywords = __webpack_require__(498); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -58146,7 +58200,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 497 */ +/* 498 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58305,10 +58359,10 @@ module.exports = { /***/ }), -/* 498 */ +/* 499 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(496); +var conversions = __webpack_require__(497); /* this function routes a model to all other models. @@ -58408,13 +58462,13 @@ module.exports = function (fromModel) { /***/ }), -/* 499 */ +/* 500 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const os = __webpack_require__(121); -const hasFlag = __webpack_require__(500); +const hasFlag = __webpack_require__(501); const env = process.env; @@ -58546,7 +58600,7 @@ module.exports = { /***/ }), -/* 500 */ +/* 501 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58561,7 +58615,7 @@ module.exports = (flag, argv) => { /***/ }), -/* 501 */ +/* 502 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58696,18 +58750,18 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 502 */ +/* 503 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(503); +const ansiRegex = __webpack_require__(504); module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 503 */ +/* 504 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -58724,14 +58778,14 @@ module.exports = ({onlyFirst = false} = {}) => { /***/ }), -/* 504 */ +/* 505 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var defaults = __webpack_require__(505) -var combining = __webpack_require__(507) +var defaults = __webpack_require__(506) +var combining = __webpack_require__(508) var DEFAULTS = { nul: 0, @@ -58830,10 +58884,10 @@ function bisearch(ucs) { /***/ }), -/* 505 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { -var clone = __webpack_require__(506); +var clone = __webpack_require__(507); module.exports = function(options, defaults) { options = options || {}; @@ -58848,7 +58902,7 @@ module.exports = function(options, defaults) { }; /***/ }), -/* 506 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { var clone = (function() { @@ -59020,7 +59074,7 @@ if ( true && module.exports) { /***/ }), -/* 507 */ +/* 508 */ /***/ (function(module, exports) { module.exports = [ @@ -59076,7 +59130,7 @@ module.exports = [ /***/ }), -/* 508 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59092,7 +59146,7 @@ module.exports = ({stream = process.stdout} = {}) => { /***/ }), -/* 509 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -59243,7 +59297,7 @@ MuteStream.prototype.close = proxy('close') /***/ }), -/* 510 */ +/* 511 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59253,7 +59307,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(479); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(480); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); @@ -59362,16 +59416,18 @@ const ResetCommand = { }; /***/ }), -/* 511 */ +/* 512 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -59383,53 +59439,61 @@ __webpack_require__.r(__webpack_exports__); + const RunCommand = { - description: 'Run script defined in package.json in each package that contains that script.', + description: 'Run script defined in package.json in each package that contains that script (only works on packages not using Bazel yet)', name: 'run', async run(projects, projectGraph, { extraArgs, options }) { - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].warning(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + We are migrating packages into the Bazel build system and we will no longer support running npm scripts on + packages using 'yarn kbn run' on Bazel built packages. If the package you are trying to act on contains a + BUILD.bazel file please just use 'yarn kbn build-bazel' to build it or 'yarn kbn watch-bazel' to watch it + `); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projects, projectGraph); if (extraArgs.length === 0) { - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"]('No script specified'); + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"]('No script specified'); } const scriptName = extraArgs[0]; const scriptArgs = extraArgs.slice(1); - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async project => { + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_3__["parallelizeBatches"])(batchedProjects, async project => { if (!project.hasScript(scriptName)) { if (!!options['skip-missing']) { return; } - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); } - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`[${project.name}] running "${scriptName}" script`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].info(`[${project.name}] running "${scriptName}" script`); await project.runScriptStreaming(scriptName, { args: scriptArgs }); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${project.name}] complete`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].success(`[${project.name}] complete`); }); } }; /***/ }), -/* 512 */ +/* 513 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(513); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(247); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(514); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -59443,6 +59507,7 @@ __webpack_require__.r(__webpack_exports__); + /** * Name of the script in the package/project package.json file to run during `kbn watch`. */ @@ -59464,10 +59529,14 @@ const kibanaProjectName = 'kibana'; */ const WatchCommand = { - description: 'Runs `kbn:watch` script for every project.', + description: 'Runs `kbn:watch` script for every project (only works on packages not using Bazel yet)', name: 'watch', async run(projects, projectGraph) { + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].warning(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + We are migrating packages into the Bazel build system. If the package you are trying to watch + contains a BUILD.bazel file please just use 'yarn kbn watch-bazel' + `); const projectsToWatch = new Map(); for (const project of projects.values()) { @@ -59478,33 +59547,33 @@ const WatchCommand = { } if (projectsToWatch.size === 0) { - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`There are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.`); + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_1__["CliError"](`There are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.`); } const projectNames = Array.from(projectsToWatch.keys()); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`); // Kibana should always be run the last, so we don't rely on automatic + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].info(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`); // Kibana should always be run the last, so we don't rely on automatic // topological batching and push it to the last one-entry batch manually. const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); if (shouldWatchKibanaProject) { batchedProjects.push([projects.get(kibanaProjectName)]); } - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { - const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_4__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName, { + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_3__["parallelizeBatches"])(batchedProjects, async pkg => { + const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_5__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName, { debug: false }).stdout // TypeScript note: As long as the proc stdio[1] is 'pipe', then stdout will not be null ); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${pkg.name}] Initial build completed (${completionHint}).`); + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].success(`[${pkg.name}] Initial build completed (${completionHint}).`); }); } }; /***/ }), -/* 513 */ +/* 514 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59567,19 +59636,52 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 514 */ +/* 515 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchBazelCommand", function() { return WatchBazelCommand; }); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +const WatchBazelCommand = { + description: 'Runs a build in the Bazel built packages and keeps watching them for changes', + name: 'watch-bazel', + + async run(projects, projectGraph, { + options + }) { + const runOffline = (options === null || options === void 0 ? void 0 : options.offline) === true; // Call bazel with the target to build all available packages and run it through iBazel to watch it for changes + // + // Note: --run_output=false arg will disable the iBazel notifications about gazelle and buildozer when running it + // Can also be solved by adding a root `.bazel_fix_commands.json` but its not needed at the moment + + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_0__["runIBazel"])(['--run_output=false', 'build', '//packages:build'], runOffline); + } + +}; + +/***/ }), +/* 516 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); -/* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(515); +/* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(517); /* harmony import */ var _kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_ci_stats_reporter__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(249); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(246); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); /* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(371); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(558); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(560); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -59697,7 +59799,7 @@ function toArray(value) { } /***/ }), -/* 515 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59716,8 +59818,8 @@ const util_1 = __webpack_require__(112); const os_1 = tslib_1.__importDefault(__webpack_require__(121)); const fs_1 = tslib_1.__importDefault(__webpack_require__(134)); const path_1 = tslib_1.__importDefault(__webpack_require__(4)); -const axios_1 = tslib_1.__importDefault(__webpack_require__(516)); -const ci_stats_config_1 = __webpack_require__(556); +const axios_1 = tslib_1.__importDefault(__webpack_require__(518)); +const ci_stats_config_1 = __webpack_require__(558); const BASE_URL = 'https://ci-stats.kibana.dev'; class CiStatsReporter { constructor(config, log) { @@ -59805,7 +59907,7 @@ class CiStatsReporter { // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm const hideFromWebpack = ['@', 'kbn/utils']; // eslint-disable-next-line @typescript-eslint/no-var-requires - const { kibanaPackageJson } = __webpack_require__(557)(hideFromWebpack.join('')); + const { kibanaPackageJson } = __webpack_require__(559)(hideFromWebpack.join('')); return kibanaPackageJson.branch; } /** @@ -59817,7 +59919,7 @@ class CiStatsReporter { // specify the module id in a way that will keep webpack from bundling extra code into @kbn/pm const hideFromWebpack = ['@', 'kbn/utils']; // eslint-disable-next-line @typescript-eslint/no-var-requires - const { REPO_ROOT } = __webpack_require__(557)(hideFromWebpack.join('')); + const { REPO_ROOT } = __webpack_require__(559)(hideFromWebpack.join('')); try { return fs_1.default.readFileSync(path_1.default.resolve(REPO_ROOT, 'data/uuid'), 'utf-8').trim(); } @@ -59880,23 +59982,23 @@ exports.CiStatsReporter = CiStatsReporter; //# sourceMappingURL=ci_stats_reporter.js.map /***/ }), -/* 516 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = __webpack_require__(517); +module.exports = __webpack_require__(519); /***/ }), -/* 517 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var bind = __webpack_require__(519); -var Axios = __webpack_require__(520); -var mergeConfig = __webpack_require__(551); -var defaults = __webpack_require__(526); +var utils = __webpack_require__(520); +var bind = __webpack_require__(521); +var Axios = __webpack_require__(522); +var mergeConfig = __webpack_require__(553); +var defaults = __webpack_require__(528); /** * Create an instance of Axios @@ -59929,18 +60031,18 @@ axios.create = function create(instanceConfig) { }; // Expose Cancel & CancelToken -axios.Cancel = __webpack_require__(552); -axios.CancelToken = __webpack_require__(553); -axios.isCancel = __webpack_require__(525); +axios.Cancel = __webpack_require__(554); +axios.CancelToken = __webpack_require__(555); +axios.isCancel = __webpack_require__(527); // Expose all/spread axios.all = function all(promises) { return Promise.all(promises); }; -axios.spread = __webpack_require__(554); +axios.spread = __webpack_require__(556); // Expose isAxiosError -axios.isAxiosError = __webpack_require__(555); +axios.isAxiosError = __webpack_require__(557); module.exports = axios; @@ -59949,13 +60051,13 @@ module.exports.default = axios; /***/ }), -/* 518 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var bind = __webpack_require__(519); +var bind = __webpack_require__(521); /*global toString:true*/ @@ -60307,7 +60409,7 @@ module.exports = { /***/ }), -/* 519 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60325,17 +60427,17 @@ module.exports = function bind(fn, thisArg) { /***/ }), -/* 520 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var buildURL = __webpack_require__(521); -var InterceptorManager = __webpack_require__(522); -var dispatchRequest = __webpack_require__(523); -var mergeConfig = __webpack_require__(551); +var utils = __webpack_require__(520); +var buildURL = __webpack_require__(523); +var InterceptorManager = __webpack_require__(524); +var dispatchRequest = __webpack_require__(525); +var mergeConfig = __webpack_require__(553); /** * Create a new instance of Axios @@ -60427,13 +60529,13 @@ module.exports = Axios; /***/ }), -/* 521 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); function encode(val) { return encodeURIComponent(val). @@ -60504,13 +60606,13 @@ module.exports = function buildURL(url, params, paramsSerializer) { /***/ }), -/* 522 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); function InterceptorManager() { this.handlers = []; @@ -60563,16 +60665,16 @@ module.exports = InterceptorManager; /***/ }), -/* 523 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var transformData = __webpack_require__(524); -var isCancel = __webpack_require__(525); -var defaults = __webpack_require__(526); +var utils = __webpack_require__(520); +var transformData = __webpack_require__(526); +var isCancel = __webpack_require__(527); +var defaults = __webpack_require__(528); /** * Throws a `Cancel` if cancellation has been requested. @@ -60649,13 +60751,13 @@ module.exports = function dispatchRequest(config) { /***/ }), -/* 524 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); /** * Transform the data for a request or a response @@ -60676,7 +60778,7 @@ module.exports = function transformData(data, headers, fns) { /***/ }), -/* 525 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60688,14 +60790,14 @@ module.exports = function isCancel(value) { /***/ }), -/* 526 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var normalizeHeaderName = __webpack_require__(527); +var utils = __webpack_require__(520); +var normalizeHeaderName = __webpack_require__(529); var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' @@ -60711,10 +60813,10 @@ function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { // For browsers use XHR adapter - adapter = __webpack_require__(528); + adapter = __webpack_require__(530); } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { // For node use HTTP adapter - adapter = __webpack_require__(538); + adapter = __webpack_require__(540); } return adapter; } @@ -60793,13 +60895,13 @@ module.exports = defaults; /***/ }), -/* 527 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); module.exports = function normalizeHeaderName(headers, normalizedName) { utils.forEach(headers, function processHeader(value, name) { @@ -60812,20 +60914,20 @@ module.exports = function normalizeHeaderName(headers, normalizedName) { /***/ }), -/* 528 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var settle = __webpack_require__(529); -var cookies = __webpack_require__(532); -var buildURL = __webpack_require__(521); -var buildFullPath = __webpack_require__(533); -var parseHeaders = __webpack_require__(536); -var isURLSameOrigin = __webpack_require__(537); -var createError = __webpack_require__(530); +var utils = __webpack_require__(520); +var settle = __webpack_require__(531); +var cookies = __webpack_require__(534); +var buildURL = __webpack_require__(523); +var buildFullPath = __webpack_require__(535); +var parseHeaders = __webpack_require__(538); +var isURLSameOrigin = __webpack_require__(539); +var createError = __webpack_require__(532); module.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest(resolve, reject) { @@ -60998,13 +61100,13 @@ module.exports = function xhrAdapter(config) { /***/ }), -/* 529 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var createError = __webpack_require__(530); +var createError = __webpack_require__(532); /** * Resolve or reject a Promise based on response status. @@ -61030,13 +61132,13 @@ module.exports = function settle(resolve, reject, response) { /***/ }), -/* 530 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var enhanceError = __webpack_require__(531); +var enhanceError = __webpack_require__(533); /** * Create an Error with the specified message, config, error code, request and response. @@ -61055,7 +61157,7 @@ module.exports = function createError(message, config, code, request, response) /***/ }), -/* 531 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61104,13 +61206,13 @@ module.exports = function enhanceError(error, config, code, request, response) { /***/ }), -/* 532 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); module.exports = ( utils.isStandardBrowserEnv() ? @@ -61164,14 +61266,14 @@ module.exports = ( /***/ }), -/* 533 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isAbsoluteURL = __webpack_require__(534); -var combineURLs = __webpack_require__(535); +var isAbsoluteURL = __webpack_require__(536); +var combineURLs = __webpack_require__(537); /** * Creates a new URL by combining the baseURL with the requestedURL, @@ -61191,7 +61293,7 @@ module.exports = function buildFullPath(baseURL, requestedURL) { /***/ }), -/* 534 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61212,7 +61314,7 @@ module.exports = function isAbsoluteURL(url) { /***/ }), -/* 535 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61233,13 +61335,13 @@ module.exports = function combineURLs(baseURL, relativeURL) { /***/ }), -/* 536 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); // Headers whose duplicates are ignored by node // c.f. https://nodejs.org/api/http.html#http_message_headers @@ -61293,13 +61395,13 @@ module.exports = function parseHeaders(headers) { /***/ }), -/* 537 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); module.exports = ( utils.isStandardBrowserEnv() ? @@ -61368,25 +61470,25 @@ module.exports = ( /***/ }), -/* 538 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); -var settle = __webpack_require__(529); -var buildFullPath = __webpack_require__(533); -var buildURL = __webpack_require__(521); -var http = __webpack_require__(539); -var https = __webpack_require__(540); -var httpFollow = __webpack_require__(541).http; -var httpsFollow = __webpack_require__(541).https; +var utils = __webpack_require__(520); +var settle = __webpack_require__(531); +var buildFullPath = __webpack_require__(535); +var buildURL = __webpack_require__(523); +var http = __webpack_require__(541); +var https = __webpack_require__(542); +var httpFollow = __webpack_require__(543).http; +var httpsFollow = __webpack_require__(543).https; var url = __webpack_require__(283); -var zlib = __webpack_require__(549); -var pkg = __webpack_require__(550); -var createError = __webpack_require__(530); -var enhanceError = __webpack_require__(531); +var zlib = __webpack_require__(551); +var pkg = __webpack_require__(552); +var createError = __webpack_require__(532); +var enhanceError = __webpack_require__(533); var isHttps = /https:?/; @@ -61678,28 +61780,28 @@ module.exports = function httpAdapter(config) { /***/ }), -/* 539 */ +/* 541 */ /***/ (function(module, exports) { module.exports = require("http"); /***/ }), -/* 540 */ +/* 542 */ /***/ (function(module, exports) { module.exports = require("https"); /***/ }), -/* 541 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { var url = __webpack_require__(283); var URL = url.URL; -var http = __webpack_require__(539); -var https = __webpack_require__(540); +var http = __webpack_require__(541); +var https = __webpack_require__(542); var Writable = __webpack_require__(138).Writable; var assert = __webpack_require__(140); -var debug = __webpack_require__(542); +var debug = __webpack_require__(544); // Create handlers that pass events from native requests var eventHandlers = Object.create(null); @@ -62194,13 +62296,13 @@ module.exports.wrap = wrap; /***/ }), -/* 542 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { var debug; try { /* eslint global-require: off */ - debug = __webpack_require__(543)("follow-redirects"); + debug = __webpack_require__(545)("follow-redirects"); } catch (error) { debug = function () { /* */ }; @@ -62209,7 +62311,7 @@ module.exports = debug; /***/ }), -/* 543 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -62218,14 +62320,14 @@ module.exports = debug; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(544); + module.exports = __webpack_require__(546); } else { - module.exports = __webpack_require__(547); + module.exports = __webpack_require__(549); } /***/ }), -/* 544 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -62234,7 +62336,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(545); +exports = module.exports = __webpack_require__(547); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -62416,7 +62518,7 @@ function localstorage() { /***/ }), -/* 545 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { @@ -62432,7 +62534,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(546); +exports.humanize = __webpack_require__(548); /** * The currently active debug mode names, and names to skip. @@ -62624,7 +62726,7 @@ function coerce(val) { /***/ }), -/* 546 */ +/* 548 */ /***/ (function(module, exports) { /** @@ -62782,7 +62884,7 @@ function plural(ms, n, name) { /***/ }), -/* 547 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -62798,7 +62900,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(545); +exports = module.exports = __webpack_require__(547); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -62977,7 +63079,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(548); + var net = __webpack_require__(550); stream = new net.Socket({ fd: fd, readable: false, @@ -63036,31 +63138,31 @@ exports.enable(load()); /***/ }), -/* 548 */ +/* 550 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 549 */ +/* 551 */ /***/ (function(module, exports) { module.exports = require("zlib"); /***/ }), -/* 550 */ +/* 552 */ /***/ (function(module) { module.exports = JSON.parse("{\"name\":\"axios\",\"version\":\"0.21.1\",\"description\":\"Promise based HTTP client for the browser and node.js\",\"main\":\"index.js\",\"scripts\":{\"test\":\"grunt test && bundlesize\",\"start\":\"node ./sandbox/server.js\",\"build\":\"NODE_ENV=production grunt build\",\"preversion\":\"npm test\",\"version\":\"npm run build && grunt version && git add -A dist && git add CHANGELOG.md bower.json package.json\",\"postversion\":\"git push && git push --tags\",\"examples\":\"node ./examples/server.js\",\"coveralls\":\"cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js\",\"fix\":\"eslint --fix lib/**/*.js\"},\"repository\":{\"type\":\"git\",\"url\":\"https://github.com/axios/axios.git\"},\"keywords\":[\"xhr\",\"http\",\"ajax\",\"promise\",\"node\"],\"author\":\"Matt Zabriskie\",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/axios/axios/issues\"},\"homepage\":\"https://github.com/axios/axios\",\"devDependencies\":{\"bundlesize\":\"^0.17.0\",\"coveralls\":\"^3.0.0\",\"es6-promise\":\"^4.2.4\",\"grunt\":\"^1.0.2\",\"grunt-banner\":\"^0.6.0\",\"grunt-cli\":\"^1.2.0\",\"grunt-contrib-clean\":\"^1.1.0\",\"grunt-contrib-watch\":\"^1.0.0\",\"grunt-eslint\":\"^20.1.0\",\"grunt-karma\":\"^2.0.0\",\"grunt-mocha-test\":\"^0.13.3\",\"grunt-ts\":\"^6.0.0-beta.19\",\"grunt-webpack\":\"^1.0.18\",\"istanbul-instrumenter-loader\":\"^1.0.0\",\"jasmine-core\":\"^2.4.1\",\"karma\":\"^1.3.0\",\"karma-chrome-launcher\":\"^2.2.0\",\"karma-coverage\":\"^1.1.1\",\"karma-firefox-launcher\":\"^1.1.0\",\"karma-jasmine\":\"^1.1.1\",\"karma-jasmine-ajax\":\"^0.1.13\",\"karma-opera-launcher\":\"^1.0.0\",\"karma-safari-launcher\":\"^1.0.0\",\"karma-sauce-launcher\":\"^1.2.0\",\"karma-sinon\":\"^1.0.5\",\"karma-sourcemap-loader\":\"^0.3.7\",\"karma-webpack\":\"^1.7.0\",\"load-grunt-tasks\":\"^3.5.2\",\"minimist\":\"^1.2.0\",\"mocha\":\"^5.2.0\",\"sinon\":\"^4.5.0\",\"typescript\":\"^2.8.1\",\"url-search-params\":\"^0.10.0\",\"webpack\":\"^1.13.1\",\"webpack-dev-server\":\"^1.14.1\"},\"browser\":{\"./lib/adapters/http.js\":\"./lib/adapters/xhr.js\"},\"jsdelivr\":\"dist/axios.min.js\",\"unpkg\":\"dist/axios.min.js\",\"typings\":\"./index.d.ts\",\"dependencies\":{\"follow-redirects\":\"^1.10.0\"},\"bundlesize\":[{\"path\":\"./dist/axios.min.js\",\"threshold\":\"5kB\"}]}"); /***/ }), -/* 551 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(518); +var utils = __webpack_require__(520); /** * Config-specific merge-function which creates a new config-object @@ -63148,7 +63250,7 @@ module.exports = function mergeConfig(config1, config2) { /***/ }), -/* 552 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63174,13 +63276,13 @@ module.exports = Cancel; /***/ }), -/* 553 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Cancel = __webpack_require__(552); +var Cancel = __webpack_require__(554); /** * A `CancelToken` is an object that can be used to request cancellation of an operation. @@ -63238,7 +63340,7 @@ module.exports = CancelToken; /***/ }), -/* 554 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63272,7 +63374,7 @@ module.exports = function spread(callback) { /***/ }), -/* 555 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63290,7 +63392,7 @@ module.exports = function isAxiosError(payload) { /***/ }), -/* 556 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63340,7 +63442,7 @@ exports.parseConfig = parseConfig; //# sourceMappingURL=ci_stats_config.js.map /***/ }), -/* 557 */ +/* 559 */ /***/ (function(module, exports) { function webpackEmptyContext(req) { @@ -63351,10 +63453,10 @@ function webpackEmptyContext(req) { webpackEmptyContext.keys = function() { return []; }; webpackEmptyContext.resolve = webpackEmptyContext; module.exports = webpackEmptyContext; -webpackEmptyContext.id = 557; +webpackEmptyContext.id = 559; /***/ }), -/* 558 */ +/* 560 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -63364,13 +63466,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(134); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(559); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(561); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(239); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(366); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(562); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(564); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -63534,15 +63636,15 @@ class Kibana { } /***/ }), -/* 559 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(150); const arrayUnion = __webpack_require__(145); -const arrayDiffer = __webpack_require__(560); -const arrify = __webpack_require__(561); +const arrayDiffer = __webpack_require__(562); +const arrify = __webpack_require__(563); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -63566,7 +63668,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 560 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63581,7 +63683,7 @@ module.exports = arrayDiffer; /***/ }), -/* 561 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63611,7 +63713,7 @@ module.exports = arrify; /***/ }), -/* 562 */ +/* 564 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -63670,15 +63772,15 @@ function getProjectPaths({ } /***/ }), -/* 563 */ +/* 565 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(564); +/* harmony import */ var _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(566); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return _build_bazel_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildBazelProductionProjects"]; }); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(812); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(814); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_1__["buildNonBazelProductionProjects"]; }); /* @@ -63692,19 +63794,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 564 */ +/* 566 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildBazelProductionProjects", function() { return buildBazelProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(567); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(774); +/* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(776); /* harmony import */ var globby__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(globby__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(812); +/* harmony import */ var _build_non_bazel_production_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(814); /* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(372); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); @@ -63799,7 +63901,7 @@ async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { } /***/ }), -/* 565 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63807,14 +63909,14 @@ async function applyCorrectPermissions(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(156); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pMap = __webpack_require__(566); -const arrify = __webpack_require__(561); -const globby = __webpack_require__(569); -const hasGlob = __webpack_require__(758); -const cpFile = __webpack_require__(760); -const junk = __webpack_require__(770); -const pFilter = __webpack_require__(771); -const CpyError = __webpack_require__(773); +const pMap = __webpack_require__(568); +const arrify = __webpack_require__(563); +const globby = __webpack_require__(571); +const hasGlob = __webpack_require__(760); +const cpFile = __webpack_require__(762); +const junk = __webpack_require__(772); +const pFilter = __webpack_require__(773); +const CpyError = __webpack_require__(775); const defaultOptions = { ignoreJunk: true @@ -63965,12 +64067,12 @@ module.exports = (source, destination, { /***/ }), -/* 566 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(567); +const AggregateError = __webpack_require__(569); module.exports = async ( iterable, @@ -64053,12 +64155,12 @@ module.exports = async ( /***/ }), -/* 567 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const indentString = __webpack_require__(568); +const indentString = __webpack_require__(570); const cleanStack = __webpack_require__(244); const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); @@ -64107,7 +64209,7 @@ module.exports = AggregateError; /***/ }), -/* 568 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64149,17 +64251,17 @@ module.exports = (string, count = 1, options) => { /***/ }), -/* 569 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(570); +const arrayUnion = __webpack_require__(572); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(572); -const dirGlob = __webpack_require__(751); -const gitignore = __webpack_require__(754); +const fastGlob = __webpack_require__(574); +const dirGlob = __webpack_require__(753); +const gitignore = __webpack_require__(756); const DEFAULT_FILTER = () => false; @@ -64304,12 +64406,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 570 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(571); +var arrayUniq = __webpack_require__(573); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -64317,7 +64419,7 @@ module.exports = function () { /***/ }), -/* 571 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64386,10 +64488,10 @@ if ('Set' in global) { /***/ }), -/* 572 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(573); +const pkg = __webpack_require__(575); module.exports = pkg.async; module.exports.default = pkg.async; @@ -64402,19 +64504,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 573 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(574); -var taskManager = __webpack_require__(575); -var reader_async_1 = __webpack_require__(722); -var reader_stream_1 = __webpack_require__(746); -var reader_sync_1 = __webpack_require__(747); -var arrayUtils = __webpack_require__(749); -var streamUtils = __webpack_require__(750); +var optionsManager = __webpack_require__(576); +var taskManager = __webpack_require__(577); +var reader_async_1 = __webpack_require__(724); +var reader_stream_1 = __webpack_require__(748); +var reader_sync_1 = __webpack_require__(749); +var arrayUtils = __webpack_require__(751); +var streamUtils = __webpack_require__(752); /** * Synchronous API. */ @@ -64480,7 +64582,7 @@ function isString(source) { /***/ }), -/* 574 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64518,13 +64620,13 @@ exports.prepare = prepare; /***/ }), -/* 575 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(576); +var patternUtils = __webpack_require__(578); /** * Generate tasks based on parent directory of each pattern. */ @@ -64615,16 +64717,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 576 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(577); +var globParent = __webpack_require__(579); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(580); +var micromatch = __webpack_require__(582); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -64770,15 +64872,15 @@ exports.matchAny = matchAny; /***/ }), -/* 577 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(578); -var pathDirname = __webpack_require__(579); +var isglob = __webpack_require__(580); +var pathDirname = __webpack_require__(581); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -64801,7 +64903,7 @@ module.exports = function globParent(str) { /***/ }), -/* 578 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -64832,7 +64934,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 579 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64982,7 +65084,7 @@ module.exports.win32 = win32; /***/ }), -/* 580 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64993,18 +65095,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(581); -var toRegex = __webpack_require__(582); -var extend = __webpack_require__(690); +var braces = __webpack_require__(583); +var toRegex = __webpack_require__(584); +var extend = __webpack_require__(692); /** * Local dependencies */ -var compilers = __webpack_require__(692); -var parsers = __webpack_require__(718); -var cache = __webpack_require__(719); -var utils = __webpack_require__(720); +var compilers = __webpack_require__(694); +var parsers = __webpack_require__(720); +var cache = __webpack_require__(721); +var utils = __webpack_require__(722); var MAX_LENGTH = 1024 * 64; /** @@ -65866,7 +65968,7 @@ module.exports = micromatch; /***/ }), -/* 581 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65876,18 +65978,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(582); -var unique = __webpack_require__(602); -var extend = __webpack_require__(603); +var toRegex = __webpack_require__(584); +var unique = __webpack_require__(604); +var extend = __webpack_require__(605); /** * Local dependencies */ -var compilers = __webpack_require__(605); -var parsers = __webpack_require__(618); -var Braces = __webpack_require__(623); -var utils = __webpack_require__(606); +var compilers = __webpack_require__(607); +var parsers = __webpack_require__(620); +var Braces = __webpack_require__(625); +var utils = __webpack_require__(608); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -66191,16 +66293,16 @@ module.exports = braces; /***/ }), -/* 582 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(583); -var define = __webpack_require__(589); -var extend = __webpack_require__(595); -var not = __webpack_require__(599); +var safe = __webpack_require__(585); +var define = __webpack_require__(591); +var extend = __webpack_require__(597); +var not = __webpack_require__(601); var MAX_LENGTH = 1024 * 64; /** @@ -66353,10 +66455,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 583 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(584); +var parse = __webpack_require__(586); var types = parse.types; module.exports = function (re, opts) { @@ -66402,13 +66504,13 @@ function isRegExp (x) { /***/ }), -/* 584 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(585); -var types = __webpack_require__(586); -var sets = __webpack_require__(587); -var positions = __webpack_require__(588); +var util = __webpack_require__(587); +var types = __webpack_require__(588); +var sets = __webpack_require__(589); +var positions = __webpack_require__(590); module.exports = function(regexpStr) { @@ -66690,11 +66792,11 @@ module.exports.types = types; /***/ }), -/* 585 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(586); -var sets = __webpack_require__(587); +var types = __webpack_require__(588); +var sets = __webpack_require__(589); // All of these are private and only used by randexp. @@ -66807,7 +66909,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 586 */ +/* 588 */ /***/ (function(module, exports) { module.exports = { @@ -66823,10 +66925,10 @@ module.exports = { /***/ }), -/* 587 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(586); +var types = __webpack_require__(588); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -66911,10 +67013,10 @@ exports.anyChar = function() { /***/ }), -/* 588 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(586); +var types = __webpack_require__(588); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -66934,7 +67036,7 @@ exports.end = function() { /***/ }), -/* 589 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66947,8 +67049,8 @@ exports.end = function() { -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); +var isobject = __webpack_require__(592); +var isDescriptor = __webpack_require__(593); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -66979,7 +67081,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 590 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66998,7 +67100,7 @@ module.exports = function isObject(val) { /***/ }), -/* 591 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67011,9 +67113,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(592); -var isAccessor = __webpack_require__(593); -var isData = __webpack_require__(594); +var typeOf = __webpack_require__(594); +var isAccessor = __webpack_require__(595); +var isData = __webpack_require__(596); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -67027,7 +67129,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 592 */ +/* 594 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -67162,7 +67264,7 @@ function isBuffer(val) { /***/ }), -/* 593 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67175,7 +67277,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(592); +var typeOf = __webpack_require__(594); // accessor descriptor properties var accessor = { @@ -67238,7 +67340,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 594 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67251,7 +67353,7 @@ module.exports = isAccessorDescriptor; -var typeOf = __webpack_require__(592); +var typeOf = __webpack_require__(594); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -67294,14 +67396,14 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 595 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(596); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(598); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -67361,7 +67463,7 @@ function isEnum(obj, key) { /***/ }), -/* 596 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67374,7 +67476,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67382,7 +67484,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 597 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67395,7 +67497,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(590); +var isObject = __webpack_require__(592); function isObjectObject(o) { return isObject(o) === true @@ -67426,7 +67528,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 598 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67473,14 +67575,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 599 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(600); -var safe = __webpack_require__(583); +var extend = __webpack_require__(602); +var safe = __webpack_require__(585); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -67552,14 +67654,14 @@ module.exports = toRegex; /***/ }), -/* 600 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(601); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(603); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -67619,7 +67721,7 @@ function isEnum(obj, key) { /***/ }), -/* 601 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67632,7 +67734,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -67640,7 +67742,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 602 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67690,13 +67792,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 603 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(604); +var isObject = __webpack_require__(606); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -67730,7 +67832,7 @@ function hasOwn(obj, key) { /***/ }), -/* 604 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67750,13 +67852,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 605 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(606); +var utils = __webpack_require__(608); module.exports = function(braces, options) { braces.compiler @@ -68039,25 +68141,25 @@ function hasQueue(node) { /***/ }), -/* 606 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(607); +var splitString = __webpack_require__(609); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(603); -utils.flatten = __webpack_require__(610); -utils.isObject = __webpack_require__(590); -utils.fillRange = __webpack_require__(611); -utils.repeat = __webpack_require__(617); -utils.unique = __webpack_require__(602); +utils.extend = __webpack_require__(605); +utils.flatten = __webpack_require__(612); +utils.isObject = __webpack_require__(592); +utils.fillRange = __webpack_require__(613); +utils.repeat = __webpack_require__(619); +utils.unique = __webpack_require__(604); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -68389,7 +68491,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 607 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68402,7 +68504,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(608); +var extend = __webpack_require__(610); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -68567,14 +68669,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 608 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(609); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(611); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -68634,7 +68736,7 @@ function isEnum(obj, key) { /***/ }), -/* 609 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68647,7 +68749,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -68655,7 +68757,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 610 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68684,7 +68786,7 @@ function flat(arr, res) { /***/ }), -/* 611 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68698,10 +68800,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(612); -var extend = __webpack_require__(603); -var repeat = __webpack_require__(615); -var toRegex = __webpack_require__(616); +var isNumber = __webpack_require__(614); +var extend = __webpack_require__(605); +var repeat = __webpack_require__(617); +var toRegex = __webpack_require__(618); /** * Return a range of numbers or letters. @@ -68899,7 +69001,7 @@ module.exports = fillRange; /***/ }), -/* 612 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68912,7 +69014,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(613); +var typeOf = __webpack_require__(615); module.exports = function isNumber(num) { var type = typeOf(num); @@ -68928,10 +69030,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 613 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -69050,7 +69152,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 614 */ +/* 616 */ /***/ (function(module, exports) { /*! @@ -69077,7 +69179,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 615 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69154,7 +69256,7 @@ function repeat(str, num) { /***/ }), -/* 616 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69167,8 +69269,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(615); -var isNumber = __webpack_require__(612); +var repeat = __webpack_require__(617); +var isNumber = __webpack_require__(614); var cache = {}; function toRegexRange(min, max, options) { @@ -69455,7 +69557,7 @@ module.exports = toRegexRange; /***/ }), -/* 617 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69480,14 +69582,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 618 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(619); -var utils = __webpack_require__(606); +var Node = __webpack_require__(621); +var utils = __webpack_require__(608); /** * Braces parsers @@ -69847,15 +69949,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 619 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(590); -var define = __webpack_require__(620); -var utils = __webpack_require__(621); +var isObject = __webpack_require__(592); +var define = __webpack_require__(622); +var utils = __webpack_require__(623); var ownNames; /** @@ -70346,7 +70448,7 @@ exports = module.exports = Node; /***/ }), -/* 620 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70359,7 +70461,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(591); +var isDescriptor = __webpack_require__(593); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -70384,13 +70486,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 621 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(622); +var typeOf = __webpack_require__(624); var utils = module.exports; /** @@ -71410,10 +71512,10 @@ function assert(val, message) { /***/ }), -/* 622 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -71532,17 +71634,17 @@ module.exports = function kindOf(val) { /***/ }), -/* 623 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(603); -var Snapdragon = __webpack_require__(624); -var compilers = __webpack_require__(605); -var parsers = __webpack_require__(618); -var utils = __webpack_require__(606); +var extend = __webpack_require__(605); +var Snapdragon = __webpack_require__(626); +var compilers = __webpack_require__(607); +var parsers = __webpack_require__(620); +var utils = __webpack_require__(608); /** * Customize Snapdragon parser and renderer @@ -71643,17 +71745,17 @@ module.exports = Braces; /***/ }), -/* 624 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(625); -var define = __webpack_require__(653); -var Compiler = __webpack_require__(664); -var Parser = __webpack_require__(687); -var utils = __webpack_require__(667); +var Base = __webpack_require__(627); +var define = __webpack_require__(655); +var Compiler = __webpack_require__(666); +var Parser = __webpack_require__(689); +var utils = __webpack_require__(669); var regexCache = {}; var cache = {}; @@ -71824,20 +71926,20 @@ module.exports.Parser = Parser; /***/ }), -/* 625 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(626); -var CacheBase = __webpack_require__(627); -var Emitter = __webpack_require__(628); -var isObject = __webpack_require__(590); -var merge = __webpack_require__(647); -var pascal = __webpack_require__(650); -var cu = __webpack_require__(651); +var define = __webpack_require__(628); +var CacheBase = __webpack_require__(629); +var Emitter = __webpack_require__(630); +var isObject = __webpack_require__(592); +var merge = __webpack_require__(649); +var pascal = __webpack_require__(652); +var cu = __webpack_require__(653); /** * Optionally define a custom `cache` namespace to use. @@ -72266,7 +72368,7 @@ module.exports.namespace = namespace; /***/ }), -/* 626 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72279,7 +72381,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(591); +var isDescriptor = __webpack_require__(593); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -72304,21 +72406,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 627 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(590); -var Emitter = __webpack_require__(628); -var visit = __webpack_require__(629); -var toPath = __webpack_require__(632); -var union = __webpack_require__(634); -var del = __webpack_require__(638); -var get = __webpack_require__(636); -var has = __webpack_require__(643); -var set = __webpack_require__(646); +var isObject = __webpack_require__(592); +var Emitter = __webpack_require__(630); +var visit = __webpack_require__(631); +var toPath = __webpack_require__(634); +var union = __webpack_require__(636); +var del = __webpack_require__(640); +var get = __webpack_require__(638); +var has = __webpack_require__(645); +var set = __webpack_require__(648); /** * Create a `Cache` constructor that when instantiated will @@ -72572,7 +72674,7 @@ module.exports.namespace = namespace; /***/ }), -/* 628 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { @@ -72741,7 +72843,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 629 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72754,8 +72856,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(630); -var mapVisit = __webpack_require__(631); +var visit = __webpack_require__(632); +var mapVisit = __webpack_require__(633); module.exports = function(collection, method, val) { var result; @@ -72778,7 +72880,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 630 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72791,7 +72893,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(590); +var isObject = __webpack_require__(592); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -72818,14 +72920,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 631 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(630); +var visit = __webpack_require__(632); /** * Map `visit` over an array of objects. @@ -72862,7 +72964,7 @@ function isObject(val) { /***/ }), -/* 632 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -72875,7 +72977,7 @@ function isObject(val) { -var typeOf = __webpack_require__(633); +var typeOf = __webpack_require__(635); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -72902,10 +73004,10 @@ function filter(arr) { /***/ }), -/* 633 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -73024,16 +73126,16 @@ module.exports = function kindOf(val) { /***/ }), -/* 634 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(604); -var union = __webpack_require__(635); -var get = __webpack_require__(636); -var set = __webpack_require__(637); +var isObject = __webpack_require__(606); +var union = __webpack_require__(637); +var get = __webpack_require__(638); +var set = __webpack_require__(639); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -73061,7 +73163,7 @@ function arrayify(val) { /***/ }), -/* 635 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73097,7 +73199,7 @@ module.exports = function union(init) { /***/ }), -/* 636 */ +/* 638 */ /***/ (function(module, exports) { /*! @@ -73153,7 +73255,7 @@ function toString(val) { /***/ }), -/* 637 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73166,10 +73268,10 @@ function toString(val) { -var split = __webpack_require__(607); -var extend = __webpack_require__(603); -var isPlainObject = __webpack_require__(597); -var isObject = __webpack_require__(604); +var split = __webpack_require__(609); +var extend = __webpack_require__(605); +var isPlainObject = __webpack_require__(599); +var isObject = __webpack_require__(606); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -73215,7 +73317,7 @@ function isValidKey(key) { /***/ }), -/* 638 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73228,8 +73330,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(590); -var has = __webpack_require__(639); +var isObject = __webpack_require__(592); +var has = __webpack_require__(641); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -73254,7 +73356,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 639 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73267,9 +73369,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(640); -var hasValues = __webpack_require__(642); -var get = __webpack_require__(636); +var isObject = __webpack_require__(642); +var hasValues = __webpack_require__(644); +var get = __webpack_require__(638); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -73280,7 +73382,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 640 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73293,7 +73395,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(641); +var isArray = __webpack_require__(643); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -73301,7 +73403,7 @@ module.exports = function isObject(val) { /***/ }), -/* 641 */ +/* 643 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -73312,7 +73414,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 642 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73355,7 +73457,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 643 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73368,9 +73470,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(590); -var hasValues = __webpack_require__(644); -var get = __webpack_require__(636); +var isObject = __webpack_require__(592); +var hasValues = __webpack_require__(646); +var get = __webpack_require__(638); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -73378,7 +73480,7 @@ module.exports = function(val, prop) { /***/ }), -/* 644 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73391,8 +73493,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(645); -var isNumber = __webpack_require__(612); +var typeOf = __webpack_require__(647); +var isNumber = __webpack_require__(614); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -73445,10 +73547,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 645 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -73570,7 +73672,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 646 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73583,10 +73685,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(607); -var extend = __webpack_require__(603); -var isPlainObject = __webpack_require__(597); -var isObject = __webpack_require__(604); +var split = __webpack_require__(609); +var extend = __webpack_require__(605); +var isPlainObject = __webpack_require__(599); +var isObject = __webpack_require__(606); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -73632,14 +73734,14 @@ function isValidKey(key) { /***/ }), -/* 647 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(648); -var forIn = __webpack_require__(649); +var isExtendable = __webpack_require__(650); +var forIn = __webpack_require__(651); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -73703,7 +73805,7 @@ module.exports = mixinDeep; /***/ }), -/* 648 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73716,7 +73818,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -73724,7 +73826,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 649 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -73747,7 +73849,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 650 */ +/* 652 */ /***/ (function(module, exports) { /*! @@ -73774,14 +73876,14 @@ module.exports = pascalcase; /***/ }), -/* 651 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(652); +var utils = __webpack_require__(654); /** * Expose class utils @@ -74146,7 +74248,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 652 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74160,10 +74262,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(635); -utils.define = __webpack_require__(653); -utils.isObj = __webpack_require__(590); -utils.staticExtend = __webpack_require__(660); +utils.union = __webpack_require__(637); +utils.define = __webpack_require__(655); +utils.isObj = __webpack_require__(592); +utils.staticExtend = __webpack_require__(662); /** @@ -74174,7 +74276,7 @@ module.exports = utils; /***/ }), -/* 653 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74187,7 +74289,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(654); +var isDescriptor = __webpack_require__(656); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -74212,7 +74314,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 654 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74225,9 +74327,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(655); -var isAccessor = __webpack_require__(656); -var isData = __webpack_require__(658); +var typeOf = __webpack_require__(657); +var isAccessor = __webpack_require__(658); +var isData = __webpack_require__(660); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -74241,7 +74343,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 655 */ +/* 657 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -74394,7 +74496,7 @@ function isBuffer(val) { /***/ }), -/* 656 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74407,7 +74509,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(657); +var typeOf = __webpack_require__(659); // accessor descriptor properties var accessor = { @@ -74470,10 +74572,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 657 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -74592,7 +74694,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 658 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74605,7 +74707,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(659); +var typeOf = __webpack_require__(661); // data descriptor properties var data = { @@ -74654,10 +74756,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 659 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -74776,7 +74878,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 660 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -74789,8 +74891,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(661); -var define = __webpack_require__(653); +var copy = __webpack_require__(663); +var define = __webpack_require__(655); var util = __webpack_require__(112); /** @@ -74873,15 +74975,15 @@ module.exports = extend; /***/ }), -/* 661 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(662); -var copyDescriptor = __webpack_require__(663); -var define = __webpack_require__(653); +var typeOf = __webpack_require__(664); +var copyDescriptor = __webpack_require__(665); +var define = __webpack_require__(655); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -75054,10 +75156,10 @@ module.exports.has = has; /***/ }), -/* 662 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(614); +var isBuffer = __webpack_require__(616); var toString = Object.prototype.toString; /** @@ -75176,7 +75278,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 663 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75264,16 +75366,16 @@ function isObject(val) { /***/ }), -/* 664 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(665); -var define = __webpack_require__(653); -var debug = __webpack_require__(543)('snapdragon:compiler'); -var utils = __webpack_require__(667); +var use = __webpack_require__(667); +var define = __webpack_require__(655); +var debug = __webpack_require__(545)('snapdragon:compiler'); +var utils = __webpack_require__(669); /** * Create a new `Compiler` with the given `options`. @@ -75427,7 +75529,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(686); + var sourcemaps = __webpack_require__(688); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -75448,7 +75550,7 @@ module.exports = Compiler; /***/ }), -/* 665 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75461,7 +75563,7 @@ module.exports = Compiler; -var utils = __webpack_require__(666); +var utils = __webpack_require__(668); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -75576,7 +75678,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 666 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75590,8 +75692,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(653); -utils.isObject = __webpack_require__(590); +utils.define = __webpack_require__(655); +utils.isObject = __webpack_require__(592); utils.isString = function(val) { @@ -75606,7 +75708,7 @@ module.exports = utils; /***/ }), -/* 667 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75616,9 +75718,9 @@ module.exports = utils; * Module dependencies */ -exports.extend = __webpack_require__(603); -exports.SourceMap = __webpack_require__(668); -exports.sourceMapResolve = __webpack_require__(679); +exports.extend = __webpack_require__(605); +exports.SourceMap = __webpack_require__(670); +exports.sourceMapResolve = __webpack_require__(681); /** * Convert backslash in the given string to forward slashes @@ -75661,7 +75763,7 @@ exports.last = function(arr, n) { /***/ }), -/* 668 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -75669,13 +75771,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(675).SourceMapConsumer; -exports.SourceNode = __webpack_require__(678).SourceNode; +exports.SourceMapGenerator = __webpack_require__(671).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(677).SourceMapConsumer; +exports.SourceNode = __webpack_require__(680).SourceNode; /***/ }), -/* 669 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -75685,10 +75787,10 @@ exports.SourceNode = __webpack_require__(678).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(670); -var util = __webpack_require__(672); -var ArraySet = __webpack_require__(673).ArraySet; -var MappingList = __webpack_require__(674).MappingList; +var base64VLQ = __webpack_require__(672); +var util = __webpack_require__(674); +var ArraySet = __webpack_require__(675).ArraySet; +var MappingList = __webpack_require__(676).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -76097,7 +76199,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 670 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76137,7 +76239,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(671); +var base64 = __webpack_require__(673); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -76243,7 +76345,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 671 */ +/* 673 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76316,7 +76418,7 @@ exports.decode = function (charCode) { /***/ }), -/* 672 */ +/* 674 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76739,7 +76841,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 673 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76749,7 +76851,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(672); +var util = __webpack_require__(674); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -76866,7 +76968,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 674 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76876,7 +76978,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(672); +var util = __webpack_require__(674); /** * Determine whether mappingB is after mappingA with respect to generated @@ -76951,7 +77053,7 @@ exports.MappingList = MappingList; /***/ }), -/* 675 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -76961,11 +77063,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(672); -var binarySearch = __webpack_require__(676); -var ArraySet = __webpack_require__(673).ArraySet; -var base64VLQ = __webpack_require__(670); -var quickSort = __webpack_require__(677).quickSort; +var util = __webpack_require__(674); +var binarySearch = __webpack_require__(678); +var ArraySet = __webpack_require__(675).ArraySet; +var base64VLQ = __webpack_require__(672); +var quickSort = __webpack_require__(679).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -78039,7 +78141,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 676 */ +/* 678 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78156,7 +78258,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 677 */ +/* 679 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78276,7 +78378,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 678 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -78286,8 +78388,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(669).SourceMapGenerator; -var util = __webpack_require__(672); +var SourceMapGenerator = __webpack_require__(671).SourceMapGenerator; +var util = __webpack_require__(674); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -78695,17 +78797,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 679 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(680) -var resolveUrl = __webpack_require__(681) -var decodeUriComponent = __webpack_require__(682) -var urix = __webpack_require__(684) -var atob = __webpack_require__(685) +var sourceMappingURL = __webpack_require__(682) +var resolveUrl = __webpack_require__(683) +var decodeUriComponent = __webpack_require__(684) +var urix = __webpack_require__(686) +var atob = __webpack_require__(687) @@ -79003,7 +79105,7 @@ module.exports = { /***/ }), -/* 680 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -79066,7 +79168,7 @@ void (function(root, factory) { /***/ }), -/* 681 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -79084,13 +79186,13 @@ module.exports = resolveUrl /***/ }), -/* 682 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(683) +var decodeUriComponent = __webpack_require__(685) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -79101,7 +79203,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 683 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79202,7 +79304,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 684 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -79225,7 +79327,7 @@ module.exports = urix /***/ }), -/* 685 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79239,7 +79341,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 686 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79247,8 +79349,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(653); -var utils = __webpack_require__(667); +var define = __webpack_require__(655); +var utils = __webpack_require__(669); /** * Expose `mixin()`. @@ -79391,19 +79493,19 @@ exports.comment = function(node) { /***/ }), -/* 687 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(665); +var use = __webpack_require__(667); var util = __webpack_require__(112); -var Cache = __webpack_require__(688); -var define = __webpack_require__(653); -var debug = __webpack_require__(543)('snapdragon:parser'); -var Position = __webpack_require__(689); -var utils = __webpack_require__(667); +var Cache = __webpack_require__(690); +var define = __webpack_require__(655); +var debug = __webpack_require__(545)('snapdragon:parser'); +var Position = __webpack_require__(691); +var utils = __webpack_require__(669); /** * Create a new `Parser` with the given `input` and `options`. @@ -79931,7 +80033,7 @@ module.exports = Parser; /***/ }), -/* 688 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80038,13 +80140,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 689 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(653); +var define = __webpack_require__(655); /** * Store position for a node @@ -80059,14 +80161,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 690 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(691); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(693); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -80126,7 +80228,7 @@ function isEnum(obj, key) { /***/ }), -/* 691 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80139,7 +80241,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -80147,14 +80249,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 692 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(693); -var extglob = __webpack_require__(707); +var nanomatch = __webpack_require__(695); +var extglob = __webpack_require__(709); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -80231,7 +80333,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 693 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80242,17 +80344,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(582); -var extend = __webpack_require__(694); +var toRegex = __webpack_require__(584); +var extend = __webpack_require__(696); /** * Local dependencies */ -var compilers = __webpack_require__(696); -var parsers = __webpack_require__(697); -var cache = __webpack_require__(700); -var utils = __webpack_require__(702); +var compilers = __webpack_require__(698); +var parsers = __webpack_require__(699); +var cache = __webpack_require__(702); +var utils = __webpack_require__(704); var MAX_LENGTH = 1024 * 64; /** @@ -81076,14 +81178,14 @@ module.exports = nanomatch; /***/ }), -/* 694 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(695); -var assignSymbols = __webpack_require__(598); +var isExtendable = __webpack_require__(697); +var assignSymbols = __webpack_require__(600); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -81143,7 +81245,7 @@ function isEnum(obj, key) { /***/ }), -/* 695 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81156,7 +81258,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(597); +var isPlainObject = __webpack_require__(599); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -81164,7 +81266,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 696 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81510,15 +81612,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 697 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(599); -var toRegex = __webpack_require__(582); -var isOdd = __webpack_require__(698); +var regexNot = __webpack_require__(601); +var toRegex = __webpack_require__(584); +var isOdd = __webpack_require__(700); /** * Characters to use in negation regex (we want to "not" match @@ -81904,7 +82006,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 698 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81917,7 +82019,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(699); +var isNumber = __webpack_require__(701); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -81931,7 +82033,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 699 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81959,14 +82061,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 700 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(701))(); +module.exports = new (__webpack_require__(703))(); /***/ }), -/* 701 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81979,7 +82081,7 @@ module.exports = new (__webpack_require__(701))(); -var MapCache = __webpack_require__(688); +var MapCache = __webpack_require__(690); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -82101,7 +82203,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 702 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82114,14 +82216,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(703)(); -var Snapdragon = __webpack_require__(624); -utils.define = __webpack_require__(704); -utils.diff = __webpack_require__(705); -utils.extend = __webpack_require__(694); -utils.pick = __webpack_require__(706); -utils.typeOf = __webpack_require__(592); -utils.unique = __webpack_require__(602); +var isWindows = __webpack_require__(705)(); +var Snapdragon = __webpack_require__(626); +utils.define = __webpack_require__(706); +utils.diff = __webpack_require__(707); +utils.extend = __webpack_require__(696); +utils.pick = __webpack_require__(708); +utils.typeOf = __webpack_require__(594); +utils.unique = __webpack_require__(604); /** * Returns true if the given value is effectively an empty string @@ -82487,7 +82589,7 @@ utils.unixify = function(options) { /***/ }), -/* 703 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -82515,7 +82617,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 704 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82528,8 +82630,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); +var isobject = __webpack_require__(592); +var isDescriptor = __webpack_require__(593); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -82560,7 +82662,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 705 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82614,7 +82716,7 @@ function diffArray(one, two) { /***/ }), -/* 706 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82627,7 +82729,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(590); +var isObject = __webpack_require__(592); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -82656,7 +82758,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 707 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82666,18 +82768,18 @@ module.exports = function pick(obj, keys) { * Module dependencies */ -var extend = __webpack_require__(603); -var unique = __webpack_require__(602); -var toRegex = __webpack_require__(582); +var extend = __webpack_require__(605); +var unique = __webpack_require__(604); +var toRegex = __webpack_require__(584); /** * Local dependencies */ -var compilers = __webpack_require__(708); -var parsers = __webpack_require__(714); -var Extglob = __webpack_require__(717); -var utils = __webpack_require__(716); +var compilers = __webpack_require__(710); +var parsers = __webpack_require__(716); +var Extglob = __webpack_require__(719); +var utils = __webpack_require__(718); var MAX_LENGTH = 1024 * 64; /** @@ -82994,13 +83096,13 @@ module.exports = extglob; /***/ }), -/* 708 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(709); +var brackets = __webpack_require__(711); /** * Extglob compilers @@ -83170,7 +83272,7 @@ module.exports = function(extglob) { /***/ }), -/* 709 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83180,17 +83282,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(710); -var parsers = __webpack_require__(712); +var compilers = __webpack_require__(712); +var parsers = __webpack_require__(714); /** * Module dependencies */ -var debug = __webpack_require__(543)('expand-brackets'); -var extend = __webpack_require__(603); -var Snapdragon = __webpack_require__(624); -var toRegex = __webpack_require__(582); +var debug = __webpack_require__(545)('expand-brackets'); +var extend = __webpack_require__(605); +var Snapdragon = __webpack_require__(626); +var toRegex = __webpack_require__(584); /** * Parses the given POSIX character class `pattern` and returns a @@ -83388,13 +83490,13 @@ module.exports = brackets; /***/ }), -/* 710 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(711); +var posix = __webpack_require__(713); module.exports = function(brackets) { brackets.compiler @@ -83482,7 +83584,7 @@ module.exports = function(brackets) { /***/ }), -/* 711 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83511,14 +83613,14 @@ module.exports = { /***/ }), -/* 712 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(713); -var define = __webpack_require__(653); +var utils = __webpack_require__(715); +var define = __webpack_require__(655); /** * Text regex @@ -83737,14 +83839,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 713 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(582); -var regexNot = __webpack_require__(599); +var toRegex = __webpack_require__(584); +var regexNot = __webpack_require__(601); var cached; /** @@ -83778,15 +83880,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 714 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(709); -var define = __webpack_require__(715); -var utils = __webpack_require__(716); +var brackets = __webpack_require__(711); +var define = __webpack_require__(717); +var utils = __webpack_require__(718); /** * Characters to use in text regex (we want to "not" match @@ -83941,7 +84043,7 @@ module.exports = parsers; /***/ }), -/* 715 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83954,7 +84056,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(591); +var isDescriptor = __webpack_require__(593); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -83979,14 +84081,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 716 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(599); -var Cache = __webpack_require__(701); +var regex = __webpack_require__(601); +var Cache = __webpack_require__(703); /** * Utils @@ -84055,7 +84157,7 @@ utils.createRegex = function(str) { /***/ }), -/* 717 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84065,16 +84167,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(624); -var define = __webpack_require__(715); -var extend = __webpack_require__(603); +var Snapdragon = __webpack_require__(626); +var define = __webpack_require__(717); +var extend = __webpack_require__(605); /** * Local dependencies */ -var compilers = __webpack_require__(708); -var parsers = __webpack_require__(714); +var compilers = __webpack_require__(710); +var parsers = __webpack_require__(716); /** * Customize Snapdragon parser and renderer @@ -84140,16 +84242,16 @@ module.exports = Extglob; /***/ }), -/* 718 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(707); -var nanomatch = __webpack_require__(693); -var regexNot = __webpack_require__(599); -var toRegex = __webpack_require__(582); +var extglob = __webpack_require__(709); +var nanomatch = __webpack_require__(695); +var regexNot = __webpack_require__(601); +var toRegex = __webpack_require__(584); var not; /** @@ -84230,14 +84332,14 @@ function textRegex(pattern) { /***/ }), -/* 719 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(701))(); +module.exports = new (__webpack_require__(703))(); /***/ }), -/* 720 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84250,13 +84352,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(624); -utils.define = __webpack_require__(721); -utils.diff = __webpack_require__(705); -utils.extend = __webpack_require__(690); -utils.pick = __webpack_require__(706); -utils.typeOf = __webpack_require__(592); -utils.unique = __webpack_require__(602); +var Snapdragon = __webpack_require__(626); +utils.define = __webpack_require__(723); +utils.diff = __webpack_require__(707); +utils.extend = __webpack_require__(692); +utils.pick = __webpack_require__(708); +utils.typeOf = __webpack_require__(594); +utils.unique = __webpack_require__(604); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -84553,7 +84655,7 @@ utils.unixify = function(options) { /***/ }), -/* 721 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84566,8 +84668,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); +var isobject = __webpack_require__(592); +var isDescriptor = __webpack_require__(593); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -84598,7 +84700,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 722 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84617,9 +84719,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(723); -var reader_1 = __webpack_require__(736); -var fs_stream_1 = __webpack_require__(740); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_stream_1 = __webpack_require__(742); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -84680,15 +84782,15 @@ exports.default = ReaderAsync; /***/ }), -/* 723 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(724); -const readdirAsync = __webpack_require__(732); -const readdirStream = __webpack_require__(735); +const readdirSync = __webpack_require__(726); +const readdirAsync = __webpack_require__(734); +const readdirStream = __webpack_require__(737); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -84772,7 +84874,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 724 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84780,11 +84882,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(725); +const DirectoryReader = __webpack_require__(727); let syncFacade = { - fs: __webpack_require__(730), - forEach: __webpack_require__(731), + fs: __webpack_require__(732), + forEach: __webpack_require__(733), sync: true }; @@ -84813,7 +84915,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 725 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84822,9 +84924,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(726); -const stat = __webpack_require__(728); -const call = __webpack_require__(729); +const normalizeOptions = __webpack_require__(728); +const stat = __webpack_require__(730); +const call = __webpack_require__(731); /** * Asynchronously reads the contents of a directory and streams the results @@ -85200,14 +85302,14 @@ module.exports = DirectoryReader; /***/ }), -/* 726 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(727); +const globToRegExp = __webpack_require__(729); module.exports = normalizeOptions; @@ -85384,7 +85486,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 727 */ +/* 729 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -85521,13 +85623,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 728 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(729); +const call = __webpack_require__(731); module.exports = stat; @@ -85602,7 +85704,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 729 */ +/* 731 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85663,14 +85765,14 @@ function callOnce (fn) { /***/ }), -/* 730 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(729); +const call = __webpack_require__(731); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -85734,7 +85836,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 731 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85763,7 +85865,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 732 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85771,12 +85873,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(733); -const DirectoryReader = __webpack_require__(725); +const maybe = __webpack_require__(735); +const DirectoryReader = __webpack_require__(727); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(734), + forEach: __webpack_require__(736), async: true }; @@ -85818,7 +85920,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 733 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85845,7 +85947,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 734 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85881,7 +85983,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 735 */ +/* 737 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85889,11 +85991,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(725); +const DirectoryReader = __webpack_require__(727); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(734), + forEach: __webpack_require__(736), async: true }; @@ -85913,16 +86015,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 736 */ +/* 738 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(737); -var entry_1 = __webpack_require__(739); -var pathUtil = __webpack_require__(738); +var deep_1 = __webpack_require__(739); +var entry_1 = __webpack_require__(741); +var pathUtil = __webpack_require__(740); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -85988,14 +86090,14 @@ exports.default = Reader; /***/ }), -/* 737 */ +/* 739 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(738); -var patternUtils = __webpack_require__(576); +var pathUtils = __webpack_require__(740); +var patternUtils = __webpack_require__(578); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -86078,7 +86180,7 @@ exports.default = DeepFilter; /***/ }), -/* 738 */ +/* 740 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86109,14 +86211,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 739 */ +/* 741 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(738); -var patternUtils = __webpack_require__(576); +var pathUtils = __webpack_require__(740); +var patternUtils = __webpack_require__(578); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -86201,7 +86303,7 @@ exports.default = EntryFilter; /***/ }), -/* 740 */ +/* 742 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86221,8 +86323,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(741); -var fs_1 = __webpack_require__(745); +var fsStat = __webpack_require__(743); +var fs_1 = __webpack_require__(747); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -86272,14 +86374,14 @@ exports.default = FileSystemStream; /***/ }), -/* 741 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(742); -const statProvider = __webpack_require__(744); +const optionsManager = __webpack_require__(744); +const statProvider = __webpack_require__(746); /** * Asynchronous API. */ @@ -86310,13 +86412,13 @@ exports.statSync = statSync; /***/ }), -/* 742 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(743); +const fsAdapter = __webpack_require__(745); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -86329,7 +86431,7 @@ exports.prepare = prepare; /***/ }), -/* 743 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86352,7 +86454,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 744 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86404,7 +86506,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 745 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86435,7 +86537,7 @@ exports.default = FileSystem; /***/ }), -/* 746 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86455,9 +86557,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(723); -var reader_1 = __webpack_require__(736); -var fs_stream_1 = __webpack_require__(740); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_stream_1 = __webpack_require__(742); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -86525,7 +86627,7 @@ exports.default = ReaderStream; /***/ }), -/* 747 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86544,9 +86646,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(723); -var reader_1 = __webpack_require__(736); -var fs_sync_1 = __webpack_require__(748); +var readdir = __webpack_require__(725); +var reader_1 = __webpack_require__(738); +var fs_sync_1 = __webpack_require__(750); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -86606,7 +86708,7 @@ exports.default = ReaderSync; /***/ }), -/* 748 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86625,8 +86727,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(741); -var fs_1 = __webpack_require__(745); +var fsStat = __webpack_require__(743); +var fs_1 = __webpack_require__(747); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -86672,7 +86774,7 @@ exports.default = FileSystemSync; /***/ }), -/* 749 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86688,7 +86790,7 @@ exports.flatten = flatten; /***/ }), -/* 750 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86709,13 +86811,13 @@ exports.merge = merge; /***/ }), -/* 751 */ +/* 753 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(752); +const pathType = __webpack_require__(754); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -86781,13 +86883,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 752 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(753); +const pify = __webpack_require__(755); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -86830,7 +86932,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 753 */ +/* 755 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86921,17 +87023,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 754 */ +/* 756 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(572); -const gitIgnore = __webpack_require__(755); -const pify = __webpack_require__(756); -const slash = __webpack_require__(757); +const fastGlob = __webpack_require__(574); +const gitIgnore = __webpack_require__(757); +const pify = __webpack_require__(758); +const slash = __webpack_require__(759); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -87029,7 +87131,7 @@ module.exports.sync = options => { /***/ }), -/* 755 */ +/* 757 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -87498,7 +87600,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 756 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87573,7 +87675,7 @@ module.exports = (input, options) => { /***/ }), -/* 757 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87591,7 +87693,7 @@ module.exports = input => { /***/ }), -/* 758 */ +/* 760 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -87604,7 +87706,7 @@ module.exports = input => { -var isGlob = __webpack_require__(759); +var isGlob = __webpack_require__(761); module.exports = function hasGlob(val) { if (val == null) return false; @@ -87624,7 +87726,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 759 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -87655,17 +87757,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 760 */ +/* 762 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(761); -const CpFileError = __webpack_require__(764); -const fs = __webpack_require__(766); -const ProgressEmitter = __webpack_require__(769); +const pEvent = __webpack_require__(763); +const CpFileError = __webpack_require__(766); +const fs = __webpack_require__(768); +const ProgressEmitter = __webpack_require__(771); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -87779,12 +87881,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 761 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(762); +const pTimeout = __webpack_require__(764); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -88075,12 +88177,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 762 */ +/* 764 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(763); +const pFinally = __webpack_require__(765); class TimeoutError extends Error { constructor(message) { @@ -88126,7 +88228,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 763 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88148,12 +88250,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 764 */ +/* 766 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(765); +const NestedError = __webpack_require__(767); class CpFileError extends NestedError { constructor(message, nested) { @@ -88167,7 +88269,7 @@ module.exports = CpFileError; /***/ }), -/* 765 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -88223,16 +88325,16 @@ module.exports = NestedError; /***/ }), -/* 766 */ +/* 768 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(767); -const pEvent = __webpack_require__(761); -const CpFileError = __webpack_require__(764); +const makeDir = __webpack_require__(769); +const pEvent = __webpack_require__(763); +const CpFileError = __webpack_require__(766); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -88329,7 +88431,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 767 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88337,7 +88439,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(768); +const semver = __webpack_require__(770); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -88492,7 +88594,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 768 */ +/* 770 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -90094,7 +90196,7 @@ function coerce (version, options) { /***/ }), -/* 769 */ +/* 771 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90135,7 +90237,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 770 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90181,12 +90283,12 @@ exports.default = module.exports; /***/ }), -/* 771 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(772); +const pMap = __webpack_require__(774); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -90203,7 +90305,7 @@ module.exports.default = pFilter; /***/ }), -/* 772 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90282,12 +90384,12 @@ module.exports.default = pMap; /***/ }), -/* 773 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(765); +const NestedError = __webpack_require__(767); class CpyError extends NestedError { constructor(message, nested) { @@ -90301,7 +90403,7 @@ module.exports = CpyError; /***/ }), -/* 774 */ +/* 776 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90309,10 +90411,10 @@ module.exports = CpyError; const fs = __webpack_require__(134); const arrayUnion = __webpack_require__(145); const merge2 = __webpack_require__(146); -const fastGlob = __webpack_require__(775); +const fastGlob = __webpack_require__(777); const dirGlob = __webpack_require__(232); -const gitignore = __webpack_require__(810); -const {FilterStream, UniqueStream} = __webpack_require__(811); +const gitignore = __webpack_require__(812); +const {FilterStream, UniqueStream} = __webpack_require__(813); const DEFAULT_FILTER = () => false; @@ -90489,17 +90591,17 @@ module.exports.gitignore = gitignore; /***/ }), -/* 775 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const taskManager = __webpack_require__(776); -const async_1 = __webpack_require__(796); -const stream_1 = __webpack_require__(806); -const sync_1 = __webpack_require__(807); -const settings_1 = __webpack_require__(809); -const utils = __webpack_require__(777); +const taskManager = __webpack_require__(778); +const async_1 = __webpack_require__(798); +const stream_1 = __webpack_require__(808); +const sync_1 = __webpack_require__(809); +const settings_1 = __webpack_require__(811); +const utils = __webpack_require__(779); async function FastGlob(source, options) { assertPatternsInput(source); const works = getWorks(source, async_1.default, options); @@ -90563,14 +90665,14 @@ module.exports = FastGlob; /***/ }), -/* 776 */ +/* 778 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.convertPatternGroupToTask = exports.convertPatternGroupsToTasks = exports.groupPatternsByBaseDirectory = exports.getNegativePatternsAsPositive = exports.getPositivePatterns = exports.convertPatternsToTasks = exports.generate = void 0; -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); function generate(patterns, settings) { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); @@ -90635,31 +90737,31 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 777 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.string = exports.stream = exports.pattern = exports.path = exports.fs = exports.errno = exports.array = void 0; -const array = __webpack_require__(778); +const array = __webpack_require__(780); exports.array = array; -const errno = __webpack_require__(779); +const errno = __webpack_require__(781); exports.errno = errno; -const fs = __webpack_require__(780); +const fs = __webpack_require__(782); exports.fs = fs; -const path = __webpack_require__(781); +const path = __webpack_require__(783); exports.path = path; -const pattern = __webpack_require__(782); +const pattern = __webpack_require__(784); exports.pattern = pattern; -const stream = __webpack_require__(794); +const stream = __webpack_require__(796); exports.stream = stream; -const string = __webpack_require__(795); +const string = __webpack_require__(797); exports.string = string; /***/ }), -/* 778 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90688,7 +90790,7 @@ exports.splitWhen = splitWhen; /***/ }), -/* 779 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90702,7 +90804,7 @@ exports.isEnoentCodeError = isEnoentCodeError; /***/ }), -/* 780 */ +/* 782 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90728,7 +90830,7 @@ exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 781 */ +/* 783 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90768,7 +90870,7 @@ exports.removeLeadingDotSegment = removeLeadingDotSegment; /***/ }), -/* 782 */ +/* 784 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90777,7 +90879,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.matchAny = exports.convertPatternsToRe = exports.makeRe = exports.getPatternParts = exports.expandBraceExpansion = exports.expandPatternsWithBraceExpansion = exports.isAffectDepthOfReadingPattern = exports.endsWithSlashGlobStar = exports.hasGlobStar = exports.getBaseDirectory = exports.getPositivePatterns = exports.getNegativePatterns = exports.isPositivePattern = exports.isNegativePattern = exports.convertToNegativePattern = exports.convertToPositivePattern = exports.isDynamicPattern = exports.isStaticPattern = void 0; const path = __webpack_require__(4); const globParent = __webpack_require__(171); -const micromatch = __webpack_require__(783); +const micromatch = __webpack_require__(785); const picomatch = __webpack_require__(185); const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; @@ -90907,14 +91009,14 @@ exports.matchAny = matchAny; /***/ }), -/* 783 */ +/* 785 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const util = __webpack_require__(112); -const braces = __webpack_require__(784); +const braces = __webpack_require__(786); const picomatch = __webpack_require__(185); const utils = __webpack_require__(188); const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); @@ -91381,16 +91483,16 @@ module.exports = micromatch; /***/ }), -/* 784 */ +/* 786 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(785); -const compile = __webpack_require__(787); -const expand = __webpack_require__(791); -const parse = __webpack_require__(792); +const stringify = __webpack_require__(787); +const compile = __webpack_require__(789); +const expand = __webpack_require__(793); +const parse = __webpack_require__(794); /** * Expand the given pattern or create a regex-compatible string. @@ -91558,13 +91660,13 @@ module.exports = braces; /***/ }), -/* 785 */ +/* 787 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const utils = __webpack_require__(786); +const utils = __webpack_require__(788); module.exports = (ast, options = {}) => { let stringify = (node, parent = {}) => { @@ -91597,7 +91699,7 @@ module.exports = (ast, options = {}) => { /***/ }), -/* 786 */ +/* 788 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91716,14 +91818,14 @@ exports.flatten = (...args) => { /***/ }), -/* 787 */ +/* 789 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(788); -const utils = __webpack_require__(786); +const fill = __webpack_require__(790); +const utils = __webpack_require__(788); const compile = (ast, options = {}) => { let walk = (node, parent = {}) => { @@ -91780,7 +91882,7 @@ module.exports = compile; /***/ }), -/* 788 */ +/* 790 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91794,7 +91896,7 @@ module.exports = compile; const util = __webpack_require__(112); -const toRegexRange = __webpack_require__(789); +const toRegexRange = __webpack_require__(791); const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); @@ -92036,7 +92138,7 @@ module.exports = fill; /***/ }), -/* 789 */ +/* 791 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92049,7 +92151,7 @@ module.exports = fill; -const isNumber = __webpack_require__(790); +const isNumber = __webpack_require__(792); const toRegexRange = (min, max, options) => { if (isNumber(min) === false) { @@ -92331,7 +92433,7 @@ module.exports = toRegexRange; /***/ }), -/* 790 */ +/* 792 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92356,15 +92458,15 @@ module.exports = function(num) { /***/ }), -/* 791 */ +/* 793 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const fill = __webpack_require__(788); -const stringify = __webpack_require__(785); -const utils = __webpack_require__(786); +const fill = __webpack_require__(790); +const stringify = __webpack_require__(787); +const utils = __webpack_require__(788); const append = (queue = '', stash = '', enclose = false) => { let result = []; @@ -92476,13 +92578,13 @@ module.exports = expand; /***/ }), -/* 792 */ +/* 794 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringify = __webpack_require__(785); +const stringify = __webpack_require__(787); /** * Constants @@ -92504,7 +92606,7 @@ const { CHAR_SINGLE_QUOTE, /* ' */ CHAR_NO_BREAK_SPACE, CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(793); +} = __webpack_require__(795); /** * parse @@ -92816,7 +92918,7 @@ module.exports = parse; /***/ }), -/* 793 */ +/* 795 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92880,7 +92982,7 @@ module.exports = { /***/ }), -/* 794 */ +/* 796 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92904,7 +93006,7 @@ function propagateCloseEventToSources(streams) { /***/ }), -/* 795 */ +/* 797 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92922,14 +93024,14 @@ exports.isEmpty = isEmpty; /***/ }), -/* 796 */ +/* 798 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(797); -const provider_1 = __webpack_require__(799); +const stream_1 = __webpack_require__(799); +const provider_1 = __webpack_require__(801); class ProviderAsync extends provider_1.default { constructor() { super(...arguments); @@ -92957,7 +93059,7 @@ exports.default = ProviderAsync; /***/ }), -/* 797 */ +/* 799 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -92966,7 +93068,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); const fsStat = __webpack_require__(195); const fsWalk = __webpack_require__(200); -const reader_1 = __webpack_require__(798); +const reader_1 = __webpack_require__(800); class ReaderStream extends reader_1.default { constructor() { super(...arguments); @@ -93019,7 +93121,7 @@ exports.default = ReaderStream; /***/ }), -/* 798 */ +/* 800 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93027,7 +93129,7 @@ exports.default = ReaderStream; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); const fsStat = __webpack_require__(195); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class Reader { constructor(_settings) { this._settings = _settings; @@ -93059,17 +93161,17 @@ exports.default = Reader; /***/ }), -/* 799 */ +/* 801 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const path = __webpack_require__(4); -const deep_1 = __webpack_require__(800); -const entry_1 = __webpack_require__(803); -const error_1 = __webpack_require__(804); -const entry_2 = __webpack_require__(805); +const deep_1 = __webpack_require__(802); +const entry_1 = __webpack_require__(805); +const error_1 = __webpack_require__(806); +const entry_2 = __webpack_require__(807); class Provider { constructor(_settings) { this._settings = _settings; @@ -93114,14 +93216,14 @@ exports.default = Provider; /***/ }), -/* 800 */ +/* 802 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); -const partial_1 = __webpack_require__(801); +const utils = __webpack_require__(779); +const partial_1 = __webpack_require__(803); class DeepFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -93183,13 +93285,13 @@ exports.default = DeepFilter; /***/ }), -/* 801 */ +/* 803 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const matcher_1 = __webpack_require__(802); +const matcher_1 = __webpack_require__(804); class PartialMatcher extends matcher_1.default { match(filepath) { const parts = filepath.split('/'); @@ -93228,13 +93330,13 @@ exports.default = PartialMatcher; /***/ }), -/* 802 */ +/* 804 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class Matcher { constructor(_patterns, _settings, _micromatchOptions) { this._patterns = _patterns; @@ -93285,13 +93387,13 @@ exports.default = Matcher; /***/ }), -/* 803 */ +/* 805 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class EntryFilter { constructor(_settings, _micromatchOptions) { this._settings = _settings; @@ -93348,13 +93450,13 @@ exports.default = EntryFilter; /***/ }), -/* 804 */ +/* 806 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class ErrorFilter { constructor(_settings) { this._settings = _settings; @@ -93370,13 +93472,13 @@ exports.default = ErrorFilter; /***/ }), -/* 805 */ +/* 807 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(777); +const utils = __webpack_require__(779); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -93403,15 +93505,15 @@ exports.default = EntryTransformer; /***/ }), -/* 806 */ +/* 808 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const stream_1 = __webpack_require__(138); -const stream_2 = __webpack_require__(797); -const provider_1 = __webpack_require__(799); +const stream_2 = __webpack_require__(799); +const provider_1 = __webpack_require__(801); class ProviderStream extends provider_1.default { constructor() { super(...arguments); @@ -93441,14 +93543,14 @@ exports.default = ProviderStream; /***/ }), -/* 807 */ +/* 809 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(808); -const provider_1 = __webpack_require__(799); +const sync_1 = __webpack_require__(810); +const provider_1 = __webpack_require__(801); class ProviderSync extends provider_1.default { constructor() { super(...arguments); @@ -93471,7 +93573,7 @@ exports.default = ProviderSync; /***/ }), -/* 808 */ +/* 810 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93479,7 +93581,7 @@ exports.default = ProviderSync; Object.defineProperty(exports, "__esModule", { value: true }); const fsStat = __webpack_require__(195); const fsWalk = __webpack_require__(200); -const reader_1 = __webpack_require__(798); +const reader_1 = __webpack_require__(800); class ReaderSync extends reader_1.default { constructor() { super(...arguments); @@ -93521,7 +93623,7 @@ exports.default = ReaderSync; /***/ }), -/* 809 */ +/* 811 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93585,7 +93687,7 @@ exports.default = Settings; /***/ }), -/* 810 */ +/* 812 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93593,7 +93695,7 @@ exports.default = Settings; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(775); +const fastGlob = __webpack_require__(777); const gitIgnore = __webpack_require__(235); const slash = __webpack_require__(236); @@ -93712,7 +93814,7 @@ module.exports.sync = options => { /***/ }), -/* 811 */ +/* 813 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -93765,7 +93867,7 @@ module.exports = { /***/ }), -/* 812 */ +/* 814 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -93773,13 +93875,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildNonBazelProductionProjects", function() { return buildNonBazelProductionProjects; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProductionProjects", function() { return getProductionProjects; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProject", function() { return buildProject; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(567); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(562); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(564); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index b383a52be63f5..bad6eef3266f8 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -66,7 +66,7 @@ export const BootstrapCommand: ICommand = { await runBazel(['run', '@nodejs//:yarn'], runOffline); } - await runBazel(['build', '//packages:build'], runOffline); + await runBazel(['build', '//packages:build', '--show_result=1'], runOffline); // Install monorepo npm dependencies outside of the Bazel managed ones for (const batch of batchedNonBazelProjects) { diff --git a/packages/kbn-pm/src/commands/build_bazel.ts b/packages/kbn-pm/src/commands/build_bazel.ts new file mode 100644 index 0000000000000..f71e2e96e31b0 --- /dev/null +++ b/packages/kbn-pm/src/commands/build_bazel.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { runBazel } from '../utils/bazel'; +import { ICommand } from './'; + +export const BuildBazelCommand: ICommand = { + description: 'Runs a build in the Bazel built packages', + name: 'build-bazel', + + async run(projects, projectGraph, { options }) { + const runOffline = options?.offline === true; + + // Call bazel with the target to build all available packages + await runBazel(['build', '//packages:build', '--show_result=1'], runOffline); + }, +}; diff --git a/packages/kbn-pm/src/commands/index.ts b/packages/kbn-pm/src/commands/index.ts index 0ab6bc9c7808a..2f5c04c2f434f 100644 --- a/packages/kbn-pm/src/commands/index.ts +++ b/packages/kbn-pm/src/commands/index.ts @@ -27,16 +27,20 @@ export interface ICommand { } import { BootstrapCommand } from './bootstrap'; +import { BuildBazelCommand } from './build_bazel'; import { CleanCommand } from './clean'; import { ResetCommand } from './reset'; import { RunCommand } from './run'; import { WatchCommand } from './watch'; +import { WatchBazelCommand } from './watch_bazel'; import { Kibana } from '../utils/kibana'; export const commands: { [key: string]: ICommand } = { bootstrap: BootstrapCommand, + 'build-bazel': BuildBazelCommand, clean: CleanCommand, reset: ResetCommand, run: RunCommand, watch: WatchCommand, + 'watch-bazel': WatchBazelCommand, }; diff --git a/packages/kbn-pm/src/commands/run.ts b/packages/kbn-pm/src/commands/run.ts index 5535fe0d8358f..9a3a19d9e625e 100644 --- a/packages/kbn-pm/src/commands/run.ts +++ b/packages/kbn-pm/src/commands/run.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import dedent from 'dedent'; import { CliError } from '../utils/errors'; import { log } from '../utils/log'; import { parallelizeBatches } from '../utils/parallelize'; @@ -13,10 +14,17 @@ import { topologicallyBatchProjects } from '../utils/projects'; import { ICommand } from './'; export const RunCommand: ICommand = { - description: 'Run script defined in package.json in each package that contains that script.', + description: + 'Run script defined in package.json in each package that contains that script (only works on packages not using Bazel yet)', name: 'run', async run(projects, projectGraph, { extraArgs, options }) { + log.warning(dedent` + We are migrating packages into the Bazel build system and we will no longer support running npm scripts on + packages using 'yarn kbn run' on Bazel built packages. If the package you are trying to act on contains a + BUILD.bazel file please just use 'yarn kbn build-bazel' to build it or 'yarn kbn watch-bazel' to watch it + `); + const batchedProjects = topologicallyBatchProjects(projects, projectGraph); if (extraArgs.length === 0) { diff --git a/packages/kbn-pm/src/commands/watch.ts b/packages/kbn-pm/src/commands/watch.ts index fb398d6852136..5d0f6d086d3e8 100644 --- a/packages/kbn-pm/src/commands/watch.ts +++ b/packages/kbn-pm/src/commands/watch.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import dedent from 'dedent'; import { CliError } from '../utils/errors'; import { log } from '../utils/log'; import { parallelizeBatches } from '../utils/parallelize'; @@ -34,10 +35,16 @@ const kibanaProjectName = 'kibana'; * `webpack` and `tsc` only, for the rest we rely on predefined timeouts. */ export const WatchCommand: ICommand = { - description: 'Runs `kbn:watch` script for every project.', + description: + 'Runs `kbn:watch` script for every project (only works on packages not using Bazel yet)', name: 'watch', async run(projects, projectGraph) { + log.warning(dedent` + We are migrating packages into the Bazel build system. If the package you are trying to watch + contains a BUILD.bazel file please just use 'yarn kbn watch-bazel' + `); + const projectsToWatch: ProjectMap = new Map(); for (const project of projects.values()) { // We can't watch project that doesn't have `kbn:watch` script. diff --git a/packages/kbn-pm/src/commands/watch_bazel.ts b/packages/kbn-pm/src/commands/watch_bazel.ts new file mode 100644 index 0000000000000..1273562dd2511 --- /dev/null +++ b/packages/kbn-pm/src/commands/watch_bazel.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { runIBazel } from '../utils/bazel'; +import { ICommand } from './'; + +export const WatchBazelCommand: ICommand = { + description: 'Runs a build in the Bazel built packages and keeps watching them for changes', + name: 'watch-bazel', + + async run(projects, projectGraph, { options }) { + const runOffline = options?.offline === true; + + // Call bazel with the target to build all available packages and run it through iBazel to watch it for changes + // + // Note: --run_output=false arg will disable the iBazel notifications about gazelle and buildozer when running it + // Can also be solved by adding a root `.bazel_fix_commands.json` but its not needed at the moment + await runIBazel(['--run_output=false', 'build', '//packages:build'], runOffline); + }, +}; diff --git a/packages/kbn-pm/src/utils/bazel/run.ts b/packages/kbn-pm/src/utils/bazel/run.ts index ab20150768b78..34718606db98e 100644 --- a/packages/kbn-pm/src/utils/bazel/run.ts +++ b/packages/kbn-pm/src/utils/bazel/run.ts @@ -13,8 +13,12 @@ import { tap } from 'rxjs/operators'; import { observeLines } from '@kbn/dev-utils/stdio'; import { spawn } from '../child_process'; import { log } from '../log'; +import { CliError } from '../errors'; -export async function runBazel( +type BazelCommandRunner = 'bazel' | 'ibazel'; + +async function runBazelCommandWithRunner( + bazelCommandRunner: BazelCommandRunner, bazelArgs: string[], offline: boolean = false, runOpts: execa.Options = {} @@ -29,7 +33,7 @@ export async function runBazel( bazelArgs.push('--config=offline'); } - const bazelProc = spawn('bazel', bazelArgs, bazelOpts); + const bazelProc = spawn(bazelCommandRunner, bazelArgs, bazelOpts); const bazelLogs$ = new Rx.Subject(); @@ -37,15 +41,35 @@ export async function runBazel( // Therefore we need to get both. In order to get errors we need to parse the actual text line const bazelLogSubscription = Rx.merge( observeLines(bazelProc.stdout!).pipe( - tap((line) => log.info(`${chalk.cyan('[bazel]')} ${line}`)) + tap((line) => log.info(`${chalk.cyan(`[${bazelCommandRunner}]`)} ${line}`)) ), observeLines(bazelProc.stderr!).pipe( - tap((line) => log.info(`${chalk.cyan('[bazel]')} ${line}`)) + tap((line) => log.info(`${chalk.cyan(`[${bazelCommandRunner}]`)} ${line}`)) ) ).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end - await bazelProc; + try { + await bazelProc; + } catch { + throw new CliError(`The bazel command that was running failed to complete.`); + } await bazelLogs$.toPromise(); await bazelLogSubscription.unsubscribe(); } + +export async function runBazel( + bazelArgs: string[], + offline: boolean = false, + runOpts: execa.Options = {} +) { + await runBazelCommandWithRunner('bazel', bazelArgs, offline, runOpts); +} + +export async function runIBazel( + bazelArgs: string[], + offline: boolean = false, + runOpts: execa.Options = {} +) { + await runBazelCommandWithRunner('ibazel', bazelArgs, offline, runOpts); +} From 4f6bd31c912c46254853bfba1886b56a63c9ffd2 Mon Sep 17 00:00:00 2001 From: ymao1 Date: Mon, 12 Apr 2021 21:12:45 -0400 Subject: [PATCH 51/79] [Alerting] Fixing `notifyWhen` terminology (#96490) * Updating terminology * Updating wording * Updating wording --- docs/user/alerting/defining-rules.asciidoc | 4 ++-- .../application/sections/alert_form/alert_notify_when.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/user/alerting/defining-rules.asciidoc b/docs/user/alerting/defining-rules.asciidoc index 63839cf465e98..05885f1af13ba 100644 --- a/docs/user/alerting/defining-rules.asciidoc +++ b/docs/user/alerting/defining-rules.asciidoc @@ -28,8 +28,8 @@ Name:: The name of the rule. While this name does not have to be unique, th Tags:: A list of tag names that can be applied to a rule. Tags can help you organize and find rules, because tags appear in the rule listing in the management UI which is searchable by tag. Check every:: This value determines how frequently the rule conditions below are checked. Note that the timing of background rule checks are not guaranteed, particularly for intervals of less than 10 seconds. See <> for more information. Notify:: This value limits how often actions are repeated when an alert remains active across rule checks. See <> for more information. + -- **Only on status change**: Actions are not repeated when an alert remains active across checks. Actions run only when the rule status changes. -- **Every time rule is active**: Actions are repeated when an alert remains active across checks. +- **Only on status change**: Actions are not repeated when an alert remains active across checks. Actions run only when the alert status changes. +- **Every time alert is active**: Actions are repeated when an alert remains active across checks. - **On a custom action interval**: Actions are suppressed for the throttle interval, but repeat when an alert remains active across checks for a duration longer than the throttle interval. diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx index 95fbe9c6ae614..b774fd702fadc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_notify_when.tsx @@ -49,7 +49,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

@@ -62,7 +62,7 @@ const NOTIFY_WHEN_OPTIONS: Array> = [ inputDisplay: i18n.translate( 'xpack.triggersActionsUI.sections.alertForm.alertNotifyWhen.onActiveAlert.display', { - defaultMessage: 'Every time rule is active', + defaultMessage: 'Every time alert is active', } ), 'data-test-subj': 'onActiveAlert', @@ -70,14 +70,14 @@ const NOTIFY_WHEN_OPTIONS: Array> = [

From 9a10bcb6526c3e7773a278462de732d1b8df70ec Mon Sep 17 00:00:00 2001 From: Andrew Pease <7442091+peasead@users.noreply.github.com> Date: Mon, 12 Apr 2021 21:57:04 -0500 Subject: [PATCH 52/79] Update README.md - broken params env link (#95820) ## Summary The link to set the params env was broken. ### Checklist Delete any items that are not applicable to this PR. - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --- .../lib/detection_engine/rules/prepackaged_timelines/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md index 901dacbfe80cc..1b8516ee16012 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/README.md @@ -4,7 +4,7 @@ -1. [Have the env params set up](https://github.com/elastic/kibana/blob/master/x-pack/plugins/siem/server/lib/detection_engine/README.md) +1. [Have the env params set up](https://github.com/elastic/kibana/blob/master/x-pack/plugins/security_solution/server/lib/detection_engine/README.md) 2. Create a new timelines template into `x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines` From 0da55781826385461148942e4857f2436080700e Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 13 Apr 2021 08:19:26 +0200 Subject: [PATCH 53/79] [Data] Pass field meta to value suggestions api (#96239) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../providers/value_suggestion_provider.ts | 2 +- .../server/autocomplete/value_suggestions_route.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts index b8af6ad3a99e5..3dda97566da5a 100644 --- a/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts +++ b/src/plugins/data/public/autocomplete/providers/value_suggestion_provider.ts @@ -59,7 +59,7 @@ export const setupValueSuggestionProvider = ( return core.http .fetch(`/api/kibana/suggestions/values/${index}`, { method: 'POST', - body: JSON.stringify({ query, field: field.name, filters }), + body: JSON.stringify({ query, field: field.name, fieldMeta: field?.toSpec?.(), filters }), signal, }) .then((r) => { diff --git a/src/plugins/data/server/autocomplete/value_suggestions_route.ts b/src/plugins/data/server/autocomplete/value_suggestions_route.ts index bdcc13ce4c061..f0487b93b8ee5 100644 --- a/src/plugins/data/server/autocomplete/value_suggestions_route.ts +++ b/src/plugins/data/server/autocomplete/value_suggestions_route.ts @@ -36,6 +36,7 @@ export function registerValueSuggestionsRoute( field: schema.string(), query: schema.string(), filters: schema.maybe(schema.any()), + fieldMeta: schema.maybe(schema.any()), }, { unknowns: 'allow' } ), @@ -43,7 +44,7 @@ export function registerValueSuggestionsRoute( }, async (context, request, response) => { const config = await config$.pipe(first()).toPromise(); - const { field: fieldName, query, filters } = request.body; + const { field: fieldName, query, filters, fieldMeta } = request.body; const { index } = request.params; const { client } = context.core.elasticsearch.legacy; const signal = getRequestAbortedSignal(request.events.aborted$); @@ -53,9 +54,14 @@ export function registerValueSuggestionsRoute( terminate_after: config.kibana.autocompleteTerminateAfter.asMilliseconds(), }; - const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); + let field: IFieldType | undefined = fieldMeta; + + if (!field?.name && !field?.type) { + const indexPattern = await findIndexPatternById(context.core.savedObjects.client, index); + + field = indexPattern && getFieldByName(fieldName, indexPattern); + } - const field = indexPattern && getFieldByName(fieldName, indexPattern); const body = await getBody(autocompleteSearchOptions, field || fieldName, query, filters); const result = await client.callAsCurrentUser('search', { index, body }, { signal }); From d7a09e4dc53232e38bba2fc1e1521e7793594304 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Tue, 13 Apr 2021 09:54:40 +0300 Subject: [PATCH 54/79] [Security Solution][Cases] Fix create case flyout on timeline. (#96798) --- .../public/cases/components/create/flyout.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx index e7bb0b25f391f..8f76ee8f85173 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/flyout.tsx @@ -33,11 +33,25 @@ const StyledFlyout = styled(EuiFlyout)` z-index: ${theme.eui.euiZModal}; `} `; - // Adding bottom padding because timeline's // bottom bar gonna hide the submit button. +const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` + ${({ theme }) => ` + && .euiFlyoutBody__overflow { + overflow-y: auto; + overflow-x: hidden; + } + + && .euiFlyoutBody__overflowContent { + display: block; + padding: ${theme.eui.paddingSizes.l} ${theme.eui.paddingSizes.l} 70px; + height: auto; + } + `} +`; + const FormWrapper = styled.div` - padding-bottom: 50px; + width: 100%; `; const CreateCaseFlyoutComponent: React.FC = ({ @@ -52,7 +66,7 @@ const CreateCaseFlyoutComponent: React.FC = ({

{i18n.CREATE_TITLE}

- + @@ -61,7 +75,7 @@ const CreateCaseFlyoutComponent: React.FC = ({ - + ); }; From ebfbe6fc8cb99fa8f67b9094fab55968b1b7e2b8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 13 Apr 2021 09:45:21 +0200 Subject: [PATCH 55/79] close popover on dragging (#96784) --- x-pack/plugins/lens/public/drag_drop/drag_drop.tsx | 14 ++++++++++---- .../public/indexpattern_datasource/field_item.tsx | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx index 88de9154ffc34..51021a3e50b3f 100644 --- a/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/plugins/lens/public/drag_drop/drag_drop.tsx @@ -44,6 +44,16 @@ interface BaseProps { * is dropped onto this DragDrop component. */ onDrop?: DropHandler; + /** + * The event handler that fires when this element is dragged. + */ + onDragStart?: ( + target?: DroppableEvent['currentTarget'] | KeyboardEvent['currentTarget'] + ) => void; + /** + * The event handler that fires when the dragging of this element ends. + */ + onDragEnd?: () => void; /** * The value associated with this item. */ @@ -116,10 +126,6 @@ interface DragInnerProps extends BaseProps { activeDropTarget: DragContextState['activeDropTarget']; dropTargetsByOrder: DragContextState['dropTargetsByOrder']; }; - onDragStart?: ( - target?: DroppableEvent['currentTarget'] | KeyboardEvent['currentTarget'] - ) => void; - onDragEnd?: () => void; extraKeyboardHandler?: (e: KeyboardEvent) => void; ariaDescribedBy?: string; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx index 8ae62e4d843c2..2da7902038345 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_item.tsx @@ -193,6 +193,10 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { } } + const onDragStart = useCallback(() => { + setOpen(false); + }, [setOpen]); + const value = useMemo( () => ({ field, @@ -244,6 +248,7 @@ export const InnerFieldItem = function InnerFieldItem(props: FieldItemProps) { order={order} value={value} dataTestSubj={`lnsFieldListPanelField-${field.name}`} + onDragStart={onDragStart} > Date: Tue, 13 Apr 2021 10:42:19 +0200 Subject: [PATCH 56/79] [Graph] Map request failure for text fields with better error message (#96777) --- x-pack/plugins/graph/server/routes/explore.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index 9a9a267c40f32..7109eee3b9111 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -67,6 +67,7 @@ export function registerExploreRoute({ cause.reason.includes('No support for examining floating point') || cause.reason.includes('Sample diversifying key must be a single valued-field') || cause.reason.includes('Failed to parse query') || + cause.reason.includes('Text fields are not optimised for operations') || cause.type === 'parsing_exception' ); }); From f31e13c42625da7ee04368a7e14f4001ea5ce371 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 13 Apr 2021 10:51:43 +0200 Subject: [PATCH 57/79] [Ingest Pipelines] Migrate to new ES client (#96406) * - migrated use of legacy.client to client - removed use of isEsError to detect legacy errors - refactored types to use types from @elastic/elasticsearch instead (where appropriate) tested get, put, post, delete, simulate and documents endpoints locally * remove use of legacyEs service in functional test * fixing type issues and API response object * remove id from get all request! * reinstated logic for handling 404 from get all pipelines request * clarify error handling with comments and small variable name refactor * updated delete error responses * update functional test * refactor use of legacyEs Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../errors/handle_es_error.ts | 4 +- .../common/lib/pipeline_serialization.ts | 9 +++-- .../plugins/ingest_pipelines/common/types.ts | 2 +- .../plugins/ingest_pipelines/server/plugin.ts | 4 +- .../server/routes/api/create.ts | 27 ++++--------- .../server/routes/api/delete.ts | 14 ++++--- .../server/routes/api/documents.ts | 15 ++------ .../ingest_pipelines/server/routes/api/get.ts | 38 +++++++------------ .../server/routes/api/privileges.ts | 23 +++-------- .../server/routes/api/shared/index.ts | 2 +- .../routes/api/shared/is_object_with_keys.ts | 10 ----- .../api/{ => shared}/pipeline_schema.ts | 0 .../server/routes/api/simulate.ts | 21 ++++------ .../server/routes/api/update.ts | 25 +++--------- .../ingest_pipelines/server/shared_imports.ts | 2 +- .../plugins/ingest_pipelines/server/types.ts | 4 +- .../ingest_pipelines/ingest_pipelines.ts | 26 +++++-------- .../ingest_pipelines/lib/elasticsearch.ts | 11 +++--- .../apps/ingest_pipelines/ingest_pipelines.ts | 2 +- 19 files changed, 85 insertions(+), 154 deletions(-) delete mode 100644 x-pack/plugins/ingest_pipelines/server/routes/api/shared/is_object_with_keys.ts rename x-pack/plugins/ingest_pipelines/server/routes/api/{ => shared}/pipeline_schema.ts (100%) diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts index 42e18b72057ce..6a308203fcc27 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts @@ -17,8 +17,10 @@ interface EsErrorHandlerParams { handleCustomError?: () => IKibanaResponse; } -/* +/** * For errors returned by the new elasticsearch js client. + * + * @throws If "error" is not an error from the elasticsearch client this handler will throw "error". */ export const handleEsError = ({ error, diff --git a/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts b/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts index 997f14fd5e28d..5360e2713aee1 100644 --- a/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts +++ b/x-pack/plugins/ingest_pipelines/common/lib/pipeline_serialization.ts @@ -5,14 +5,17 @@ * 2.0. */ -import { PipelinesByName, Pipeline } from '../types'; +import { Pipeline as ESPipeline } from '@elastic/elasticsearch/api/types'; +import { Pipeline, Processor } from '../types'; -export function deserializePipelines(pipelinesByName: PipelinesByName): Pipeline[] { +export function deserializePipelines(pipelinesByName: { [key: string]: ESPipeline }): Pipeline[] { const pipelineNames: string[] = Object.keys(pipelinesByName); - const deserializedPipelines = pipelineNames.map((name: string) => { + const deserializedPipelines = pipelineNames.map((name: string) => { return { ...pipelinesByName[name], + processors: (pipelinesByName[name]?.processors as Processor[]) ?? [], + on_failure: pipelinesByName[name]?.on_failure as Processor[], name, }; }); diff --git a/x-pack/plugins/ingest_pipelines/common/types.ts b/x-pack/plugins/ingest_pipelines/common/types.ts index 5a8bed206175a..303db8423d401 100644 --- a/x-pack/plugins/ingest_pipelines/common/types.ts +++ b/x-pack/plugins/ingest_pipelines/common/types.ts @@ -19,7 +19,7 @@ export interface Processor { export interface Pipeline { name: string; - description: string; + description?: string; version?: number; processors: Processor[]; on_failure?: Processor[]; diff --git a/x-pack/plugins/ingest_pipelines/server/plugin.ts b/x-pack/plugins/ingest_pipelines/server/plugin.ts index 23accb49ba57b..7e2f7d5e82e33 100644 --- a/x-pack/plugins/ingest_pipelines/server/plugin.ts +++ b/x-pack/plugins/ingest_pipelines/server/plugin.ts @@ -13,7 +13,7 @@ import { PLUGIN_ID, PLUGIN_MIN_LICENSE_TYPE } from '../common/constants'; import { License } from './services'; import { ApiRoutes } from './routes'; -import { isEsError } from './shared_imports'; +import { handleEsError } from './shared_imports'; import { Dependencies } from './types'; export class IngestPipelinesPlugin implements Plugin { @@ -66,7 +66,7 @@ export class IngestPipelinesPlugin implements Plugin { isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), }, lib: { - isEsError, + handleEsError, }, }); } diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts index afa36e5abe31a..388c82aa34b3d 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/create.ts @@ -11,8 +11,7 @@ import { schema } from '@kbn/config-schema'; import { Pipeline } from '../../../common/types'; import { API_BASE_PATH } from '../../../common/constants'; import { RouteDependencies } from '../../types'; -import { pipelineSchema } from './pipeline_schema'; -import { isObjectWithKeys } from './shared'; +import { pipelineSchema } from './shared'; const bodySchema = schema.object({ name: schema.string(), @@ -22,7 +21,7 @@ const bodySchema = schema.object({ export const registerCreateRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.post( { @@ -32,7 +31,7 @@ export const registerCreateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const pipeline = req.body as Pipeline; // eslint-disable-next-line @typescript-eslint/naming-convention @@ -40,7 +39,9 @@ export const registerCreateRoute = ({ try { // Check that a pipeline with the same name doesn't already exist - const pipelineByName = await callAsCurrentUser('ingest.getPipeline', { id: name }); + const { body: pipelineByName } = await clusterClient.asCurrentUser.ingest.getPipeline({ + id: name, + }); if (pipelineByName[name]) { return res.conflict({ @@ -59,7 +60,7 @@ export const registerCreateRoute = ({ } try { - const response = await callAsCurrentUser('ingest.putPipeline', { + const { body: response } = await clusterClient.asCurrentUser.ingest.putPipeline({ id: name, body: { description, @@ -71,19 +72,7 @@ export const registerCreateRoute = ({ return res.ok({ body: response }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: isObjectWithKeys(error.body) - ? { - message: error.message, - attributes: error.body, - } - : error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts index f30b3a49f5fe1..8cc7d7044ad08 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/delete.ts @@ -23,7 +23,7 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { names } = req.params; const pipelineNames = names.split(','); @@ -34,14 +34,16 @@ export const registerDeleteRoute = ({ router, license }: RouteDependencies): voi await Promise.all( pipelineNames.map((pipelineName) => { - return callAsCurrentUser('ingest.deletePipeline', { id: pipelineName }) + return clusterClient.asCurrentUser.ingest + .deletePipeline({ id: pipelineName }) .then(() => response.itemsDeleted.push(pipelineName)) - .catch((e) => + .catch((e) => { response.errors.push({ + error: e?.meta?.body?.error ?? e, + status: e?.meta?.body?.status, name: pipelineName, - error: e, - }) - ); + }); + }); }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts index 635ee015be516..324bcdd3edb46 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/documents.ts @@ -18,7 +18,7 @@ const paramsSchema = schema.object({ export const registerDocumentsRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.get( { @@ -28,11 +28,11 @@ export const registerDocumentsRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { index, id } = req.params; try { - const document = await callAsCurrentUser('get', { index, id }); + const { body: document } = await clusterClient.asCurrentUser.get({ index, id }); const { _id, _index, _source } = document; @@ -44,14 +44,7 @@ export const registerDocumentsRoute = ({ }, }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts index 3995448d13fbb..853bd1c7dde23 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/get.ts @@ -18,33 +18,26 @@ const paramsSchema = schema.object({ export const registerGetRoutes = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { // Get all pipelines router.get( { path: API_BASE_PATH, validate: false }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; try { - const pipelines = await callAsCurrentUser('ingest.getPipeline'); + const { body: pipelines } = await clusterClient.asCurrentUser.ingest.getPipeline(); return res.ok({ body: deserializePipelines(pipelines) }); } catch (error) { - if (isEsError(error)) { + const esErrorResponse = handleEsError({ error, response: res }); + if (esErrorResponse.status === 404) { // ES returns 404 when there are no pipelines // Instead, we return an empty array and 200 status back to the client - if (error.status === 404) { - return res.ok({ body: [] }); - } - - return res.customError({ - statusCode: error.statusCode, - body: error, - }); + return res.ok({ body: [] }); } - - throw error; + return esErrorResponse; } }) ); @@ -58,27 +51,22 @@ export const registerGetRoutes = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { name } = req.params; try { - const pipeline = await callAsCurrentUser('ingest.getPipeline', { id: name }); + const { body: pipelines } = await clusterClient.asCurrentUser.ingest.getPipeline({ + id: name, + }); return res.ok({ body: { - ...pipeline[name], + ...pipelines[name], name, }, }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts index 527b4d4277bf5..e1e4b2d3d2886 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/privileges.ts @@ -36,24 +36,13 @@ export const registerPrivilegesRoute = ({ license, router, config }: RouteDepend return res.ok({ body: privilegesResult }); } - const { - core: { - elasticsearch: { - legacy: { client }, - }, - }, - } = ctx; + const { client: clusterClient } = ctx.core.elasticsearch; - const { has_all_requested: hasAllPrivileges, cluster } = await client.callAsCurrentUser( - 'transport.request', - { - path: '/_security/user/_has_privileges', - method: 'POST', - body: { - cluster: APP_CLUSTER_REQUIRED_PRIVILEGES, - }, - } - ); + const { + body: { has_all_requested: hasAllPrivileges, cluster }, + } = await clusterClient.asCurrentUser.security.hasPrivileges({ + body: { cluster: APP_CLUSTER_REQUIRED_PRIVILEGES }, + }); if (!hasAllPrivileges) { privilegesResult.missingPrivileges.cluster = extractMissingPrivileges(cluster); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts index be63897567227..40caae32cbb0f 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { isObjectWithKeys } from './is_object_with_keys'; +export { pipelineSchema } from './pipeline_schema'; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/shared/is_object_with_keys.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/is_object_with_keys.ts deleted file mode 100644 index f25b07e191329..0000000000000 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/shared/is_object_with_keys.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const isObjectWithKeys = (value: unknown) => { - return typeof value === 'object' && !!value && Object.keys(value).length > 0; -}; diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/pipeline_schema.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/shared/pipeline_schema.ts similarity index 100% rename from x-pack/plugins/ingest_pipelines/server/routes/api/pipeline_schema.ts rename to x-pack/plugins/ingest_pipelines/server/routes/api/shared/pipeline_schema.ts diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts index f02aa0a8d5ed6..a1d0a4ec2e3d3 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/simulate.ts @@ -4,12 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { SimulatePipelineDocument } from '@elastic/elasticsearch/api/types'; import { schema } from '@kbn/config-schema'; import { API_BASE_PATH } from '../../../common/constants'; import { RouteDependencies } from '../../types'; -import { pipelineSchema } from './pipeline_schema'; +import { pipelineSchema } from './shared'; const bodySchema = schema.object({ pipeline: schema.object(pipelineSchema), @@ -20,7 +20,7 @@ const bodySchema = schema.object({ export const registerSimulateRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.post( { @@ -30,29 +30,22 @@ export const registerSimulateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { pipeline, documents, verbose } = req.body; try { - const response = await callAsCurrentUser('ingest.simulate', { + const { body: response } = await clusterClient.asCurrentUser.ingest.simulate({ verbose, body: { pipeline, - docs: documents, + docs: documents as SimulatePipelineDocument[], }, }); return res.ok({ body: response }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts index 8776aace5ad78..0d3e2a3779527 100644 --- a/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts +++ b/x-pack/plugins/ingest_pipelines/server/routes/api/update.ts @@ -9,8 +9,7 @@ import { schema } from '@kbn/config-schema'; import { API_BASE_PATH } from '../../../common/constants'; import { RouteDependencies } from '../../types'; -import { pipelineSchema } from './pipeline_schema'; -import { isObjectWithKeys } from './shared'; +import { pipelineSchema } from './shared'; const bodySchema = schema.object(pipelineSchema); @@ -21,7 +20,7 @@ const paramsSchema = schema.object({ export const registerUpdateRoute = ({ router, license, - lib: { isEsError }, + lib: { handleEsError }, }: RouteDependencies): void => { router.put( { @@ -32,16 +31,16 @@ export const registerUpdateRoute = ({ }, }, license.guardApiRoute(async (ctx, req, res) => { - const { callAsCurrentUser } = ctx.core.elasticsearch.legacy.client; + const { client: clusterClient } = ctx.core.elasticsearch; const { name } = req.params; // eslint-disable-next-line @typescript-eslint/naming-convention const { description, processors, version, on_failure } = req.body; try { // Verify pipeline exists; ES will throw 404 if it doesn't - await callAsCurrentUser('ingest.getPipeline', { id: name }); + await clusterClient.asCurrentUser.ingest.getPipeline({ id: name }); - const response = await callAsCurrentUser('ingest.putPipeline', { + const { body: response } = await clusterClient.asCurrentUser.ingest.putPipeline({ id: name, body: { description, @@ -53,19 +52,7 @@ export const registerUpdateRoute = ({ return res.ok({ body: response }); } catch (error) { - if (isEsError(error)) { - return res.customError({ - statusCode: error.statusCode, - body: isObjectWithKeys(error.body) - ? { - message: error.message, - attributes: error.body, - } - : error, - }); - } - - throw error; + return handleEsError({ error, response: res }); } }) ); diff --git a/x-pack/plugins/ingest_pipelines/server/shared_imports.ts b/x-pack/plugins/ingest_pipelines/server/shared_imports.ts index df9b3dd53cc1f..7f55d189457c7 100644 --- a/x-pack/plugins/ingest_pipelines/server/shared_imports.ts +++ b/x-pack/plugins/ingest_pipelines/server/shared_imports.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { isEsError } from '../../../../src/plugins/es_ui_shared/server'; +export { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; diff --git a/x-pack/plugins/ingest_pipelines/server/types.ts b/x-pack/plugins/ingest_pipelines/server/types.ts index fc702b40d169d..912a0c88eef62 100644 --- a/x-pack/plugins/ingest_pipelines/server/types.ts +++ b/x-pack/plugins/ingest_pipelines/server/types.ts @@ -10,7 +10,7 @@ import { LicensingPluginSetup } from '../../licensing/server'; import { SecurityPluginSetup } from '../../security/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { License } from './services'; -import { isEsError } from './shared_imports'; +import { handleEsError } from './shared_imports'; export interface Dependencies { security: SecurityPluginSetup; @@ -25,6 +25,6 @@ export interface RouteDependencies { isSecurityEnabled: () => boolean; }; lib: { - isEsError: typeof isEsError; + handleEsError: typeof handleEsError; }; } diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts index 41d37cb798833..2df2727ed869b 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/ingest_pipelines.ts @@ -204,7 +204,8 @@ export default function ({ getService }: FtrProviderContext) { expect(body).to.eql({ statusCode: 404, error: 'Not Found', - message: 'Not Found', + message: 'Response Error', + attributes: {}, }); }); }); @@ -339,24 +340,16 @@ export default function ({ getService }: FtrProviderContext) { { name: PIPELINE_DOES_NOT_EXIST, error: { - msg: '[resource_not_found_exception] pipeline [pipeline_does_not_exist] is missing', - path: '/_ingest/pipeline/pipeline_does_not_exist', - query: {}, - statusCode: 404, - response: JSON.stringify({ - error: { - root_cause: [ - { - type: 'resource_not_found_exception', - reason: 'pipeline [pipeline_does_not_exist] is missing', - }, - ], + root_cause: [ + { type: 'resource_not_found_exception', reason: 'pipeline [pipeline_does_not_exist] is missing', }, - status: 404, - }), + ], + type: 'resource_not_found_exception', + reason: 'pipeline [pipeline_does_not_exist] is missing', }, + status: 404, }, ], }); @@ -501,8 +494,9 @@ export default function ({ getService }: FtrProviderContext) { expect(body).to.eql({ error: 'Not Found', - message: 'Not Found', + message: 'Response Error', statusCode: 404, + attributes: {}, }); }); }); diff --git a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts index ce11707dbe32b..5a4459fced624 100644 --- a/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts +++ b/x-pack/test/api_integration/apis/management/ingest_pipelines/lib/elasticsearch.ts @@ -30,17 +30,18 @@ interface Pipeline { export const registerEsHelpers = (getService: FtrProviderContext['getService']) => { let pipelinesCreated: string[] = []; - const es = getService('legacyEs'); + const es = getService('es'); const createPipeline = (pipeline: Pipeline, cachePipeline?: boolean) => { if (cachePipeline) { pipelinesCreated.push(pipeline.id); } - return es.ingest.putPipeline(pipeline); + return es.ingest.putPipeline(pipeline).then(({ body }) => body); }; - const deletePipeline = (pipelineId: string) => es.ingest.deletePipeline({ id: pipelineId }); + const deletePipeline = (pipelineId: string) => + es.ingest.deletePipeline({ id: pipelineId }).then(({ body }) => body); const cleanupPipelines = () => Promise.all(pipelinesCreated.map(deletePipeline)) @@ -53,11 +54,11 @@ export const registerEsHelpers = (getService: FtrProviderContext['getService']) }); const createIndex = (index: { index: string; id: string; body: object }) => { - return es.index(index); + return es.index(index).then(({ body }) => body); }; const deleteIndex = (indexName: string) => { - return es.indices.delete({ index: indexName }); + return es.indices.delete({ index: indexName }).then(({ body }) => body); }; return { diff --git a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts index 2a51983a990bb..3c0cdf4c8060c 100644 --- a/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts +++ b/x-pack/test/functional/apps/ingest_pipelines/ingest_pipelines.ts @@ -17,7 +17,7 @@ const PIPELINE = { export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'ingestPipelines']); const log = getService('log'); - const es = getService('legacyEs'); + const es = getService('es'); describe('Ingest Pipelines', function () { this.tags('smoke'); From 1ec21a5d88e26314e6da511a9677192601588dda Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 13 Apr 2021 09:53:54 +0100 Subject: [PATCH 58/79] wrap tests with retry (#96764) --- .../security_solution/timeline_details.ts | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts index d653528fd47e2..61b75931c3c14 100644 --- a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts +++ b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts @@ -668,6 +668,7 @@ const EXPECTED_KPI_COUNTS = { }; export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); @@ -676,41 +677,45 @@ export default function ({ getService }: FtrProviderContext) { after(() => esArchiver.unload('filebeat/default')); it('Make sure that we get Event Details data', async () => { - const { - body: { data: detailsData }, - } = await supertest - .post('/internal/search/securitySolutionTimelineSearchStrategy/') - .set('kbn-xsrf', 'true') - .send({ - factoryQueryType: TimelineEventsQueries.details, - docValueFields: [], - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - wait_for_completion_timeout: '10s', - }) - .expect(200); - expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); + await retry.try(async () => { + const { + body: { data: detailsData }, + } = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: TimelineEventsQueries.details, + docValueFields: [], + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + wait_for_completion_timeout: '10s', + }) + .expect(200); + expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field')); + }); }); it('Make sure that we get kpi data', async () => { - const { - body: { destinationIpCount, hostCount, processCount, sourceIpCount, userCount }, - } = await supertest - .post('/internal/search/securitySolutionTimelineSearchStrategy/') - .set('kbn-xsrf', 'true') - .send({ - factoryQueryType: TimelineEventsQueries.kpi, - docValueFields: [], - indexName: INDEX_NAME, - inspect: false, - eventId: ID, - wait_for_completion_timeout: '10s', - }) - .expect(200); - expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( - EXPECTED_KPI_COUNTS - ); + await retry.try(async () => { + const { + body: { destinationIpCount, hostCount, processCount, sourceIpCount, userCount }, + } = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: TimelineEventsQueries.kpi, + docValueFields: [], + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + wait_for_completion_timeout: '10s', + }) + .expect(200); + expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( + EXPECTED_KPI_COUNTS + ); + }); }); }); } From 3a7155eaa1d4cc379197d67f52c73f964c870262 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Tue, 13 Apr 2021 09:58:26 +0100 Subject: [PATCH 59/79] retry users integration test (#96772) --- .../apis/security_solution/users.ts | 75 ++++++++++--------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/x-pack/test/api_integration/apis/security_solution/users.ts b/x-pack/test/api_integration/apis/security_solution/users.ts index 77b2dc4092b01..5afb2bba745a9 100644 --- a/x-pack/test/api_integration/apis/security_solution/users.ts +++ b/x-pack/test/api_integration/apis/security_solution/users.ts @@ -20,6 +20,7 @@ const TO = '3000-01-01T00:00:00.000Z'; const IP = '0.0.0.0'; export default function ({ getService }: FtrProviderContext) { + const retry = getService('retry'); const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); describe('Users', () => { @@ -28,42 +29,44 @@ export default function ({ getService }: FtrProviderContext) { after(() => esArchiver.unload('auditbeat/users')); it('Ensure data is returned from auditbeat', async () => { - const { body: users } = await supertest - .post('/internal/search/securitySolutionSearchStrategy/') - .set('kbn-xsrf', 'true') - .send({ - factoryQueryType: NetworkQueries.users, - sourceId: 'default', - timerange: { - interval: '12h', - to: TO, - from: FROM, - }, - defaultIndex: ['auditbeat-users'], - docValueFields: [], - ip: IP, - flowTarget: FlowTarget.destination, - sort: { field: NetworkUsersFields.name, direction: Direction.asc }, - pagination: { - activePage: 0, - cursorStart: 0, - fakePossibleCount: 30, - querySize: 10, - }, - inspect: false, - /* We need a very long timeout to avoid returning just partial data. - ** https://github.com/elastic/kibana/blob/master/x-pack/test/api_integration/apis/search/search.ts#L18 - */ - wait_for_completion_timeout: '10s', - }) - .expect(200); - expect(users.edges.length).to.be(1); - expect(users.totalCount).to.be(1); - expect(users.edges[0].node.user!.id).to.eql(['0']); - expect(users.edges[0].node.user!.name).to.be('root'); - expect(users.edges[0].node.user!.groupId).to.eql(['0']); - expect(users.edges[0].node.user!.groupName).to.eql(['root']); - expect(users.edges[0].node.user!.count).to.be(1); + await retry.try(async () => { + const { body: users } = await supertest + .post('/internal/search/securitySolutionSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: NetworkQueries.users, + sourceId: 'default', + timerange: { + interval: '12h', + to: TO, + from: FROM, + }, + defaultIndex: ['auditbeat-users'], + docValueFields: [], + ip: IP, + flowTarget: FlowTarget.destination, + sort: { field: NetworkUsersFields.name, direction: Direction.asc }, + pagination: { + activePage: 0, + cursorStart: 0, + fakePossibleCount: 30, + querySize: 10, + }, + inspect: false, + /* We need a very long timeout to avoid returning just partial data. + ** https://github.com/elastic/kibana/blob/master/x-pack/test/api_integration/apis/search/search.ts#L18 + */ + wait_for_completion_timeout: '10s', + }) + .expect(200); + expect(users.edges.length).to.be(1); + expect(users.totalCount).to.be(1); + expect(users.edges[0].node.user!.id).to.eql(['0']); + expect(users.edges[0].node.user!.name).to.be('root'); + expect(users.edges[0].node.user!.groupId).to.eql(['0']); + expect(users.edges[0].node.user!.groupName).to.eql(['root']); + expect(users.edges[0].node.user!.count).to.be(1); + }); }); }); }); From 69f013e2fb64544bc9d16d3fe9f4ec6c14ed9c11 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Tue, 13 Apr 2021 12:21:11 +0100 Subject: [PATCH 60/79] Added ability to create API keys (#92610) * Added ability to create API keys * Remove hard coded colours * Added unit tests * Fix linting errors * Display full base64 encoded API key * Fix linting errors * Fix more linting error and unit tests * Added suggestions from code review * fix unit tests * move code editor field into separate component * fixed tests * fixed test * Fixed functional tests * replaced theme hook with eui import * Revert to manual theme detection * added storybook * Additional unit and functional tests * Added suggestions from code review * Remove unused translations * Updated docs and added detailed error description * Removed unused messages * Updated unit test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Larry Gregory --- .../api-keys/images/api-key-invalidate.png | Bin 129223 -> 0 bytes .../security/api-keys/images/api-keys.png | Bin 111824 -> 158901 bytes .../api-keys/images/create-api-key.png | Bin 0 -> 377920 bytes docs/user/security/api-keys/index.asciidoc | 57 +- .../__snapshots__/code_editor.test.tsx.snap | 16 +- .../code_editor/code_editor.stories.tsx | 19 + .../public/code_editor/code_editor.test.tsx | 4 +- .../public/code_editor/code_editor.tsx | 44 +- .../public/code_editor/editor_theme.ts | 9 +- .../kibana_react/public/code_editor/index.tsx | 58 +- .../plugins/security/common/model/api_key.ts | 4 + x-pack/plugins/security/common/model/index.ts | 2 +- .../security/public/components/breadcrumb.tsx | 20 +- .../public/components/confirm_modal.tsx | 84 --- .../public/components/token_field.tsx | 140 +++++ .../public/components/use_initial_focus.ts | 38 ++ .../api_keys/api_keys_api_client.mock.ts | 1 + .../api_keys/api_keys_api_client.test.ts | 16 + .../api_keys/api_keys_api_client.ts | 27 +- .../api_keys_grid_page.test.tsx.snap | 243 -------- .../api_keys_grid/api_keys_empty_prompt.tsx | 148 +++++ .../api_keys_grid/api_keys_grid_page.test.tsx | 368 +++++++----- .../api_keys_grid/api_keys_grid_page.tsx | 526 +++++++++++------- .../api_keys_grid/create_api_key_flyout.tsx | 378 +++++++++++++ .../empty_prompt/empty_prompt.tsx | 76 --- .../api_keys_grid/empty_prompt/index.ts | 8 - .../invalidate_provider/index.ts | 2 +- .../invalidate_provider.tsx | 33 +- .../api_keys/api_keys_management_app.test.tsx | 65 ++- .../api_keys/api_keys_management_app.tsx | 88 ++- .../management/management_service.test.ts | 2 +- .../public/management/management_service.ts | 2 +- .../edit_user/change_password_flyout.tsx | 5 + .../users/edit_user/confirm_delete_users.tsx | 18 +- .../users/edit_user/confirm_disable_users.tsx | 20 +- .../users/edit_user/confirm_enable_users.tsx | 16 +- .../management/users/users_management_app.tsx | 11 +- .../authentication/api_keys/api_keys.ts | 3 + .../server/routes/api_keys/create.test.ts | 133 +++++ .../security/server/routes/api_keys/create.ts | 49 ++ .../security/server/routes/api_keys/index.ts | 2 + .../translations/translations/ja-JP.json | 23 - .../translations/translations/zh-CN.json | 24 - .../api_integration/apis/security/api_keys.ts | 22 + .../functional/apps/api_keys/home_page.ts | 15 +- 45 files changed, 1869 insertions(+), 950 deletions(-) delete mode 100755 docs/user/security/api-keys/images/api-key-invalidate.png mode change 100755 => 100644 docs/user/security/api-keys/images/api-keys.png create mode 100644 docs/user/security/api-keys/images/create-api-key.png delete mode 100644 x-pack/plugins/security/public/components/confirm_modal.tsx create mode 100644 x-pack/plugins/security/public/components/token_field.tsx create mode 100644 x-pack/plugins/security/public/components/use_initial_focus.ts delete mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx create mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx delete mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx delete mode 100644 x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts create mode 100644 x-pack/plugins/security/server/routes/api_keys/create.test.ts create mode 100644 x-pack/plugins/security/server/routes/api_keys/create.ts diff --git a/docs/user/security/api-keys/images/api-key-invalidate.png b/docs/user/security/api-keys/images/api-key-invalidate.png deleted file mode 100755 index c925679ab24bc64565b780203a05bf0d1183ee24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 129223 zcmb5Wc{rPC`v%;ZPOHo4u3CyNQ(7(B+G=mBN~cuST1!%0EJc(cA|hRx(o)shMNvDk zhX~SA)Rs_75R%#wM1)2XMC5zS`+K{*Gv9H1U;gliJoob4*LGg#b=`S(?W(c(7O5@k z)~yr2Y;y7Xx^-ftb?esaZQ2O@XV<1Pmh09%San4-6{LbA6U%nUWq$240$qA;f zE^pGRf)g-IXznSf3%z_li66`spd#PMX+GI;Vbh_X{+QhUQ_Z!mrM+Ek!-fqmBlS<| zD7)i7J@NMdDa%ON{8OC67BO$$-2M9XtEsc|p+@NcPPXaLi(-y$;_V0q4u{)A+WhlU zz(i`qi|GnFr2W{Ge{LjWR@Zjkk%P9>RrtsHpL4E*e1HJsvJA^_TM(XGXQOTD}|w${O@x#cEHim(aye5sF{Vu&VN;r6B5nl@yW?c@+VHT z1)x~ES)^%PtF=lK#9lh zzbVvN9TL>ncV!g*|8_tsU5m+#X>EfHhOR--^MB~{C$#u-|G&LkyCU!70B@K4jZX0jb0DEkaOqw78AOv!i9x@#O``A_W_ zhb0BF_`fTqmN4cO2Fmwu_O8NJxBm-N_)aj%V5B6x5E2MKdJUfp(+{jecx z{93~CqyN~`SFlWPD1XM_js|4-b}WW^ug`O6LI`s|)=-rnI2-B(c#O)W)KJA_Nw!kS zQ|QFGmYtN1sL_v`*8@KL|2}R9td8j0fSkkmv568Ml}Qyz0SQ8}?FxH3rPBj3WS^8Z zKbTcqJmWMSYU^5xVjFN41xYIbfGm;R#h51qv4_Pv6|E-RV>gw>nU(g z;pp~XM~%DTQfR?b(_R5p0e>>Sk9rV3rqCWay540Vk+mi~O8oG(VMdU+tuDEkWn^TO zT^WecSs(dYMl}gLTf*XnwMCYSeILHAU>I?w{j;?L%Z+Z_+A3Wjs2RGVCdG|lWZxxn zJ|3c+XdgM!+};#8@A%a3hhZ0G7+qd9+7{qp7S5>t;0G%4EwI*f7*f?3`vX~l6-5>V#+fpNEbUnRu zBm)~r-C*wTSVPv?}9A zyHRr{!U4ld(fUzC(l+D2;f5bxU`JwSs8`Y15lK?7Ke3qg!_u8k1_1s2FIxCf9nApU zR?^wkae82202>IpcJ17cNAkBEj+6jFf;jclcI9XOe`xrAtjzbl>_|Mt&`s4;xurwm za5%?*RyTSwLkg>>qeB^=m^l5@Iq3esD)eUmexAukAnKg))cL;5K}Wzq+D=?ugC>6m z%q#zxE<;OewV3sj)Gn+^4ILL}{L>+z0X-$nM=$_q`m@Gw$`8a`9D}0snE>wl!GhD) zOd%(Qu@J@fQJnOeJJ|9+6D4tK>i@>^TeogKj!xYcu~$U{N^h@7(1D?E zl(}>qS1GHi>Ib`McQNS`6G`II8bOq|J;`T%(ytcbeJ2!A2^@yXDM}?fhp^t657F84 z$YHhGpJ`@f)OcKpvp7FSMSyO&J9!S3KU3ZQO+fcUHx~tl45^?!>Y&;r^T%dM8VQJ5 zMCS@YBq-|;t&W`J%pHkJ0(_gL{p{=1?B%wCE10Tj`|fHlMA@y0R(pw@BxL^(xXIcU z?A;V8sH67vst{K0Dc3AsM=i-1i0WUze%%9ySD*jF$n9=7)1;y221Ik&P|sh!E93iw z3DbRRwq+hua+{!PR#c;r7KrHZ*Gx=K-jH2BWT}J};|y(8Nsu^U6Mwt;9#xhQH;RL7 zidRQO;zSRPcj+_Xl4_@8P|5W-zPp{X>;Ko#i_F8e%boVF6{45MQc1a1?^!*#2$}0O zm{$=D*A!?Su3*aAn*OQ{Q(lyhVOniih)}@|nI@|ZypD~v5mXbxMsZshQt7KID5Ua; zss?Uhi)!#k&19c%r};qD$}NQfO7F@6>$(y-K{nTlJ9XzeGmb~}Mj6bP50qrga-NDs zFr>Dueh8COr$tIx-mR0Je=)UUWXqvDu;X1*5nV$*bQp`}nDoE~&k}JcZE?2So+U47r0) zW2`5KA;mceo|a_1O(7-Wh;d|(`;xyBT7l3UO^ej91tDt*1qLbpXe7;AB9Sycx-_eY zXvedWFN=vP_k3~gw7QIeI;5DZ)PL7m_vLrV!D5z#!pbNRzxHEmWjhk}nuq*J!jPSO zv_STZM_)k&ClYUfm{uVr-ffE`yDxDqbS1^RsAab7C%8`UT9&e^N8ilrCWLo0^_EQ0x<<tb9!pF+}|4GP8)iN*&Z%gojXydqX#FeoSGpt$T=3BxJmJZCIJ= z-U#(4!lwF$w~=HWjk3#TU|=17x!} zEE~(Bcm+sECXF>SLG5I5aSCk4#15p*Hey<&PE^v)w8xjw61%yrda1)Gvy?3i35n*8WGzm68L8Gy zx$a#q(qQ_d0iz~j#(+ea+vSiWYEva8ZQiXwKIcULz5Pls!?i(wa}b z{Pw{J>sz$gkyp&nC4KsIYAqqj$@yz^DOd6MadmLWfI?clj_>#}OB)*(5vZ|GgQdNQ zDoSf2%&+SA?wIP$WdnE!{uEgRNif&vKEF}wcO$bo<#;2su!!Akev0e}v%Js+HXJE( ztf=lQDoppW=sL_9;-FMf+RU0KcWvTMV5^WKBE6Cl4Vhgt~N7;_lf<;BsHIdq= zu3i*M3iqWkZZ*1;v+^nW4CgMp&K(umziG2%;`T)33~ln;E38v12Vmh3+`+a10J3n; z89o(p6Bxor<8N%it`Vh?;RUL$9U7<=4sP|f{^H#2GNgcKUwaiPNt!6lvR!HmUW{(G z4EUG|#5;)y^_9gG*l^snm0p3ZYk-%GUQkbayjI%~LS*=6ODiJ{^pGwc>q!W%VdoJ(rNSPiJ5>!MV%} zE04DuxCHU0y~x~T9JIFiZsITN#gZgNyzS6lb^o-d6}nn_srm*Hx@8cJUfjDkn$Eywx;-_x#T& z=5Tf@E~n-0!=1mOxl~eL5Y7Jc$JO#qwh?nnUNh21Y*K8en+3Zlrs z6#OpnCkJ#U5S#M@>-?EE95xwbejrb{oYe8x&Lft@2+(dMb^{JaUzYa1Jb6>Zt^8d_ zEx3BG%8gr)s;RywTC>eaJ~A_s_3G8df*F*x2O(NZNf1eWRC%S}v_ zKX^8IW&3XV%!**W%3`QhRQkV1hGrTB|kN!UVE%kX2y8Fk>_jgK2cx!8G zd%g&+nzm2hrM*f~C%(6fg7+3ZxpkkUB8YDY>z1yrtqW%;?y2Xg{ecHlgFQv}L3#oI@ zv>v-yewX>0%`A&d6$@P;bZ#34#omfsuoBpC%=OJCU$XmZZ8Mv51t7z6F@A1+eR{b; z7_a3<8KWQGC8=cZ$(tQc;&pt_{>~wR>{Yg-Wj@R+8!$4@!SaCsPkef&HZ1>yV_vr= zV)=KSnJNTPkFS3-fK5&5WA+-)2Mc&CI)~`gel*T$MCHl``;JT}d;7$%TH;Q+G;2ps zeK(%Zg^~W@n+v$3zE`xxTP~P!lE#Zy%>m^twl~o$vtz82`$GW^~}-wkfnJ zYVXSLMJr78vVh@gK(&IGeDMYwQt^=CH zrx<8d$r8;Vc|wI5D(zhXjj+u;6amN_Nw1|gW3T8`K_n#pvc3i?5A;b1ua!#IwR=hS zU=Eb{b+y~PXo(FK~F@h-q`eZ+trKf!4NPg2qlng%wH=4&>VFCntlY~`E+3vNV;y}z0lD*#L{reBrS>A+IdP7uVl9*gbdw^H|L7ACr zHdZDg_;mTbh%WIPx*}lYfb^DETNxjIUB+e0GJ5F5!mnr zAD_kS?i+*AUKA~y70=h7VeVWV^Xq;TiY}U!-zx4v8|}8)ta;Wn-j1tFm+}=3G9iCf zm5fdibUk8E-e^tO`^~kv*34;>qLSQ#0ggIFl>wJ=51G7aVcXbhoo|!P9nu0K5`v95 zbuF;=6S3JT27uMM7dP~^C4#(MNC^&OOpQD9y89%e*m%*Q?n;_1ePwIXA1h9x)%7#M z!&BGE0fbfX5{WPsOX)DT48a=BRF8l8@R(k?=EBrwm?OvW4U#ILdx?&NJ*c4wlpqX< zI$m$s{?5+P@WZF>;IlSf6GK>Ay+yGMysfII&OtvCIJ06d%g+;BNa)%CS$_%P{`Buz zpX)0CdD!!6OnQiL43Se`e<7SvkvVufzt3y^@Mp$gT9b9V^`WvX&*AFc6+tg~_m-61 z3PQUvCi>pIW8)-8+3i-?Hq->IDB(qtMkW{Dpopdce%@swuQy=RSIlrwB9w^#B*Fs# z@jU=-ue>o#Q0N{TEN8te4)(_}o1RvWCvz0HpP=DkVCeX#=s$~Up$ugXPlqiQJ`xu{ zGo^ZcZwXhwIZ{Y;^qarCx%-!#Ij59~HrqroxuEutEGy=9%|BN-6Sf@>9zHfz)24Y+ zYZ_;01Ok!M{LN*r;~woHa@-=7&#VNF?FZ9e#dMt@EX*{c)g6QR?P; zLemRAoLqvg!|g+=xWJIrr*ZKP5PbgNP)M^LoeE?)+YOeh;Pe5885yEyYSA@nFJabj z44tp4t71Sy4FRcsN0N7~2T_~x=n1^3H#23Mc${cC$(!uJ2+8wXP0weUm}g1LTkQ}* z-xd!SB1o^Jl$5Dn`d$NrsG%E-WbZ8V)aX95U!^r5XNqc9vv8j&ms%P)3Jf4GJ*DJO zsotF>yrtnacyZ@<5vF}7OL*Gxx=YPK-iN%~n`hly2LX&p`71$}QzLWfZS^>vJ}@#4 zV<}>X+x+*J;?p7+nl#@qR{OTk0-NTAF>_t7uw+;iax%KPW6+;@FIYx`1YRZQZ!UsT zmn4(glnHs%^t5pR9d18LxbK{X6QBjVsr;rF(kJvVZ*QjIL?6P0&3cP-87^(Hh3-Y> z;v$i7aWfroa;iXpe#bgngn5aPHc;F0p@?ud>m>cVfU+qGop8)3j;@y-2--T`iJSlz z_4N-p#N|liv_AmZ$0v4;Amni{J@h0R3>|(!50jJ6nkm;0DEONi7`i`JxS44tGqq;^ zJbx=)Z(dgm$!Gqk84HC{{YnXH`4pYe{_5zJ8Ron6$gehqSGRGCmRibUv|D@A^dUpA z0Fxko6}mQvqSfbdrJ)%pJtY7+a%n@a$DJo(vLQoiKqyCy8D(BwaDec!Oc?#<<;||H z-+Y2v4q!zS5(=h&>{3riNRT%$NF9ZH721`2c0>%DZRWVp0tCbKNc?P}KL$S%`$Q{` zUB~|g`sMvYMKJ#K9uf^fNJts0Q9OF|XqGuGB&#LUY&|Cx?52%eHG1PjOFzt&biyAE zj=b#Nt_lWQS;-lMJi?8CKC!}oo6Z|)P+*wQa2~c79*VwZ*(MD@=~;S32y3;`_w#_`tdfSDV?B81S-BI^6C0x^W?dNGP-g|$L(4W7Jx@KPE z?ckeRV=Bxpxy0fCfVna{VI&E5DJ{*mT^f9m7D%1vsCYiC(Qa&P{M$*F5%p;(s)VhR z2fiSGdd22^U_ZG**zB=7fBtc3kNZABwSCQdvCpgO zxnul{nQ9G`smCdO>i+p;O>qKd)Nf927~S+~DrLWF)@L4D$4 zsBJ?}=K9CX6YjRfLPLK@ZZ>-pIY5yaC^xE{JozpVjadr?fcNNDQA0ziMlQq4-0br9 z+-@t~x^^(#2}T3otvubxiLv?}TzV&0i~UG`GjrTqMc5r+on7kQ?LtYLl@$T`X8zZb z?6x)BP9DBrCXxlAI9>vVRNgAMsBTuYptNMm$a+-2BDW z?Yn};&xJFZg@A$S0F;>hWPfg%Qat{`m@29EersdLtIyZF7g}RB46lVLotKutk(w|; z4MSo&xL%ylhhuK$<1>-gte(0e)}enjaOUj?LuSu#q4O8@@#;%Qa$coWdgMooR#qpFRHKMH#s7j5k>Q72twAiJjke z>2_+ZmI6>%TOF{TC5BkBtrffFuVUivR(gnTTsX}I7p*qGe)GmU=s+WLb#dlEU}A{XN5-DbO!(??-{jT;{SoJG(7<*}j!^f)>@YnzuuupDwF_euKVktluV zy~-y|?V{|-2Ij(}&N;yoY*Aq2#lu>?+eR=1hq_ytO0UII^Dh9%Pd;n7w$7C#%c+kL znN&F+u5vP1slp8qP$>I0`--3n_-HOc{-mK{w?_ycI8(*cY=2z@Q5VV><}Z^h`5^+5 z_8|Ye_fmTelu@>Cqo7k$&F5?6uf!>-lJw*H3w2#*Yl4vC9knu!t3aHG1h59*%)(Fu3IZrwWw z|D@^J@77mv!-Gx&-wgd-IHhh zsSE-u60r*;h}2=Lm${O1xjT5@tmJ*}rtcN6nvK);vTM;`=4<}S;W4Gk;k#j5ip-NV zys=O1_);(`GBx4!ZI}An$I!OyFCTWPdiLLkPV|zG?s@7^0kO@^aBQe#Ry;C9pHO@b z_U{m~C5zv==I_Md{1 z!oR)IIul#B1}sp)=}NX+tBeB7@E36`SL0zqXmIst=+cx%4ziNBWH+{-^3zA#Is)z&cd^=)Y&r=E({8_lGSWpQcd zfyvV;r`OUbEJ%99&n6yKO$je%$$pznVFfQhv#1<8c>;S`haUR^ar7_ZF$!D9W>Cg z>xDx%8q!ppstZ1ksXuxUdaLu)Y~^qW>_sDe%}3i;Ks*4bXu(VyjG2=E&A&*DaWz$g zMb=jFW@5bpC^cLjel4@;S-de@#gXkUx$Vln_|h9-_OwoXLtO{=0A<07856bUI{+WG z)|2r|BWL=6esF&QsJ01c8XMT7WN+oZX>K_^(u1!ZUj6K`)29yvaJgHFQmn=kAfp!) z;XMa@%TS`#AO{}65PDfqlu!&}${X#E9{K6%^+4j?vPVKm8#i2oD$2`n+Q0{Bfj|np zB0-xasp{8%zq`cJm9hbI0jE6x6iIH$n=N?mq-Zlp(GU*l_K&5sDPwjd1QZ8+5};8u#g&CB)vne;sIo# z)3wu)UTtwc)d-bE+Wbu$k~d!fx=<2tpZT$?L9JW}Fdh_k4VkS?sF~`;8p?hf;N}U$ z2-^b&Yu#5#1qCN{?&vgN*NdADPwbXAJrf7^d7+dnV#^WFMCr|!=jNOxwtn0Gxs2UF z9hgHm@?-!-&x5}ustT7R!DrSg4M4k_XeFR$pUv;z7Lv*Y93jx@0@Sa84l%l}m=vH^ zVI7)Bj1%%PII3JJkk@)^03i9+#J4eTEw-!`TQ9MSSxZYxdq@wvGiz#w3Y_pnfHh2d z^=elcoa3Sj8RF9;-#ao|-=8q<-*9yh;izv#zy>&A145gX03RurN!}@Pj;p}?9TBP746zG(Sl>n z3fhx#96O8e%k|WsL>ohWSDYz83I1vfo?T05tzWVG3lC*bq$50^0i;h9eRvK1D$*T4hI8>tRzw<6P>t}QgAUY&#T~b?{>fa^n zhutFpfe)R#`3wkAgt+ze_YZ+)oHRqMTL{oKWwYbknrmZ$ejbf~Y47-+W&>*YZNR#N zR%t-jA=GoN5hCuVCL!IC4tPkt3L0n=1R4o{7@_NWfdRB%l575_nPpQMM|an=#P)Vm z3c$I3C&Sk5NK{P=#Qd{MBw-CZe#pY2wtA%R>XW%XLAsEucBK#26;SiqlU7o?6Ju>@^ylYP6Uv~ zN_Px$r8|Ji=D@+f_xJ^-Ff)o{u2qKby^f1ZGE1sVtXVN~X>3&oMB3o4=gq(jXBE38 zAgSnDf#)G8H<9b|`8Cb7?>U@TFE!maeu?qKA)N`jQW*&S!%=K2PM& zG!khs;o)1#yeL-%IRA&d$*z6@dBflhC?m;dEIVF{v$tRRw|j;HR(*P=OV=w2UehCy z#$6!pR|KUvc>{4&6{vIM^5(;$4$RA?Ca*k==ormB(a~h)kmnf5J5fk_`t(XiqzwME zKBItE+)~#RTS%$(>M+}p>jnl_hGaOIUx~$(HZLSxU5&FVmX6*W`D|No8S^1>nt*DU&Vp4OB(6fw?>QiR6xFEPS@U=G}X@+Xn zwUw}4Y+G!6W}8jI%A-ag?{$A%3vr)11OP}WM9t(1Z3lPi)fbQ1qLoCk8vf~SftQxw z$Ftp;&F8vt2$U*a(6Z`X-tEpH-r{)BPpaOZJjQ`nuF@l9!3lEm=C{Q*{4EuNPMSCc z87b zbb%a8Tq-@HMGiZ8$G|p;6ci8WLGL{r^e`^VDI3Il#Kw#cbD_V#k*OFq*MUh;q-E;M z;cXh-736bxYBfr$Kfu-6*r@RXII1Md{AxaL*i1mIH6zs-wokIKZL?d$zng)d7NJs%fcsYOA!PL2Ck{jh8^eW9K%IeY=G+72oknvPW4x^ zAMDqIlp@hvl(nxqUobG>N-w?`md7m_6}+T1!h8~hm>#|Z9B!21D7LJZ$?lG2RSzUB zNwp?cNUw0{r4ksKd%Fpjr^FqMxD*? z&Q5eu(w48m*8Lww3h@R!U)06h(Ek9We4mxY%@p-z=ZHvp;r7A!=QCRBpx-F9uBlU; z-usA=Jf~(fmSX)B&6Z#B6czY~&B_sf@2jvprs@D1Gh-3Sz`4aGJrM8AQpb0zl3ft-V)S!32rwbl6|{Q}3@zTag| z_bp88lv87GNKQ|pB|Fr;>u7_RRG^dfv?}8wtK^1+1R2kja+4Dg!@N4eQUezH%oLLN z`mh3Z%1bjAfh`O?JCBU9t63Cb3^nUSXanyco9yv1O>TEe;A(JsEKXj8XD0XY{<@COd z<_QX`q9gadwXm}??F-3|wwDSIC#NF2n(H7V>37A?1cg=;{mWuOmZ>_6iPI~6FT$4b zM5<50h2@o@3kb{StKiXcOvFS!>qStI*Kl!WQqJCSiNQld|MA0s6xVXuXt&4~A`#oj zdGzSla7M2MBal{fsXyYnP%NsTSQsZ#Jq!afkLm;#e+*V1 zr#!)&EYy+6&);H!DOaCJ1}JV`2n5N z&JOo3{?ef$t&zfXK=mCNsdew!si}QY5#S0h$#lY=5K_JR@5C0`-PPGtAy+$GP{_je z`2Di2kt5o+hlE1WFYfK<5U>fC6y2X6LpOUagg~d{b&w2^ntJytv4?-&upK7rbnPZV z`t=fSE9;nnICto9c zJS$l??c@~B_=>12wgdVcv2uN0i~S<{=u#}u2jLpw&kHC`VaNr4O;ED3IcleCYjFln zz0NWi+-tC!>>iho;iWe!sEk_b8tAEshi+-Kuav7?9@=}7D$J>sxqNWuc2Hqul!rm5 zhPR~3UmjbwOThAg(-XbY>K4c<6)n`JO&#xPnG7G-!fiF5rR*VzPF&L!3DW|v&JP(x zc@OPMO`O`gyQRgu)9aKh*2IIgw2kWAcfoY35iFT3KR8+)1YqJy_`! z74g^9XXxAgXKoc#M#g5Hbj&??YNQ$5eo^Hpb-5qUevqK*UAOJ1#9F== z0jhrJJXQL}4SSou=Qy75-Sz81pW4xt@Aa=5=~sH6O_UQb zz-1i|4$%@-YDkNBMMW$#>BJ~K_^=hwW?nNvi%fCb>Xlnbvrt7~Dp$iIv62rfQuD4^ z+S}XT2KvXs+=wJOd9w<35tLtKDmqUaWGJv7SHIMC{;Cq7+yfgkH1}|GK!$X5{V%B1wqj6{z9FmR3|-K_ znykxM0kMTIQq?+?jT^Sx`v0Dn1Zqgfj~!j|X|pN73U0Gk8(46U@%U#vc6x`a#Q_YESi34$wC#NafEv09|RbwyjJ;FFTwcnZ0BAbRWa@B1J+m;AdO@)%Re zh5T%C*C$y)Ez*p!ns%gVfc?R{*1sfhs#S`{*7mM_vB1-;b+Ki`wdHc>>@DY4a$umY zo*f2e9mRy53o9eE5HPDGDZI{rehJD7>J#)bI@ueLl5W~D^HjJ(;MJZ|viFc@NW5-` z4oSSCD4+eY=22plWNj=@Shv2eB-QKFoDXl~5hQp|kf;ZKG!W8Lm$B1%fx3XqWq179 zwm^KQS}Qb(x?%Tv8P*(KdWOBUoN~Igj`q;dIDuB`G5`hnZwn+G_iKm?ksM%;gn$u@ z=f~fK7#7;$`p{SE=IXNjj@Zm-(#Sl$DUPSv^6E)FzfapT^yv5&9^Q=N{R(F4Nxja0 zB-qKW467?sPY;jHrJ?P~AG*w6ksjWzz2Rbl2&#T1T@!gDJ- z{E|IzQguT@NZvFII?ArG=IK&<1ztHL&Hk$Q@@QLLIqH(NOa%>Y>`M3T?BsVl4g7*_ zVCxe>&;)*hF^ZotHIky^7b2q@{JcfQG1-@P6=)Mbc4=@Haz`4;@JNNyx|Wr!lg2EW z=9b6>X|>N3F|IUIT1Vnj9a3;Yh`BK;LkJR2c|5#uW^ntEM_f=dxO(A6ADqBeYkkH; zuteIBbPk8LU|BJ&sjf&0WLJ*n^)fB>FA3fXQG3|IiPKd*B}K~1U1RbC+_}3VXI_Z| zb~cAs!Ijp9E<5U6kHTUQWFjYFy{qew@?Z}_Oh)ZS+ow450Yt)fPb;Vx=Cq5S29Z^4^N>2cg+u);uY9FgFJ|rl|JU;_$ zlxi4u0z65nMHGQe{feg0zya)V+aQ8klxYay!>mPEFJfdLVOv zM*qOiTbNY+cpzLd$?v?~)00A>v62o2r(kK(m0=bpOC7t_s0g#L6#S)(6(eVauCpC# zyk@px$BcXtCUBNM)YI5>5Q(*>B04qx911Dn4tRetQT1K;jWZjT8^KTdOg=(TMewZ9WwwnQBrXgET>Isd#V`dWT?RUVtll~eodNr?_p3-gO$XiMf@%Wuw|WGhdTVe^g*DV8fF zY1_UtiX#Uy#%@l|`OwW!1|AXyQ6FUu+9`46-Z<#P)q!l$Tt6R7ddPE$v5GBnWTE8V zL2KM_iLUm|r8=lVWG5xEgf-AaDI%D|yB|fGRT5*OBwm=Lk}XqCXiGucwIlt?uKN01qiHH$+CU%= zeU(7k$n0MJU1>02J~f;3l-C@?m>xbJwH^jq^||Mgc{rO_8>P>B?Mn&iDUC_c@jjo4 ztPe53fV{nH?hDGPNeI#M)gCic^H{2-rvDVoERS6Yq(}YInnS)^yMA)S(7D-RD#GS?EO5t?>6g~AG!ebe@Eq~0;{*ybHie=i2y|zAIy}5B!WSmcQS#CFJ zZLVVPd(oK=HMy_ncJXb%Lk@HOxW2pwdbB#IDHLLDVkCY{!&p&+UYvU=W zlQ2o7L;2J#jg4D=>y;q&ifbc?Ef=0{IXAu6+S&AJ?ecrk(3bw+kt2od2 z_S4Cm26qxZv6b)V=a(M7pLSWYY4~X_FNI*rtVf4iVcarDFHs_EJ+sl_5XGLnx7k~- zPSnaJsSmJ1Pu0vnlXO40_aw=;`I zU_l~e0BEx&2U&|@!zUFlZC?&5XSG^dd~kZ--RN3L?OEcxQpc^DhZM>|rCFpG66b2hOq*Sc>KbN3dUCIT z37zR%#~~uC4_2vdd(k>PlL7yn(TCx(*mb&r@4yA*I*Rv~O(MIgo{FMjN%iT6TfVa4 z6vwCW0PE$CzI5=743){O>S5-cQk)JY!>_S@mJ3W;ksl>U!*Clx6*ap$(*O6W+zpN+ zSY@`>83P(-YYL-BJ)ct^Znw+^f{FFO(ImjTHaghO^_67)YBUpUgKC7PY;^_t??~i9 z)FVx|$)=Z^W3mTs+cPZ~(P*mH&G(%aysBPoNu#i9x2JJg1{94=P|9lm)5+9FGp9mS zQ+Z=d2BF?%c0%zM{!tpypy5)m>(+WH=ffHWC|AnE11ncQn~6=|>xE@tVa>|O8U0S! zmlkMRY(j#bLfU$Y1nG9Op96k-ZojtQM?0OoP>Ptr0`bHR>eA7w!V2obl4D%yrBaLe z>y&eBYsTS1OFkd4XNc5iaXCyejVegCkA=)#*Q}&Qp;PrdK=uN!;=oyAp_ewVoZ&mX z4S6E0h9t38R zYhF`S*KU4{P*zYL!wR-xV6e%J5Iib-o$f{1(;Bgp3?#t=;r`?ia4D5SQw>TT4C^bPyzFD2 z(sN-AimxvHNk}SR^HEd=TM8N~CEC(wAIZF8tT=NkJ!4lS-@EaT23iR+XE zsq_Qf9`Z8dT#96QEm!ACV-R1MA%sNmk#dfuA>f>bAw8O&Z5IKbbkVQj+MLc=J6&?! zJL&Tk;CqCnRsrU70F;z+$9r7+z#>S;EeM`EkfONP*(lcr70A;{iHrS9-E9OJawNRK;~<4k+I6oA33%Kb6jW_I%&HmUv_QCX|X)BqMQe?U#lFo@&f#WyhoRd+a@a)hpJ|8MJsRFh`;1F z|4CdySF0l(9a>^_$TH=P)IDp-++m|e!?4IjL!0Rffpd>wfLJ2vQu1WLE0$XxGLr0H z;7je*=y!UjY)cl;9DNrWsT2jSu?!RZ)n%}#xw*%6en`T;w=sXD$B2DH^0I$)uf%VE zj?*yjO((a(mO}_8IL(dNz6l*X6NgxmG9a1R@kSEWHeD2%nNwl zp)N#Mqcx%?GUfHPJNG{yM|%jB>`Ut@Ate!uQWGC!B7o~Wwx^=2&~~rUi@OCl5}t?aY*QnHR8;gnrjg3*;C` z=aO2TlNeI3554|?cv;a%LdCGZbG1IhD^*v$VYVU20Mk!6p|ALe8lfDkAcqQY*ci?j zj8Z>(!TpP4Mx?q#+0bk0508Ab{SHj-3m*>t+(PKOY;>Amn+9D<+moU2&QcdEC5_rgrIcB~8ss|(T5|9bv@)$lNaH|X49?b#Jl z^*%CwCk1R(9H6-`a}eUC{(SJq99s;+!p!#Eh&R0zMQPRaa-)`o+|1iz>{10 ztq$)z3!Y3Jqye4i;nv!HYQbehtyvwg<`oaj9WQLj)J_} z{Ul0=sw?A8+k@}fv1cn-wb@p2HHKtwLW!Ld_ zUU*gy>`d z_6kZad2d+_`RryeJa%NW8)bvHTd$vqQY{xl^O2GV4L3scriV_S)o+dL^O@w+d(M?`HIiNS&w44iZj{0B}#D5GA8Jtq*H{p7rE& z$#`|6dPRgzC~MCj%Stk$5VH4eSeadMAjJ6QSEBI4Sd~Tt>+e$BgXOiW?7LnJI#d3> zb)0=glHZ$|v$d%DzU8ick^OtMIkzNbt>1S(O}cp+lLvHPS|i1N=i`g6i*L_~7^%Kr zxb<@NmnpZLsgWZDKHkacgNY=fnjh}u*lTp$so0Y#v`o1fxy2as(xx}~Rg2&0lM0oc zcZl&ZzKcznZMIEYic$mqta`uyB$`t7sE zZ0*6Ah>z+LB#xx=SDiUO3q9O}w~w6WMuQ>eS@g`lj(ycy=bNK=Dz@=G)x9;JFgg`~ z7lIHbv|Xu;ZT_Sqi*~3G`o|zc}uOy02NglBlIRQQZx{Z~5iI%JJ04z%|(v zJaPgyTRmo?*)#bF;rU1SRhPy*^*72v!2Q+fn_JPYL%6^}?`~n*N}TQK6`vrd>$Yv86cED;4T& zk&K1Ns0(k$($yM+d#ZEOy=&H6GURO^bQ?IuRncuhGLx$@$s0C=JT%qpe0O^yuOc9c zS(B&^#h`A#$(S59OoUIUxq)-rH|Q2 zR?B8UTD<0^PtBa-1MC{~VYtr;%l2Oi#w;mhGxAPADFkD~i;N!qMBM|G;moyS%~)%j zhit+TfFvh#n%=pCXW(V;dWa;JY{r<@QvCJkE>_HR^{}i+m_AkqUm1E^Z}k;c7rVR0 z9RplZseWieYf~IF=k{6VdFfCnatp#NhO%S%M*~#edr6%lzaq6?!|>7PI04M6T(fz{ zBRy_-X20nwGW*xBr9h=5=l8@jM^ZTNLN}51-yyFxj^5GUsP^kaZSUI*`i)hAcmSH| zk~i*tZK3M?e00cyQ?xD#Vf~0k(#Bp#iY8%M0;LRGf^>;?6+T53#H{lI3blABE}*ip7&`}@cHORw@@L# zuU^E|AgN~V@BSaQzCDoX{e8U4(S>x=D8lJpgl_J)aHJ9{X2PtfTxKrK+{a0w5-OG4 z=6)Nt#Ky)fgi~U!TMQ#H%-qI?+5FypzQ51sb3W&s_aFT6e(!a8p6B)4pXbFg`Mj8M zW-R&i`dWH;g29{bq=eB;v+KJm*VLHA!ew>VurF&kq_|)mRs;teAKE0h`;3{vD=pVj z42_UHxxTd11FM-JWr}4>b16ypRNPQ^?fPt|-9fz!_{VaMzKBcsdF3?Rb18i|wgpBhI+Ka{=#%?5*LsI$G$JP6~RdRF1H zXC5lorZ(lO?l1n9yYm#(ySwA9%F!oE_&76_%HkI}u2^+bQo<>U9OzQOo-5{U`5f&ya%r1ijPCzcxIpV;m8b$XKaO=&UwaO`rpop;udG3$|U=JFlHbnEcyL3d74uZ zj5{1HSyDW4{1^QnrB#NqTO=J+h{Y4Ks+HCPsj{i8p1@~qnyJAuX6F#f&};UGETK2a zIvjrD?fMF06B6ox5|%ylDW>O3n&gf)y|P1MwoU^6ikezRZ%4{ZLmAYUbsSeU1(Zo7 zNaFk3$rHs=^kRNw+X1Zl%^J!%cd)!HRd0rZ_K17%R2ZxNfqd8u8#f5p6a3_2>V2l5 z#6zKEf01tbV6+R0x`lnksP{Wb?|We=K6kV)aMP}XFBi7_0-XzNQUNVt^%9FVLztJN ze}QCyM=rO#5Ib{BTRzkTmZHC*&Uy`T9zFEYhEDtJnau+;L<1otVi-=najMX=`$+W+ z6r>R48vS}m9KZ9k@G_t?9unGu76Wb!p-ye_-EcM5rCa(IF3Q1=gses7BD3h_36nn< zJApDi-IBDlfrtXv;z8PX?Y)yao#CDJ3SriSVBE`_JuB0fj@)?Y74~eUPg5DMuRN(^ zH*zRMxjVC1zdu3)C0@82@VslrGo;~YZr8-| zqEYZZ_YdyvOSJC#!*6`5J!F*ioP`hR8RBV&kii53Ads5|U*(Yo2i*pUbgM1>uvd&E zzKOQtQyP6PM&o3vH)7nys)d*L8{Al+muR8i7*m+-mEsEu@U1 zW2=j`$PzJ%K|o1&Fs;aS>M`IKZmM#R3~Rq;@jh5dU+^|ReAZE}JZjOM*l~JQH0+a_ z^4+isJB!22H`GKJ*No}|=ggT?9MS59u3;&+*7Cx@OxxUx9s$3I#C9)5EvF8MUmvC3 z!x+$QEashOo!DsYd)Bu=yf%)&zHnJk9UVR?5QHl-p_e)DP*0=AWoBUraHZbbh@_;3JRJh0H|{eN@Zw+&=CkJ z!}}&#N3KBMrEWIRD!mU|Zy@p|+6=jpgTk8u6(ujS4xQATEtIrTahY^2m%QRrQ{~O* zjSL6O7!}!m!P*VsYwa`D+~IL55Ltj&Zp_rx)Z9w+3HC$gd!1z8InsR0 z08jqD^72D2KlmvqPwyLU*-l86G*tU;2BeT`pSo9m0)(&uRklEeY!oZG!25^Q& zKYFOOE{IY%Twat@&hpfE!$pq9s%zD(ye_jZ zT^bUxIMErZFPI{r&Ob~`bc~K0Cial7BVUteT7KL(0*Ks9m>k#kma=hj(XVM~u-E*s z?sC=%qo^n9?Qn_&D=lU^kOSfIj|YWsiDUP2-BoT+e6v(~E4BknU|O~MZOZ)zkm}q` z@%lS8&9A%>-YWbSQ6P{x=;FXWrH!vla1#WnT;)_u>ZZCL8f-Tn{FW22s+KVoA5jzBTu-u=AU4k<+e}_I;uC>hR>+jeVim!-+csueHrCe}vk3SDz~>8Dq{?ndJ% zTkJ@KMhHglI%3QR2YiL9um$H{uH@{~FSrnT(NAHjU_a=~lF&By(L}-1hDj8Bh*l3f)xnE- zX(*!v8_wRe4^!;Qw?SAWh7*()W+i6+!Mc_(Esq*qFwL$bxo5 zX%;5@B?0?AMBH#pY~B)3ar#VNk>u?eSM0MFp~gopiEc}|sN`VHEac7)p83H+B>UB| z&SD#oWV`o(Ya#(E>~~F#xrCa%!aGFXRQznPp411Ocg~mt%hD=TCvF3|PjNBv*M_t&2nr;3_wQOXsIp#~?Y9o~ zDa~T2-ErzE2*z!)oQ~qiozj@or{4Yg9-rn6coM+OthlPL!_jsO#LW0Z$c?LVS&y)) zn38R{LwfLvb*94ZuIZgO%RizvbH3Nc;27p!a%D##zup{)bm}r-0HR$ItKv}n5$|E` zAiZw5!rl=!;-YPS)R+0QNzSO=VcWJ8#T3}EoL|>dpS#;-FZI01=fzNhXSs3m7^e%2 zV%1B9sY52_&Y;bM(id@ad8QL=s4(S}ZN(JVwUQj9$)<#5{6G{_k-6JEplU9}b9cf3 zIVtwx*Y-r)#P7fPv+>Rszn0(Wh&lABDOqn>jc>*8-WXi-1EjvGiJx(I+m+S!PH}Gb zg00mte{2))w)aM67{v~n?js)WKZvi_?M!_ArteN3bkA4Rea^vBx8GQMfPkM>7zHjA zNAlYnDP_D`W@JzDrxe61Evr=Wm9&6QR*+Z6tx|PdJ{*P5QUq@af}y;VpI>eJ{g~I9M@V=KN7M1kTQlNa)4iyGqvOe95e9sCB^lVlScCP#nVz9 zMp_=-RMZWOq3qlvP1Dqp-AT)|zWg+XjZI*D%VMO64D>69*|(%A*ZbL-clf$DcmKP4*rN6K5(D4B?fpb!{YkRSoi~`eZIIh#f!Td1 zW5_=5An0iWh=MaK(7snmQeJ=I#t*3n(W#P_rKUdPuEK?O$eWU;%IjxxFJ8*FP=U_U zOIbcJ?386udnJ`FClJS#njAUq1S+Z)Aah5{qdi<|~cA#ho@ZU^u3wRn15< zdgxmEWU+=zXw?t;@DzEtMAPyt6%>5IfK`1jkzyUqyjkY2Y#7R-kEq*wG89Kv5{34pOYlP^f{m{+w$xGs_`N*l~<|B_)B_br#jMPV5 zRF46P1vr9Vz=@vMsntI{-M*bs~{ZO(aibJNf+6m^BqikH*S9h8VD z^Wqj7u~7l-iz$6Jzj6Tb-yR)t?$L}p>V8+)^O;-gA@%}$o5$?b#qY-2?YF8w42CsB zFz}SOC!yPEgz)&;<}88t;Oy#Yt})B=Rd?2ad-co?n&{KhwYjxPL*BRcXcy)b%85JM z5O!S0eRYk?8Wp*3wsKV+ptqYs_ zPGYxk)J8~3c+D#yos(aCB2{;H5wdq~T*=1Q+1WkGWtFHvWzOz9zQW6K8&z!sSe~+> ziy}@RkBW72?2plwykALt5D|>yaY`CdWh!<(EbL9%Y@xW=t*dX&88!vy`oTs*xT|G5 z@$05Smu=v;D^HJlp>Y~ZQpF#+Wdnw}^yRWmjLX5ej@GL48gBqvCi{ZOMN>Txvnk8z z(a!-TEzaKLy|_1}pXF)i5+(?%FI+}1H-0{UhrJbiDe!T7{>%{xgQ0zOT0Z(Oqca2q(;Xgfv6xdl@(%>YR=4CbLH$WXF{;6}H#q zjI7ix9Ld=EqQhSEjh3F9(7yLFHW zh#X4t%CR#Gjl3xQ`C#RacT<~#;%RKlZ3dRxX_`0whT3s-x5)f~f_RyAwARgy(hNx(Zw;sW`{D6Q+%lHEsxf8jC&{9)>mU+Q9;mfz|c{XJBp_<`-jIvH} z<-`L;$M%-Qkvh)H288k!xT}f#Pz`|d#1x$q;Th!E1!EePXKEA84s}rmI?A1j*)%y% zl0>1~;}j^PF%Sus5`WDy^E;*m~A?e zvMZ}HB7g|y=>+9n(g+hArUicO>?d0(b~%9rsj!t7nWd>RgyHs z;M@vH(-k7O=IRWizx^;C5|N*IJsP%tsBah|V6Tv==J=fkh~Oc|7fvqbY~#qb&}=`C6>Pjs zjm&Qi$oP%@z7(awAVdqfdqjAxTzK~CsS%RtDdir34L1y)-0jM7t_$N9NJMug=(Oye zoKv#Pk37`#p>JzT=bW=!vdg2)GjEL#T&#zHXKKe|f%d`q4Nvzp9bY^7dyn|kLG-E~ z=9}(~dzBcC$c#X4QgidP}F1ny=TEjUG(At3dey|&RJMHKg_rh%oFTyiOHLEFDrf)>Jui3uir|CT z_|$s3SNg4rJ7N6w{HXPyg;|@a%6my?qRt*e=FF|fdNw>WO0rltemwX8%eJzD_CkVj z2*~kQ#Btp>Y}ymY?UxvMJ8+dsoU+`?_k4$7b-r@=3Gb9@uQpk!-HZ3i>YQ&b0VB**sC9|XWu7&hf}x}4Nitto1&p%s+hV0m%W%0dB27pIvk&nqSK0v<=v>chOmGg z#^{9&CFCa*W5<<#!Gn8=5g*JT4M_CMVvTL0We;%v5v<^*tbLDrM|eVHL22i5#+_UR?d+pNjWqqvn4vXU!yp3^0~2IR`Y z1|)Pn_1%b5CX9v>o{Yf~&~KLBa&3~gO<0YPI!K+A($TV~x9Sel6D8`VAgw0hi=7sM znNxbU5Lv6^b7eSCWu)`%?@cL4xyFvxlF#xPCJ$%ZDPw(0M3y{!GzzcOi^lo~oB0fU z!QmrL9hB;|AAp>MyT0Tzn+GorYCEdbQ!k+&&u?4>^uF)I*`s$hXnL)kmhv=eqa=Ht zrVI0SmFED$v`{n_+qgZj-t zXUqf*YHWTWlUdOIMP1ecNTng-d9KW2v*;hAk=(I2o!Mj zgSe|baBx`gAUwJODFGK#F3A)w>RP&GCcH_=#SY3z%qB-LB`4PA=Q6kMo1Wr5=U3|5;9`UT-$N%G+}J`^Co*Fcc8w|8oNq_JgWe*rItA+n!A zzb|ayHOD~)-gow0F%)f?-rPkyLARO zHKDieWG2(F>0QYoMm?N%KwBhQr5YJ4q;H{>?RLyAr@Px9%On<0H4W{C;As!ZgSeNO;{JIc#wKr zghK^mM6dcGi}9E<-BGtC$|N^{6(`JkOh;B?l_R{>u826-{m7n7m!Q${ZeA%tQ*-1a z3?_LLq~vLJ4O&Ge?n~#adib^lCYs{5lQga_HHBC>goy^^7?QA=vEy$S3+9|pzHpFp zKB9Li)hz+#VVE~1@xDU2W}%)v@qH z`&?I0*HN?*`D+ZE$`QmMFQSo~+9lU5)-1{inwoC9b8-)KD0{!;r|@3(iUZO+-Mp_(MaWZEGE$on&za+^+Xn=va(9#Bek);v~?kMspS7;WSfS4FeQ%67-R zIQCoJUSJQ4gwaur$5iP|Qmg`;Ot^u5A^S#G8Ne`La~y!%M(+a)R@vwF_LowvFuY8f_GJv^D6xg#WsF|2l9#QmE^ zWwaAsvH1Lx1>eG^o($+1A32!3D8~DD**xhHJ|~upouzM0gb1XmN{d6AV;z-qha>i5 zYJyNR0qUsJ0nZ6Ay}DI{ml>bVwh&ABKR8;mJItU6$cU>2922&0tyuLjrFnQi{A<|` zB1wBWT*qf2-B~Vb-u|JI;R%|)0fUk^<`tmUF5SEfyn-Dg4cRVY@>m?o$b-BGu-bKT zEI;J5)1=|K>aCFeu-C_*okoY51>5Y9#3co^b^DW4M?!A-;oWP*JE~}sWTSwLQdVti z^S>`aQx^F-D{WBY9 z5Awm7Xh+s9SdYbxZ`hArklBe==1nFn%8F z4df*00f`goE4R6Sl;15G&B{W!`uQ$j6&6WeEogVX3zJWNFgP`|D0o%(yM~0s(ny_R zoBdHfan9L8bR$DGUTW3U5jMSQzbaY3FKa0=AmvHIk;dk0D5og@tU>yB4h9D?IH%*@ zW{xclP+H6n%!WhO%h1=Klwl*+&8aSNd?=NH&K^6e=cs2YqZ-bZ1{+4&HRNU5p`g{5 zYRR(SNYmqU&O&I)H722$k-OnLZ?CEEpoP&#{Wl77p-#c-N=D)Ptn;orgfDv>u5Gt_ zSUSaBJKMME({1k?AG4QQnfj2`ZOqG73CAn^*gn;e6J7Ee{Sg5nz-rFl4N0Ob z!0^pcgTkFLqkykLeP3PzMyKQt&9ijVWvOJxNEh2^PQNr$s?M4_I{Z>{l)rCB`-SX_ zvox{4yfGGjek4${K$eNaqL<{a7V9M@7)lBWbaXvp3k@%ztO*j>iWrW|O~$iR-EuJ| zW8Jj#M-ZlQZ}qR_kKy!tjXodKGcqUkL%ckAvqlt3$3FD*= z&9cw0Qmf_AhO1wzgID^8D`w}T;23y#*AErLpn8t}e%lM#zplIVkUH*eMrf>`$7S9w zxuV-?{hJ~ne7_db@T=iz+)rlz?z7M#vl#OQNkA~j?8j`{@7Q~G=wslKK9~0e8mN6Z zj&JKuCBpDw<>;-F4feP`*E@N2cOfMY%34aV=JM-?>29EdaG|8y!sbipQ$gbc5+wuI zkAW|Eb9bzsy9PUoZ7Sm1m}J!b=vvJoM!3(dVaar4NV}#>r3MQm5Au5IAq*sTDwS+F zPY%Qy2TJY$i*hAR4GZhO^>D7YLJeIK2gz=*5>yt`3>I;!wsUk}`^xB8RsNJb`he3m zMSY5_sO`-kauGN7Sx-s%BjC&gkp$yiLR~}|vxEu`F8k7w56G+0rF&ITw<&ZF_svjv zzp>$^-m9IXSi+elB)Zdig4C|5MT)Q2?LvAMZ&b9EsKMs+CXad1Y7mTvTHs2osU0CV zYD30mf+J8If1Gn}4(QSEc)55qM?*sjy6zZzlgc33E+zE)%Jo;NF$I0BiPbK7PUucT zV{f^KdigmUm=}i>a8SwMG$LdZIE^!_?b8P$F7}|YjKa)J9Te`zmy3xFX5bH=F!^WB zHi+4=MDLtxml!5=QSL>5bDx7ZiV8H|6O_sAdXmAQ zFN>|<=Yf?GAKGK-Zy~)Tj9G7tq^jn9PvwQ=nCrXo7ya8-xq-$dib#u-i}ZInjL^xe zo@%zJ9T!NSn#av78j;1BlP=`zchft%BtxHXYH}6uyBR~SBUb#>KCTa2WkBCF>0v-T z#~=GzCiPuj2eUc%JlnFaJu|^QvB977n+xNm>qG>x{ZRerK94b&J6T!t__9DJn$*d; zmShm%Tk1gR!)`}W@7G+N_CFEDSh}(m)FDs3^utswUN5oYDk8&-*vq0&||K^Mk7qVkP=0Sg2wrHMKbXML0|eAoOq zo+Q|ew(bRt5AfDq5aNLo1Vt-ih446ZdA(vt%Bb+JvHtW2w0fZQ|L^K@QLx8-EadRZ z0H7VYeTa4NQ0wwn`i_X{nIVer#{2G%UbExtRMviU8~4ct5Vx$j8zPOC@^k8T(~%K# z^%k&d)YheArR{w@&S0|xcLs<-69MDprZhoki?SR# zJpy-|hQkYYO}DnS8(F|CS}eg0yOg$!{z2I)tMz6f8s97*eb({8MZ_u)_o3#o;Y7}) z8N3*ySmD^;SLHqTm7ERRapCOc?DTe#P&wS;ru()Gp_zqB*`^ze)Z^TMB_#%-gyU=y zEAC6-dcKM?MHEN+dWMKcgeUl4W0v>4W$T$wYk_Z#XkFvios@ zDUEl_N92n-6a1E8J004gnFnX!2M3*o9<9N9t2>;(qp&TL2KS8J3?>%YN$x8KCBqIh z6pbzv+v0U1$nI-^`+2UyT77D9J@OY?F1u&`wzhpG1sDTfR2-EmOS*}c*E@U3n)@SBdBU;lfJ((mElop`l<$6nc}($1&s7}4Eu z5%wGB>n;VuHcEeFp0JT)Rwf@T5KF>Yk{!?@iTR27iYSiv>*#TEd!-oRPM?a=ko}KC zdc%enHsK>7misXkWyK(};H{$!NV&JVc^G|n~{rusRcpX)m%;)J;wx*`0-GA}+uR$+{ ze*$6P%%^w1fbHxYf?XgGi+`&q|9TJnncp2WM)GA^kNtHomrCK76Slb0(y#g5o{Wx; zi+}$mBzS0G;Zy!iuzd*bEw}Q|*OLJw|NiX>vW@rU%VRJ`=CA82{X`lk^%Eh$s{R4Y z-=3T|usNDUCsF|WAip3#L&g?H*7UvgMbT@`F}L~@0I-P|0gp5PK8(? z+TI(2MjQTZRx{@j5)qnmxj&WtH|&&tas&wCR)e5UDd*iV1!dXt>&H1_wmmw@vUS90y>X#<eE zSMs0dli5$e-3@HxrKZZO{JCWr@X4=6-z8}T?r-PDR8&+>8{kVyj{KE11V5=+7^p%N zF8rw_+t0yWkI;ejo`L|^)4XB-<6ixv*==ez&MM%00Q2bkrvuI~z?WWg_y=@eO^5%4 zleu}cWnp(|$e+5r{hWIKCkaW?fO7(5u#{rvg& zBQr#9-9q}`V0@&G$o&aOX^_en2k#6`z^TyxI@v?)+RL#lV0no_koc zI$T$?w7f@K=P#%Ys_u`wyR`95XORj*^{L+7Aihbh`(%qu9cm>&B_DoooE}8-2d5 zf@kJZlI-K#r4#?MF{n2abGdq9;Q_?S$&$$*+KV}%feA8W;2*H$yEJyRNv(`-KQh?W zIEI_&tNDcfbs?_IK-f<;U#fw}tMVbkG~V_4cqlFDH*T#a2?gI$CkB@PXTr6B5Za$EvUziM3rB$kA1kp*h;w*DHs#ut23|3k4BsceG-Ola%C*L z2&#OIBZUP{$TdZ6I7_{1@p=k?nj5i+y8c|KFpOm6Uzyi3oSAt74@H$EC=z+K6$Sm2 zuyvQ3$jGeh_BO-!Cu+57D+IHFajHA3+ln8TPzHYZ7=&Iw69CQBxgQCO_)tF>zW6>* zeJ4jHvEMBr!+6w3mb7z;Cz2+N)KSoRIkfafN{VAxTS=l~$jqRsiUN(q(nw*8;e5L) zf?nqs7qO#x7}lOK6H(#!w#1A4(eUYiVFxgyrk|c78^>TAk(+WYX3n5}E}UMMF{4mV zWJUtk`^O)~l0M2xR9H^K<{s>IuX$rT1Hi4C;_8dU^?dp4Juuki!0 zgH*_MBM+{mK+cR+iH1h}$f{}jeKDFx7bXtG{J0@#EH~piuQQ)tku9haycdPR#5=;C;~Evr zHwmC9PXX`t`uYQpNCD-xGJi;=ybI9$HeC75#IBdYPvPm@VIJ2vqQ29oG=AO%Rbuh) zw^XeQ##?71$7xdw{0Lp&nE0o#19Mob@QE)(U=lVqRUZ1ifusdom=w&LHw*LOnvHVP zo}c|Q%#;AxU#ucU1EXAEFq>c{_h2J$k&4uZI?W$#TG=bG_~2I2ozDh>_CDJ((~BZu zbC17Z^l7f%4q|3+qq{Xpt|jq(hvi0gepnOp=q$Vl$x@=T#C-Po697T7+%B$e!m4l0 z)y5-XlI<1kOUaMm0qzJ?=ai4`?cN8Rw^`Kn$scEDG7Fw-MjG776W&+1djnHkPED&s zSJXV1^;!P*wO4h<_`X#+E0IO)gsL3^VhuUhVTm{Hg=NpJUG_ti>~$34sZJr&FKYPd zs_(;+a<$4T7@~?vnSnRLpiB<(O-4}=>wwlrjBmuskh-t$%)`N%5?{)F`*fd)E~1yZ zHi?>frlFZ7P4m2x!1pHeFosq9J5Bnr``vc`=QzEbGl&87EZnuDh&H4OEzBesmu2&? zLJS3uZMD&Qhk~f1cKc*j`aw62@W|Q6XZy$Nj3!W0b;oB19N&Y4V_`vRPwc*}H++O3 zsQaJSy~9eAaSV+yQ+HjzHYPWw-^c)hS|P-ity`D%qbO^xGnIa6zRJ4G&W%Wv@A6=d z?ww`o@UW3!=1~}W%7A}2*yqsvpuqdolbQY@l9P7Ly*A#Ir5s{MZ@GihigjXUY+R5J z6A4$YG}z*{vu65)OVwg~P)4M-Pj%VGwOXGdsoBB6 z^)vpJ5(Zx7=xJw;?7ZWn(9U|%oqLl{tl&&_OLbnNaVU5 zS`nQgH&kiElOn+beDwez%NWSEw)V9Uda6ua9gxjPX!}|N z8Awx2?w_rkwGBiNI7Qz$y(`9mO{02; zG@0X}U%6DOl87fOMPs!=&BXomm9C{AqhL|LU_$hkVpKt;`K$-lU)k257)N4oY1D+5 zN^rr7XnHAJ)VaCl61Q##j?rrFlFK(qOp4Z1@-U4#zUchqx%P_S4*%Uy`OaThibfp? zO!Cf!i2;zjR6sbZO%ax1%dJVMQpycma-CV4%l;23?|BKRF3xa_@%Bbuf4^ft zQ4pg^>#ft1F@O{&-q5+=*>Yb{;VDx}S>@Fh2lf^q+P>p=1UFGWcxTrHustmU!C%(l z?{`W5E)JZ2uf^G->c`A1M!8k__)%TXbcR*M0tkItV4B}o9M8wDKnRRoH>C#cDFoaMZzQo zp8S-lT~iN-kSR-=L~5v_hD4%}Q9L@Z_5nS@Qt8q2%Ne4-61PR&n#uPZ9h{Y;p+=*q zc%9ykaa3U5CVWryrK&SJTSWHi;-B>7|f^JNy1{Be|ko?69qrtvtPx40WMzXcgqWJJ zXUJYo+TyBuO<~0_xl3;hm|-Wb@3lm6)ib-$S!o>OZ2byD%<+$O(4(oHW+@LCL!@HZ zp>-e&l>vo%O4Xtt3BD~s^kF!2v%Lm#{XqKW454OAVzq5OI{SCM&fs=6$%NUolDGYQ zm<#!1+b*1yVxq^Hu<>S;_T531u)$dC%x(BttIt=3eb}I#HQ2cL`f~lLOTVx!T2>*v zf|^bzdY|6n3TWJ`SA5?uqjk+IFy7d)+4Vc$YG(cISCp~TOgC^C5sU<& zzzBh_&n#JQ9}Hr4Uy3LEmjnX|0pRy# zy)()6#JquEmy87|qU7y2oiDMM z{;SM2ARBn}pDrouU?pU5T?Ot7I_Q&=?A&X|E6}i&S7DybzX=w)R_sTX4SZF=K|395 z4p;G4oP3tb8~Nv>9VN5sbgMI?U#2Z2#BXworm~a`kIFgJO^WaL+!n~yv!ScGqwQYw-j%JuSp3D`j+?0 z`nylj$Qk7m@qv-LrBp?C<$WBKhrZ;*2blvi4ki9zH+RNNgr-jBX0>hf$Vd&H`xOH2 zhcB#~d~ol$Q%RE6*#M0=1z=5pA>6YtZMqq!Ae`rSC zk{Bi-%t1aNBq6?lRu%Pwnnln#c|w`4FsKGuj}oLwR3IY7=qpUkM!|!UQX#%_+c7BUR4r$gDqA}=yF+>ZLFP1p7c?4kt4e~Z zA(_N-AbpA`yj^kqWbvq>s?G^A-R_=q;5nIt-F*GMCvPi~Bf!Bk0Y>YgKYnG(DI$TK zU@mX-U9cW)psE{ZqWpD=;GqxuAoMB@L#*UP0a^Klnd`oOE-Z(aXe*XdKT9;$FHC7FpwHLM7pn(WmPM~ZX2$MGeUJg- zL;D7YSP2Kbk}l*hI19rr-J}kJk|8~HI_HX}eC`?=%UNQQc+0uNVsLd?xupzL3~#@n z5F{cC$dEd@=Qiu-5VaCCE&n2Q#UVkrz`Kgw69V|Kg^Io#!lK9l<6a8q$7h;9be;j$<5S>x1_;TCMlC z4&;XgIf9M6SP>T@9!pqg@-oR6{Z?fPP6ghLfdMIi>R~O7nUJTuQvn`>~BP_$q5XZjh zp^p{>maGzpUyp1d-?R8)=|w>Q`1*Nnf2~eLb@V$VpBq?`05tMiY4UGWbGADcaA5-v zr~xRnpOQR=DkZ%AfDHyIspQ#a6(wxU&|MGB=uaSuY;2p!BGQ+EK4GBaL}4Z;*bojR zq8gpmDUftuKm2Xz%>bcCafEc-|C&^Lbb}ee3v!G~H`G{ku-f3@U)j)bCAxU(Ro2Yr zxSdMxMZ%S@pZl^jzgeJEJKO@S*~wf&=BW+z2a+pgm7vE|a(K2Oe`(ldwrC21AS{Kh z9}eMh%n1K_lsBs)xW%gIF{P+qk4}^?f+YIyRNJIjk#Z01m*|d1#k<8F2CV~;b-nwN z@u=`cXUP^#&x=VH7sJ354#ro*fPwVf6xSe_zMCvTuXlC({w__SmY;n8+WWBab)fc= zkeqhfgXIkNWHePhND5hW1oik{Rzd>mE@3+jbuB_Kq!KKGKZrVhJx?7|h4qsdjfi@v zXLIs0gXHK?M{D5Vw=yzJMTp)j`Vh%Z!Y@(8krT&iF`V)DxP>D9H#K+^t4%85X|tN* z32*|fkJ_5>5NCnP?Fga2dtX>ra2u%M${)pVzX*g<3GZ_%hGvH^2RoxFO)OGV5z734y)FJf6dqW z=p{A1*$jLNqJ=>kXBad$C`i0mMPJat)dkdx1nHinz|W(n{lZ;>PQA_Hz@h#z+9w$<|v!fHv# zd1i)Ami>=NW1nh?Hc8}gvr^qgP zr7p%%DjjrJUDu92!s*D{hSKWLED@Hjb~Vk3J0tAU_4X*qQ7##~6kWZQO{Crj%~>%2 z;Dp-!UdpPH!w7lB&(M)jtfNz;?;T%11JDbhwRE7jb>{o`F2Fvx$tn{k4HzLeI>HwE zd*^ClAEwp~18eO(%YG$O=A79kNx!TSGJVMVeJq6qWAfrK{soSX-|4Qs&=xT0N9aKR z7oyBdAR!mWqAX4NUD+7v(~E;B*D~W*?2G}UzfQ&hEyNq`dq&zk$VEw zhA9eKst;*pB~}17-|kY;{)5j!b&AVFtNq?3OT!LWvj)PZ7E9p(kWq&fu1y6Sw!By> znHHp;d||xjHfb+^&7%Ut_R~H`K^R@ zAmKkthpYzGZh2H*slNX_E4~!MN|_699^y99z;lkf(*O8H;9#4^*dxu~io4|l|ClU~ zDY2|ie$aZ)BkB^99pNzu>bYUk;yKpYh-lztT4hcoWEwMzg^K?*mH@9VJ(b<2{_-mk z0k}$jx|M2yN*c-7eKQFx@@La~;6-uwYk%2&s_Ed5Ik$a~;0CN>*y{Mr+m8O&dU`m2 zBn|5TSBH6nz7_#DCHBARTMCq;d4e|%>TRg?N!ob&B#hM4Kw<-~>Igsl7kdJR6`3>S zXfB+ZOfInTkioze*~dUz`yabH#@;HcM3iwz-DZ3)e z`hnt7W~w#0o?{=)EeEB8Bx!gc zBLGjw>92N66vsPqQZ%a4bXtF6NtC^KHtH7rl~c<%JgDr~<-6(u+=Mv;SedpF-rFYa z^FbHFAcN%@?*U$s)ytGTTmkRf^+A>;PQA&Tn;oNb)b(#E`&5if)yz~$u^)r_{vTm) z9uMXI{sEs>rKFM;OX^ez-}614-#O3oyk7m`)obp#@A=%H<+|R>burv~gEDwjTvx`kvs*92 zn|W?qUHn*&#+^%u;mnmN9oZbV%(LaA`*pgvQIQhwL?>3tJG=zAWmfkY;S*UOJB&7Q z%d$q!kX1%B1gWbBYVW$N$4C65e$#D zKRq>D11H_-Yw3aWp{{!4e(#U8KH9l<@bS5WMZ3r>$uq9IZ@y);1U{%)k1fD;u$KnN zH66y$fr7uzfvwbM;fx$q-?K_@soN?e4|{_Xt+EfA9{(^;ZqKr;XqeX#w%Wv^UEi{& zL@)HB)*<5`@AtBo+B>ptE*xfB!~5J?$01^yxxX1O+a|n3iNN`4zG?M&nFfOCh^@wb zUJfJ^u6^`M_%%+E;vVy3^%(#5Y`dJILa|{=$80BEuRDlnd^O+wZ-**m1ZLXV&mP*FJ$4to2I5;z$^FmBPtm@zurlH$>bC; z-I=zAGWKHR_P45c2s*!i%T(Q5<|hBqlJ=&r88mu&Dj_;15aWsFmIy?!{*y{l+j6Eec1c_6Te>&|uFsn2YRw4E$lmM}vtR zhf;|W$@)BE<0lLz5*gZO0}wtR za57Bexe(KlQgzYer`m(Fe+w{ExVciD;!NCrNwB0lxV zRZ2_l8p(IwxR#J)AA|1>2eH8FTb(NQ2y_RPKaXve6!Z33=Fha`@wz|La^SVN?s2o2 zeZ@y0AT;zGewi9YWQ?n9Bf=!Ke}^cfhcQ5?33{l7;N7+|p^B6w)vA>n4eNLQ${`J) zoC=tur~XI4K_aJZ?ijC{Hsm-Ul{d=#aKa{zd@%=Zbe8GEo92lEr6{=nycre*2zFNmd5z`Q~!{=;{1~iZ{ z+gvN`z3!vcQ$HUCg8RPN4#WOi#tw|D^nOcg1bubErn8OS>=t5W#Q8gPnZ!jzE@Jh`cI}@`IY2*%t*yT zg!Pa9!EV|KQbkP=*Ck1ua>4eXQ*D+}DQXUgvVM!aFR4XbH~bcAdpGm~Zsn?Ibf|1l zdS&`*(wX*$)ufJc#Dx!$&#Z*ey7Et)4CT)qzKwE2j2Tt2tHbN_5!C{vgG3XV?q%Bt z8M4s?sno4h(P8eX8Gt9AEGF^{tnK(Wz9Wg&$lr@X+RIY14|Z5}gmyf<0X^q_7z(`Y zg1td6@yL49F6*!MxrXX8>S5}P@ALT}sYM38i(PSY@AWux7MFM51}d;us;6J}=nB%~ zAoh9?YtG{Z^Wwt&hr<^oHzu)qi12F@hLqhYwSeL1=9t9h@XFd~*sUUKGy=z1p242l z5ITFQ1M4b+p(^%^`~LQANYJpGEMlgNzE$bXEMeL8I;qaevXD(55xi}j1e&W9f981@ z10E@b0*)HV^=+?UjO!xOvl@sLO0Zl?+c#VP@(=HF434wXc)j*^VZ}qeE#EItWNS7s*MUxP|ws{w#WvC zYFi&)ROQWY$}mYM;8!@)>)comG!fDQJ^qWI%Yuh*6UBlo(4;rZ&V+pY`cwpNHlztwsY<1*>xG~3sDs_uI^220K)G(*s~Aw-ayiZ7x02h zmwF*J#!+ossGsg>h7YMFPrj+!gVqiyqC?FkSDIR?9HMhe@|zwDNLB&(*X`o86`>SL$Oe-9-dPsUAW!-0S7yOlF@OjdF4NUg!9w_Su*N3+%+0?i>{Escf;I zkK|9XIJ;QO#$02uN}!532zx!f?0O0#yNm&7pn=6#Z3e+yfh=XP-EKKfS?woZ1~=8`yznDSKa+7V;Rtl)AV+h>fnWHE6<#8v+HKXyHTF8e@C)!82mHzFD#=?<}D<{@)r z-1;Rp2bal1XlQo>ncgUw$nCV>@x}fX>#rc)Z@$TaUy)~%%w*&z+h(XXWs@97m!4-s z1@ISgCYm8D`pFDD;AdT$5b_Ogb{3uyu|In>Ds-WC_Q;4CG-#%5vdlQ-ce}Uw?%GnI z7=gs<8SRpl`Gu=OUafxaB;0RFIqxTKiL77d(qSK_mw(i4UWlqt8DGFc7Tq_E7pKiF zZ+pZWPOeCp?H$-)MbM#zEFbrSw4J^nk~rR1W-mGk4u$0G-9(?7Z58B}`MAgwIc9uZ z+B8EJn6lt4pf~~(gt$e1&8ftp(WtVTGIWwsM~%jdD<$Juh!QcFkLLKwLM)pCgHWN( zsSS}pih*vL@q2jMF;biuV)~tR=x>mQOgB+g+q2HZx5?WybxqyF#%!mWs%6R6PQ4Iw zjctHJvu!R!tlu4aDl3Z#G^i`M!MUvqX$1upnX*Bmji0X1VpSTj@62K;f@%w@Z3Okr zSz*fn^Tr}eE~W$VfCIA-t%NEh$kL{Q=wn8e>;e)ld|l2#{BNkmns(gk_y`M@5lI?f z#0v%VVy2v0Yy4!vi46&074k+(T?s{5h9LYd7biPoCrEdnBDBTda0*Uk=Y8c)*@iX< z-VhCvebO-`u(?(bk+z!aQ@+U|%c%XsM#8-9vP2a-!dQqHMt)cVm~ub=6w$BzK%AIW zY;VYc2Yq`nPLFSy2f_&i4;DhqXZv$6D*WowQy6rVK2YQS40E20k3mP@{aN1_9)S;G4Vyd^HS~RTz z+`)gdLnP$+P1SXhbiZcIj#Woku42;9w}C6QB3CVS*1b?HKG?3%$0BVyV=X`P4dlTi z+mtQOQ)s7+_r0_Zy8pNRq3VCa9GFM!Nt=(X@J9~R&Pxie$ z=lHh4P$tS*bx)hvm#o}XW@Xo2&eExw)sc%cfOKIJX(poERp*QP~oh%a^YU^9VhY1I<;262wwi(*d@JE?q*OM&X3ieOhKi%b~ zXZns7#xP11*yAz29OCdJ+szk;AmQjxLOYAL%`t2p(j=6oE|BG-Kokbzl4e^*MINuI z((=wR?@!$-qCE}cebeowaE?X=S*kdMV36?@#_rKJliU#^Vl&m>i1e7ws<+ZRs^3Lc z36j@3I&}_0v17b>4e*RIC$GbvhFBDCzM*zAdg0OcOI6K#j8<1v-wV*cY%neEb+4E(ypU@ufgev}eas!c+3J^Vu)V;={ zZ2uHu1u8+z{omdExu%#n77m0nrm;}7*gV1Zwz#DlZ<__12ie{D2^829=|VfxE1T!i zpAq{OO7qLyYWgIJjtj+KZke~YS>$@r!mkIUTs%UswDm3O0he@+sVz8+yGjX$x>e^y zPKI|}fSKg=Uko7lnWbl9;IQeWcQF!$VbgKrmo2VXm)>1|kX_PZk0{?4H&vjLP1rjd z;V0|bG;|SeN6PHhl6z@A(|nj7cTty6Zf@SoW$}AE29QKsUA6JifD^7PjG1Yw6R0EwAxRL{>kAlk#Y+xE58j0V=SK# z=5r!-f8i*ob3D}AXa5RBAHEXvq><>Y>jl4ME1V6ZHOy(!dwYHqLxU%ulpTa&p^f|s zEnb!_R(w@o#B|RVRj5buL!R|QmFJM3XpsU1Ps)L(X@r^Z5 zeEr%pXzTKr2ShuD)}pmG&UtqPjxBFmH?6ijAAGiGMOi^!!|Rkk&~YH)CpNvX6FtqesHgXa1CAX*YX#Gn=vYWqFekC<@Cx7odP<;@#wkF1ur^vj49)SBDXw3qVC z=^X_8)0@Sc6^QKaU*#&LiaGz2^DTJzhp;*fRD-I-;Ky?2vsZm5zbRz9*b$~pL+9B! z5VV;)B;?+O_$(dK2&D97)14nYiZyI$SexTCp9Rfruhp~4L*hQOU2=m7MP}21;G|bT z7~-L#6m_@sjkSwKbSk67S8J+sKJfh)xJTRQd>67-XNQZa^Ky=e*`6YomdDcXeMYrz zUdxJ@)~lFjLuA_Lq>-h{wl+JO6PvA_Y)J?G<_Gvr2jRtDxVg?r3-e`9^|Y0MHtiC1 z>G7Ge8UFge67#Lpz@R3!eSK_ftmO!5%{qXJ!l70cqY3h6->~%*mWMU6CF>$yvxs{B z{{Ht_i=Zv6%f6M-@pS!w^MCEEKz?c~fl(zvJU^Ml0*SPP27(@V4g&0l6AA}@+c|WL zS>`lF2j!hCT%d=R0~ju<%`+GNc6ndfD9XX1QWi)l?Y2{Q%{+VC@nVGmgbDGy)$e7e zrcD=`4)c3J1S=u_%XLRVF**x2P@K!j%G#)>I{u;doQwd>x(`4&{8mfuMT+OUrOVfS z_Wb+pw8Q_8qVdyCv;&Lbo=m7ssvn&H_9#224*&gcI#l`A?+W&{(7XFL4|6la5KsgM= z{r|ko8yG!abrG34K=JtxfvhsbeMi;5D~tPyssHx#V}Rj-S;h8IXNrl5{hMYt$`k`{ zVqYe%iKG7^k6!q$0`${LrfW)j*Cub>e-rI0thqzhoGEM8f`3so+tN9sG75?HI20&V zr7wNS-}bqgF|t=5GCqPX>S8#m7yT7Ebrzfy)ED(O-q@YX+M&?Kw~) zcl}|p!QpU`|5p(|c$lR^4W|44!!~gCE)*z`^NGx?HHGv)zn484C!-LXl@Ao4u9=8m z+P|-T^vhMC?l(7+wz0H4@DH1Z-v7VyUtS^cy5+yPYb>4;fVEM z!0O;eXUT#6;s5;q*+DpcWzFpKZ$=OC8B>gE$wmbI?-#vM-3j|cT`mvm&-|fk7x>#C z-1mQiY+{?lf|aQLFTel4)|oIk&=mh4LqXoJ1yTO5bx9}a7opfMed)oy&j=sdxDlT4WUC=an{$w7&jgD5u(Yl zoxImTrqPpEklCBQ;8tR&&3it*4BRP{QF0}asnQ1wD^_47OMe9hGl`SV7>K8YpX&(n z3u73&aIi#UK`Yl&M!^kIG-X!m{gr=b>EgoRhg&Ro6?e@UvLVlK>BYc?o~X>{o_MUy z;92RY5&@}@^ZtFGTuXre@C2|SW@OX=d&n*fs0s_8Mz20H{OjP+o2_B$Xdc991Bm~r zCLWW86TzySKP;ZEs|f z>=&&e&Szw-PVXW!xkvkkBqQY9oe#-*e&KYOBmC;L1VfUlzm1qA$z1#SO+gKly) z|Ms$ns(+Piyj$^)gCUC#;BL-$2JZdz4(jWfWRyK5kcf0<_Sg{sr{BLAumckjD`;L#1lajYm zqo*YT&q8QLtuc1JytJUn%~+VTd{)B^`+(AM$v3L4yL*JK7M8)-7H`!!3!b&;kI)5# zP+b9l*826VLXQqC#hik;OG9)jy(b5zOUXJkP4mFEHgD;z~V)5|dGAf~GP?Wb=O#h}4MKW2138d&0K ziecdTWi!*|y!s!iHmfQPy1MHkdSn!vk_K(}w@_6`h09$gX|=#6QCbRYj=zK`i&wT% zldZR`4WHc>sdSU1m>R;DQ zc|dBPzX?x)mHCDL=iY{C!Hx?}X_w|vz%>c@XW)dzW3lP8rM=V3#=k@!lNbH_F0PHm zHE_P~0LwB^?!Wdh+5Z@Wf0>9Or-57RkDm+9q_ucL0~A$8l;DBc>DTzF()%bx=O{$r z5M&Afplg;(aBlqFptbhkN3^{C`}EiDx#A>-X|VsZsMfEM2>2AZKR9!N*36Z@a&$X* zwzBea%-`Hof6QO-4(?BvP67YMa3zR%4E~5#7FW9hg%^%DyHPo-Mc$*0y55eg9t{RY zZJGNg!?Nh|9J34C)*m+$YPPyEikm9}P`sGid`8x%-IK1}Waj4XN%G{*&mDReJ zZM&@|P#uqYp{7u%={ODtWAgtDT>khiz^A0Z{Kdp zQcJG;W71&9vpd{g?|NSz;}?j&{g92?7FWkf4IJqS)C;Xmv(9k9q%4qNdZ^PgiMg_=D-$oDH8M6kY!u z7cjPe%)GTB25Q^0L`;Zm>cYN@i zg%?|Jxbx{5CHu~6W3BGFHpPQ>HF^v^@jpifaJl^ZYYU`L)6!@B<4LvPoZfhAGt*=H zEdi3(Ucki}PLD6A&Hb)AL0pm(OsMnalpq6YG`mP9@i9UQuM|Y}RO845m z$IgnM_o@Cl-B>n7 zuQ%=+Q@J_X1mm$3YuvbHnJJ)6R`_+SKl0Hc_saIoar@7RQjh3T5Vvx z?xVgfZxI~1?4KL&|Mv(5V-{THU6x^PWqSuV?|j(MIzur~J=n1LU}sFFx8{y|x$H*I z?-mSr1@zNbJq_YGcMJ)S+*--Lv7}GreZ<`mY-p(FxfS_KXdjh22|JFbCc3O|deAW1 zkYXn=a?wB6UQ{&6IaRekaGO)&ZSs*y_Ew&65;Jq%Qpq;Qq&I!}w92m0JXOt%WalQf88N>yNY2M^>)8VKqvh_0tS4z{X$ROg z#~^-}{14vS7Dv4r8EvTZJi2i1dAuzK^YWx^u~lDCCPm_$Pa^^rr$*l7#SAm>WopW@ z<8os9GZkK{Z8K{iLc@n67KG{=nM`6?umS#C3SBbW#5+9UHNYO zy=4C`YgY9aT~FOaI$prs#H=oF$f=Rb)bMEAqv>h06XSQ(aHY^adGL2NFJ4$sv0G?q z$?dqHBClvZCBEU~i|iNNeyJRb<5OK}JbR{+nm;>f&+Awu05K;9L`4;J$z40_lv=7+ zZC|_26(5(vS{-%w*FV$Re6G|`iOF7D)0C_M)SD*hzP`SU*_6}N-kq2Us2jWk@yahy zNXagaWRA?oJm1b6-ZrLj`{V1lSikZ34}FM)mkGa%6PFKD^b?K@Nu$0FCq8rFrZ^~} zYVNXExr>9M;>XlS%hh(+j>Lo^lNS;eH2f-DZoK<576sE5;U6+PhgyoF^~DHpMoSOu z^0OAAPHhZN9@FOfKei!{*ZwhN0hg*7M<}OxOL+JBSieAF&=rr)Zya*LYe{|WUk&Ip{i#ydMUnDGvw|n#8Cewp5&k8+@Gjz z>{eTwB)-8ayT~1WjR#6=H<*1+4xZlKkjptduq4+;EMpnwZEBA~;Y=CP(a|6@mBh%T zf2dpB+B#CO%dQa`z$8zSIf|>a{>dY%Zkv3^1*WI;0KneED; zpXRsalEBr|iD#V>Q6b%v4!cpRAXWZzKU>@A^Tfj$BGOGS5gIF}TyB;-_Btl2I7J8X znbcWM;D!;bAFmuyrYE4(ZhtG~_e0OncfCo%L=W%`4%R_5h<;xEO zuUan>0<6L5*DdyxfM*aA?x{%y=rkve=zQ%h+;ien&OMbq9gJ-a4M&w!ESINxW4(*F zmuUGNX(TFQvtD=X=oRA;uYGMU#8*Mpq^zY+WdBYpM*NpLC z{f01W5C>M4%=7KL#CZGk?vyC-KuyLBB=6M-Tzy{8i@Gu-eG;Q=7i}|NcA`4nK?p<3 zqXy%8~4_ zW&W`+W)AR$ir3>%W^0|Wt>W<6$za1Hlk4x~Z6*!qZ)iG@|Q?l=v6xPN*0~S2+ z{I#jMihp@&u@&xWSi|HI0ev1y?j8+s>Mmb45k32)Cn74kzB_53AXUV>z#)9fv;fDC zYd+J^ao(cVNvfdTxWiUm#;?`5DPacp@#CknqHMY5)K~bk_?U7%NaJ#GiY><>h!p(D zb-vuyE#RIn`gLrb6pe4+gJHIyyCA4wmI+%c9BU~XvoLkGf*dyfn9-{Wp*a9Odn}_6 z;b-h0GT4R!XXFzJb+5{lYlV#bs>=7n%!SQgqqGfw7AjurohsOuqjYEH-H7;y(!`Q2 z*>IPpPKNqsZZ~KxUV9bWATn7Pp6Es2D{Eege@5yz*eVnimy;4#d!5FZ-S+I5^6yqb z_)TBavuTZ#)1JZ~^0UUcuj9)m?~*r89-3Uk~~Kxhp1wuoc0>diSZQBbI%rt{FoZU0>cptwM0 zD8sUg>?L!wanE{5%@8XE=uGq?@2O)^!)P3q)@fwaT1{IGlhE+AicC{?ig?}9a>nLH zTC({eRd<1>q$%VXSI3YGZ#Q;c9R8YT>{Y2X#eaM`o;Gj$CGhl2K71jB7bxgHqghy; zKYM+KO*s+BIjBU_EwS@=L-8H&2QAm#JJVaO*N0oZ;WayKcK)SW^PI(W-jrh54SIL^ zccz5I?mIPRB+e(+R7%f7X{w3ACi;QQze+md5S)sR@Vp*qCHRYa%l)CbL-tJ9MoMm;W{Eh6l?7oSR{MqF(>E?6!)aNQ%p8YpSphJuxx-{TvVo3Xvv5}eS$R2&# z*SB<e{3k%O4wEFXvjGYD%} zZEcq~L9rvbzy9%Mqw%`_H(3-`-eT?Lx+g-}5)NA^zbd+2CDag za>aAVwn=liq;k=eJ37j}5eo~ac3@v#;W%%hmbm-7y+FAU0C)@5I8H_jPgyEo+8r|Y zF_>a8{|r_ecVO;&W-8b2Hn%lyk{zD|pV@e8!_Oj~?yH29j^T~h4DTx1aAIR8hz*S> zxh9J-V@HKuQCHyA-t2>9PhRAw+wKAHOyHPSXP@p*1@TFO{s>`;$I|AmV)h&Uw$hIk zkh)I?@{zO)i7M#yjT=MjFdJm_r}0CD6Qvp}=kDH!%NgNu`{Pt5{OTt+Wj84sJITkQ zI19y&RSGkC+L7+LW zGePSfRLu4YQ|eycd4F`^LcV^2vay!9+EuR5eivNoLG^(A3p0%k{+#7w1zNCRcFe}j zp$YIGiun@qg~dKy^c;qORIua2UX}!Ag7D*XKmTrqb-5Sx5yOYxUy6u%YV6oBO{K)G zd!%)mcXa{ZTwq7js%x%8jLuitb90uI?#_{B2VPRsr|xryE|iq5XgP;_*h4~YYTaFX3knkme% zKb$>5$h4b8@0*$vI|};8stznX6JVeQPoqCMr?2)dPymed+c8m5CI9xjA`&_oM+vr2 zTQgahn-#p$z-_YjIhI* zPGni%^heH|5}I;77}u3^!9tq(D0jz zUkxgYUxbykABKILnqNBKM?k;z9gTWmilIv)=zKgK!~0S&`rXd>J0f)2(bb2CrdsCK z7oyCOC+Fd+kCIGs1VS$>C%CuFUCFq0WKP4ko%m7iu6@V0+%Y?`&u+l#fVM;V zlDp7^ijQ{iN|qX`<^C!ct0BE{6?vCG>lBdla+99s2ub&#V^{0w$yj!LP?K)Z(6mi_ ziT~2o15Rna+#ZL)yn8S7Q;44-nQR3p*)mK0=a>KKqCo&>H>i=B`Any;u@ee~{Xpy|J-nIMbrq=lR~LRoBHresmaRLmKIx3SV4FKsec z9VgE%HNXNJVEyB8G@okll{tYf=;a`dypkF*1=spe?r+E^4k<%s$=T6_+@D!dl&uGRIw{5nX3V~%3J&>BSvWmA^90wSz& ztzbMgT*Mw^$?_ALHnG4+JH zOMBJ21^wd`DwB3Tk>t^}ZPQPyUOe51MEy1ZyJl~)9iT&R>^$H%$v=h%q(#5szB#}0 zDmW&6q&NM*mC(^My{QAJyEll>`%9{JX-(`Oh)QcytGPsrnBrdd7Va9GLbz?o?JGGb zctvz1e>H=WIBF0zmcKaXc>uY`ld8sP+TSZ}jkE&Quwbka%fsqVp#=V`M3 z@Z>RySUx(>89QY0lv;l;*8mCKz^Y9&W2raw3<(^|9Jc%|&7ogF-M36MVv4hH8Zoiz zQ%pXenTVXbZzm2GCulyTO?mi`brZz`<-=sC*FlC`msqeby|_5|kuk#gJ?x=2Un$QR zz5VjlTJ`mJgx(M|OUx2T1cPYerL+={7nU^<1iHRMz@1y4_Uw(nK3wK+Neu7~cHkKq z>R!|LY|LbS%WSqXf{m2jj&**9NsV#WK72B!XZfP>trhw(swVeqph6WJ#&jvd*xMFd z7MzSTZ6mctJ&8~e9paYz@LAQ;3CI==p&^l1_Dkbjhm#gW+jn*>)vZ))E>vy^*`*FM zx?b#T?Y@a8*35pi`+xDovZ*i?y@Grz@%bApSwr5(+I<@)gbr4mrIg*i$5rrl8 zZsJsIRT%ot`gT~Yp-OD8*dZ5jPC_Ry<1RF<^_1$*8wm~5HXGVOr{6?R5+SvudBWvm z?LJf58CAvp6|roCuWJ53w-mS`6i#LZAEmi(?4&9O8(O`!YYUlU*?Bh^p_NDY#5 z%`)kE-uNL;1VuZf(nkamwSOM#T!u-EJ+c&fdsBe4TK7o@&|a9Uy=FI}#W*-`h|5_^vpHydCax`UQA@nMR<6lE+qmh}ht-S67> zRQD4w24Mi%(9n!IWp@YmbPKbE9PjPFQq^oA{gK9Wu|)L8e_BF$oj!TF#&yQ0P>1#g zazeE>FXd82lgE7Y*-1K7rE5ZT!b527Wwgah_pA}|7^u-n3aig?bTxSDhHibGFT_vj z_-6=i%6qm#v#GqcFthf^0BYeN8*=E-&C)VZ2ME#w4&=IlrO;=#qc_yc#9PQ#WX-}Z z!-MnBJVsMCrLH_WU%QR8-i|RolNuOx{Y&q;c)JuDx?*oa$GAGb(qY2jXRmTf)S32v z^%s%4wkyg#9iEP*LGDRrQ|fX4LrZgCu36{6uLaLeipCEtEg!?YilNP^Ah*v4hu=ta zc5H+y2)}AcTHgPpkb>W8JRw(yeD0gA86}D_Odx)Cjjw6AaZNu~ZJT+rGw6F$A$6maN4P;V2JDM#YL$5?ok(k?`gH|{rWTX$79mD#NwSc zlmo|OGMi!vnB?Tr4BImuOg!89wku)QT@QFwPaZx^u9%(kZzYa#mv8fCY;0Plt;)$` zL-AbkiyW4Ai~T(+XD;O8D`%0_8hWrpU4Hv$O-#q=cjvtdw$7v9r}-C*uJh2%yD%Dy zj`YnUM;Yp-3V!p`JuaVEMwzD@kD=L^bEt z=f_26RqW2^J|cx+$^4j5SEVaZ@{OdWI)^OGHP3bjK@!KgCl8eSqJk$Dpw?n0gNB_Y zturfxD}j3V+SUJc$g3 z$efefSiCBAG^3s?AE5XCjLE3pLY5?G?W2gr^FayO~ccIw3qi1@U1Cj zKZjD_kDg($9?q7+>}~RT9<`Nz5bI5d(6Q5_}kvJ zaFu;JPre0&vauKQLV(lM%}SSW2S`rG_TdASY|^Bc~x%*?~AbvQHT} zeN*O}yQcz#JUKmAI7pV0Y~7Lfti*|7Ty}(%+C%TMb)UVf%;IS$O1%7W1H`_$H*TGU z&^Y|uYp_S2#xf=@jg@Eg7R{FzW(5wuwj3n`_&HIQRl_s6~*csg^F zL4bj!#$JnorFhut@leA{vqU1o-OKLk4IDYly>fArUh;OzCmII{n_y3U>1Y z?27)LVFZ)=W{&N;y%J_)8a-hLvO-5#>AV}dwdn%sPM}$e2uAhFC3_(%{n=@25cFsh z{jU431RTLK3J1A7$>hfUqc3XLrv)y*OdKuSRA4_?4$ZITrrEF*Gr`v}dg zTR)U2Y6Rc5P}8?VFcwiVp4|#MS=?DQYIfh*Jpm21R9nh87BJjvF+;f%BjSWhxNlP{ z4(f)vl{8E*o85k*rjXn>>_L5=hLi8VRP{a$E^YzHA6L@Kv{*@Zxuj3B|4EywzwJil z2;TEVwxoPMVYk@^Pec_C`+;!1gUlo-Ot|^vB-+Y##A7XFY&jz;oE8Un9VAy+d(}?Xe6_(cV+BiSEr9rrsTRfxHhWsY}+uRaToJgeapO=UF(V7d? zN4Xkho9nEo@)_?Rc^}8EV(I?v-3E?H_?l(={;PK+J5Ue6u_uMMbg5*E?PFG_#bJgk z|7EsqY};%I<*<@>$3q47kqea9QjYcwqG{s!0aZIh?4y#E&3%~~ql-o~7axY;d`DYe zZR?qQNqjWtlwLDz_RO8dDdSO%j8=ymNqJxMT!pfs;f%K+4%2uW?HHWdY%6<9!RjIb zH25jGjVx%j7y{_ppTyB_%);Yoo7&=zye2|YkDvwNS;pp6y*O;NxatN8ED>o*auXnk zeN*RE)3mQ$yLUMTd{}*Il8+btxot0A*>vPRh1+LBu^s}aDrzej)z&9^$7A@=k>R>eXZAUCZx`0N z>DK#l#&$}080*JPUZu@6q{%>Fr9SNqp^OPqVFPl7IQ*NcJKes~PdoW{^}}89O5<0<%%wB-;}x2cfRcv$Fs{Sp zJTSqN_ffX})JkKWrlJvpDz?0%7}OBEo3`q1fc>x#u^kTJP3kU;`*iUk2kc`8J-AF_ zfK2WE%AMv>Wp+FD@8c$!S@faDAO$#XIa2&&E`=myuJCmWH5QD%u(^VAl(5BFn3t7v zEm}eO$@H@5J-XXkJrY~Iej{E+q7w@zyN z*a6G@Md$2UG6*7p!hkoF{NQgqk%# ztaPXZG<3LQv%KMeYI%A4(v$D~y8iY)kD9Xspf+-|Yq9V1<-roOg|R`0Yy-dbY^?}A zgFv6ue)XD=B}7DHz9GrA+Nz7dxWBCAmT$>nye~(ZJfvJ7AZO9?@x9wm$um`s(sFIG zn+W1LxJ)e$b75#Wj6X#<@(6(LVfR7{!siaMWHyH#58^0CRA z#7GFKvQOp6p#Edtk`184A%6@G(l{ECiN1dGo1~6^a{|LnklGFoRJ!4-@>ASyLs^)Q z+9zk>rUbd}J|`Zo%%J>>Mt4f-#ivE$as#(czk+?6-C8;3Z5!!3>S<_bkM9?J^|WBnF@Gg+4p7a*>KbbnWHCXG5pJ?Xoa4%7%3#@VY}iU%!I{c zjFZCIb;a`M2FW##|B^@?g`h<%+2W-22IoGCwus#zDr3z|-1~@hef+=(^S(yYjcnS~ z?ii!tnx^wzM7A(*u0LV?j-;IRqw{5Z_eqL?A{Q&)oxBFy)I`FFAHqI#Aj<)RoD9z- zqw)ioS4Cd0rT*TnpKH^epS@zL;me%JHDjiVV$wAFb5H4KPat8Ib{Kdk)-Kwb3QwN7 zQ$=}6>koo;1h}1j;H}3q*}$&PO5)y?;Z1}-fBEX3T3u$&SP&ADj8`#4d#!9)bq+`F+lG%*zDX1~d25trF_K z%_)yn$boGu31O>F%qb1&l?OwQL9fSOGW9J~f{{g41+DL={E*Cz_~puXj+yPZ=K$EK z)VjLJ-e?>;X(OMU@`{!J!Uyo&liEJ|TpE+TfDR(iv?k0UfV^=rteZLro$aP%JsDbUMzuUc+chBpI87J{mc8h2$c zzljRdv$*_vC8VeiMR$1jM!}178jxi(N8Z_Xa&8wMR>j)$?KGTi0fw?cD)tqkU4v%7m_L3Z@hzb zn)gLi6l@PojEJ0akE=oNkEDNgo}3=7WY3PfSbDNv+bvl=fKA*ziNfC1_U9#8YNvAI zd+8FfOjP=@*&$rh=*&!@-?h6p%vu+mwd2-L)z$$@z8F|(`T9JrnsF2HI!Lyc4kCmH zM}(rB0n=1G3z8(d{uvo8@`0e` zG}cwn>f8h7mNQJ5tpL0~dnd24$g@~cs-dY1&1)silE{NAmb;It_($xJfk|7xw z2|+N*J3`(icmLS@8-FS=Z>zK`Ks=uv&<1qk&mYle9LqQ9(e!+8gv69c5RkmZI(DOS zb_W8Dn=UQ-(AbD?{TTqCC$oLJ!+yren`BU-ZnACeJ;g&GpP;`_mxkCR+01Y#$NVn7 zZ|uA##4>48+#|*Fe&AP`{voHd!u#n7XD{xusqyLlF4dcVmeLx`K7fJW{%Beb@=dAw z`gZZ*J6DMfKWu8>c4Wbj)#LL*m2Q$l*4B9V{mCJ}_jQD-Rwsp|j%lvCx?2E$o@@lv z8m*ACUUPmKEkw`bA1^S4wGB>*76GcqcjJ5LF$k$I&FljFNA@MRnlF8RHvK`ORo=Zi z>Nl1I9%-Lx^>2iYe$1u8i6EaJ;tL4H*vT{}C~OqMH^kNVp!DqL;_V!|B9N~ZeO95R zlL*1DK7ETk;UtMna~wBG0nc5|JW?JqQpM&X_?;`N(>Gl&9CoDKi?<5SY1LwEp^EQ} zO>(XL0Z8!rvT$drCS`M}_GG7P&D0KqfNzHadOJOh0B22Xk z(03GVN?Sr6`Wdjauet1gQ>?zEWcSf98J^WCV~~o_UXq;xY^c!l1u1oh%DV_pE%aD` zTEO7L$B+AC@A01gXu5y9i?HyqF^SajxVjs|A8dz5-ZzgD6{_8b@BF!6nE#H36sd&I zFF5cD^~@1x8#eMM?Msi-W%8wiRy74cc++!!jq=j)6cVi~Fn%Az! zsEGS^m^VkdEx13Zwy4pugf-+pED# zWm~t-bJNXY-TU**gZOS=0r;1Hr`30jZJ(u)xJfe^Xg(t1i_Fop7FUOR16wBe!c9ub zKOo4_hdnPMV{)gDca<^jKj+@fDdk+1TDm=*898{UOu<)C+BPJYf*~#)%wqS`ul)U; z1B1n%O$<(*+dr;5K&ET2P2xxjpCQtPY@$Cm$F(<8TYWXPAhM#Z z!J6v$KK5yr*&!4;y=e1OQfE_twWDS_gYpP3^#*L*jVb*LfOelTLLN zn!rvOL+~l_<2nRv7{kn;!X0!zlh&;LZAINXQt~xmQ@tu#`DbK?-^V^1enD=<5aYCu zFFB+OSyhAR$84S(F1N4#`4Qgm4=mj4Cr2pJd--J$U~7-e`HEsD0_UDa`atla<3lT* z3Ox74hP=<#`OA~KOFq3`sNm*3OP|Yw0Kp^<1Zf-ABO*@&T}a7!f4ANj@u@L#$;8Q* z<$Ue4GD!iIw34aY#QoBUuw#8#rTO-qysCl*>Uh^6E#YqpvQy^vc{4pS{ zgrqfRbD_c>g?ThIcK+Ma4NTc!(YnKG%(sox7BfC{`CRv*Lm?mjoU^+zBfH=#Io7wd772Q>5hP1DR)RDZtWCE67j8jziiL7s4}O78_wGPQR$i$n>U zo7r-vSJe)!LbJj$b{{;N`1`5g=Hk6LRr8-s2Lj$d9y>c-?U!-Azj$+2ZFPpcMpFpQ z?!qe5L3_uX|F@16bzG^fHSIUPsT8o*l)}OMsAh8;7zX0**ogAo2W3qOgeJz#D3=i< z|0mBaDD{m6qk{*GXL@cGsz6BH@21GCiF>*$K$`T**r?}?X&GAo^>&olMfgn~2=_&l zCMeuhY5z3UOM6q)WkW|s5Mu0}i`t3n6#zcy8`rsocDD*o#TG7?N)LFr z={`M~Pr<3AHsc$N<|*??mE~1u+TQp9RB&iusziBkZ{BlSn!+X8B~k-CBR}|)zb0Y^ zRIi8D0^=(4f;2>U#|~-8l6;=SX0?@?W_l<-nWGc%aEABc zK*s+=*LTM?wQXC2sMr7z5$PyRML?tpgrcG}5v3|sMYt1Xq1*J9DpIss+cS3M z^{vd(k5W9V_rG@bDsQG{i^afm_6L#Y(a}zGdAF8Ib5XTT>2Ht1_cvo_M6oe;5FrVG zbv3djdXA@!qZ;u)FTb2ajzc^!SBBofY#3RgM%u$s>C?#4r%dYky5-%Pe&Ps^<0Q2R z%WsHYxeKy0N5g{I5s}fdwodG)vslp^YEQe(*B?%g=1Q3&Qu)L?YodKlV>~dc4^0VI z1n?rcfqNnT|HVG$t=;5RgNWM*|0p>f-eW0+GjQDzzv?^|!60BDsX0s42aKH$AwX>yO^(-zy!7Vi}mEgN0_2r*7f(^#!9wrFKlIdzBAJp zU}|?4#ZwxDQs@_>1YA2?x-qHfL-zIs!8*??h^UCp%?N}ktH{bwVPCGvq8-`6UjLkx zzF|7r%P7I9*akYV4&l|ga@)`Xfo6V~GY9H2E9?3|;f~{wrB%FP z&E<_8(MnO7%G#*r^x*G>qoUzuzp~b85q!;3w;Jc7=a{N@OHs+4gFCBvz1FD>TV~5( z`@~BdHvRJT1pFv$6rVrBV{(RxW7KuocVCTEe_A&6?B+HLQc*U{yDbfEJR+@ui1dMz zX_yHlFFVZ3=ia_YE01tI{;osy?#CkaFUo47r2Ks7)Q~8E>7s$MC+K8YV?Y@SU`=#k z#3IZ3BYZ{se2T_eMOR19@-LwR(OLf6w<3IgULFXN<}IH>w!Z9JvbT=O%f%E!@v(8y z6*(UHSs}9}7rs;_yd^aXSlT;VxsI`X1AX>>p%IOh{N%wM)>10IY)k!GGXu)pGFyQa zbSy7Zu=sz*NcdoJu7tt|AYJ9U$MxhvQ}UBD2N3k{l8^T|h4oe^zJ?v1RibKnY>TTj zHuU)MvxQf2tLBApxQSjK^Ns^X3Sw(pdhy0JAtlh$Q3gG6W4EoLyul?R&DY);!STKt zI&kJcIls&6cqufRV}>;RS~tk*TOMvqv`bUeZSb5mm0l}ugs030rZG2Wu#dMGtO>%f zz4M&xTeJw(Qy+U9x4|%pP~$4jcy?VQj9X;?L#9Z+&5xuyA*KndXKVl}`9_#c`4LdP z&-X6-2=dDC=l}<$ww_S`@0N>M??tBf@#WT_-;D6{@wteo^Pfi2%cy<7+gaj{u3FrB z8!q@?YUTgX3f5R|Sy^@sPaoPG#0XW@O(KExlFWOOjfO?u%qR4a^c8@L&(najv9Udc z5pMyNqJyT}F+x_g7Z@egpT`<-GP^j?e+olr0RbEjVJNfZ~g;x7tg0h_PvpU;2-?V|Gonp1rn+eZ5Z37OV^)2 zfBpx_1khzb6%8=Ze6Z3voBFK~njA1)>@Qx#f4e&1NB?>Y&L-&K6$gc*qa)Bzi1@$w z6|KJ54`Q(}SJ&7MXv_aa`hZ(wmWh}3o)Mz4x{hp&jDIpm|Mum0kSQNyH2?Cj|9t^m zwLzc{V3K^O(tnUt|F#Ih=TW&rsN8?Bb^reTMb=f_SKf}6fql3;|9=onQ0GzGc)lA8 z)PKyt`TJ9l)~@jTwY9E5hBv{i zERkcNEdDt1`Zqck=<5Rx25prp4DtLKhi!MP0I0)6>KFY3ewB*AvZ zUOwZ3L9wRHw!Xk_zs4C9#yqjxv=RST`RH%!Q3#lpP6>Ze-zu*`JdgGYA^Ui>#Q-%a zcUCG2=-01rgX_xOO6)`2O5yP=mF^EOL5os93HAQ)+ept8%a0fi@GELs%W?yp#<3Un zk_%}>}c7zX$u71fp2S51RCvtmC)BEc>BMzrrw1yj&R2_A--}# z^6S!KLJYb*%gW;7mQry#aEU352LNB*NgE6Y z-R@dsCM8L3<@wX$a+TVN-#6C;5mrEEuslyi`#wTbTc>@dveM?c8hU)qCaIy}G1sCV ze0)3I-n5Cto`YLWIP7<>w11XvL9f(Sy-IT{vP?4~(EPml&x*7T zE3aQKYi1cFB12+zxZwNE+X?TR=jQQ`WU-c(>@leLo(ovSJ*@0JA@^x-50qx)F>Rg=S3g=XmQ?e>YHRGM5B5|v1 z_%YM-cR}CP?#?va>emR(ZC2iltjZRjg;W!IfX6W<+21Hlfc-l`xc){>2P8|P4{MxW zJb%eOI4HVpOePEOUu>2Gm6_nwvYil3Ez#<<%w^q-D-j&m%f{0a-gBpd_m-E{*#a+b zmV>GU)xF#$DC=Q$by5FG-;w?O(8zf1c19X*A!${%lvL%9-~IReEoE!B@1bX;B{-YL zWyk52d5e zZh!yrXt%@p zSYMA%n%deiC^~|Erf7nr<2*i%h|j|pGv{UvdKDC9DRe@^4QA*Oq@)Y-QT_W`hR!HL(1*Ov8SC3lHOdMm5B zm0mK){SSBTCtJQ_&9i*=B}e(}{$(|BdJ5?(#7k@iB6EhqO3GROC&P2uhf5Ur17@H9 zSx}a8dRZCx8{LX!v-ggwfteM9mRl}-7meO82Rqh$kPr4GNcF}lR~p+*MBG9(H8q_@ zVO(0%5L>!K|K`_Rzb>;qQraY&@JVU4D$>ID<G1x;D&}&anlzWv3sD01iL?F5@`4sY>1DiHs zbn4(W!;RA}R}!WC7Ss@@jXr-yWoO6wj4{WF$4-h={5b3CFTKAu0GARWy1wedmuVp= zN8j;4{yw8{ioQicQ9=G4`~Ev$2ExUq>M(=l=O4~l&~^!~E|9AipV@$v1)Ra;y8k2#ZGWe;xzu+kGyk@4TZKH$*bk%_lJcsc7@yGu| z&A{clPuVbi6Moa*GEB%Bgn8-4T~O;0Hd@;Ie-7{L)!@i!UIpa8yc{^x{quy-G}3ew z^j7DPWdJn(hXMkudGQ?iVi>R5I(?CF6aM?CWcsZw7Yy`Y9Fl+j^yv?n0ch(!clKQY zb+>W_FOVl#mw~GsB)Y)?0mt0Tzx{y?F8JS8>Egv3fLV2kjm_qtPd#O)VE)uDK#(q@Tn9!>BCjm_ zZ_M$B`hy|8BoqudbB2{OX_ak|1&f5n!@f0|A8IR^)G;;mE2V0PKn z{+*v322J>?4H|CY^cNi+*@m|uo(M97^uaQm0QB94F8Gr7= z0=0vtIkD(9GW%I+Sy`Fh3k{6{?iB${X9SKm2YV%4SyR9A?OO&Vi5lO!jqk z{j?CgTmsxD0RHc`#NS_?%pfM{OplR>$XY;Rb+*r_bVJXXK|n&ot1$Us=_5qdUuq55 zOv9n%m7hSIVE{BzLCpMzX)OG%kP@-AdNvUWaB!7ORUtU~#`4Q4Xg{n)Xo%J@2L!!@ zKRCB#1`4jH7Y<(h&kg;D%!V98I%g9;@&w+GhP|WExb^L5iN$6!gHc;s2&d5~E$8D` z;ziOsNJ-0IHyFij;@*u{YW@EGo9X0Y&oo@g!uRX?(R1(hYH13GSpGI2Y(qoCKSV3Q zH=gU!%Mw8=zxMR>+&@^(PgP5h>Ce^?gs;`?UNlmc4szLD^ z9Q`0wBeXj%_sS}cF%gi$257pmx@+uxSOI zp8(W+wSh7Uw^V+H63p=V%W+GPfsG-uCV`^P$Fm4LCodYF>stLV|%1B`7Q z_f#{r^ZHbS{rc2>B~({f+ps1)TC0}>$W^%$Z$oTRWHs;S;4s*|KNVf%bY@Mw@hvm4 zRY2BH1TBUeeIO++t}~Qxq*m}XejX)Dx?O(aBR53yW&hpH)QXhq`!GZHMDYtKOtHJG zoZP=|h(9D!dbJimPgR3v17K9mXKBZ`wzPB%7aE^+EF54Fdl$Vj3YB}X#-p^<1i5)j zAgpLJ>oJ&y3M^3ighO{Z1%>sW*90)X#Pgd)xKRvKoE|-vDGqd5zb|3#d!MFVdXqj< zZfo?i`dx&Ny!5y55|*2wIMH9Ed^118-Ul0@RmyrPQPw5L7=dB2#{6Sq?81ZUCw3E& zz17I6>YSV>CnzXVb@B~;Du*qWHpBqVYV2&Cpi<^b^=B{Mv`7o`XWtpbZqqN6sH?0R)|wf?#D=jRT#mwk}K zi#>7DMmCdQA}QM$sT%P`n+H3%p*<{)6_G8b_YL3=+dgJw-1yV($eaggwXnIRR{2?3 zceSvosj2GEo^hb_3^==*pE|d!!0m+>^0>ItRBQ}?Fz!~Tl#J0LqI5K!6}@a)AT$?5 zj)%1VamZGZ#gF9GfNj>ojuuIai=#ECF3rC&lX8#r)2I3TZI3cDG)!;m3@V}2#?}=^ ztR}D2MgT}?Od0OhpBCuFytt4BrRrhUiPz)Dbar&4WMn876&IgH^;u8vmtv+bxyn8& zD#E8G7W|||peYx&r{M~k36!Tte_}8gO@uHQ!Tz^D|9TvDYD{Yo!12%<05@>_oGv8z zXKqDrP3Dw5JVGvCi4b8%=#jgq3}UU1)rExqw|jT2z4IhC*B2Y;4IMn!Q@S)}Ig&RM zZ)Rc=b56fef8||zcO0Kb`2%hq0j0ae_S|b0u+_23aV_AtgV^u3;MNU-=kfomp7zJm zjWu+h#`2fGEB^@9nMR(*($mwYnN-mu%uG!$A(8<_)HP_my4MHW%x%IW=S&aaQto*# z9j(YKW!Nv83m>O&{}@hPuj*iGw>p+LE&&k~5QwaVxe*1@0qacw(Z| zKW@dTr(=CZ$>tkBWPCDc+ zdb;J5Cr^K0hx~+CX{2^M9l}_L8in6H zuL?7Kp*ad9&oLmBx&lrpeO}mOdQ3}rK3P)nUcu@}H^!MkI zId2qcVL=>cWw#2MKz_nDH#dhSB&flH46Z`}FC^+$iC<_$JemU-lLZq(JWmFEX*|W& z5LiITW2oF8QMbcg93w)n0c^o4OWg4#a!-(-KO!mVx&jOl8VaOV3^*aEj)<}96E|<% zcJje$Zy*0u-+f|eeM~|Q?S{GH&`SGHCzj(KK+MF8Q-h}+L*n@{q7YPPCnuy;0F2~R zQqIa=ug)16!fAUEuTLX0Av7g1>yX>G1O8O5^6E)R&guay(%h*rFht#?O8fYUVPRo& z{i!O)$LLdpm`dK>Ddh?c7y?j5fx+;|l{Z2MwP1N-)F8eBu51Ggkj)C?6bqyg_)wgj zoc}llCBH^d0fVl7Zu-(2`BoY}K6Jybc|H>eEX+Fud5jshBHaNgq6yp5X zVkVRy=nt-sUAg=gjF0&LLL_-fVTi514zS&Kv!wmKHZ=X7KD*g%r5Nn?# zB`0%pb3b|<%r=fYF=1Ha(q8L+*yU8|vYmC|>YaLsjN6`_(vv457)c=h;juUEht*w8 zQ;nS^5)MtPF0er=%>1ylLSRse6mNd>`Vm~WyqpCgn+Up?1C@zmX@wu^dFXo9B%rf!Fqz8Q8jk7kp)D#oq4ylZ(#9PvNoB?6~-AJZY164vW$HNJL#Al)7OEoqxd;6Sx?WzqC=u8oo|Lc3TW z|LwiVdL7@J!EChRvxi`yEsqEb1#5{e;yO!hu25Q}rJkCGK9FDYY6 zkaWy~!C-9xjKXhMh6{PB!Q^@pVR!CutuD%9AT7p3LKgduL?q0mCjeMmAhUQ+fZpoS zQR02?uB&sxW{Co_006kcS1q?4ogWCOhW<1lR`)>Re4pZJlQxt+PPVXRF^ffCbG3OK2|AY?faSOh`DZhNV#KgoiH#hh0 zB3QqLq7oe-j&{6d$EQVB0}`MhBs6ruV4{O1zAwSSQC(d?;~AO`l8d2|`)Ds+bX`F~ zug`bM$UOyi_t`Ves03NTeNeQEi+iV=UggpaOnp%E^0Gta6Dt4srd{{Lo%Zo6=hTdh zw)e>&Y=Tvy1Y+*g@J}N@b%AJEH2?kTm;|HPyBFR7Vo_#0DFAvhrd7+!3o@Sr0>T}a zhuXoqf>c#>#jwOYWDi7tLkE>Kj?7C(eSLjJCY@IprCmylU0AUYe*W%~`SKtsceNp> zet!q-oacH2Wsz|!b+L^LWTA3Va;ebnXzx8;(sJ3-%KFjk$B`N^!yCSjxCH@^=kd7u zapO`H)R(Hg6;(ZNTzgF$YD=nLRNNw~C2>3lT|*&umsO79MD?E0{<&G4=0SQ^|3$UdH7#U;Uagce58I~_(9LHRGz)kO=pJ{pPP$`Sv8G1H24mNU_X?^KX zMO9U_p@jZm>9DvgAus#%vbN|>dl)ZkXEB>?&Zi-!dwi5MYz~?kR#Q1YDz^`xMA`ac zhxT)9=yDrvz?gyph=4#3#ABu<@HBbF>E$RvBbz%Xi%U_L-PPc5_^gd<5 z>gUg&(S(7DH+33~_!WHxz{?u8Kfal@VIT^#6@cha#2t5?q!yY4iA0KlqMBsE&(Sm? zB@T4IqGW2kQ;L!FVSMae_>c+VytZ@|0{0CW)SN>ma_(+h6XuDyeY0cxt(>JkqfpVE zx(HRQ@oddv)SQur1Se?+Bf*F~`mRsa`K{Ga|EM-)MOqt)l!i(zfofVFUhhK5f#LOwb?P=EO1scar!J z3RR~;+HqbP<|i$a2s-<9#QC*Z!Th?BsU$A45*n8c1KVe;2kmB?LsB&>o={T4QIjlz zWAL=LvI-*A30BbR)U)uah2u>JlJqg3t+dKZtwpAegOF~w(H?{B!3bbcB5-M{Jkgb* z{Vm7R?JAkP+#b>1AzUWPLay3kfG0-EIS;N%m7Hs(`r*R|;OOl4?CRfBc-h$II+Tw# z8CApjR_L&6B3O8-d-W>AqjJ75~*3M`W|x>~u`6Q%sM^hH)zYn|5&p#d*2`q@*CHCv)b1VT4d+0L#Y`Ar=- z!}Cr!Xuxe;wc$@OJJbhc0bjm~1KG!?6))n5cbclTzYx58Soai6+pXvX+DOFxfTQJn zv$=Ft@=%R2k*jJQ0%3KK_&m}|_U4_sqE-*3p<$AYSlri;@^r&cnt6Ea`jM=sEU&v6 zynQ{D_?tOI$%}gYqwLPAFhj<#4LTmt!(>PFs&vp|+LQQ(U_CFJr}tm~=t9g)?^W46 zrRs0-ASGKM2VX8=J4cGKtjl*ir{CPXefv#1d5JJnfIGyY;s`fM&?rtIC+$s=(l0O` zd>Q`uvGVOVlj8A^YwR@QuRYgf2$DzJCF*N4uM;H8)G}i|>d32ah}%qrllD4|^aw;9 zL8G0eUr-O+$3feAj{Vp=g6qr5cc%;l$t1nzI+tw`){C}A54ZUYby)G{cZ%%?neYbL zhE+~8si?C8MjN3G4`qZpV<9Gz@%m(mdVN|=P-{O>`B+?B;%|WQa-8%TGpGdF35;HttKCcWwcZzk%*2d>y5} zRa)eEugh#GrP&-tMr+6SYph(I9+fX6q+P$Sd_7CY6TO>nZc-K0Y7;n^m;Yn={Qyx) zE-K%0Zz5!RKLtc$p}V;})9|M@wO~fq5Dv$NzzoLvf`J83PmTh?#%crEqn{=G&MGiN z^;@fZ?h7DTJD9XWjfE)$jt0{q(8=^5q?Uoz29Kd`h=Pz6mvHt;#IBiP$d9jXbEXbK zzoe%rWgV=~pvYZ#0-Nw~JVT6jAd!fkoy`=!3usxbP|sZ&((UsM{ElO(qkTY?*8$?9 zbC@Am8%jEuB=&8Ti@pA>DPg}A9am?kG~c85s#11g#uHyodUdOBOTj~DW^|i5?SU2= zpFqNr2Bz~BVASKR*!3e@oF{2wqISM&GNOl{TZ*DaE-og9`z#Hc;!8$QIxi95U2SfY zCE!VSJxK>^I>xmT8tJGu9Oi@RLZtYlhZ?cdli-R!Do~Sw9ubyzN#OcQ7=Hn6UQTVo zhowISKz?l!L3%#65Y%0JbInnaxWFbT_Foml%xs{RkY0Lj6y$66$ZQ|1zRqR;pPy>Gj0!`KVL1mY0?wW}UQ~Im` zYVC2bWuai6A2FXdTD06vscqt^z{UWI8;Zly@Nd!m91v7DloeEuK$@M6GJ=E9gF+SK z)QG1(SRbGf-1WuAvzLJca*zgl_}S=fQ(@5PB?K=vJa(hk*>yaMPF#bF>xY~ryR1b? z;G+k);6wShOnjCrtucfbRdflw=_l0Z3@t-4SITZ=yKteC?cXM>QJnqU9pRrfAAFzH z&tMWYZKY%{aAUmWyDjRRok2c7!^~{%O@=wnL$NF)A{`yV6M66np3iHO8m+ym8^RX% zZSrR8#rm)PU^Xuz1ZqVsE%9q8y)`)D+qmmaN2wLsASgQv z&}%tK{i%HOC*U@Q2#dZfV^JGStwz%7gbDA7YBFGwAQF<$)nBZ0>96r<;%UWC1`_V~ z_xH!%<`s~cxIij^qHU0Hd?XTslx_}D+VBoSkd+PB25YGes$mFudXKbf-)StfVX(bCaDOK3TIn62^CQ1|r-g?ikT z+-r1Biudko6NVpnzUuw*}#5&%;XT>(4%ujOK{3|6jn>HN2FGD##_wqKs zDj0$%m-_;#?B${F_OZ2wa@0AXZywvN$FVrSy<0DHiqh}uE=MXoVk0#0@XL7BE)zyS zSHSQ>n3>>Y>!LW4a-`C_O44EPvs?aF0C0j|d;oJn$|vpCg$9jagDHMIYd$?fj$>H%^?ejB zEBqGQX7$-e@RmU2hc{a{J9Tn283cS>wy5@2hKD=V>llN+evMjNAjHgyYrQJ5M(=1V zW!_quoM}~q6+O!bl9{>rM!A7jjHB7xM4+ZtceKq4C-KjWevgRY4>KnlmU3RS$&+$C zc;YzQCWNo8cNv#=ZFWCe614(7G%1sS`q9Vl#{)!0hf{721fcf(-mP_>4*WIUq>xH; z|FA{+j2F$QbbylRkj&Ysle;@GnqAJF+XyYz*u)$DSy^XL1V8LDI^S8JA@Rjz6)3Lv zaNaUdcs651kn9iR6s@Wy$-gblcc)NP=&-Mc$-6FVE?4ADZ!gtDSyTw_SX9E=9@1;b ztL&iCBp6Z7^vx5rOe`1sQ}4Eu)x{Xv9`4}S>}v1A0F28b@2lpv;wfA4vL6Y98w5*w zL@;B1P<$Q>g45BJ1CWWFX93cpAjR76EQ(@mzvec~BR!}d@BE1+)YC8Lj1Dq&PPn$F z0lH%EF+w5w-Zp)y=mly8%&lw7{3GOPVZ{Exj$Vyv3>Pv35Y-&-Rzu=Tj+Fzsjg<=+C zVvs?#(T)vY6OGmIuTTn0Zx2Kafuwox+Yip!nqV2CeqijKc9u53Lz=5y>>){6*Q6km zNT{=~>$k0~3&G@K7ksS9BOTX9T7{bNIU|$IG;D}IOIRM9hCmd7JLwebbz?SX0) zO-?~Ki7;a{SKEXbIf0?H8=!qZy5_m}}1b+S1M-FkPl;Var(vX9tUvgQil<>rKVsRyt$ff9X{_#8GNUj<=xtN zxzUoqvXz*Rn3!$Db!bgYi}d5B$Ew|RlftWfTR+)LFk;qH_jb<5mcJ8T6Ly&VX0}E3 z(9d&OC`3{1ie-O^?~{7tGX5pU{@>gx-T3N=db}QeUQ>oM^_@UG`^EXaFJXoQRnCi} zugcp0x{Ay%7u!1cSx3hlzDzOY!eRlbgmV)3p#LPr9Gep719?;hJR<-ZgI0myxkGbzApX*%sh1=^H zjtu(VvimVEX(S|#wUbQW%FdQh(~*B|&<~rzoX7jYdFfaC^82%A&k98qH$vUmhA4Vl zu_J53xrZ;cVAP8G^wVx|K%z`6*z;|GjzLeJI@OM>|Bfx~-A5*|8lHgE;WwxE>-^ZC z8!+g9BasI7Nc|5pFYQyoy|K;Wp3f)qzTADiZDy=?MkG&mI_2Q-Thpd47LL_&i{$&= z3F`7{Qk-5a7QG~im4f|)`h><`yv9M#J4R#nYGhKuSZ}(#=-fZVj z3$zg%+LE(2!UvYJ+tUXRISmfKhA#=l3{o!adcxDh;@dTif@FV4cAtO#enh+*+C+`o zb{=yO(-4h@DT{K-r7sm&oU0v$L_a8WlZ$ILRyK6+9?q~^&@%0Ny zx}C5069(%h4;5>6iLG}lQU@O9>6K5gRAgU3B-fF(9gMG$GKdE|vMFL!hHTH)gFQV| z)bG_1dSs<1m_=Q3B)-NZO=w;_eCVD3J=%nktgz-LFRwy8wPbP-o0P!z=TuTbJ+ws7 zi&sB^1QN#+Kfk3>d!OP}LxiGxTS$A@^3?7Vipex~4GU^o>gq)!*c1a~lB6^4^#o@% zKG-vpMz%%zfrOjwP*TK&UZX1QH)Fv8!mFG_iwDwMN_zoUjLIWFc)ly{h4nDqb*OMt zs}9!OVt3y}jyX@+Zrvwah+a>Qh>YaH^kI%V^*p2QJC8Y&RU`*zzHuvrZb@?mLNlT}{95 z2C+5X)nJ0q`4x3`x(O~%6BYMw{vn7#X$bR(B4|`$_k%^a zntIP#RVBz_L2)tfgJ=-v>NTMqZ>Bkrc7<9uJ5nL@(w@Yq^TTX>e=F7*(!+uXtan~{ z)$6-cPgl+CJ`lOrk=1a(qy@Sl;{qO}jsTPkeEle`621)JuYTDlP&hKyCsJbucos&= z=(3tCl6B&Y2=Qn;WdlWl2+s}Yu$%Xnw&t!&h!<7Y<5g~&WVI$e`!L6EhR~vH+~j^k zaI^nnx%O$6Y?K+LAE9V11tB?fy%)7rOB!{)sT25u&g6mJ)hRfx{eq)NmK>EX?d#g0 z1^h2Joh`V_w88cum(Hx@&?D(hK3``|^;LdRXSL9G>}%UbSJG@IvR!J1ToCuUF$xyH0k^pOYe%o zKZJ*m=%@~64%}By*jf)szQlOoIZp9zG@??Dp57A-@5wMF~= zV}VCCHYd&;PZ}AsPAa6lx08MBzE{0x2nRIkR_I(EDQBDz;#xvhQqr64yed=4SB(8X zfvB%y6+1Yr80m*H-V4uo{0^~tHM^%o{@%O}H|iYP04^CyA9Usj`A;X@2ybVyT4M?` z>qw9c8Lib-mIks*r>|V$KX^{aKP=WQv*EKyhT7Yw8d`mgoNQZ;{^ob)#f73*RpYK( zV!V{EJufz2aoI?ETeY_rCJ4Dcat4*DqpA%H!n;jW`&v#XanrlaE(jsfes%^j4EFa4 z+qk+;7o4O+sKe*zi`l$Q0j%N;ES+-9#ipOg%wvVk^6Izgc%Qnw4ZMHS($q9E2Mat| zs+H^eBNdNuEZv0*lR~3Xh=(FR%#gPgm9WK7xe8Uivgpa(aZmTe2M_d7E5n}hQOmb4 zgSbBQko`j057{QJy2q;DJC`qSS1wG{9By{yCQKUacCBR-TWapsLo@mwKi6$Wm`dNi zt$(*7+${PfADzmb2I~9Q6(@rZPmIyLI)Rz*#+;PoFv+T&tpBMUZe5R;8pVC|{W>=? z@qPLnioL=~hZ2ElJUf5%m@~a;Nf; zYPk70y3bTNNJ6p<8}7vHIK!u`RyEHE$EikUnn6Wp&7-O&Y8}O0!*{>w9OzW}!Fzvtm}(SsX8f zRKial`|(za$)I>C?n=$__mK5{3X~9exA=gcbm#0U!CCMChuLc~> z4hVL&72z2kz)w80E!kNa;k|R-I_Y7$z=@{pU!E_fB-C?~Y8C^8bfcfl_;Zlh8N*xU z%2)ZwmSeB(Qg{uJ8Dyy4zB6Xv48!cO-VzdOuQ?J%GW6n@zgZC*AI)QZ;_5i)pdy;Z zw`66Fwwdp=xvh;M@%?JAt1`?2MzcN}HXlv~vI-vhHk^w$Wa>+gzL=PxVdv@nYkTP3 z6%Yz%^YB4-P4tbor&xW&WGpr>7qgylp^!(a7kl2s>G1Kk0+2%z%K;g`eC5)OA;Bfl zaF?IoWY?>m6NtBj`8tiqPNp)FRc*+0+)6a8+-5ZXBBdqTucQ_)X)q+=@jLv<=)g0= z81kZHX9Z=$D;`U+)YJ&ujB? zb0hx>akI5^LuaU8f4n+W?)fxIz+kq*YSEEA8}NE&MFYMBUKMKObYW{rA#*2epGIvE z=euM1+*U1Sn9`RkX;>wHZA4Q{89(@tFS$~B7L{+n=jelVUY*z_TM+$#-D3_TUW5mg zfVn%Rz2vov>nwIxMY|M8Ec~J^B%!Tux)ri7o6RO2e6alzjN(O#%WR>VNlrd*vm;vb zGDf86-U4L6#;&&l>Q+5oqi~KU1>S-Udck@#e+H_t84%P*V0#5wN}s72D*kxt?M#gY z=69FFnlyuN)||q?6Osd$s(Q;Z>YP}ul@_&7e`)fL>#i?D0E*8sfMxd-$|MoDpKsZ^ z|L#jyLb>=CXGYC3Wa&ams1rFPx1~u;=0j~w1frS7x0{tkQ**ah>V>C#UiP@>uG2t z>}Hqovl_KY@=eKpvC~zQi#L~42ZVcJjP+D*?+pSc+Q%A+d^5MH;!%q+P4gvI zYB2|UoXDFjDBKbv>#HcUdcDqtQ?B&lA9H>6QodxV>(W)X1TptCWVIlSjg^j%HY-ob zbxkqBY~1gCi@RTt3&ld!_n6e{$w?`b(9|Eq;*N@?n0_|*0WkMV8TPcMsHhQIa=#>G zo?lnz4$rUgS_7F47PRRThr$VpivAy@=eXfQ%nal8WACvYFB|97yR6<^?v(yP;Ytku zYJdi@9xrNzhRaEh8ObZOy8O~c+;yx}&|`lMnORuFMmld z{Ms2H#l31Rt5IbB?AJ0+38Q}Nb9IZIION@Hr*JKjLKk+=EPDtfK<3XQVrL8=yS8_9 zM5+t6%@>((ToP?C*HDbe393Dr)VhHJI*-0)Ab><_Me2cz~ntZc#?*68oPyC-l} zr?G6E)vWuw7VWPf`v}^GQnKxzxHXU{j^=n9hJD8lWBb*0xt~puj&Ic@F*9Iyr1z`t}-RdCx3EK6Ywa+t2yd}I`T$*IF zite(WL^n9icK!Crk7z0|AFUpmVr|xq*Igi+bbtjdJd^wVH8b_nbRb%m-Sq)jRp{RI zx%H#@8eU7RFN>%-yuwn55P;;bs%gM41Z*2 z97UdX$X}B}oL`4OmAQ)0Nu9hLcA~(uFh;y4O+WtvqSGm0%}p||?TM(`bH_()>ux_Y z&e1gJ!eC`h&@-7)Roe^vrApg}WUoG(KLi2c{&}sZHhEjtpNYQ~V+cYvVma$97Dgct zLX$GSgG5#K@JMAa&49Ad2IXa_Xkf_-k@fhU;)06zD7@==FcM`dxsZ`@^^4g3bDHv= zdWRRRCZ`1<3OoMR7^mn%U&@vV_rus_F0M%Qjpm$*`wu_SA~eGFKCeDb$ygpeK%~6b zKoWVK`r5Ia&Bs-sgnjvTo*F=OGP> z*5s2qEpbHwvlE|UdN)aR(mwrjt5;nO!{6FV{r0u-Okt}IjD%h3wm*Q%FZu)4Eqn>XL69~G_G`#_a6}jVY zwYW*2&1KKLOEN!ChLN1n1F5ll6KYvpDO_Szkhq>+eoMd3iWzYzEfx~i*}ixA(O>~7 zM{1pAhND8Kf1Tyq%O>dC=?DA%O@1_Wq3bL?Jv}@3b`)Ew&R82!TVURM`pukM2t2@k zUO6pdRQSd>eDh}ACrx$)r#gs~W;SD|tFEOU9mAq8x&sW#iE2@ObpP^+AluQB_UgLk zwT}cju6y;n4c?Th^)w^X61AMQ>@o~=oI`mAe3>)bJ9;JN*Pa<9Xn+%o?nO|Bo0oHn zORLL4wZnIxi{>zoEzJynWZVilN<}&UJf!aZfE1*TG{uk)M3NmQEijy1)>2avJm)VW zC@UzqAkMfZK{Z4+)qR~)aWge8Gf&TPl{Mk%`h2sdQ#3Z&uGhQXz7V0fo!juzvY|Fb z*B47R`cV6EhVyCmP;{&9=jYi$1{+J7@}!!7P|Y>PYJdO=kIT1p%gxT`(`H<@V3cL-zqmVLid4Fj3k zL9M#AYArL_2YZ?RB1p#HBzM1_y2+38XA=9Z2QA2SjCH!(mg1`miYbdm6E3Z0%!a%8 z)9}P5cdf6Hxh8f@7n4S))XvCkt!(u75HOhI^7fnX=+!n=g zxWgW#(SnM{GKWiWP~98F$)V8Ao@CcxQ;&5`F^2N_Ja@`Iq%Eq-*fOGKO?# z;DPhP@T2||+tF$*o;@v~Vs>7__UWa)75sr^OV?V>Dy3zqapKIcMGm?N@`INes-1-@ zPwpm~e-Yu2KkGrA?o(~?Zn;O_kiTcJ}BrP3hkT3_F= zDN}?>q`o_}OM_V3@^Y#G^Vb*&9s~3IgVpt9RFAzqPwt4H0iMRxnjr zB`)9C-j&%?BxAqNye)YKD|A=*hxB84dF_ltLZ@J`CaB#>EUpB5x~Q;9c|4fFpF(Ze zw)vjF%cvHmAh*w}pN+O1PH9n#);EULnytwQ)AMWU>#v+av9|Jzx%<2SRW0BlSg1B2 z=^*9aP!Q+tEySKdG2DLfip`mYMm|E^yf?AYUU9%zl|tz}z|{pTj0PqNfe`4AemFNlt*C@ash^on+Zim{)D zhDP_H(aE@J@v;Z&Pva#s0q+#b_%^z!_Kn0!Y;F3Z4tR>4=e2dnPIDu49d~BtYQa35 z-w6yLn>&2wgbiV;5zr+VFT4&uV?%{nlXc-q)PLwGpU&wnwUj37cBIdGrZXFq6bY~! zgJY!a`#OUZGtk@?mvVMU1WDGFZEr>eT)`dZsHuh6d6+#0H4Un-YQJV23$S?cX*30j z6Z!FE%hB+?A1cnkqH}j}<(V;<7&VL*B(>(?Yrt>hJQQ(R(MO&$x4dp4pm-X3yzI z&F<2;+9*#XrW&4|OX$h)oVn~^eXWL9bxMg@wm-3^8+KyEq#4p8w+lX z{G_b&D1&YKa^=TosM@w_Jjz$4rHe#nXTzJITkJ1Ty2e*=)V<^M;|C#r-eAD&OKNw>K+U5$v)a{qOwRxPIjKecn4=($m#(>9e8kkip@}0GHlJfD%i&XZf=Er_9SWpsYu9c#jdY)MDR&RE)EwayBWEjzUgih zICqJ@~V^$B8c4n`n^DC z$c^ysL1D}U%R0;R=~60`TFd2^Ni%90R^^=<`fr(*DTwFaG#JMe6z=kn2 znVy$JUu$X-SfaTe&u5x4{U9Mc?mEqg%hXY_TJ1C5r-(?$kkhAbBAzpE&8W$UTcst@ zNV7T&J#qCXw<5@4(kIEF_0~%zi}PKwTcQ}JmGFi=PvRj3z;e8T$FIjFSt`^{NsO+8 zV5RGW@h`r|C8=zhRs>ZkXbrb4@vY3ADQPlmd2l&xm|5<`t)1$>X8A~PGVh_8@Tpre zw%bdkOM25Dr@3%eiuqC>xx5}7613;3U zLWOCt;}sOuVmM}3WYWmkmzD+xVn38FduUr@oJBBbg!EyT3Dh29g-HH&-+$wF;+pU? zYUOkXP#j2oo8SdJBR?WMdhT+s4hsa<#&WM&`jq<5IzHx$Q2P?aVxX|Jo0l zXfgLlm-Y1@5)1n?!JSDIL(9S`IVojnO{#p#Vtl398j=+(bjiU<<~EHK_Jq2 z3i0+Xg`TgVKH(p`OHzr7p?gDCdPsG3wo+DnwYdDe{a5{{<{d1ESd>?fH|_qO zS!RvNa>9*fexhRjp3f<%Uq}6Fr9>dI;qXNd=C0RoMV+8IvLCoRBel1aGRgZws97yK zz*)~%&-jex&>#28HIPa4F8X05lptC5|#-X1yy1{cwwEqf?Q9?qg#Leyt7#? zCMnm5_dhI<=F)SA9K2|5!y6$o_YXt6(U`8Y+RIPVvN=Oeftqu#ZvOwH>no$8?Ao>m zP!Nz3kq$vXMFgZ_2oWg>DM3l4ySp2uLApBymF`lIZiWsKhOQZg7~pjt35FG@BV) zFfM)8>h3l6*I3wu%06D`y)}=pdmJ&VxFDKx{)3Q2CKne?J;(j0{aZ@3OZ zBu_HUrV1_Ug(H(<&^$1pRV(}0Skhw?7Tw}B6{?Kc64Q0S?()g_+ADnPU?5P*x!R+@ z`fYd7y;M`qYu$_^PKhn@dY9;08{*^UOmktqs4sHdjC@KCd`?fsI3ABH{o^dXU9QhFzC>v0vKJ@&pcPnB<&!k& zoP?auC41;9O%}Z|)ZK-J$m|s(S)nj1(l`Sxud%^xt;;grDLhAti^9v-_~S!o}8?;R|V>+yX~Sts5d7|AHx$`0%~JEC5t5_Le)L|O9H4O-6w zBIHC}v0HO(r-QtAWhA9OrdOi1NdGo*cVs#z>(u6ue7L zRNp&y@`F1~b6^s~kdHF;A!-vOvZeaOdX z#`)8Ce11LGx}Pt&8T*k~<=!$Edy5y(jm?KLE&{s4ZEtNXa3o$WRr zY!dmn_i5vB?(N4g8SB*;_3<6GOJ@q9G?Jj4oDfq=J%LDfO{7#P-OlF)n zC*o-{(9d#uM(E0t9}q9ou14OKrXQ`;ps!31zqBqpuskotf%>4+eM;PqT4Q^h8tB_v z9}Tm&7xm8x|@@{gGeK2tT#vF!X@JT{@XaF)7qxK0gO zim~(0(PiD?+&#TIn_OHKva~^*xzWVUz3%F%+^c)|)}Rdb(lDXj%fryH>g?Eq-mR&y zb|P;B21ZIAqXMvrR9n$rry`g$Hl7mxO;MJdaV$!4cOI{jT6=R?IRqbAX0f(vj@A4N zn|tgI^lgrxTy{9*olDi@bvQJXPmx1GL+QXTgvq9gCItu;j9E_#+7U_N)OVN(vqT(? z0s*{s*h^Qmi{F>X*0@3?+3AKAE#m1EXwJJ;X3op4SyPLWZ1^ zN$aJwO7j*>713)E8o%I{caL5s2G+=4mxWg@#_NXV7qtC;@JMs~ zva7)go)+gNKJGR<4IfAD96V;eQvBlUK)HPlq+%@_KY=nzC?xZ}sJ2>^oj@P5+4W6Y zjR(rcO#lY%K+0p3C^zFXIRZ){ZH6iGBdT5qWscnI)F&cLxe~3xJKA-&$^;ZVk`p$= znv?Aeo~1T3a_)R{NVE6)_%Rz1KtVsf(UG6dnd7iKrwDS3J5Hlao81au6i|nPYVh_a zKxNAznO4}vZ9GcI?l5n2A-7i2fk9}s50E@UB6TK$BT`qh2Oj4so?nKzsUT>q=s4*{ zj7PY>#kyh6c3UKI28d!hjU2%l&(yF=KE@UFT2MUfgj+0Q+!;A=PY*9mUh|;vM8Dtl zbfYCP1q?AL#=~BFg~(5zltJ+OvlEQ|XcO)n>G*Z6xnaa6+w+EbNlxoolP=ae%F6J7goSZ@L zDjK3X50dx{62yOeTVYA@iYRZFR<8K;wEL%QguNFb`Y;)gp2f*2nf6`6Dzr*LCV;FX zZ*yvT(fxGS!qUTUKu~ZMl$^$usE;WgA~5%ZQLwqG!BL|=^*0HuX)lELrix1~@#FRx zZr`?9_&zaL2!J!Un|h&jnA+Wv!btjcn_=%+m+i7hIuYG$6w}!=h=<{Du?-S8RPCo1 z9?OO0R9(*#?ib1xvrb}yf(|;rwsj|DGXofEOFHzv~rRH+V|fmuldd zZ}jXDHYsd4!Q*(Ng3oH@{JRgXM?7#eXCK>Lkm83K~QiaPqtAxEU6{ zy7N^#LWe+0@}AIhM}3WF<#@w2V)_$Z5f-}MexK@TY@liL9{DzZAcZmaXo=Cai6d%zE`0Oo?OXo%wU-osll0R?0*Aby*R&4j2?U7yh_CCv8-F^2PZ399@ zYg-QcOIn(0hT1$fa&j`Ca&((91fY4KtGDk(N(Gu_Ze?kikjP#!!2a&bX6^U-*f4KH z3voGP8&dO9glEw63=5dtkA{|#iilOQ=zL!T^{O9h2 zokA4h9U_D{bB}t(R{ywmAa1#=QB_%0iTPxZnls+!NsNy#W?b=JsTuKRvuHfiWf2w* zX-fmTn>8vQv~9RpK3b%Y8M+l(2#FJ9tn9!{wKWc)z80?PT`F9%M6lKF-9t`A^v5Bj z$yJH)bp*M(=Rg1!6sF@!SU~$;c6RWZpM#r&)z{Of!Iil3;ES0R9)E}icct0~`%fdw zBCg8;&$4^+&6P}dsg3f2iSjs=^>=z4odo!P6pp;fzw4LyYR)4F+|9vVqyz@(%@3AfJ%v^MQMbL5(1buC7sPtv|q^Q(D& zi0$nkS+r8-&;GNh(0iPx)UjGX2#*#2KpBpN`x=?xz)C-5jzh zqUN{J@P$)lXd8>X0_q z0V^%)m5ho}mn@=x{h^-YA3^i@z1GjiQuXU=FLVzc{re6dK5mnkM}Wk*MC^Qn{m3Xl zN`*ZAU$^^4#}^I(ZF)clFivi)2ITbr+@2DS`s-Euoy^RI#Zz@R;$xDTkG>DPP28}3 zH6WH-$;PLckoTE~j65^%Fgy;PC>@nwX~-_}pC1G}?xl!3QZv7xpcr(V7AGZXIvp>F z(B1vVU+9|MyaO6^!3rz5(g^^mwiBN^W*{!-)+Q{Gr+O7NVn(@m)uXy1-Bb!Dz^Xf1 zrhQV9QIYK_ek{nWz+Cudtg8V`;oh{@_Rk9%8esbzw+z}0q)bjz8HEe~^H~4=#{INJ z!POcE4loPQ-$9%)76?cEa_FSW05Lmp2JDT=t>_En26yfYcgyq8V>Mmx7&*meM&)(L zh74Sn)?mIa&@kSc_mvJ?{ZrN-fO2|s5^LrkPwziZ^dE0H^eXV69BgdBO)YMNP;4~@ zesFLAAhQw529?^zoS_4(`FgZ?&Tz$IQ*N*I9G~|et&%*vuETtoO;5vA*N(6h-1&+; zcg5qwMiS~yYN33uWkb3voR<73@xUimEXS~oB`wX zM(BsVZ{#gU$&i&sG52#dZ-$Fb^$AV#S=U2kvL&K^{?9duNVYpbIr2WJ zMZiNrPomHB^c9;{w>@OZ?qTHTf1TKWKU(myMC)Ix0xXsu1yp**xy@nnZ&nYu^V0Hm zhbekj??1mtI<(L7+YknYdQfOc=aU$){r`F^cggf8oLs%pD60;KvxpElxGZHlx528N zzyH&luH_gF`~dbr04^|T#u#V<<1ZVl0h_ESFz^OQoc?<4czHlxQt0(HfyoY`$o?{e z$;zWH&k6@jESCr6y#Kn(fBkQfcnMGf070=gEih1S4CWa?B(DTCNXyO=lK#`A{QE)t z^CH!J)wTA|PZ4lkaJpHQZVhd~ut6~h&^H!a94dMN33sJXblFYQH4qOEFZS2}v2yQT zXD!v!X5mm}=ruY=?5CA;??buSYB8ZOWw>83Tz@7MDm{Rhn$j$=`n!vsA-*=*UaoYz zKua}dS&s84udvg+iyu@k5Ju1+Y4a3MPu`JDwH?Fbr?$m>hlohVJ5X+F*pqPyXm@u33| z8~cpNb@$7)wu9nQ(`eOBa7+#DKFy=qWS|_9R$!L$zh2%EOZB+Jpn4kc!eFAO^8M!U zfqu2)Poaq#9UOn1hROM|x7^jP2Or4pP(9s;@_}jExeIPtK)W`rQ)ZTqhUSUcEG4jx ziho&mg;orJTp@o6kVA>0WJz7U??a!bH@d!EI*o~gPhjz#8#A}?mp2(#OILPWmDydHko)Ye#mutB0vKgUsyOq2;9E#oONQwtDMEX+T@f%`S z%8#SFga6k(fsfvmW%bFeHPS!Lh4V9Nq^CNl9&Cnc6HsE%zK6(29xT+kX|?^Sj(w)C zrk!($z*jeZQzNG+tIqW;5ey`KZ$6MxU1HE3Spu|%N`Sb*jTXm<2*fS8Dkx@6l)bG~ z)6`5Fry>dqRb~z7RM-AZ79XdTg%>7wBLgd~Q}JjJ3u7n~>Q(TR65V#q=J@M=aHPdr z=fDTU3GNneZ)?XpGwb|heNYnm>!H(qWX_DwQuNvTU2R_dCGm;u7dR6&R>GZDY;_9M zthKmxt)~U32({lFm=Ao8CNxTn`rcHdM9f!CIu%=6IchCnigPVq9uti&HoQi?cR@qX zQ>Tql=8rKV#*zzug}LOZC|6R2f(T zGk}0*D7)dL?)gQH?C!nNxjE1H3_EoYxX2(+^S|1X*^8>B6A&sF1`L*vvMPl|(TWuV z#_;mt$jaug7=z9I<=g^5fngMN-TxYLO+a72Kogzqc%cc$8_1t(=e?^oS1(;^QqGkE z^+M?qh18nynPUOh{j7}_A%yBZfXj>8WRi|d5_Lxwod8v-0wCJj=H*j zdDcn_jf^8e#)gXyashhYHpe%(KM;&YWePBJ>S$p(?J!x_S>tRgew|O;VQ8socI}BO zv1)X##vF|Kem)zMI}T2P#gVY){aVs}lo8wQ+F88rYTgI6R9|VN~hS!O1#Jua@?k{wFQsEv>MQ1-!1yE&D8|#_FCdB&xA6- zl>fW|LlOvHdjp*SF5lB2eDWJ9xm|9ChIzPy02ovUUPMktP8I(2XK~)Re7xT4C-4V@ z3P78o7X`S8h2aE>`!tJw*CE5-9Up%0s* zdoa0Xhjd_;>0{tUt=Br$e`h}Z_vh%mbOFA}B*Mq&m5-_yK7+EA(q!dg3*`(OaC8^1 zCux3^3c(ofWMcdY*^)UZC-_zVym3DC!?7`Wcttsf+E@H_jYZ#6i^(!|od(A*BmxzB ztu0hy-u^>%A!KBr!S}Q8k;GJ=d1~SR_EsAs5v@TnMpcqIj)CpPL8Zm zrNP(MsNmXuaV zGcsTJWu7&Rqz;Hx5M(VjIdwCgX&2fIaXeJ1uRm3}g07esG{k1Smq7l=GmMW;dcaBB zLPO5+ns#IZvh~K|7ab)TJGSMq5y9^ZF8zzJXIYnL1k~z)eOE#-#Hx@|=0BL`m5nBd zwhEcPq!?LVxJ^<1CA)3{@&_`^DK5y^4}^biCKcxA7bB65PDh*S@#*R8Bq06)>PL^+ zMYMFn<)2*+@D4s77k0_F>=yjwK(^oXSQ&oVfhY{-g?1k^0imA4zqrlwA&@W8>|e4M75P&W%S)ECN_$a=%Ze{M-`* z4rrw%d3logCTk_HlzTw3nqfznki0)f=Hy+``)D?Osb1AX!UI3*VHDJ4;BOyD17SOY zJf>m((b*g>*iQ|4c0+?PGRV^8w|63}LpDO?YiICa)fm&w(|C()mB9#(&e@v1*E=)S z2yVW4^9cB1&Q-S3%_TWXaOZjXZFGz;}PyfT=3r>3I_5_5M zx8Tds5XP`FmX35bR?e#j?#_|oj#dO?)x2u8)Y0?itW&#DU&JqsT+t}n5Pnw&uKd#%hB?BYJJ=MpR57cN)tNW?kunYh1Fv4aQn2+#tSQ9Ds}?bS zs317m@Pa0CSvgYWqI_de~Sq|?DCXUHktv~YZfDytK+PL*TfOGr+Z=<4g3 zK6>r7N-G_-l{cO74Ua!qNP0L~h8$IIh)ND+NbQE2Q&()>*!EbtXLqo?MFJrP6;Ad+(ZS-m|?O12;*Mn=N zN)mqVAem}m>kS{IjgQkS%lXZ%_C_(rZegBh@CEF&Xh@l0(BfuB<8|TY{|FNLwsxKW1ld9^6h8 zc|1pc>li0o7z1p_FIn21_N@BWA#3{}+fuh~=Ou>@vGWbqYjwG4W|Yszf4?9na;3Xv zyXy}h5@7_|C%8j@wM-i@fwMV{GiPD(+5)>;EJrKai1IJ@8)`(J%~xTm@h>;rEJJ^% zZ5Ugyy1BFJ@1u^a!%|PbEG&7(K`FB75;B1Z>T7fHGE8)l{1B+RI6KWkHQ$ZYW9roJA-Y9yW z&9kE4yW>>I%%>&u=ULJT=F)|>GMsc6;V@<$K^Zg|kBj~|smzv1;rh`+`P$Suf_*ULC$%%Eq*lcd)(ZwQu4rBP#pQaU>UOFEVIkjG51KMkv651 zhz|O-4RHr#wbJPtfpEW2p~PPZ_$u!Z#t?|a{;%c7$O0`#VD&o?B@jo8$1bHnQjRX( zx|$<&mqUhC^LR*r?-KK{1k=Gnt)7&#_5~2Z1QM)EG{f4BSw-?cS&WnS&r#`UK382~ zWw;Wvl}8?+F(p*(a9+Yf!>!|&`+3xt2bg&XNATS=)n1yr)!Xb z9u%M&`=64<-yY2^PAHM&jsqL`S@@-7mc^`U{j5cfu%Dw(?wAGUoSBjaS)fP23Ru-O zRLa^U&yLW`MpA*R)kHmc*9@)Kn>RDWPqt$3?r$|0Z5=v&XSpx%u|0`Ra&lzHS zHNQ)cgZ1Ve({S|;MCXMgn|LkkW$i4vOvPh)R>R|}v2R6q4~|(08f$-nleBwd;LG*8 z1~~Y^VdbTLJ%W!bvih+ywuG+h6a2Q<&2PUDz*5TeqE1d+|J$r@Y*G$+xt+dzjIz&Z zaDAi_lt!ew9P{llGw+M}I=605Vz2mcoP+H&PqNIU9JyX=s>Oo|G@&a>DxwY<{$%Ai zRGD0&Q~iTV$#}EJNne~Ctknsf(U6i{nqxpZ?q)E*KIIx8pU%n)H%IXQ%nA`jod*3> zzkHGw#hlNt_RhTk4?pS+oFlOG&u2%Fbjf3@Kh`4emY<9WeUnYj6mF1EfQMJn<8ir* zh3yopI;Jj{Qc9p!bKZ<@EebCcd<A7PHO}6VV^zMD zS`28})&9j?U$FY;UQy#Q{hQ(|ApRS_mz1ipu92x~u5B>9V7CLMy~}vfMwUy2nYJW? z4K_F+pD(*HU^J9-?c5mtvN4y=IFimd$`-JpUd?f$=rOZsYG>o@|3|THE+b)HD-Xm- z&bF89BTVX!JHY^izo=7$fuP|s&*GRoZOfVL9^L)xV*me$wRPA5T2xBgh$;om3Z>4p z?nS{7?@E+fO>#D`PJC9k#TtS#4JShewUU|2CP_2=QFH;(zE7!6zGL|EitF_ z@waXQKJnhz!W?0HSsO(&{e3aeK)8ML$HvX;b`@)xBw`M<+jr(k$%>B$#)(KR4E&Y=$RqOiHu ze7CqIvr!X3sw%wJlU-BCYb7R`?dCK6^&r=D(Dl1JsUW8qH$=xdRc(Dy(eh^AuD_uF zjme;H^g%>#_A2*jnyy!H(K1sveigDBI=I-3CMwNl>MCIwm@Od^Ew2#0`d+*_l^>M? zwTf(@xc8QAhm{zs8PdNsX`{{*zV{Jv15Pm*pRnloCLlOmr8L}udCguTo`voNJ?)!q zdL^KDeyUUQJMRyh`FN77Jz)Vj3F=#kc9x1b>8jlDC$GKE2;vZRek1(HJ7U^1*bG+c zgO9{d_IxzU8c-Ar-G&1V3c9Rwb>5_KTqccY-^Z&c9gL`xh7kg7@R!!QF@SDmeza4B zTT*Q&>V0X%J<+17bTnUAY`#?^QUYd`TC@h?K1;3Ledc}q@%t`oCVzs(%=#Wnse+bs z&+#F==>}(Jt!=VrH(BWF<&QWCyjwr%xIL;>o_^(ig+I~1UNPcx(e%Dl{>Cjw zbT)8mi}J3!YG@E23%|?+dN2H;)=`o!m>UZ@V_wgl^vVfseN=U58Fb(B)Qfzdqvoug zp$7I_d&x~!5bQqYT`Qmr`gmie6n`kDCY|@copkBVBo?LgFQR8iHp+dUtK72^Gek{^ zQ|eR9;eD#P0xuW8JAT-{$#3fqPhTaJ#}>_tTMMt4*HH0&DBBie!5M$)x&eTO-wju7 zB$yvCqA=_s(bRnA%!|d!V>aW#3bRJ9`&ur8!br@2?e7`Z6Ny@9y`yYpowS;-2|Li@ zzpIqNr?pCU7uI{3sMDI?6`9GBDQ45I(#raD$%Dzf(&G4Xvze&%E^$x~rieR{N<@mM zK}Hywh^81a%%x~2DEgKF?Ob-YABP=G-Fs}k_^t4qe(U$%1y1B?_=A^fKl}o^)j|}T z4b$&^5h@`q96Sq+ZLZ+6nSS*3cAwALOM_|YHWSYfAD`DsPU~~8cII8`1~IB$y&o~( zA0Dxf)L#@kEsHT9`5tiA;KW*~Q>%wNbMr+Ahzg6qAxzRcH z)K%YsY=v5DxGWc%h>mwK*SbqkO^MfXPL9RiaZ6Bqg8K|J4NwhG@rcpPen?W&ccWRl z>Bvh3lfjI=y6wSv^eX>!m0MxeBQ*M(ovgj6KJ{~xLE)#WI!t5jb+}^#-lgdy&Iiq@ z)bIx_h(vt~^*kxY7gmiK*fiFCeO#y@`8utpllWbm_Wj~uV6 z+g-K5r{Yt2yb#CLwnlvPXO=9zT5?Zo^4yAYn{BqsF6RQptLOOzI;$c$z}{iF$73|K z63-J3Io)Weedy>T-k>VkAf`W0|BFsW17`}xtg+ho2D|kF<9Gbac#l#e>nPy5*Y;Ps zqkjHm=2zF2EAQoKdOGxqY}sw8y*{}{q$3_w7@+s>`~7!fHREFfzgxs*P@*&u&&z-~3@P z|7uy;{4&P~a|H6)XQA$kVmo50PI98~iZl6#->qpd__(<4#PH+xji#T1B^W}wmE~dM zo4FB)Xid&tyxY?*nlB~0dQJ8gTx2G@0poB%Ph|*}|B;(k_?Qo4F;JHK<`iQu-4f-Z z1?o8L#)n=zi^Uh|&&~qHy+pBx_-y9cGl@<@Kk@{^pXSOx2U~izj)+!tQsBy;d0uFy z17n4MQ&>L_oNFQhNwHffQo<%Qt8J5|~5$I{{)5N_QMlD>ezY>H` zUN*g)d3we!IX#rtEWRW-I1vz{dgb9}R_xI!-*WEkA&8G+ox@JyZ; zsM?GJ(eF>tmb}^a?q~f*$iq z(g{{aDc&!yc6*zn{++ft!og*W{95h(rgf>CuDe2F7KDQU0z&9ruJ^#;@-voZVoPv6Jj$bcGnk+_4WjbySme2CRef1qwpmFVum412k{w1j0`; z9=iGN{GKtqIdI~AGls$av$^54dhMFDuc2M%_*!x+LnB$2Hrlx1Rwj}=+&+cMG>TjpF zl2>$4Pi^*7yLfS(HZ94encB>hgls;m>0;yl6pq;&`#1914si%+NpCIRh#$NWf~SA; zN2c>4${ePKfLLNeph zG)+nID@p5-|Ia=ntQ!90Rlt(Vza_Bu%?5?s7l3`;0T!nusOy^~ z`lXm}4RUGOpmj+QJB);^$$kR+bdXJ>LL)}`TYP~?w+&Th^b<6n#h`?snDdTcR=Gf$ zfsnv zAiEEctOKIg>GXAdsRHjhcUFYVn+U@4phJDwFc~= zRb_aQ%jG!K2aC+9oEE^5{s94iR>Sn)QQigWEMm4lrQ8m;XVZ+iZ>8S0tnr>zZh=*J z$Wkm`AHzI?->MP!2jumQC*tO|zJ!wXj2GqD_Zp9Bo-v`mDu_eJQC){ccC?x6l=B%H zuK~cd%dQ;fK6^r*8Dx4=H07EJMpxb9yg>@trz}6(qMk*|jRs2p{2}ab3!S1DaMYf- zD#`au&?VySjG1xitrEoS{;~y$1X+!|D17~3%($v>%awnhqpr63<6g_!qzG!6ZIXoxOm zA<-UT!4ct~xVP@?@uC*JBex#jD)V6$DOGWebkOC%THmrF=A9BK*Ced|p>$T1d6@(4 zN?)LB)smf#kW0k5wKHuJS~V79z#^pB+a`Ih%FIhNY3GOO6}QR2anjO3uATLUbLfhH z!-6JG#eIpB$eU~UCp?k#;&t%)N%0vn^F9;!y^}yVdBjx|jmSvu)d_JNov6)&N6q-O zXWSxhvA)v9VV7NCw~))xL$ntgyoL;D^E7eFx(*+{*jJC6G+y)@>ipx+DhiHI7E`!$ zl(R1`o#GAyKbZL^z0~+cv${pX8P`fuGm1iR;bgug8u>e^f2_vq7;nD`d8UvI3GkBA zf_Xi=>}omYI^$+__3mF^BqoRyrR)AN16Se{<>;PL@)?q?;`jCTw6Vx*o=8IGS|lwt zUj0hHST2`0k8K%V-%D?6%oz8zz;7YnESQa=JR_F>(RR?q$5r1;4(+yxD;%_AH_*mu zN`E1+_VAn}9jOn7am-_aq;$a=h7F2LYMxRm%U$MD*<;+<(@<#PYNt2<^KO~5#o$;s zV2;;0wUIF{VrX;pOXlX7PZ8uO&S?}w<#+0%iin!IVheg+^Zo~?H1mql%MvDT z;#XWFQrw!^Mc1CjNiHL43Do3{JCL@B6J{w$H|8h_<~PL#Jzk zhBTv^D{^DRpim(+VlS9yPpCvBdY^`bA~YDr$@7G92AUwl8U<6XgsyM9(L;7)ZJcu)@M(6DS;@qEcMVopXX!@KL6B~GWg^v)T zwuKqH@9yk3ep7|cRFGwKCOO6=>Y0{@6>T4o7N*8|l*K*e%yKz5j80~x+4$uU;jNl! z^KihoVHQ%q?lWAT28_|rlJMpe;#CqZAa^C#XD#!&F5?&RPP7x26>rx`v!ik7Dw_|V zY9pw_mzIJSfmx~3+w+Ah4$7AhQks2H@5(=)QxYGwCH*1E%{*;TXEn1)63A6u%pKZ; zm%Fqe3#*?kcW6momGDv<2^#ADvitSsHJPim!fYfirCm*r#+j>R_m5YpcIg3~YMb$; zO+=t(!~(5wa^1zx(jd$6z}mRXb3cY%N}TY|UrK#M8x%IIUWz-|ENQ<$03UEtFc-qu z8AS$Y&g0j~I;{0XmxBYmZz)7K6JwqCVk z`0Xx9Ov3=kjlG%X)++K1@^B-JsDrbUkK8xXg#ElETMf_-;VrV#5?afGgBK|s_Ctu{ zn3-yL7C0mxQWtK8hqpt!x_&^m#d6Zep0x2r?|bfl9U)UWF`xzVUO%!qBDVZhTXiRT zB-VYL5@JF1)m8h2Jpznz^TXvTdbGPWWi`8;;>ctB>!IvNJs?-*u)05@YQOVb?{>7V zVUM?O3l(Vs@oG6*LYv%n@IW;1A6eWmh)pvrG{~~h{_B^%NO1|LnXzizzEb>w^fo~F z!!PZGtoUKrdMGetP@s=q;GEkcf{dxl;af4MnaIA(adbl7R|CZC1U|Tq}ssmvo zt%NM9x8!A9P4$_7o*?Wb^n=D%;_-((_e=q|0*GzlxEtgxrq!$?*YOYl!v{WncJ!kP zje98Nf@hJJ5g934#Pfl)x{l=AblzL7kFklN!@C!TInNVzl7Pz3FT=dw&Yu?2YW0(; z(;}2ND5oRQA7yOMjX^tTtmxttdEORIUWZ%4=WX09#SA>>bm^{dAFCxrg%w;+oW~{K zI?n}e(7``AEeTFaM;PZ-k$;7`6wTD5edTR$3Hrz~8j>tmY+2_1X&*``BjD&viXQ!u zt|IoBWnu5=StUXty4husg!Gxq?xd2gG@gYG-0J!Jk9JUQk+Tp`M0&_m;XQyZwir|x%(mR6 zGg5pauIBB;O03^H#{E4{9%qK=EI+G~e4HcULSl_n9vkCFmm!^=75$@Kr+fZ+f2ye3 z9R%2fl~{ta5=2Dl&6bs@VWh~hr0oE{0}S)|M)W*JfV5m|$G!O@%3%p*wn$LdPmKoPRt+YU z2(RV!{X4Aw{FH&~4cUu{9Lv+gV{3Sf9U3 zby>JZo*j)4>XVHu2(5~3o^Mi3I%7RS+0&1ubReesWbZ65F_3;cHded1`+WLdDEgw%u$;qp#SOp{4bpx4GxfXBu{JbClDk2O{k%plNx z-`2Gr{k=s!b#RpRi2meD6@IrY;aj)SOw4sg4{VmFZx@NcG~IsK$l052ZN=B~?ze4^ z{7fV~QS&WlPd`&6b6I3aelzIku1WV`L#`x6Pkei2Bd9do>NZQ)t7JLz{S^uqCMJFg z7KgPG5kkj>S$$tRpmubi9e(>kdpI?xntI1fj8uYWE|~B&k!!vu|2M-_SHhSSWPv}Z?)u8_r)u*Og-+q zpQBnhbJShbU5-%-1YU57p-OVHi8FhUXQ8@MJZ4TcVQ>#fanP$bxBj$??$(YsPwvxH ze>V8QCRKfnhhX2PdeoOiSVgKG0CN4L?iQAP8sAqiZtGJ86Isf=Dt2Fu^ak{gV*Sjv zdw`r47UrY9fvCI)f^&}6GR=@jml5<_Z}u zKTYIW>$FvS=CzQ9UvD~DFO6(q>kyckyO$Gzg+G*u$Me#s(sA0nVz!-uhSBp|dQ2{B z3aL=p{`rTbV=w%$(3nMvzWZ8`nF$c`m5g6X6#b%mn!=rs&??rKdE z|9vBmtKF<4kMcXsGhQ&<^Uby@)~%s{!8DickUS#CMDC)eK;f3#<}zxNd*!&orivAL zAS1oUqm1*GbN?-5L6K8W$$kWe>)GAp|C_4QVV=)6jVl|InPz`>MJUkwju-t5-sqNr z7Ps*rcZCv1AOGIG#)w9FQ5@IErmNb63rx>vLUKCtiYh^{3XM|sVV;3o$eF-(XEfGm zDc*!0wyBSfjw*vL?Q83hRp-~%H`lIBCxEVhd6n?H(#hCfZl6{>W_+eHTEles7Z8Kq z^hjp3oawB_XiHI7^~-FtRW}_5q{zUdhaeZ2?gyKM6&3F)pMyRihOB*>m@Z6*M$}O~ z?YIhzSPRxs(}7U8;CG+r&)b{ubwPooe$@Omu)n8J-T0i#h16bEM^-wuRnC5#Au3i zoB(|xp_AQ#BsOL+owk#2(>M`y!Y^|ixA(83`iiYgt_6$DatwMpN~Rs&leAJ=21eSW zem0A%q_5dyy@1XpO}+Egqrz|P4+F%)Chm>@3yFLb|Cuu~Ikak_#;YRa8-d2fj^E*N zOm3l??8cE0kR3{W`nKWS=mAr{Xs^rNySfOxX;y_~E0pZXteu3?1_4lGq(anatAp{T zOh)|HvUi18__F$MZ1!WJ5)=6le$d^GDQN}OCyt|c(VJwVo!%ud>22#<5jCJB`2s{gNZBR zWbtz-pv8|n=#={Hx=Q`uCS?{#xyJF-`9ARgq(Dc`+U+xiiEIZB7xV2w7M_=P)3NW! zeA&jA0fpQsO8%D&^!lGUI2U0A<`|7U{og?I_9d8c;kF17o~g^_0ua!Bq9cmBVP#Zb zLPZRZLrZu&gdh|XcL;$W#(eY)g|$S(DPNfH@+@PXqnouD1iOGP(rHqamyzUaum=R( zJ{=d2weshBVuYmE5SbQ@B>_GaxgMMPi}_Ys#+ur_850R*o00BN->%c}9>Rt1kFrDs zYd%)3wlIzZGXohpi5LnAc2npMH;8DMnVM|7pR1Og8K-XU0C8FVy^5K?9QHmYzB~>L zwKQ9|^JG;_HcD_8FF8b|YhQGLx-2H%(1>+|qf2^jZkK#QyQWQ?-!SWYK}nr0XH!6@ zW>K@`oB90Iz*iKj8^^4B6>2v9TxDEi^3v{dLzE|aA1cVJBd5W@EXd-OhKCo^(lvW% zF=-V$^fL1!&+n6gZ7`6C!-(=6bwbSmW#MZXY(uar`2Zm=%jkZJ6T*i?F?z5698m>X zjC@ir4?WE2@_s-1Y0TbiBiY#fl0NYZYMK>Dq#mRe=Ih0yq6~iToB{FA`=pe^QQ2$m z49{ZJVC{Bx=4+D*H)c3Ua`psms+OgdTla zdYJVjR#H*o7I89ea5AFexT<8`+UXU0@yl21UYYRYTMebwK`pfn4GSKF(+q_ykqL1x z&R&@ci{TROCx(&)FngnT>fHsj=@y#dh>L!938>gn55gb`ZkXa>p5bAh2A_b!s&-N#k50qiQ>yOyir)R_rKr3KhgHomjXLW1emr^-%MoQG|+12|L-PEm5 z87QL?weKhmwGu|qu#hlfq{D*lhlRS07X-Z}r6skwxMvN^ZcKSii{-xg2O}}K|1>P6 zVMY{lR%=a=g>k8g={JoPyJnNbu8C+bZ;8NH53yqH>I|ub2JSS@jcl`ojp=#|o2UT? z&je#^f1#t)p3C7E(=~o81k>L%#A?rz;Gxz)U?eHU!&nePktaxP&4D~Ob*#11mif8U zbQ@LbC9P#;Iexz;s%D_V%^N)^!nm|AH2M9)xH!v|nH0}k?c?I&=!$~};DMP6jEX!zTLd(X^y%qHK= z{-khp(yl`>vt)9YyP5p?nEq{#9dRD}jfnHB*kxW0naCBCe9US-m_21dgtg!9DElCJ z`3h7ZZ8_5v5HwB`cHtGXP`zg7%Fpb2FDiPH)cisbX)G>g5oe^zGnucv-pRiDB#?S3 zV6(+pKyF^u4;<4ugEz#4k zK((jrI5Rv6UfxcITBk%t7Rjx>&MVa-5hbtY`ehII6{TW$h7jhFa2U_=4*{Rk!+gUz zpR_PHU4bsaE^Wg+_QN#ZA-y=aNC!XqX7Z;xT;4S4eBPW7w6CS_!jlBAF z5ru#4=b%$!9An37su~$6g;II{qdf7D(~slzn&sY|_of*$V{#hoRDIZ}rsY$uF-h-@ z6w@a>uGnzd*_!Ma?sAVWozTdgi|@fF{TUt|e1F$eUNjb!?q2lB-$>uP@!3%OSCSQ4 z6fJyd{hLg2a{TvtC9!Uq%^l%m<9y5tW(4_@KGZBhfuj1#`THwdPvdv*ZIa-eM!Q@W*dAfkXu=crA(L27hM z31jqt0aFJXJz?}1JU8Fp?|D7XfBd!A*nQpSI_L9wpL5J~5UfRPqZIS+#eD9(jSpIZ zrZr^j3M|NoB7AFK_Bd@(hY^p2*XDx1n>7XW{Q+h7o=|0(;$-=;$FDF=DsdSFG{sm& zDs2S%v$~;g(zQ4yqFvFrc)oEBTL5)GGrN!XFzn)4s?kRKiRGspZr+;2Jv&G|ag)Bf zPK?cjB?>#}eH}T1%KF#96-7_LVXaks!uPFCeb;6T2l!4TpM+mVwdE7aov5J3_xmi z@~Pn_EIeT7)F+0MY`^xY8>$_DZP+8J>Dilr*a_Vn8FDTi`^Pp((e01Etq6?PxPgI5 zxKt^k~n z(|Wk^m?vau_WB8wYao`ArD)BvF&t{I_XEwz>68JgKkxd}Y_GZ_;zk4tpu*P;K`ou# zz5NnAQRF>MkW6$AU|5Wb`B{hy!`zF)&94Ti60+|wt|~1k98M`7rX_CQ67pC|I$}t+ z3r?M=ZP>J2F+wXe*ZZf-bc9(#=npSz#{`G;9UU&-qFWyM^~9^I8sd7qjHqpOw`*?m zZ`3|OnO+aHi1E^<@}3kC9~zQr+*iUg;YR=K?!c1 zMHA1cS`nxxQ$zI*LjLmWaBLa>d0z4@;Sy@_58~?xTSNUZ)D(Q+KHv9F6y-wQXMMJ0t?RqFpdQOA{C6z0a}(~Z2=D!^pnp&$ zOTIjoNNY0O6lB1N{+hbs$mqfRjdYwLVh4l8OZ5QtauSe&I)6H?z3)=!DSTeBH~7}} zZ%T&6H<qFP4EA?(#LI0*?281M7!G1-5 z*0#ajc~8ojH3T#yx*n4W*FM;9qvNx1+JncMAX#u%a0OqWoo2qt?sC;w$&=loX!_Cw zyWa-obEaSpBx#8V&vmlo#NuSf7PK^6*X%AX zx26(AIUqIh(e!+7Aw`aCiL*yMd0Kr#*vi%zM87t&(%QAR8QanhytVg@6zU(zegiulM62F;n`I zGmWjT#7z$`_L8WDud#{6+K`o&ZJVnzk*=vE&5c2~XoQ_v9sX#4cNl1T^RHEk3=nxlF7b%A<~JRY)2y2P$7IUd#KPR7#D zrEzWYX&rgdB}xiKe;}ehikcdQ4#yZ1re#F(yELurdp?-eIJals9X;$VN4if9dQW~( zxam*4;bp4$_p<1m#;c*L{fd3heQmLupXUySmSmuE-ViNH0V*fjdc!(DT2V@xC!5cG zPofiPr%pvC6Hp#ZrI5t9c{y|c7jW-_W>#@e6Bwe0$pqUrLjMl6|G2{t3_N`Nd=Gx_*Hb9uN=RGAuv965P0yF(MveisenE_cLi?E(?+^f zO9v0rKlE-u%(ICf&?GDVYqomV#swvth%WzQzqbgIoKjIa@zhvVkbjEJUTGM>O6R#r9h`k((biF{kem&&cXqSYU^Fk%cDP-xe2G< zlx%^($lfF86}0id2he=*l-_`Z_=Yda68ZzbAs%Uq;tD2&pbi!@8M@l~!6ENLre`snCgohCoc-ayS2_bv4LwsnJ z$e$Md4qqt9uwsnjZZ?`9p79VuX0ny|IwKZpfYmat=ohB2t*ODFJxLMhIhM5#x*G0D7vTXiDoc=sVm3xaA2-brF+8`FcO5yS3^+Ly?*csCA$amRv# zx88wUh2V=BxBxMHX`n@d;Kv%jUE5EeK7X#k23lw0Bo5Sp9iTst3%Lzr&%;)xLBtc6 z5l7HdAnO6vq6?qoC(x|+;T~ZUa&2n!-juCY?48zoukHHAR=sFfD=5YEdg(4Gd?KHy zJJ^t)%ALV&T(|597z`&jtm>zkc#}!peEnLy*YW$9J1;{5sZbAUP4VP+;P%jo%mn2A zL-MsrYm2K_4-)-YTGdbo=7kaCT#0}E%E#pslO!6^&yV<&fAYpaVOwOP_eIvRx0xg@ z;LrS@lv~NMGcxNyW;rJmdqX@~xg2fA5_DR^N{nOOzH{=$3=iFROZec(I9 zZ_)DHreAv+^HS`%rU+MS&!m7c#c9^cZmP`p(~lzs%n`k4fZn8^r@p z9=l`-;PtYd-?YA7Yj@sPUR!ec5XverH5QieoONzGA@-76F1DJh(|c^-0guU_9Zzxw z5CL1cw>bdf*Ml*T?qEStTC#g}FJ~?lY>DY@1Yb#0I%kx6*OvXunmzoOc!yT*4jeTu z0r;HKzH3ixxHOVMYFakPR#m`85uu-Mm}>-ho|%Vtgx}bZd(jPWoJ?|tit+gmn#u{m zCnJF@Osm}DliR8jYu-U51?Y#GZyPr_Z+~BEET3PRX%Jk=Q+`D5vz3qPJ5muNr!P`P zZDqf`oFQ~V%~*xf6t7|lB*~276Xv=nJwIrWkJ6AN!)tFCA>L+C5)rnr9ne@GJ!171 zuq0~Q(=IE;M~PD_qtNYV|C1J(LvWMGqYXxS8y?J%H=knL;Wpjoj;D>6BFy!svZeBM z$~`-?jWNS3EeB~sdiKLLwBxMu9=)RU$UCm&8M4{xi4qY+nCOPs#$(z&_3S!#W%>Sw ze<6HGolKkvg5r?~0_64`H?4RG%Q$NpIV5*-@-o}VGE5LIPjfOkIEC#WosoT^pSScg z;OF>4aQKxUeAsTEfb-d05*TQ=z9@O6vv5xStn`Nl&|p^xoli(xz;UdiqH|O$?CQO$7;aV6Zj3|FSzWos8dq)JsZ_N%2#F-v{w%G~Pr5I%m|uPZ z`gG^-@Km!mj(@1EnK_%wJ1U_^V8SYd{U-hXPlC-!uC@(mszdp!hc?x%gGtlFQ-q_JSXPR+mWRnI^{sq=?#_T(YWXD_Vpo(NEX^;N*2E=OLy^u=EeM z5)~*#PCpF(xAU0Xh62ARG|GFzq??NK-IYAK3JXWy7Lbu8kzxrSM(!C_FHY=0Jr6?@ zM;^bSKjdnRC7c$}XM*hI_x2d^IYS9yNc{B}Xr6>MR#OB)&YQ^(ve!!f{p6Mtg!ZN5 zcjBPpVPPVKuD(C8FaB}hT1cStLN#5ztYS8la+^DN!*D!ktr|lgVwZyJ6KNDjB#NOLbA5;KORa__#G_+6OV#zy-;#Vzc&KvT?J4>1 z85L+`$rR~ii?$C~$w!~Ax6o1UeFU#6 zO&fKx&~ZPyFPB717BtkmC%xo!cubcB0Y9`p)BLYM2s?hAD2%w}oPwJcXvDc=X#t_a zBbFjL`iALppsOnnoX{hH@Z7}d@jF)%dcI0E#>;j3ks^Qgb2*p23jJ~~e{Q5-)8mEx zy-67^5rk!Ypu2MB`;T`YeY*G_fM}B&Xz@O>(&%|%+1pg;*(vT9^mS@d7X_wm=Iq0< zcR}yJ+zFwQ>b*=c<3@)Lr0=QG@fsqO^ZXz6`NOc&RgN`DUm@TJQvD(_eha0_@9ItR zec$Hs-IbeY*v!I&nf8Oqh4A1E_|q2~eyRWJZ}=R_yd>1yZkK=}i=SlG2L6{gvKa0H zxA>TtT6RPRtUY0bAdJ(kUphuQ79;>&w!Di5Mc-Pyt2f@UF#0LN@Uz?w2=lSqIba`y zjj!Fz(m^~g^^!Ak?&xHfP(_}6{FYvkWZp{cm?=lBcNd7%A^cNVeWZE3>W~zcFY|+K`KR9=RHv+P z&#ZOgTS`4X&pQBkV}dNP=O$ccATTbbHU|p8_;%%MQ8m#gDM0_u6v(2+tKE~8+U4eJ zBI3$ndk0W2XS|TRZ=_?}7#_-UtczBecD{YUW9ELk$gUCx@yROV z7bh@B-dVrpPBoVg^2zN6*ok@r3oWOI)?NdQNct&^*$>BN9`Tcd?tl52*Ab!3x9F;! z!*YOm;7Bf0FgTpYyu*Ocq67OwX5tx}EbAjc`%DQWr2i^{ zqn@w}aUv;ssQ2fS9s-HZgkZbZzH)n`z(9%gHvJ)MR2(zXq@*d-Rdhy0x{9;Pn(a;A zWel5dtS8G0s^4$>eQ(;ayw_|BSj(Kz5KH$Izr1|{7r~sZ{NRhiD-l=zEq?PsG|BOr z)as@&;3$ykbj0^JMR$L zy!*T1KsuJ0FYIXb)$wn`PLcU7o>ysnwM(yB4mwkKIXaJ~s|&m)Ia~NIJ|pI3*v@?H zUY>iAd{dyF-}F;%t`py6hknu7@~!-3W3MD;*F__(SSCL0ZHEW92yNe$o27eY0Ocgz zvVE_`)BRm0PEb%V2@F;xEM#FNGca)W9rBHY;#n0w1nMsvMKJ) zi%XXRoYfRx=%bp0JM}A}|w%cuDU3km3onN!b@3=S6oT9o9; zQF6#c#{RQLH}qRB5wI`)cyUq^f`N!Ki+2JV;`latyCpu;eAwDw6@7#J6RRJQPYH>``K@`ZyQpXj#@U?uy9KYuf{odOR9@sSShP;%r$TyE3mkNsnqjp-h@RdhBAX!CY#PpPN#xZ#popQpmmD zSz}>!80sarlII)SyH7Q6eUiUb#}74&y}{y(&6zdwL15Z!goVFJ-q)& zO%AlQB4(xQFWS$MR{oC1ye}?KK+sHhX9M9dZ*j_w@CYNKbX$~t8JeuB4CjIV0)^1yEK3)Z=zP78Duuky z@mqC=Srq0%XR0e+_}V?g`_ATp>iI1BRN&` zxq#~@9Rp$ELZs3pEQ}BgNtN$o2z2}w(=t(kkdzcf!a^&yd8-Lz1CbvQbuRq@ye)QN z#HKd60zQJVujr^r!&-rTJv^oCVf)GLG_A|BD;VwPYWpTs(-nN?MPED4txIoCDHGDZ?Y>rQ^eQSG=t#mBsr)X!>S#v&%Ew(FPCp*MeO06GQ zm-tey7ACG#kF4?A{#`b1k%{>jscEv&(WU6Pg;qlL20PZhz!=;O`WM|R<}hkpf}_B} zQNV$u9|@tq7C>$P+56K%lATKo!Qd@=z~Pc`t?@U%U8m-<7DT}JPWintZts7}nW1t< zasjLK&42FoRrU97Ne7Mopwx&8cF)@$$|8@!18%r6i4|1si@GEV5#YNNvwZZ^HXisH zj3T09VDn_5yeM^~JE`8|@41o3&ct*aCv03>(9birW=n;4&CSeT%5S@r#B3AzvxLIn z!S$HoE>==(DC$Kzzg|^lz2mf6iyw8;vq{`w15;y#OLoV(v1rc2zu7f;=imR#bo9(_ zIGW)EAR4b&X)jp`Jt)v1d!$4Y^JveTz6%^+Pt>Y%l9rG;`>V4~YgG_&Yum>0e-HcLcaU=|b*G^XQ3Zdh7)p#nuXTnIh-fw3IEU94 zBPjP-#C6pzm|MXM1uhDDVckd`bJuq2bP~ag>9_e>(QdJ?c#ai-gp@z~q zyOBxCDD!apnfX9$V(xGn{Z>rU15nVKOOnlNMot8{6#k(SLj1bdtlyQfON?;ZbkD z3H3<#6a%5Y*G3j>8@(VFi_ITk=Zz@!IDd2!1oUY0& z8ut`}oN=LS3@pWX4x#rL>{eUgvqukK-(5?9tI>I5d26baE`ezAYYn;T{824`sz$tH zD&M+@nSdk$X6a+^^v%8s7ARt50NueKH*PrZjSu+IS|6C$FU+{L`f6CQY;x$TbKGU! za$mTYH^CyZKt-yZQc<1W(3a$~SOYHZf5kDs6t-I(RP6U!q0 zcH4Hr0s6?JAkaOOI#2P6ZiNjc7@h7PBPiX!dQKl4eX86a1+*}kVq8g)l{2=@;}}lq zg7<(cmah!LkVxdWjggnlxB#`enAUe}JGXa0be?&`>hA-geybdy5%pJuc$?2>a?Bin<`U89Q^^J{J3HWgWW z3DX+nu3%IAW`%y)n?_GNJl~vNah};yd;Xh7y(ubhf2<_ra*xeHQ6OY8;A?bD%GlVO z*o(rgM+kxF2qiF6$AH`*94|LA^)y`zc$92~msHAhaq|lS=uVI!h;3g3o>FKX6=yuY zsfuy$yiM5@L}h9#`)Rv~tp8Z}eH2OZCFzY!fHLEZK=JB(5ZImh7OP?wbvJw-et?zK zaGX+$Wcb}C$2!p~@+p^GSXP{{kj&2od@IT*;Py63rk4U#8NfgEb!5}=R!y#s3{}vM za+FP`r5V@d=Ke?}>HVp-fS~)bDQGM5 zzM!OAzYMwe!eiS3R+iz_VB&4&U*j?=Y$`=pQ;GWHAi za!T}o&vYe*-|z%mxFFqT#I^<|c4&3P2yKC;lJ~QI?=s?k$;jJRMBOlDW;*F4fiJ19 z$%Ku2$#!6QQ5&`_2MoKMnYg1-Wgf8^}-llngjHTZ@jYd)bs?1S1%z#yH(^c6hX zD3X0{a2iAU*%A>$>e$f7v7kGF`a%R|{N;aZ)MpQ{frXs0(^Ehkm!Aq+W{IM1(5(p5 zZ>YTr&&YsF3WZA>c*yY&FC`af zt1ZL+EaLoVlFb*|L2SnlN#onOZ7sfD#<9t+p1O+x>$l^yS9!vdp&zo|bG@8Y+!e8|zfS2_L~LRU?GS(#B=Kevaxa zoMdc19e-z9Nr|H-ij|3JxPD6n(Vhik+1JWJU#K=FMDo>Fi=MPa%>j2n<{RIonWj{W zzcT(6=(YMfC;whPWUt_DF|b5e_BM4QqS!3yRg`wqHG5Mn9EDrgji{QKXg@^<8|mm% zp?tF~IDr#>D(W@;*0gaFueKEg-?KiN-I=WTLIwn24MtV9_D#P|YBtGI(y!D&)>=^Y z<`Qig?k4JGfpp${_8~6XbLqKlPGdWKAhzHf2uBvSx#$0K)0_sooL|} z+bBBhpX$bJXoXV^m&yLbP!<9h^HAY=hb8%Ut#-HK%zVdn_4LZ2aT$OA*luvIEuCUb zUbRgYz3D-+h5ZODmb=|^ZpCVL^Zg9(QocsANmk5*rcA3> zgIQJ(o1XixX(c-2Yvf(3cqgQPs3c^I_J1jNx!O<8KB6)c^s*BvBjp?}CBN2WEX|0E-ZLN(*I?G)I|*k*=MF#@`gG5btX)83 z@9(>!s74Py>qO!?2IKrM56mWr*AoEZFR(0G`GxD4nPWQtZqLv z^*Q`oSk40|r;@s5nK~0VT|N^O6VN!W?APs6r~Nr*a2;r*FGju z|EY$EVl%&G!>B}>WadX!(U=-mR%aVY2Z9WM^u*G8`?>=?+wYXBFfUq+$rn-sA)exT z{hIGP@dAAj*KhkfL8$3PddfU+doL{6HabkN$EGz$M;jFk`(9fY#Sj$R{bHQFbuY92 z#r%-jPns_F5<&=`S_R!>p>b>IH)cRn0kpccWjaIT zAaIriS~xW`GJL6S=<3Cj!c+8tC|(|SlJomijbrB0MYZ*CMaiz;NzRIM@UUS;gLAIO zt+^aPvU<#hQ}quP(7BZC?~^wQA)A5OV3G?$^B?T0%{@0c=*!;o-)!1qB$?~l#LfCU=_7poLWVj<25e6PE|AGdnM)=8`i!BsXESPUFE*W0k&aXXti?BEV7 zHt|(!wW2%}Qvn`rp)k;0`|~efvl;7a66XN{(8=X1 z!S8PiM8fy2DeIkFh&+bXsDg&Eos$pU98*6Oe;w4L7}|`Vz1Z85pTeA74ccS0DwOnd z0Ot7OM;FO$-~4;3iF8$l%u{Zzr)Ag+BYG?MA#&$0;X-?_6hEP=BPb+*Mxtq#I&Vd% z&=Fu>;5+sYMeeL}2C;kxYtyVPWV)ruO4j=$n5*lDrENVFE@Rmtrb1voypq$pH$!AqY_cH= zv;e(0?pYdRwGql+vW9_nC3$V!C1g{|l)BVqM7HAWa1-)9-#5bZp7V?bWXUnwQzS1*J^Z8jFXWjhg#z0M$QMuUt z4Xb`0SZCMuvDqMY2Y9n&i!@T8ZHEx$P_WHVaJtO6 z!-QBi{eSsfvXNO@B?99YkY3b-l;KacgxBj_8bgy3e2Th65uUs~Mn6&o=^p{B;vK>s ziU3`H@v)xPMYJ9>Z8`RlLtBi0WR7pyFg|&d#%GzoqW50gYKbgGu*3i4I@Nf~3XE8t zp1E|IKXL!!&O>Dix`Z>!KFDw&F!PwyzU~~Lp^4uVKH^hUqecDfptx=9d9X<}iA)TY zb?o|KcIdKwT6hAgcEUTLCYCVszLHTq;zu1t|BI{w1<8^_zsT`G7|BpHg=Uci+?6XB z=eH@HDJ1=+^8w4%VEI72IUo=Sao2`?(RW>VV+rgqxZN8WP+xfwy_e39#-mP7w8IXB z&2Q`A&v#Tze;vd>H-zcJkoYMbOTwpGU+0S(wyj1v$Ww5d&>P!JZs!2lSH76}mrmz@ z;8}M?AFIY4ZazNL)4R2A^fgayJMLp9?wExi7*Nl^Q0RoRhH!CmP5_v#g%8~OG`Me_ z3O2Qs6Rc_$QaeN2*XvyHrJ*_k!glA0K|>Ik8yoewwFD+yl)WGBv)!v4gBX|bK$o`8 zNc&C8u{%optmh72qh`r}W)GkOfqhFdDj)+H9?Mquy8REVr3=*--Z8qbK+eJ~XL}j< zDj(%Ajgo?02Hi#ZtiZhm3ouY4FUbwAd>8sYQ=Ib&Ws37yN{?G>NqDMhSS=eGIDlpY zZByv-b=#w)M(! z`CD*Pzg6(hrYI-(G|3mw(z6>6r)HPZfqO)c&#ZQv&7i`Bf5>5UL$pYDIw;d*DfHv* z+Ept_dg5SNB1-aulY48_+3Wm`8or6{R_>vX#0Rzmx&Pn6_@Vyc^k@+pWGjJmT{O|F z;MWPWUafzvY3+~-_u zHA%!>_z|Lmi{p8gY(Nf}aMv2r>7;y8H$q-p?f!(~(BnA}ObG788wD|vPl!wg>7Sfv zmkGs)C3=DkzjAXUP$I;nr(1?`=;(}B0Qyf`02n6$(G#RFF|9W{W^^`aaA*2HJdad@ z^GE-CIVG8AW=6QoR1u7Ii5zsrr9&1QodA z@6M`hC=mJ!Y{N4#tG86ESs#Q>tq3)3v&l0&5%*a1Th#+jLIJ57psX3Ey<;}6Q3~P? zfGKJjyl&F-nusPY8XZ9|QY6N2tXPlVtVVZ)oQi%up|4v6SUu{(L6a!Rnt_vIu(OGd zvyQ?4do{p3BIFIdvl91IcZyeeh~j`jH)+H>1j=vPEXG}@PM32D$LA%ruAjXi*#AS) zZ|87kOWJFM9y*FfScV+Gyqkyx`!7_CPnF1eumDN_(yrmUI_IGNw#@({=%}MyG8&623=V{ zleG}Tvc3L0@(f`2Tiq-(aMwiPw}8KGDjvE?1CPlS*kG86Q<|3{7s!sJWO7k*`*Yc+ z`KG7{W5ajiZ4OGev}%3SqrT?~dq^N>-v?y{%FPr8#$lA#{lLEf7$|htY~Jt2$X9xo zXHmD=8kY#Lzz%=(i}E;O-CoR}W~a^k>-A0wa)Jk~Zp5mzISLy@^CFpbRAFHGZQV}m zTY#Tr4LjU<6SnYk%_QsrF+OOjP_FVZlX%oW`+}=UX`$MHrBy+?w3b}mq{Ma6))TE=XGnx>zd!%> zWrY>z{eAqPU~;YI<_0wKl`4( zZP?pixo6QEYM!kf!WDL^rUV>;|J1+1Oq_XtT$1G(R@_qURA8O)mbxBEmwUs&4yMb)iODBD+e$4x~ zX|vqYSSyy59ljPdfwz%4Oy(Ii$oLxguP5-V|pHKhAeKamrdVK;K1AS2H# z=cT(Y=2%5tFYUMf2<5+>48*#@aT^+;pv`;FpaA%33n8PAD(P*pLCR^Gci1mJaGGmL zhR}jP7$MJ^ZcYNqn}q*ap2&`i%eQEkd6W2oO{VBBrF%?E<>m?=^c1v`F?dI~==dqEK~C`g6cXofJ8vCkG3By!T5WB#t>Y0kC8o&lTju zpV?ae?Me-d6ZwVV)IgN^FBuhSWpV3_??_b%yvVnRFeFVb$wWO81E5_N?-GBRkmZ1! zA0Kzx>`;7bGs3C^c|`$0B!860q8tPO+AFW}7lA32>kh!&pW&YxIh4QmHRqmF@Q^(ovcO<;{y zL=o<6mV7{uQo4RK#<2dD-h5m7U_3w{`$6Xxxu(RbuFXbnI38*^0Zg7d9qpAOMr1#D zR`HAADAP8>`c&iMH!JjexB5Qv7dVc%$i1Pv3BZ>&+^?CN?1i+yAQy2#l4c zL-AqG>T?!-)upJFJI3yeGk_31jl9kW=7P?Yl~%X!FE%b8vYl0ntg$|&x*|0kc6JS}aCJ2Qk>TyJ@ z1R9jKfBg59*Zelj{V{pwbmHDD80kf+)P0Yi2QWu%Fn#JN7BRIW7dx~9EtcWlAAB&p zD4z+tfBypFbBxXISWU$;3LDGunof6Ti#O`kY3YFmbM*cz&eW{pL(_5{s98Ve8?+K4 zHhV-6ntJ@FKj35fuh@UYXKT0Hw#Q2W)x_`hrky1qSTDFUDLb^rEmrt`jEY@>t596; z7o~JEBU{*iX6yf6EET9z?dLAp%Dta{0J}Un@)l00!|66G)s*j#_(sJtLwP|`>;?`a znfM>9oY1a<;UO9E>f;i%7>su#JiMD@rOY$O#`4jVaT)Rw**L+-o!`#cvz$Q<<0YQN z{lBzHg(k6)l5uOLlQ?HLUmU&!nD>ZnlGW(wRul#t9F;J|Nu5YQUD|IU-CIZ9G}&m(W4NJWpMzY+R~MBc1_D zG2;;s{#^;kPNIj4EInqC%O348WqV!`Dl0l>E$Gs$c zCU>qoS%-A{T}-`3lWN{>z(KJ|^O%cW%XrPUSR>0tsbnq-N4id#I@yntwcgbz)kACR z6&hBKD`*}9$c}3N;)p9sk;;hvnuN#kA|A+{_pN~*02WyIz6-!<=vI>f`RWP4MAR)dQ9lKwjk0gExXTw3xPMvDHXvZ{sC0NgLFe7v*M>~Zo(0ykzfwZp2UnU? zu@Yaf2J(|IWckoj z9kIIk?k0;GeYpyK9T%ugATb5->a@}(rptdJ&X1v@nWqZ7GTT2dD5z)Wa*nAKeJ-YFZZQakc8sB*cq>086L77E>PRE`aJdK4f zL(fR)sZH_2jdLPYUX38vUe9}xBV?}jL4nEnL#2PnL_Y_QfOC^FGM`(@5Y4DOwewcx z!%h_^^79{8yd@JIMN}G{njD@3MCL6Unb~4DAa1E*kLL)b&W@eH4xxm@9j4|`Oi}jN zSVO%*I_&YrrG}5zUA+~4_wBG$W8#T?V#j-ul}u-S>HG_P8?KNnTDpX^`=!?WW-WgY z!b5egfbK_LcP=R^ut?gI_Lmi+6j3@z7|#@YE@(*ZvS_}Aj^?`PE>?(?$eAdICGfi6HwtLZHqCTd{V?hJ4!w9gM zqxzmC<;=AAUAE~_tZBkc(Bs*rBC}~n&lqj(0g>7{vv$8pBm74*!43@cUO2f{6YMvH z3i%YU8i_b)*vifh!1Ho!oSa01zTSKGp889S?j;5;8HQU=6F<6K>0bpr^?eH&%r@zY z9k|xxuwtBVd#l>9HRHCOX$46vtO@*jK5@)4j@~vlPjxU2WH7@NV~E z9GBo)g?(a=>_8pPvKF7n-i_fx)NAKM^Y&hEAHpryKyowPvnu~C*LMAuUYewE-L85? zzu`!C$T(l+soCRSRPG05B8G3o6=T>KDsJwg=#b*|7^gIPlx%Nr1tBFbqeeFty$#OJ zLZY94I+~C0r9@sftuX$~RMSHSFhGCoS9nN9BN68QSt0Qq@RVmYj^; ze?AnhJiYR3Gy&Ox_ecu7P2D2U-}NmWt*WnIuMCXz34D}^qvN-H`%0S79r1E;fWPxp zm(Q$)heEA*nkR@B?k$r&-@v8MA*Ew_uR$H;_PSXJp*}(PjNOYyvU<(UuwsSk`hVCh z1-V_?mDa;#NIUr&DQJ||sbk8E8X(3dHt!E+fACU%3Kw$!D>B#4FJhT&ZZ)q2{o2@o z8lG_nN}O1~)}f;>lYpe~&$a(-kw{%&i0%e5SXwDSCg~3e9i`Fu2c$_(YEOQDp&+jd zJ?pUp!kZNwqSK;b2TzC?p&jf>OjrB(>E2gQ_n2v*B2-sEE(zJr8o{HJ2#=8inQN38 zBU1ibPXHuuno@A^gBWNgM3RQ4GI?uPxZ{nkeNVyKM5#JIckiv&6>xBa0((BDmVpSC zh`4x`VSBUBG8Z}JaTLkd0GFU}3lIhS^U;fHLk>x5*@`ve$dj;a*&PGh9UTY`AwoxTTba*3-5Q4`s+eU!UM^8$dY& zx%F$;kUSvcuxhiA7{{qngyBPr9&dIhn~nQIr==2*~&{mU^PxCwrUVmt?mlJa!I zuFO$d0=qEN>w%BEEbALs5v3+9!AZXwQ0cc=RO@nhSqlPi=>L&^R6cC~e(&0;8P}O< z#WM3m&5NbBH*gOwN!_;%>s@zF!JGpkQ!-|8UVY)Gj+ZZ<@Az~`54z>yDUA5@q!IIh=; zuSHD)h;X5G^2|xX4f)#}qOQ5r^Iv;5*OTla`IamIJTE#=)2+8Y`RnAp4M4hHdtI*f z-vj^3_0;=rEht_CBBjD8>SU9l-kgC0ItU9oJT0k{DI-xpE+l$$|7hsx9558pXZ>B4 z$Tzi2m3WnwXg*Ncv%DO5sm(zCUL)Cz+jv<%k41|~PpltD-aEUj8t7us^|fFsrbb;d zxSR>FAo?bR54$kkJ`efUj0+U^IPEGvALRs$2s*Gl9`U>U} ztzoO4F&y-}MMH~EepI_I)SGkEd75{H!WH`>Htewk^Dtq-0DwKYQKbh*}$ib|NQ-S zseVh1T5IUz|2-_RK5R;P@&OOmYKnodVD9zIwl3SeLv^&;f-~LUHF@Jtu9v*1(qtns zNd8-kwR<&@8hZRwCTwTdh1*S<{bGfX6ND}@uu8NohRyHGki*nMN62xROwgW@rVnm3 z!Eo~Q8-GWNG=u-jX7fEaCTd$&*}y8?fu;~5vA^=Mv%IeVmgK`jwtKNB5c>L~ z@vo{63Upo-3}>BQ`-161t=a$Elf)0)c) zB7kKfCBu)tdJR;8ZjY~6hVJVoX+e`;zuj;uc%oY=?J6v%rW~77@oX4Fi!iF@*bO>Z z=$q%uoAt*l%^xCIG+e<(7a(m6MA)N7y>6HVtZ0&NVB;1Bj2>=*Vm;)uqk!?4s=$_q zB(P6^?rp(P+HLte?n+<(f)m;)a6IA!vtfg2$C6|Nq}wbzHJiMzhd z(8|5xs-{+qOfP@)iiqa2D>q422dra{g%)12>vKZSd?;6Qou;-1E}g}G;dwe8DY6aL zu<4Z3tv#f;nMRY(DMkPLY5oV?HQ4GdUlKTE=1(}|YXv~oeI=|Nq6_=VEEy!Ez2CDV zP49-8yUIBMMwtEcM{bjZx|&$2*d#3g6L&;xeBgnbg``W5hG;wof{xeN&Te>?El3qI zt4ZIrd|Bq30OA}#)8@E<%l1`Qi{BZwoGl6coA6_pmH}OiHrU0D^Y*ANJm8o3YVwDa3b^Cjrw2ggk#~MD48|9L@>!SPA&AF7$pCI$vl)1HG&* zc5+mrFqf)`L2bp%{KddqY0eb9IKP^a)tY2qbNa=`iR}QN>{+=HC-Ala!hNX)V>O@O zA+_{M|2gONhc_3OmLqrKFbwjZnLzbn*dGHedsgTzgn+xTOEnszU+{&I%L>5itae^& zrlac)6{5E8>dt<*^xqh_9GC)F{92#mc{8{-c)@51VAAI5?OJ=U(h=?E>o@X1VUdCm zh@GZaw`GcLml#m$zkR@GVO<%KvI?vTnBM7hBf6*4+T7`LPy1-v;g!R<4qnnRcL)u7QmxI@b;f}<`8E8AzJh{|@D})l_*0`I1h||*>_><@RuX1h9O~^x zi&Qfl@E(|r?|wC~Do8DOw(BnmxEvR?v|~~7Ut0*SAkXT52Du!<0^K3DA>WU}qoV%- z)4rMmjw69PJsI3|^-EkV(r5d)$B1qm>wvIK*$|&xvB25{?rz?aMc7}x`d=#%P@4PM zv(u|F912ZejgZ^|CP%DlfVB>{<=?v;fv?lXJgVCkOv@4U?wAo82u?1BdxBRfI9RuO z0=(|O{Yqj2h>9v{32J~Z?H#Br38E()5GnPTo@K!GN7U1*J)vEJrP z99ZLH68UO%{HD+|2{2CF!^O=@IB%YZCejlr`91yZ?j>N)hEfHpO34w~T+qu+kWrHI zuc_>GT`>vV%)R`1tLvw7{|(@aVc0PI09+(`M*V+!d+)F&&+mU+-?Y+J6s=NFBvetU zAR@{pBq}N*tBL~3h(eJW5Sd}tQbA=zL^dQ2hHMcCD}ag+Wkp#50*S0dMhGN82qDS$ zjst5y_4nt`i_0rl@;vuE=iFz!&bj6FC)C&Sv;WxMngw#&o#ys?yqfg}J-xEL{sZdx zJXR-p#9xP<;+5CNmC~lIcmWM4bj-vAs)K> zxahL2L6cf#El;Q4Nczug7b8m{y{5(us@pgax0gC@1r4`#S1a$J_#mgAezN68tDEJ{ zwBT1yN;fhebSwj$)gL`+8#Zpfk6D4~h0^djM=VD}Cq2-+%;Wt*sTP@7-69Z&8wB-d z&kmh7)>_lBMQ`DC14yi#ZCBf^ z!v||K4z}V%g5>9Ul_)e%RUc21@XfIUc*~wp4s7&p@)^@*f2YU7gI977bay5eXBnedxN^iSAm^fl8@n>l1d~)5LoIu)U5sCWQ`U!rLE@GxYqD zff&pNsUQ8#@IzNUsMREeM$9I!C(4J8oh-a)wuKy3sQtto6DIhcrvNexp(qtvwd(n; zyNIhL4%CQp8+@~_5@If|91K|*^NBqvE(s{>mvzwI6S6L?zjzBc#~24c$Xo!zkfEVO z@PO~_A6Y7BM3_20wj8>0?cj>b^%!;-Kk8KbKy1&_vGk?&Ta@PCZ6-c6V>X;-+k=8B zws_wjW%}za>>YXUK#{Qnev@1ye&H*UQjhE@P$}drX-#tts{RVdvB;b47dHZGCrByS zJ5<9-07}`#(XHDQ0lZda?#I_$inI*NZ&IVnJsxFxeVd$%$NDSfir2+SUO7fUt@j-Q zZYPvVYlI>VMYI>|e(yEdjd1I6eMtX~{)3}D;e6?7xzRu+7rmQ-oaQXZv1m32~7{C@b_=Y^wmQLBWvLggz9^(hM5_pna1E25yxMEvX**i262&O3q&gb_e6P0?; zHvWK=*Z~EYIJBF3P*^(~QnxPub&UUI*n_aEC9sG+D{s7uqA^{|OR;|MXMYrx;!N`d z`HLMEY3~67AR(viX~*}o?)^vOus81pbL3knNtH^>^uTt9s~6BRZ* za(-g#8w(}b(FPCh_8Nh+lv@k1YWFOk%P`EA8ce01FVK2(cD;+VV-<*li((#0jG-h%p6>JgEsSnX|_ z8)2vH(bqBD-xmoPccsJ^Tc3sozjj~qmPR&*B(K-aCE)Gd^M|g@A4f{>z1Xg6T7E@4=C^<;>gy_15J@R(d-igy%$_$i zehUTjUpdzbb?+{ZZGFsHdf8>E%K@k+TJXcQJL>^dZ`&Urn?%LpV1VH4gx00U>MnyV zR}RSXAzNHnhxdI)%mDYh9GHsnj`J7)9(^xp!^Ow_5?$N2oVn(vwJ;)Oni|J{FY`v( zs%!`({1qSGkY3*F^rYds{$FDtx!*o*n>UQ`?Pn=ek7PXBkmYIF@yEZHoUl@aupfZn^pVY*MqQ9yjo|r4FXvS%0pgL z(tKVt7d9Q3)m=<7Ih~xhYyB^(oP4!R(kmaC=$S>Iff$qszE?9Co9+rqV_K)uJAUm9 zA=V#souro_caAKBwA1e?m2KkaJ%14F9jOCuwXX*5V7!UQ-rG*fy>4#vBI5=~J5*1~ z;(pd((g6qwYhY+rB`1{A^&|Om#+7?I0`9>+f>yHr*y3Yr30ld)Ob{Qkn0UWk+(LB+E>@qf+-FfFON>%Xg!du7cBS- zRG)au5UJBKwHzN0PFw%E3-u0}gbIRwKEMFoF~e}nZO>OH`V$z}wkWp08UHsE#D^>%fiAujoE+0J%T|+BG$pEi*z8m54e-O z;`5tX*3LuCMb#(&406CQ2Sbl^wK~GlJEDEby&$I3t=)JHWNA9Wcc18m64(!FIfwjK z6!&2L+`}BJ6$rb^RkHR_%b52yG(oatUc%|gZg$tHYd)C(JNUC~a@Lr-;8G2M>7OFU zX%tI$Y_Ss|1TBmE@9$AqvzI(}kHQy37#;%GGdN20cFzUv+BKj|<4#u_m-p%x9|bVo zYP*fDGst*)#qO>?=|zLjRDLOiCb~a<_eY@8=X!uQ+%e9B?cFYCW%}6C2LfcXW4BYU z46#w`lP}Qv-dePJaq@k|Ce=3|%jAh5LXusw$N^Qc zM;zujhoHEta!~KoPb(%OZ8x)H~%2% zIjh~quRIZTN}Iu|`Kz?vm%DwRO??f{yZ1y+q$tjBe7~r?5dbou_G}MWx5W=Q_mHvS zDQwC?qQWBwjmaG(-RUxLy&Z5rgQOD|8&D)BMuqDB<%bw~trgUQ? z{zO4sr&V2rf$lBS^lNiGj z#({y_T{sca?t^#W4#^P{0pMYi*AABETYCLE+H9TxKqq@vX1y=q9E!Rh4_Q+q)YN^K zv^6TGO%KYn9?WDsGRDHnvdsy))|F>_Y7U_$Jz_8F`+|s^b%p(;=4=;dQ@aibvsKe~ zA?N=*s(@S{prY5*WK7C3a3*JE z9)133Y&ljP9Y-F_&FHKZ73=)*=!Jf*HHVBP<{X+t{aaL?7X}m)N!&=4{)1WK7A&{0nw|C-%cs%HDWaPlXB=NG~qy z*-iYh+Xzv4Il4Q)Yqw49nv&<|uxjr0(7+{a^71LZXU^XcxcA8i*+&<88NC!0MvdaT$}7ju6~3Nb=ERH8V8<+5i1si=6hiIF_Tm_T_G{E#QU2_P^n*ekNPZr;52mBLK<#v0TW`zwu$0 zu4Fq8CIELZd#fu(c1b18U(cXRmA>b%ipPIZ-vYpQlCf2>F)f0?O|w06>WrA902GP2 z7%`asmO=}x&J5Ec4U9KkS2V}Ag5XL|A7M=FO8T!Kwg@2c*AGOZ;$Hb=fiU9-C&dRR z@t6PD_~Yr|CghHP2+xn3D`I(xEzx8VpO2C@(+O4FNrt#5P{>g9gL1qAl z2qxKh@%9bx>(xqD8cTPr>xS3Vw+ALr1{ z{uu!F83~FCDY(qlHT~2-c!Z|Yg-eeID)XnT{V%_K7~ZpT#%cuZ8_mn3Akxx=f2b>8 zx=*76tVVbq4dvqU+qWjrUCuz*h-yk=M5QghZ2Omi&`_@r5#RcQt2q)yQ~2*mBL7_* zcy$p$!I_`CaN!E^7ic=}m{p1WrTsTh=P{It#3wi8m#r6l0yEHKL18faDe&g1`{TOud zN@aL>*o2Hp$Ar%%&K&--CZ9fSRRqsLP>}80^F{Qv@Akvr{_h|5wH1vUos2LBQQ90N zIyTm6!2ka~1-@wsU}d5Nu_rN6H*UCgqdsJO{r7`fz?P1~DeT&%!2&rEt{980C(iue zKbXatgCFmj{?@>9UC9a_pkU+3$Zj>Yc$&_CwX$_wmfHdi=PqddEG30ZrJIn={-dw0 zC%7iS8oa&@6?c#E?MGl5LfCgqe$2f*xacl37gb;9Ov_G5nn zT)x=u^WTf-{Lx3Kq;ye%2+~RZYs+r4@@^j+L)qHyQ255CQeOwqxW*bp0|SF?-x{Z7 zy$MJ+kOJujpE>`(`-=T49`KDpEBz$01^@^D_wNBL*MY_TY*38(8+)Dq__&C@|ARhn zKMcuvxCsT3moM=HZ2JvbiZA(>4EXQo>JwEJhzh$DdZ31&FfFL%_KhANKD_hMg8%Pk zUKczXqAsdrsY?Snq7w;WS7dM*4T}q?>UmgM9-18bU zX0HgnnDx|0#l4YULJKAvTKoIf|5=}zzFp@L%&bxQ^ZO^B+0nnYNkaidUQ#nih@lR>y`S??q~FMRS)mCqzB%VtvN zY0Iqb-Q7l?{?1`qbfH-2uYPjfI_Eb`VS*%>o`DBG|GL{q$=nHnf<3XAgq5ccSFgUd zJ1->BJ+Qvg6|wzbHE8^?&!k!#j`5}4tV5Rd!(4&-CDxyK7T(Re@|@H7KVP>6uR}OP zUJec2PT(@8&{LJ&U0;o>xJ3twK1{5I_3D&BnQHj2dj~06|2lC=f9^nUN z;+`*~cFMPHWaotPy8a}IXB~)0BSN*H$zKMaSafXz(ZGXD$*y9BqXair??_ za~kR$|9UA8h{bx<4JkXwIC&Wi`thN!n#=`Fj@I)`r#Y>u^u`Tek7n8#BRP{%Lug@N z9+DvH)$n>b%spSwo0SMJIW4Q{ zVM4C-f2&M^NQJ?`eq$9JA#ib!^8D7!z#d-CDIPLwL)5;%ThOV94U1m{^_br zc9DN3P4G6?wVUn8rXCNuIyP9JoQ+K?aq%Pf<<|ub7^3jSethEo@HY&i`QIzkb?fYq@l;{S)Ds+26`OM zFI>;&P(!`k3dqIwb$j*SFym=G6Up|OZc~%%g_&F@zh%kwVf%|*8fY~(U4Pw(w!Qk9 z)w19h#TWtcxOi|>ovAlp?z(kOWo>+_}(Ne+8MeCa=f z2-k<#sf}mOzlc`N1&Ihru;rz)@+*u1OXqAtgX)Ev{dGJ_6nFmu-~oAG1ia*8W`lge zpVBJ!*WPdfru*V^OoiIQHme&1j|(DP^y{bdbBfs9z8jWz1P2p&2WP3I6VUlMYm1k}xx$p&{CSL9poH z_5nXG>nWZ9s% z-sFT7?gv*q9A;6E1&7NwNsR%V@cz2+5v)c9f_8pf_=t9E}2oO{&e6Olh7&`uu{i_n}XAyF@MGq=J-+aFt zIje>m=T@s?kb#u9auc&5&cjm%QSjvOnPpiobK_(|_askk8qX9&b!~G-qy%MNy*O#< zcP)Psby}C(UDmZH8IdmjAXu`nC3Y>HjwNMnG1FDB^B&)T`k^29V~JS{6~0i;?!0rE zC^yfq_GvJ4#s+%}QY)#bhWQ1Vp9%LDUl40Axuz4989NaR64ypGbaR>66L4bOsdO#* zmEjwKMfO;hKa9y6v*_q(a33t@2F#?-R83tkpCvRg>{~$+P}b&0%~^+u)xW_z)P~Gk zN(^iZ48>vbv<_a){NNRhHdjtH!mqu3;@%5)d9*Y$d(;f7pXuFuV z-dehJEVwD^z(~L?Y_RW}1lQ4K(}pQ3^BqBuP{Q1kY?lr$OYYK?rbK|$CnY@GtTo&R z977!8->bqX4^PI?4->`x%+5E~WS^0bI|yaIkMj+zcO-N`U1G7(Sajl0lY_=w1YbO- zAt1j+xyx})z_^B1HoK!?KflblWTiqRlS2;+YddGX-#*+kkphm94#EzseW3TsQFOqZ zlNFtCn`|FCROD)$p4j1NAGptNGRRht3N~V_L_^cl*zqS$PX+lF*S~bv62fXI#j|#l z%Jm*`S38K&ccyH%OAn0G8VocHi(#uSTS2~~Q^(8eQIB^ZBug{-?Uj_FnG3V)e?ml; zm5=(*l__R~T&bBIQ8u=@L11ob>`zj@OAqIT){L~svN@wrM!5e(s<4hYL8nV9zU1~d zEUx|Y`}JXMm3iPqfus?pHjxxMX5;9;hs@Z;>BAPnU9Jw`kTwP61|ZNUs}Xo$FA|0+M1*X1fWis3SlX6iqf8GMt3We}ecPbc@^dZ7qicw=FRL-+T*M+nS1_fsMUQ!mOD;&%WfTtv> zBDl2zewdK|*UH*^;I5D;BY~pW{L-`6Pc}bdM;#s!gUnq{qSpL{_)G>9B^Pz9Fw#-< zP(nsJ49l$^Mi~ZVHM09Uxqn3q62oWWYG;V#JBDf|A^z_l<24q9d~)f;u%W!UtQN9G z`H`dS6cJWpM9&Ctg+w;5@nc*^);PXH0pf-9Z4CaspomN=BHxL+-!$wlae>|r_<}Dp zZHH=a=fWH{e7x^-M;|t!z=voCI9Fdq@U8WCkg?d1$!e~q+I!02m5PAiiX9jNBR?oJ zO*~rdYAmv7R3INblG@1uS&fi}=ev#*;dJUANPt(gPle=p??8JzIxx0@7j}69HllW*em-r5Z*2r0 zZx<$c@`gC@vBa6Vx=FEpRvWX?{GQJu(hOGg?Xzf}cFQCc{*?_t9{#I9i{vwRZ*O@MW4E_z2UE0@$` z!Vh%75led5Up^oe?A*JvDMd# zq4Ec-QGsD>cP$abwUHfHJU+WJ6U@tQx515_uj((hLp7l!YQ#5&O+Gt4zNPc=TSsHD zE}0jLGd`Z|#U5#U?>D!pOVw72_3=Rvk|H={Qy$3_qQg-Rf{TS2vFy@l(@b4`kbXVH zzEn+`3uRf3mPpM!vd*{;QGPOtQ@{p4m|xId4_%iz6g1!AoL#B!9J)A-LZ#1Jqaclg zh=TE1Xyhww2&?I6MANvkWe%BHqbhko6+f0A5*JuHa(mPoCE!X|h!2I^QtPE{EtgYA zET>hX=vKCVju(Ayo^Ou=O{_dLF6pqzu2fl9?Ok-cBy>(pLhaXZVZ+Sz;koH>+5ZJdQ9o2ExnGJ3|lP{?4 zNG=M1#3%2N-xGM_nZmF;;Rh)4h)}+1)QTLT^v5~-TSM|A1_!+L7a4jhGvBGr%IkR^ zn+$35Mn$inVcfxYt5#p0DbU%N{p5Sg=F`bhbiO?^`ASEz7LeT+HxkX^Q-7(yFU{@R zV!q+^NDQs-EHsid$CtT6I8re_+23~z+A$o*4Cl88LAjtJb4r4P<_jXLTW>RG08sM-M&Xw2z&S7dG^|IJ!U=DYYV~))f;HgIM|sq=KYJW zhl+dywPeF)$@4(l_Bd1TFisTbn?R|PuJXD0_I2#u{!*6ix{{r+bDM~Bk?Ax0i!8mS z+O>pa{4(!lm$6MyCE(0o-W%3@k7~W$$PQdl#6r?4_j=htbJbL1gig<3oGAkeLgM_W zn55cOK5llU7{8noQcJ*u3m=c-GlwQ$r(@hX*o&0`KF}lR;w9w$V`)g=OcMU z9LI14z%y(!uC%9hT@G(mJ0dy(*k{&w+a3+MGx~euY@l3vB?-RMD*@;)U2!gkVyi0k z=>ohmQDnC2W=G{h?_KFga|Oy55I^!}xy5yzb{uF*y&x#8fDAu0dkeZh_Hc!yR!A^w zVkZ|iQtzM7yTvXM%_yq=KNh}Wg_OYTf zSf7g<#H|XgQ)jpVLg=3=8~?`2v(MV=%Q+jAAR30XGRFt61;Tv$e4+-^^enBh;g^hg zKz$zD>E)3vC;!V$$sI2jVh_J0Z)v1g*`e)olW^aA=|ICdLz0~s{f5xT{j4@Nur>N; zh%YO6^JS_mVv{CAKAeS`2v(DzJoR%Q-oC$sIMp~gF){vQLT8t7TN2ou=I-$n+TnKj zZA*cI&{6uR?pCX}4IDwY0gNUtZw7Y>gpi{M9p7_9xa);}ucj8JpTu(mZeyb*l~yQC zJjdrRPE0K7F_Uv*P-?o^tZ4e109jFnz1FC)J);4v5m`@n%MWI#jenb)JrmN+9Q zV(G(RCq3no(G|A!&{0p!mp$z# zAO`fl5gwt*_$+#KdF*WYur59pq8P)Wor+0NT;^4j!VHho)(@p+;J zD{gIjBU_O*3$^4BH0g5{WyCsdZ03-?T1FgWfxMAuC2ddgS+);)Nm7oH6;3jjD>pR+ z;KfJxFNc@{6~Do&vZAlJuFp%49da!y6GfR}{=QUOEizF#-A1TA%lMM|eH#dso+77ZwxH=`wTUI7P`UXItY15G-TE@ubighXqlrY7yE z&CSc+-gjh_V@C()aAXU@8@NTfj9nDJ*}8 z8zp2)e`4B4bszFFq%xFU6d+lBw#o?{7^Q!Ok{1~a!5{~||2O2EoY+v6XKQka_7fI9 zxMrT^&UWKAq9(yzgcLmO{Obn#EOgzoQdV_c8u2=o4E=){>QAZByUlpx_HV`W6NogrY{aDX zEsDK&*h-nfN!;?ErwpY_PIx>3N1G&l?XsyBH4#A%j4RCyn_lBRkV>Zyt(E9lb+2ti z0CkUp24xle%?KNqsVB|lMAo55KiYKFFr8iDnC&6)&S7?kK{xOwRW1ug&0*)f(PqHr56)y;4d^H6!G7H= z)*S)~h`!D-R&BVbO9*@Q7 zT_8E}-CS)5XXvkazT|qge?PK8i}vh|=DH?C+hcx@d)KwVuwET$`gLFONKh>Hpj!B! zzeY9#ry|QIZp#1p2`if9|7=kUrmk#SdlX(15xQVnAPHH)faS7OJA(Auv_tHD@XQo8 ze5S?SQ;8oYIUlSZE;BM_LF|w};zACfs=jLMOCsY83#@>NkQt5=WRBL&`{)&vI_}|I zpxcMMnS~Z#fD%XYnSN8GN?vf$g<8!}c6%l21c!MZD%5VRvmlMKHj0Y@1F13|ZrjA5 zn@@*cS$0mg=R%io%xZ8Ci1XGN_EPtwSxO*3w(=sM;p;oMLt6DhntCvs6>6YTbP97U_#Pp=ff zlvOU4m-iq=-Yeo>RZKb`98b9AFo~rR+X0ViRJ%D?9~xYb1;)UyUJob-gYDg_XLTUXgVz&ZlTAP%ICJ;}6Fu^gns%Yj?2heQD$5{i~xc%vK_%6ZS( zfswBCL`&*S5A-b0QNwt3FQ%>?tIv_tU$LGr;mGVJY^n|h=R%7R6vME9>y`+-AQ-qk z@cl6mGUS!DHsXE4e9_9k|j+fPr#b!5Jl2|?PQD!S*cAQ-nyw1(A zs}lA`yaHi|vkUWR_8Qo_A)p8t2eBCeU4Ok`FHN(zp=voYcbXZaP^Q9#YM@y`9GtE- ztc^HpH`F%YL!#yYFPw=p#>X47j_^b~a$JRY7M=KNj_qyd@FygqSLR6vRv5a>43^KC z`iUc_Tr|oH81xID2oTgo(Y=NGaZ?FVl+y2m@%E)dL30wzG6G^o|L-+OzXe0!doUwb z)(+?Bbpj2_9KLl%)sw`0aCD+CKfwlXiLMM%Ewr!G6#0+Di>*q0S$Xs^4oQPFlokh{ zi&+uq438Zq%K-!UrB4hY);*N5P?>EG%Z#t*&qS&7ADOc+)h_i(+g&^Fy5K9(V;HkN zq2?D##GLNSsj%J31*6eU}$sRoUIlOk>q|p$Ht;`5Vjy&IM7WAkmUTiL?3CGb# z>uJF0uv{!QQVJtf#D>H-*Uh`>^6)psV7GQNaowNA`B#E_e{&AF8U;Op^uUsOdZGz|bo<%Dx?=)7wI z`>Er?w5$c?Kqx%&@l>x|2%_|^UgQE2YtTynxd891(Wdk8gN)IemNKkph(xaHn|eLhKVHUjw)dy)ek}noL zjdLI~z#*t9`G;c#>irNsW4><-u{>DdS1L0Y7Zzq~AHO4_AH=3~-#gRVQj-rfu$EW) zyi8E^JB_dU9AG#vG_r0~f+Y&)2xEq?2&2|zODS7H<|q8Gs6!@KIS5G(b z^f@c7o%gh;t^p@PODm>&$BU*M^_D~IfL%{FCs;0t91%F2j@3$(e3(h)&>VB6w)sAa zDRc;g$p5l*b#fkmOcz&xF6xUW^7Gxj%={3&mBGBe5kByCS(J>Lw-Plxw={4}D+e=y zJJnPS61E@S;JhB*wHUgoDeBP33P)xKT=#t*2ym^R7)8TdBNmrtUp$DUH*p4{9TmqI z{iCJPq^Xw_=j`Wbp`r?+^l8r%c$nbPi%?+th1-QX!VCnbZqr3dVKSx5(7`TRQfF(l z)cd*Gp=;#237~Mblz5kn^2UZFbGA|nEHl0w<0xh7Y)JU_gD7AZc#;?4u4<=Z%I=TG zj5`4%EjBi`XkDgZflM(0HO;1nClgU|Qtb^DK(xyIriCr3+Z(#DurlApl8Rb+a+Eyc z?`^-_`9kBC%9~OA49@wE5F=8_qP9Pj`9<}@Q+42E<*jU&d#%x5Te(l=!m@?^26;X4 zl>y`kCx%V8!EB%|^5=jb|FkoaRN4RhzLS~@`Y0-+r_Q6Fg-=_#%FJHk56F(mw~rWz zdH13mM>U0NtIGQF`g6s@sKJ1STl}-hN88Cbjylvz$K5MX;G9lsN`LPXLV zimVxM@6PlO0mC`P%y>*lkW1PbwiDzIp(2JcfJOQLG0fBN1VW~T@SXL8RcVn0zcxOI zs}p9e&2#r4!^&+Y)n2stB&s#Csg@oS^_Pax6UD)hQ=;IB-e}?aEGbZrDenmiZ(UYN1BPk3_p=Dfg8Ksg(qg!K(@+vbB)Rw+E`)nq{puTw2~m`f15dFG zq^_VS^2W(3L?Vx7Gh+tAKYrbi$~;%ZviIA&0Ho?OF=AX|M(~C2<6|4-(yh`7zY1$) zJ^zmMIzs(5F?kB!@a5cC4J8;(WUYZL~9p!=+?(|+7eQ6yw}k<9D@AvfW< zMoOk$z&M>hijMB=EwVvRIuiT_fUsH~j=JG`CDdSfnw^FZghdad)yq!MM*_*da7j;#ZM#=xRitc9b-%Rkz63-cQ z{u(+lZi|9l>ipC9#TG(#7gkRtQ=1joxT-_$Had$^qPMWuCh&5BvnMzI^3d5H5v8v( z!QCtt*b6SE&NNT>GHjynC?Tu0**+~2+_?U)`2^FrkMgKX;$1PS&RN?IJIj2v_7*x( z^ih~h`rbM{wVqy5j<|gT^aitZ``R&jv1*~)`ktIb1#yvefc@G}!Y|qpqebxI%&MeL=Y9-@jziN0u{&Ju_0ueqRNjLtA z(UD41I0jg1!-Y1VuNI9g_)VGk)uT>R=qIcYfDPdee*NeLx9~v9xir0im+$ZwEYj2- z?LBX070?D$v~k8)3yUb|nGGQnm-}$uZo>dLPujo$R0LCUTtE>v5(5}UvE;9M6esxy z)=&Mmkr++J5t0~kP^ec+dK2b<`V9$mS-`arC2YaI}O3=DSf1?i-7aDM} zI>=&qk$`2ZLZpv;Y2sWzqrdB3QprF{J)&Hxh}>rl)WicDh#bGZS?E@|KSP|Ie4w6ik#b|j_Q+vJKOx7nC8?mK}+mzpEc?=|l zH*3y(nc_T2^-5!@1$SVf)Q6iNGSBIhn(As+A$Q{BYCGfXf-idXhxuA!%rkX00KDwk zFp9lKA13u=^jS3>WVe~S_j+}R&3pY)*jDgn)FFg3sCp5JZiB6=;;gG!N1w>9(5!pXGHXWA@@bKB$H=<`7A7Z;I z*IVUq*IUvq>waoA+)hn4Zrj|8rh-m$Wm0yhYO*vuBS35uYmqjoEr;B$d_kWTmTJ47LOfYD6+gy*c3VySMEB6gPub7 zyr0rSnoem!D4$nGaCM^&eW-i?l^3EF@@~7GUg<{7bbi&~K??I-N6$u*B@3bGVeVkH z54r=8+L}79#JtQyFLJ3e;$AaDkDdF|)03aZDfk)3Fp0g)>wtc2%JIsMveA^C@E$j@ZW^XPp-f(@w}k(>+fg zzS4a!fb9SA`!&B+j^G%jtijnGpIW(HR_Pj^4;-NK^fsSQ4{0wqvP1J0pSAdqai1Fe z{XrTF$FRsX1KPR`>eFbCd|wQuVgc^!te>Bsn~TdS087+>ihpe7{!?&Cj50BN8|KUN zO&_$R<>eq{;-^J=3`LO_#TtE_F`qiRG>!_b8`k+WlEk_%68O_sN+zF0F1SYF<9C0y z*rDt#Y|?>WgN7=EX{`=n1lVOrvSX`%X~iRh z3!#-ry-(juX+x<9KnVpMbkTYFc|D!_>vH$?bgw)GYQpLg==_5fI##E~8v6$^Z&)1_ zei(&B+CebHG>z8PJwCky!&EMUqE*O9^5?EjT9S)G21Jqi}QI2|%my*gT{oPF&nXA<%Y3NlxTc^t4(UJ(#! z+Q9jNm0cZ!cOzTGGfrQ&M9P_i$vG!FeE-29rOHIdK+o~jZPSC6xKnUL|DY;UBocxl zkx02HHf`H#^C$pGp6JS@|C=CF>HlU^M1BG9sV}V>N&d_{&j4t;vxRN#2XpS&@rCGG zKhZc6s286fZ3Bd1mj(+B0(^RvwmM2hFLD@QISC@YBh!7A4#ziq5pZ7!V+1IjuHz_= ze$Q2LqI4EaDoqo45iIdnL+~00-2plT)BVC9EoH&x7HMZJ9KXD8)B4XrY6qt;=7LyF z5)KJ&S^LZ@A_hRh*#Dr%$6)m>5^~Hk(<+gbtW_ysi?X*$2777&5*&bfR=m31iB2Cj z48*7`Fw`ivuEFX?|a=Vu63=o2~(7p#KIuLKtMpil9m!zMnJd^ML@U*LPrJm zl-hcy%|*e%_;39a2Ghwh5tKzA zy^eX9gA<4F;VU*0ovWDx%0yr|y1KKPcw6O@iWm4c5yrcimFP4B?SWXFyGYR=8|~)3 z4*Glj2Dn$VZW8=J!4#_x2w3#;)Sx zG=^;DH+0W;5ZQ9z@X4X@VdX+4Q>oXLCPEm)uVO#&0Bb(8LhgW*wvC29mRug=H=T_13>|#LovkdT#N;Z`3bgfJX7xWWj#jF7f%MUV?f-y-%^7%u>H4Dq94Yc^k)_tkQpm{ z?Tt#F=y-a1rf~Z8+f}>ra}Op5_25~97(ANV)fLIE6Pb!=Cu6(d%LnXrZI2fpe85Hr zy$xW92}G{LMi9jg5TQ$9CINT2mAeOsrzA0%RaOvF8#;;$q|<`DbiDZeV=wrRrJGD5 z=VgN(Tt34qi9Rg`gQ)mBXiGygC44~Tv| z$q16Q$Flvb=l&pb@E*(%Tj&!@Alb{{H-nEE#hHmZHH4dr8(MMMN&T zAM^T~Odc}rYr2>@DNe^Hya5?vLMlo;=(NUp$TS_+A{uJ%ukw}E9+k)X8RlvPW+&xOWe1#D99`D( z2O1$I?VO9#_O%X~b%?VT0*@6s+4siX-Mq1dPzS!Oeoy{B@tyt`+ApkA%+r}urc1JG z{s4)=7n+Y}(VvM5yoh3nRure9*rFK4Wy0CP@kD?AR-8P@Qo{KK>SNrugkM9xa(>C4HVO~PsN-HQ;VrT4 zzuG6C@sqli)FXZ;ZK5Mm5Lj4w~r!U@}haOAq)GZZAKSkXSE zRiN2@f`^BNr-x@jgMpV!n?z5l)Kof|f0|k>QImJB9AD@$S~GezioYSY!A}%rE^`2- z9L}q*skWN-m}Z}*rfi6d!jTMvRjU>$7rk0f8n!k=F~c>3@7pXREwe2zc5^47sXkE! zsd=l_(aEaPsmT}4Y7i>tmrNJEDKRf8R}w7rd|jgum1kbO0_)JASDndq)h|nbmrFe4 zvo3HhbFP2>+B2F@X%3W`oAp6I$5*pi&qq;A?u!zIx=D*feg`;89`&73_ji^`f=z-> zf)fG*s&#o|1sssJV3F%^ne=l`YHLqx42}rSO^#hF-yvhGGix&|$e`7zXK~a7d^GEW zpn_NE@$2n?u8sIP>I15$3PN0Gu@8$)ig1gn6fL6rT{2(j3C@Y=RqUwG9W>nYd?0Ao z{GeIR)8g>_X#G6vT=Yur+~UZ9jWfl@9KR~VBY@ZPy$QByjK#;9_j87mgS{*r_8~@@ zKU65fANSCVS;i}s485;taY)%CUnE+Tq{5(Lhq=HaAm+J#9f;3XO(vDNXX%4*@ zy;i+hDI>kjy-vL)y}O#&jGFkV_%n>Q+Br$`N$W|8N!+E1^cM6V##~F@(FW6&tB9)< zLuYHD+Erx-W!9yQHUwNBxae(&Ao>cNS!p~jUq<0Y3y@5kDw~sOA-GBvCS1p8!scXr zemKj*0PNQpu!8;l1+7w zq1+X5lE`!HZQN$2bB9O|kUq$|)X_%S#$~IMr?qaluK%=XFJci!X-SQan}#DDRuOiA z6KZ}wFe^|+-9=kOvp&yIrFCGTms%#sZ+*PZ+wD~6^o|-gZZYl=mEZCkr;<7`+KdDU zZIFP$JpMd=3%4I`tHJjdGekj>THyZ6Ds zY&LCAcu#Q;l?Am$Hg-`M+v}$*8-1C=wPH4uOgL^Ub;~yWgbBe?$IImPRgUR-8~tpIkO!x5m_dbct=tS&nGHuX?(x zAIKoBHQX?23H~vhWfsu$kfDHKK=*nRV+JpuejXO7Go@RoQ&;-&BJ|U9tMG*Im#S5Do6xWhJqPI&()a~@Hno|tCYrewsN|!c?=F>`qr><15<82@t)5k9To35qQoz*g+l#K($2$B?ZlgP+b6S-LJxo8!(Hif$ajT}PCvLyk=~_52w%eMAXKM*- z?2gQ9RPN6B4qf-(i$!BnZB*5GKFV8Hj#}1LvM!w9ytZQ0`bDm0yryxJz=cxsyVQJa zvSfVpJU_l;=@HMS`!+LGt4&G4#8YlEEKDL);t^v0beuFkdE{(Mh^t+>FxbiQhBing zv4B$^#xKUJ;x6F||GuRdQD^pDce;LOoVP4`pfkJG$*3dbIO4@qW&?~m=a#5B*W=30 zWfhIt@{GFfhH3Y{Gwm-f1)E2$d{^2FJG1llUzR0C}@$k`@pN$vf z`aD^<>84JIKzQ;%VqfCewU58d?P7ty#f~JL!1MUU#g|{5KNYmIA9|bn*k0hAR$sy1I+vo_$h(rh|z!oC#7D6Qc@3sUY4Z^*@_9G!6 zgn|)}|2ak;_`H3^0Pou|e}CSKdxL-me8U0W?(dQQbM$@a`+NV{z6Sz+LwKbkCM^wo zs+c&Mnb|s7+Bv`6=9~t0+_#s~aza4Br@Vb5N-Mwo1)P5Ztg7j(DF@;+v4b!fnc5kf zF}XwRZ_k4u;LZnZLd=|v$lM_|woZKRg5-Z4!3S*L?q((@`|A*AYe8~NIYlxtJ4Z7z zE+!Tx7IGmBGBPp&M^ke?WpRmrh6CRO$t|6o?fICQ-Q3)m+}N4y94(kxd3kx6S=gA_ z*cgE$7@a(9osHZXZJj9oKFNR1BW~to;s~~P2HV+^-JaLT*v`dSkevMXLjQgJeV=CT z;Qw98*6E*V0TX1t{e_v8iG}&U=LUuf-0tO51iPErXo-U%fXskv2=Q=m2>f;Y|L>Ro zUGZNdHUBq~jf0!z-$VcP)BihE&B@GB%nkxv(^=?$>-EpzfB*Q;Kmq34ssBq9f6Mu= zy?~&FFa((YyJ$ifc;9G=fpvTa7FSROJ^?MeeIWvQ83N7UpTIUE>4uO2PYD8o2!gcu zD^+*ItvR%mQPqo^8!ya=;hy2gc_-w}VPt_a6%p^>z9l#Ic=D=3qxAh-l)SeXfq{mI zudu;%G|J-PG*Y`#$t~MmU2!0DD)z44ekPsyvl}CM`5mXYCiA5;y@?a2Ox&8iY&qJ` z0SHLwWd48pM=Wfjol4m zAo%g3Dfu6d0?r;#cn|FnE@;v1kLPxKm)NWUg<_%6IG_juYTOrh87fX9cTdmkPFsKX9qL9FGw27DN2X?eZl(^mRf)*Pn|2R*)HtA}ov+ zem6~mchD4Uhk!y{YK1Ce?XOy?*mt50?D3!Lb3g_)Aj?&s)h5Ef~EZ^Qnt%_#K?+DY`fcdVZ=>4Z?&EwChPh zDtC%Gt^|dD60I-00P>$}l<9ka&W}lIqE<>4xN%5~;zw{MV!dK+KX{$yE zs5X32Z8%Z-tNJcVUrJ|)Jg}9u;Mr$s0rj6g!-8gF=4%C+kHZnIw%NJQt9MSoPxiy^ zAaYYQL_squoct@l4kP5jNT(z4w#s%djy%6z8wVcO0Y(ORwMEj=vC=Miq5iR|tn~5o zT^$=I%mtkOyWIC%jJhc^b~-*ze{-xH6wkM7YnH9DzKERM+(+q@jwGwpDNxFVMEuNL*yqLy}@^x4+&yom5hu`lK6IXY(=F|Gp7Y!8K9G_8A(6d zstxJ$DGMy=^T3$d?aSeblamA`gpfa9Ng!A_A%%rWJ952H+CHKkQp&;`E&ahU2G6p1 zso#M*5?GpGZZnh0&y?IEqb)&h&wPhdla<^r92@D+kQ;nH)6iVN8R^=hF5gpUn5IA@ z2PV*QuT<7nzmPs?`g4a6Lx{YkE?%9Mxl7)Ehfs`&9Eg!p0tKr{@bqz~K+A}sdOdaa+6`zUkqBvO9n%)o%%keERXa{r zWd%`pPzo|b=J&2u0{LZW=E1|Ni8YYO%1!q2*XQhdLExg25BWUhNl6I_FV7B^!wwr+ zpZ~d|A&a3vzUKE;Ha0dsbmK&_+!*o9%aU0Cz-jI0Sv|s-l0+nf%b2iI6^zklEkHaWghN znoHQmaa44T4RX2(QM!u*IK?0UEM0;f3EWXo!YkxTDr%lG_Qp15Kr9z)W=vS%&Txu*is|1>F&C-Xfc+ix#QB|G- z?H_Z^3ceX$&eduqC5 z#pS?vRA)S_{G_d<Gph!u|D@#dY2*JdY+H7PYy$dRgMGB36HlhwvX{#pKzETMS zV|=z9tB#!PXU}Ql*u(&j!}sJ$6w`+6kVkQFagT%o!EbQxV!I-ix6@D;j);mHL-h;< z7c;xyaqga#(&uZ<%&Yy#&P+Dc-bl)t4!6=)WMrk4o*8G?3dg5?a;?LHbC-FvA)!4Y z8MFUpsjjs zjRqn+pvj(KUixN*Sf2fSEVFF8T3CAH!DAiPzR8>sca{YwYZkd073H`i5u!E@u}FnK zUuqFD0YqSM$Y4%R&Jo^dVq4m?IC5%2;V%_GS!nzg_XN#eXyPEygg-<)VKSuFc5!+JKXhmeyst z`ZOoxi#N)fyX0xSI^RFFeL2Sfv$A$rftAjS!F2ZKS0*%Ps6*ZZQ4RLO6o8%0!Bqq! zYZPj(;QOj=9MbrK;2*v&kz#jnNXjTpf#95wk!rkQ<=R7#9%bLOI>lC?rK``6bEpa+ zsW(aIQbqK6MF&ng9?+Mv;rKGLSjG8L#>R2JfZRS`VGxG?xdZ3b6G0hK{m8(@C6UYX zR7T}Tb)jXZgdY2K;lpj!tFmjhXa+n`Re1t*o{U!uWoXo*(p7?`?@}H7=ZQZ8k`cxv z>PYphmUA^mv4=X~u?q6DHvKjsi!cUUK!0Ub9@&h?ytup z*GGW6cxwuBJ(cuOO6wVy;xFiVgxP>fY^bc#<*75Yywx8txKlv>aR1WldkmaUyP8y7 zUaNI|J z2rVk(&;8mUXR%QXw(=Sng$PCw>sUv&*bC?OQ@zY*g5a+_82^>Z`ue?LTCv>R=MnC| zoT2(CK|l2!;;Fv~I?@d(PhnXg{$j0hnl>*Z=Ub?{l&l~U%E;)LRLBT9DIh?J5jM*e zuB?p!wN}m)bDQ-dAzWPCeDOVZwT?T{J=i&)TCUJua&f6Yf);(~z|U$61h>!^6*;7` z-G*+J=mJs8oj@>M0`xN&c_e`t?G7`+Zt%x|7O|vZ6cl}du*i*R-v*=x{GG=`cq5`% z&>~Mf8HyDAorzAKJ2)tjujtSsku;2Cvvk8W>I7(u`6}MXs0?_6ECtH`>*-x?5AM&I z0;OVAmv@@(9;y3Kp`5k{J`BAetN?EBK9rC?zbI5*Mp?&$sw&Zs^&lQHO6EhEW zB&Q~ESU|_4I7Tb!A=D#uc+}0N)>ymJlHX0n(J}72NL@xnG7j#jt<2WOK@xN)ePlsC zG}Nxf2SwDNj)-Fdnko4D0vFO>jAUJ`u*rFG$lkorI8Z_s9RId7#r!EqR(@RBGKqqc z63`XY;eJ4olCrvxM;Mej?vla`BC7&!?VY*J4?!jo5dAZ`noKM@0`Q-b=8^t$x2#Mqm>l(#ByzuW$qw+zJG5 zc;y45_FoVAX>@gc8m;5k{8ga81;d~%yna8g8% z;(#o#bEPEZK<2=sfC_jR2jYW9&e5SEzsi*E!aryNij{^h5z&`ObSq|d7kULX^9*9K zN*5M6l+idGT);3Exb6hI2#_RL7^%3_b8?jL0SG&dX$FX7ryvqndgx=&2>2B9E>R@j zA29od0F06%iV*-ei<(KSf&7$Vt)t_aMFAYS9e~dT;#0I?I|nn5{GS^vLO={kDX?-p zR8$?hymFZ(fKT~Bl>)Vj8QFXQrDK#6taD>w)QXFXJ329H=U9LxI^Dq=l5rpc8dVM7(5kLi zPOC6ZOH)G`s5Ap^ay0TiQCsz(uZZehNY)ml79bT82VliC!($-Kh_Va$7%0DdjhR&h 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 literal 111824 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 $&blZvHHdhp|(U$!7gmduOC7f`) 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%0A7E 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
Evj7_xv+r{3n&&`uXuR>8vvr4;R%Zk59 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 W@L#6w-MIQtT{vfXw&2XQ$o~a}k!3aj diff --git a/docs/user/security/api-keys/images/create-api-key.png b/docs/user/security/api-keys/images/create-api-key.png new file mode 100644 index 0000000000000000000000000000000000000000..c763dcbfa53f8600a92caa42f50c11943b3ebad2 GIT binary patch literal 377920 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 literal 0 HcmV?d00001 diff --git a/docs/user/security/api-keys/index.asciidoc b/docs/user/security/api-keys/index.asciidoc index 3edd1f8f9c63d..6b92ab3c6656a 100644 --- a/docs/user/security/api-keys/index.asciidoc +++ b/docs/user/security/api-keys/index.asciidoc @@ -4,7 +4,7 @@ API keys enable you to create secondary credentials so that you can send -requests on behalf of the user. Secondary credentials have +requests on behalf of a user. Secondary credentials have the same or lower access rights. For example, if you extract data from an {es} cluster on a daily @@ -14,8 +14,7 @@ and then put the API credentials into a cron job. Or, you might create API keys to automate ingestion of new data from remote sources, without a live user interaction. -You can create API keys from the {kib} Console. To view and invalidate -API keys, open the main menu, then click *Stack Management > API Keys*. +To manage API keys, open the main menu, then click *Stack Management > API Keys*. [role="screenshot"] image:user/security/api-keys/images/api-keys.png["API Keys UI"] @@ -46,37 +45,15 @@ cluster privileges to use API keys in {kib}. To manage roles, open the main menu [float] [[create-api-key]] === Create an API key -You can {ref}/security-api-create-api-key.html[create an API key] from -the {kib} Console. This example shows how to create an API key -to authenticate to a <>. - -[source,js] -POST /_security/api_key -{ - "name": "kibana_api_key" -} - -This creates an API key with the -name `kibana_api_key`. API key -names must be globally unique. -An expiration date is optional and follows -{ref}/common-options.html#time-units[{es} time unit format]. -When an expiration is not provided, the API key does not expire. - -The response should look something like this: - -[source,js] -{ - "id" : "XFcbCnIBnbwqt2o79G4q", - "name" : "kibana_api_key", - "api_key" : "FD6P5UA4QCWlZZQhYF3YGw" -} - -Now, you can use the API key to request {kib} roles. You'll need to send a request with a -`Authorization` header with a value having the prefix `ApiKey` followed by the credentials, -where credentials is the base64 encoding of `id` and `api_key` joined by a colon. For example: - -[source,js] + +To create an API key, open the main menu, then click *Stack Management > API Keys > Create API key*. + +[role="screenshot"] +image:user/security/api-keys/images/create-api-key.png["Create API Key UI"] + +Once created, you can copy the API key (Base64 encoded) and use it to send requests to {es} on your behalf. For example: + +[source,bash] curl --location --request GET 'http://localhost:5601/api/security/role' \ --header 'Content-Type: application/json;charset=UTF-8' \ --header 'kbn-xsrf: true' \ @@ -84,20 +61,16 @@ curl --location --request GET 'http://localhost:5601/api/security/role' \ [float] [[view-api-keys]] -=== View and invalidate API keys -The *API Keys* feature in Kibana lists your API keys, including the name, date created, -and expiration date. If an API key expires, its status changes from `Active` to `Expired`. +=== View and delete API keys + +The *API Keys* feature in Kibana lists your API keys, including the name, date created, and status. If an API key expires, its status changes from `Active` to `Expired`. If you have `manage_security` or `manage_api_key` permissions, you can view the API keys of all users, and see which API key was created by which user in which realm. If you have only the `manage_own_api_key` permission, you see only a list of your own keys. -You can invalidate API keys individually or in bulk. -Invalidated keys are deleted in batch after seven days. - -[role="screenshot"] -image:user/security/api-keys/images/api-key-invalidate.png["API Keys invalidate"] +You can delete API keys individually or in bulk. You cannot modify an API key. If you need additional privileges, you must create a new key with the desired configuration and invalidate the old key. diff --git a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap index 2800c6cd7c198..a779ef540d72e 100644 --- a/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap +++ b/src/plugins/kibana_react/public/code_editor/__snapshots__/code_editor.test.tsx.snap @@ -9,7 +9,21 @@ exports[`is rendered 1`] = ` height={250} language="loglang" onChange={[Function]} - options={Object {}} + options={ + Object { + "minimap": Object { + "enabled": false, + }, + "renderLineHighlight": "none", + "scrollBeyondLastLine": false, + "scrollbar": Object { + "useShadows": false, + }, + "wordBasedSuggestions": false, + "wordWrap": "on", + "wrappingIndent": "indent", + } + } overrideServices={Object {}} theme="euiColors" value=" diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.stories.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.stories.tsx index a5fdfe773a2f8..09c46bf7a327e 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.stories.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.stories.tsx @@ -78,6 +78,25 @@ storiesOf('CodeEditor', module) }, } ) + .add( + 'transparent background', + () => ( +

+ +
+ ), + { + info: { + text: 'Plaintext Monaco Editor', + }, + } + ) .add( 'custom log language', () => ( diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx index 33f0f311d3a4a..0f279e3bfea32 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.test.tsx @@ -89,8 +89,8 @@ test('editor mount setup', () => { // Verify our mount callback will be called expect(editorWillMount.mock.calls.length).toBe(1); - // Verify our theme will be setup - expect((monaco.editor.defineTheme as jest.Mock).mock.calls.length).toBe(1); + // Verify that both, default and transparent theme will be setup + expect((monaco.editor.defineTheme as jest.Mock).mock.calls.length).toBe(2); // Verify our language features have been registered expect((monaco.languages.onLanguage as jest.Mock).mock.calls.length).toBe(1); diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index cb96f077b219b..51344e2d28ab6 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -9,10 +9,14 @@ import React from 'react'; import ReactResizeDetector from 'react-resize-detector'; import MonacoEditor from 'react-monaco-editor'; - import { monaco } from '@kbn/monaco'; -import { LIGHT_THEME, DARK_THEME } from './editor_theme'; +import { + DARK_THEME, + LIGHT_THEME, + DARK_THEME_TRANSPARENT, + LIGHT_THEME_TRANSPARENT, +} from './editor_theme'; import './editor.scss'; @@ -86,6 +90,11 @@ export interface Props { * Should the editor use the dark theme */ useDarkTheme?: boolean; + + /** + * Should the editor use a transparent background + */ + transparentBackground?: boolean; } export class CodeEditor extends React.Component { @@ -132,8 +141,12 @@ export class CodeEditor extends React.Component { } }); - // Register the theme + // Register themes monaco.editor.defineTheme('euiColors', this.props.useDarkTheme ? DARK_THEME : LIGHT_THEME); + monaco.editor.defineTheme( + 'euiColorsTransparent', + this.props.useDarkTheme ? DARK_THEME_TRANSPARENT : LIGHT_THEME_TRANSPARENT + ); }; _editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor, __monaco: unknown) => { @@ -152,20 +165,33 @@ export class CodeEditor extends React.Component { const { languageId, value, onChange, width, height, options } = this.props; return ( - + <> - + ); } diff --git a/src/plugins/kibana_react/public/code_editor/editor_theme.ts b/src/plugins/kibana_react/public/code_editor/editor_theme.ts index b5d4627a5d89a..0f362a28ea622 100644 --- a/src/plugins/kibana_react/public/code_editor/editor_theme.ts +++ b/src/plugins/kibana_react/public/code_editor/editor_theme.ts @@ -16,7 +16,8 @@ import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; export function createTheme( euiTheme: typeof darkTheme | typeof lightTheme, - selectionBackgroundColor: string + selectionBackgroundColor: string, + backgroundColor?: string ): monaco.editor.IStandaloneThemeData { return { base: 'vs', @@ -87,7 +88,7 @@ export function createTheme( ], colors: { 'editor.foreground': euiTheme.euiColorDarkestShade, - 'editor.background': euiTheme.euiFormBackgroundColor, + 'editor.background': backgroundColor ?? euiTheme.euiFormBackgroundColor, 'editorLineNumber.foreground': euiTheme.euiColorDarkShade, 'editorLineNumber.activeForeground': euiTheme.euiColorDarkShade, 'editorIndentGuide.background': euiTheme.euiColorLightShade, @@ -105,5 +106,7 @@ export function createTheme( const DARK_THEME = createTheme(darkTheme, '#343551'); const LIGHT_THEME = createTheme(lightTheme, '#E3E4ED'); +const DARK_THEME_TRANSPARENT = createTheme(darkTheme, '#343551', '#00000000'); +const LIGHT_THEME_TRANSPARENT = createTheme(lightTheme, '#E3E4ED', '#00000000'); -export { DARK_THEME, LIGHT_THEME }; +export { DARK_THEME, LIGHT_THEME, DARK_THEME_TRANSPARENT, LIGHT_THEME_TRANSPARENT }; diff --git a/src/plugins/kibana_react/public/code_editor/index.tsx b/src/plugins/kibana_react/public/code_editor/index.tsx index 1607e2b2c11be..635e84b1d8c20 100644 --- a/src/plugins/kibana_react/public/code_editor/index.tsx +++ b/src/plugins/kibana_react/public/code_editor/index.tsx @@ -7,7 +7,14 @@ */ import React from 'react'; -import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui'; +import { + EuiDelayRender, + EuiErrorBoundary, + EuiLoadingContent, + EuiFormControlLayout, +} from '@elastic/eui'; +import darkTheme from '@elastic/eui/dist/eui_theme_dark.json'; +import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { useUiSetting } from '../ui_settings'; import type { Props } from './code_editor'; @@ -19,11 +26,54 @@ const Fallback = () => ( ); +/** + * Renders a Monaco code editor with EUI color theme. + * + * @see CodeEditorField to render a code editor in the same style as other EUI form fields. + */ export const CodeEditor: React.FunctionComponent = (props) => { const darkMode = useUiSetting('theme:darkMode'); return ( - }> - - + + }> + + + + ); +}; + +/** + * Renders a Monaco code editor in the same style as other EUI form fields. + */ +export const CodeEditorField: React.FunctionComponent = (props) => { + const { width, height, options } = props; + const darkMode = useUiSetting('theme:darkMode'); + const theme = darkMode ? darkTheme : lightTheme; + const style = { + width, + height, + backgroundColor: options?.readOnly + ? theme.euiFormBackgroundReadOnlyColor + : theme.euiFormBackgroundColor, + }; + + return ( + + + ); }; diff --git a/x-pack/plugins/security/common/model/api_key.ts b/x-pack/plugins/security/common/model/api_key.ts index 08f8378d145ce..f2467468f8069 100644 --- a/x-pack/plugins/security/common/model/api_key.ts +++ b/x-pack/plugins/security/common/model/api_key.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { Role } from './role'; + export interface ApiKey { id: string; name: string; @@ -19,3 +21,5 @@ export interface ApiKeyToInvalidate { id: string; name: string; } + +export type ApiKeyRoleDescriptors = Record; diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index bca8b69d03fca..8eb341ef9bd37 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -export { ApiKey, ApiKeyToInvalidate } from './api_key'; +export { ApiKey, ApiKeyToInvalidate, ApiKeyRoleDescriptors } from './api_key'; export { User, EditUser, getUserDisplayName } from './user'; export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; export { AuthenticationProvider, shouldProviderUseLoginForm } from './authentication_provider'; diff --git a/x-pack/plugins/security/public/components/breadcrumb.tsx b/x-pack/plugins/security/public/components/breadcrumb.tsx index 4462e2bce6abc..353f738501cbe 100644 --- a/x-pack/plugins/security/public/components/breadcrumb.tsx +++ b/x-pack/plugins/security/public/components/breadcrumb.tsx @@ -9,6 +9,8 @@ import type { EuiBreadcrumb } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React, { createContext, useContext, useEffect, useRef } from 'react'; +import type { ChromeStart } from 'src/core/public'; + import { useKibana } from '../../../../../src/plugins/kibana_react/public'; interface BreadcrumbsContext { @@ -81,8 +83,8 @@ export const BreadcrumbsProvider: FunctionComponent = if (onChange) { onChange(breadcrumbs); } else if (services.chrome) { - services.chrome.setBreadcrumbs(breadcrumbs); - services.chrome.docTitle.change(getDocTitle(breadcrumbs)); + const setBreadcrumbs = createBreadcrumbsChangeHandler(services.chrome); + setBreadcrumbs(breadcrumbs); } }; @@ -138,3 +140,17 @@ export function getDocTitle(breadcrumbs: BreadcrumbProps[], maxBreadcrumbs = 2) .reverse() .map(({ text }) => text); } + +export function createBreadcrumbsChangeHandler( + chrome: Pick, + setBreadcrumbs = chrome.setBreadcrumbs +) { + return (breadcrumbs: BreadcrumbProps[]) => { + setBreadcrumbs(breadcrumbs); + if (breadcrumbs.length === 0) { + chrome.docTitle.reset(); + } else { + chrome.docTitle.change(getDocTitle(breadcrumbs)); + } + }; +} diff --git a/x-pack/plugins/security/public/components/confirm_modal.tsx b/x-pack/plugins/security/public/components/confirm_modal.tsx deleted file mode 100644 index 80c2008642d04..0000000000000 --- a/x-pack/plugins/security/public/components/confirm_modal.tsx +++ /dev/null @@ -1,84 +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 { EuiButtonProps, EuiModalProps } from '@elastic/eui'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiModal, - EuiModalBody, - EuiModalFooter, - EuiModalHeader, - EuiModalHeaderTitle, -} from '@elastic/eui'; -import type { FunctionComponent } from 'react'; -import React from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -export interface ConfirmModalProps extends Omit { - confirmButtonText: string; - confirmButtonColor?: EuiButtonProps['color']; - isLoading?: EuiButtonProps['isLoading']; - isDisabled?: EuiButtonProps['isDisabled']; - onCancel(): void; - onConfirm(): void; -} - -/** - * Component that renders a confirmation modal similar to `EuiConfirmModal`, except that - * it adds `isLoading` prop, which renders a loading spinner and disables action buttons. - */ -export const ConfirmModal: FunctionComponent = ({ - children, - confirmButtonColor: buttonColor, - confirmButtonText, - isLoading, - isDisabled, - onCancel, - onConfirm, - title, - ...rest -}) => ( - - - {title} - - {children} - - - - - - - - - - {confirmButtonText} - - - - - -); diff --git a/x-pack/plugins/security/public/components/token_field.tsx b/x-pack/plugins/security/public/components/token_field.tsx new file mode 100644 index 0000000000000..98eee9352937c --- /dev/null +++ b/x-pack/plugins/security/public/components/token_field.tsx @@ -0,0 +1,140 @@ +/* + * Copyright 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 { EuiFieldTextProps } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiCopy, + EuiFormControlLayout, + EuiHorizontalRule, + EuiPopover, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import type { FunctionComponent, ReactElement } from 'react'; +import React from 'react'; + +import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; + +export interface TokenFieldProps extends Omit { + value: string; +} + +export const TokenField: FunctionComponent = (props) => { + return ( + + {(copyText) => ( + + )} + + } + style={{ backgroundColor: 'transparent' }} + readOnly + > + event.currentTarget.select()} + readOnly + /> + + ); +}; + +export interface SelectableTokenFieldOption { + key: string; + value: string; + icon?: string; + label: string; + description?: string; +} + +export interface SelectableTokenFieldProps extends Omit { + options: SelectableTokenFieldOption[]; +} + +export const SelectableTokenField: FunctionComponent = (props) => { + const { options, ...rest } = props; + const [isPopoverOpen, setIsPopoverOpen] = React.useState(false); + const [selectedOption, setSelectedOption] = React.useState( + options[0] + ); + const selectedIndex = options.findIndex((c) => c.key === selectedOption.key); + const closePopover = () => setIsPopoverOpen(false); + + return ( + setIsPopoverOpen(!isPopoverOpen)} + > + {selectedOption.label} + + } + isOpen={isPopoverOpen} + panelPaddingSize="none" + closePopover={closePopover} + > + ((items, option, i) => { + items.push( + { + closePopover(); + setSelectedOption(option); + }} + > + {option.label} + + +

{option.description}

+
+
+ ); + if (i < options.length - 1) { + items.push(); + } + return items; + }, [])} + /> + + } + value={selectedOption.value} + /> + ); +}; diff --git a/x-pack/plugins/security/public/components/use_initial_focus.ts b/x-pack/plugins/security/public/components/use_initial_focus.ts new file mode 100644 index 0000000000000..d8dd57f81070f --- /dev/null +++ b/x-pack/plugins/security/public/components/use_initial_focus.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { DependencyList } from 'react'; +import { useEffect, useRef } from 'react'; + +/** + * Creates a ref for an HTML element, which will be focussed on mount. + * + * @example + * ```typescript + * const firstInput = useInitialFocus(); + * + * + * ``` + * + * Pass in a dependency list to focus conditionally rendered components: + * + * @example + * ```typescript + * const firstInput = useInitialFocus([showField]); + * + * {showField ? : undefined} + * ``` + */ +export function useInitialFocus(deps: DependencyList = []) { + const inputRef = useRef(null); + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, deps); // eslint-disable-line react-hooks/exhaustive-deps + return inputRef; +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts index cfb20229d3f6b..1ba35a20a5e5f 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts @@ -10,5 +10,6 @@ export const apiKeysAPIClientMock = { checkPrivileges: jest.fn(), getApiKeys: jest.fn(), invalidateApiKeys: jest.fn(), + createApiKey: jest.fn(), }), }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts index 8c79ee5bb0be5..03c256942ea5d 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.test.ts @@ -84,4 +84,20 @@ describe('APIKeysAPIClient', () => { body: JSON.stringify({ apiKeys: mockAPIKeys, isAdmin: true }), }); }); + + it('createApiKey() queries correct endpoint', async () => { + const httpMock = httpServiceMock.createStartContract(); + + const mockResponse = Symbol('mockResponse'); + httpMock.post.mockResolvedValue(mockResponse); + + const apiClient = new APIKeysAPIClient(httpMock); + const mockAPIKeys = { name: 'name', expiration: '7d' }; + + await expect(apiClient.createApiKey(mockAPIKeys)).resolves.toBe(mockResponse); + expect(httpMock.post).toHaveBeenCalledTimes(1); + expect(httpMock.post).toHaveBeenCalledWith('/internal/security/api_key', { + body: JSON.stringify(mockAPIKeys), + }); + }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts index 318837f091327..65540fd7ebfc1 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts @@ -7,23 +7,36 @@ import type { HttpStart } from 'src/core/public'; -import type { ApiKey, ApiKeyToInvalidate } from '../../../common/model'; +import type { ApiKey, ApiKeyRoleDescriptors, ApiKeyToInvalidate } from '../../../common/model'; -interface CheckPrivilegesResponse { +export interface CheckPrivilegesResponse { areApiKeysEnabled: boolean; isAdmin: boolean; canManage: boolean; } -interface InvalidateApiKeysResponse { +export interface InvalidateApiKeysResponse { itemsInvalidated: ApiKeyToInvalidate[]; errors: any[]; } -interface GetApiKeysResponse { +export interface GetApiKeysResponse { apiKeys: ApiKey[]; } +export interface CreateApiKeyRequest { + name: string; + expiration?: string; + role_descriptors?: ApiKeyRoleDescriptors; +} + +export interface CreateApiKeyResponse { + id: string; + name: string; + expiration: number; + api_key: string; +} + const apiKeysUrl = '/internal/security/api_key'; export class APIKeysAPIClient { @@ -42,4 +55,10 @@ export class APIKeysAPIClient { body: JSON.stringify({ apiKeys, isAdmin }), }); } + + public async createApiKey(apiKey: CreateApiKeyRequest) { + return await this.http.post(apiKeysUrl, { + body: JSON.stringify(apiKey), + }); + } } diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap deleted file mode 100644 index a743c4e610da3..0000000000000 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap +++ /dev/null @@ -1,243 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APIKeysGridPage renders a callout when API keys are not enabled 1`] = ` - - } -> -
- -`; - -exports[`APIKeysGridPage renders permission denied if user does not have required permissions 1`] = ` - - -
- - -
- - -

- } - iconType="securityApp" - title={ -

- -

- } - > -
- - - - -
- - - - -

- - You need permission to manage API keys - -

-
- -
- - -
-

- - Contact your system administrator. - -

-
-
- - -
- -
- - -
- - -`; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx new file mode 100644 index 0000000000000..eaded9a5c83ee --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_empty_prompt.tsx @@ -0,0 +1,148 @@ +/* + * Copyright 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 { EuiAccordion, EuiEmptyPrompt, EuiErrorBoundary, EuiSpacer, EuiText } from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { DocLink } from '../../../components/doc_link'; +import { useHtmlId } from '../../../components/use_html_id'; + +export interface ApiKeysEmptyPromptProps { + error?: Error; +} + +export const ApiKeysEmptyPrompt: FunctionComponent = ({ + error, + children, +}) => { + const accordionId = useHtmlId('apiKeysEmptyPrompt', 'accordion'); + + if (error) { + if (doesErrorIndicateAPIKeysAreDisabled(error)) { + return ( + +

+ +

+

+ + + +

+ + } + /> + ); + } + + if (doesErrorIndicateUserHasNoPermissionsToManageAPIKeys(error)) { + return ( + + +

+ } + /> + ); + } + + const ThrowError = () => { + throw error; + }; + + return ( + + +

+ } + actions={ + <> + {children} + + + + } + buttonProps={{ + style: { display: 'flex', justifyContent: 'center' }, + }} + arrowDisplay="right" + paddingSize="m" + > + + + + + + + + } + /> + ); + } + + return ( + + +
+ } + body={ +

+ +

+ } + actions={children} + /> + ); +}; + +function doesErrorIndicateAPIKeysAreDisabled(error: Record) { + const message = error.body?.message || ''; + return message.indexOf('disabled.feature="api_keys"') !== -1; +} + +function doesErrorIndicateUserHasNoPermissionsToManageAPIKeys(error: Record) { + return error.body?.statusCode === 403; +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx index ff9fbad5c05b5..ba879e99f1598 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.test.tsx @@ -5,182 +5,292 @@ * 2.0. */ -import { EuiCallOut } from '@elastic/eui'; -import type { ReactWrapper } from 'enzyme'; +import { + fireEvent, + render, + waitFor, + waitForElementToBeRemoved, + within, +} from '@testing-library/react'; +import { createMemoryHistory } from 'history'; import React from 'react'; -import { mountWithIntl } from '@kbn/test/jest'; -import type { PublicMethodsOf } from '@kbn/utility-types'; -import { coreMock } from 'src/core/public/mocks'; -import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; - -import type { APIKeysAPIClient } from '../api_keys_api_client'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { mockAuthenticatedUser } from '../../../../common/model/authenticated_user.mock'; +import { securityMock } from '../../../mocks'; +import { Providers } from '../api_keys_management_app'; import { apiKeysAPIClientMock } from '../index.mock'; import { APIKeysGridPage } from './api_keys_grid_page'; -import { NotEnabled } from './not_enabled'; -import { PermissionDenied } from './permission_denied'; - -const mock500 = () => ({ body: { error: 'Internal Server Error', message: '', statusCode: 500 } }); - -const waitForRender = async ( - wrapper: ReactWrapper, - condition: (wrapper: ReactWrapper) => boolean -) => { - return new Promise((resolve, reject) => { - const interval = setInterval(async () => { - await Promise.resolve(); - wrapper.update(); - if (condition(wrapper)) { - resolve(); - } - }, 10); - - setTimeout(() => { - clearInterval(interval); - reject(new Error('waitForRender timeout after 2000ms')); - }, 2000); - }); -}; -describe('APIKeysGridPage', () => { - let apiClientMock: jest.Mocked>; - beforeEach(() => { - apiClientMock = apiKeysAPIClientMock.create(); - apiClientMock.checkPrivileges.mockResolvedValue({ - isAdmin: true, - areApiKeysEnabled: true, - canManage: true, - }); - apiClientMock.getApiKeys.mockResolvedValue({ - apiKeys: [ - { - creation: 1571322182082, - expiration: 1571408582082, - id: '0QQZ2m0BO2XZwgJFuWTT', - invalidated: false, - name: 'my-api-key', - realm: 'reserved', - username: 'elastic', - }, - ], - }); - }); +jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ + htmlIdGenerator: () => () => `id-${Math.random()}`, +})); + +jest.setTimeout(15000); + +const coreStart = coreMock.createStart(); + +const apiClientMock = apiKeysAPIClientMock.create(); +apiClientMock.checkPrivileges.mockResolvedValue({ + areApiKeysEnabled: true, + canManage: true, + isAdmin: true, +}); +apiClientMock.getApiKeys.mockResolvedValue({ + apiKeys: [ + { + creation: 1571322182082, + expiration: 1571408582082, + id: '0QQZ2m0BO2XZwgJFuWTT', + invalidated: false, + name: 'first-api-key', + realm: 'reserved', + username: 'elastic', + }, + { + creation: 1571322182082, + expiration: 1571408582082, + id: 'BO2XZwgJFuWTT0QQZ2m0', + invalidated: false, + name: 'second-api-key', + realm: 'reserved', + username: 'elastic', + }, + ], +}); - const coreStart = coreMock.createStart(); - const renderView = () => { - return mountWithIntl( - - - +const authc = securityMock.createSetup().authc; +authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ + username: 'jdoe', + full_name: '', + email: '', + enabled: true, + roles: ['superuser'], + }) +); + +describe('APIKeysGridPage', () => { + it('loads and displays API keys', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { getByText } = render( + + + ); - }; - it('renders a loading state when fetching API keys', async () => { - expect(renderView().find('[data-test-subj="apiKeysSectionLoading"]')).toHaveLength(1); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/first-api-key/); + getByText(/second-api-key/); }); - it('renders a callout when API keys are not enabled', async () => { - apiClientMock.checkPrivileges.mockResolvedValue({ - isAdmin: true, - canManage: true, + it('displays callout when API keys are disabled', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + apiClientMock.checkPrivileges.mockResolvedValueOnce({ areApiKeysEnabled: false, + canManage: true, + isAdmin: true, }); - const wrapper = renderView(); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(NotEnabled).length > 0; - }); + const { getByText } = render( + + + + ); - expect(wrapper.find(NotEnabled).find(EuiCallOut)).toMatchSnapshot(); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/API keys not enabled/); }); - it('renders permission denied if user does not have required permissions', async () => { - apiClientMock.checkPrivileges.mockResolvedValue({ + it('displays error when user does not have required permissions', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + apiClientMock.checkPrivileges.mockResolvedValueOnce({ + areApiKeysEnabled: true, canManage: false, isAdmin: false, - areApiKeysEnabled: true, }); - const wrapper = renderView(); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(PermissionDenied).length > 0; - }); + const { getByText } = render( + + + + ); - expect(wrapper.find(PermissionDenied)).toMatchSnapshot(); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/You need permission to manage API keys/); }); - it('renders error callout if error fetching API keys', async () => { - apiClientMock.getApiKeys.mockRejectedValue(mock500()); - - const wrapper = renderView(); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(EuiCallOut).length > 0; + it('displays error when fetching API keys fails', async () => { + apiClientMock.getApiKeys.mockRejectedValueOnce({ + body: { error: 'Internal Server Error', message: '', statusCode: 500 }, }); + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { getByText } = render( + + + + ); - expect(wrapper.find('EuiCallOut[data-test-subj="apiKeysError"]')).toHaveLength(1); + await waitForElementToBeRemoved(() => getByText(/Loading API keys/)); + getByText(/Could not load API keys/); }); - describe('Admin view', () => { - let wrapper: ReactWrapper; - beforeEach(() => { - wrapper = renderView(); - }); + it('creates API key when submitting form, redirects back and displays base64', async () => { + const history = createMemoryHistory({ initialEntries: ['/create'] }); + coreStart.http.get.mockResolvedValue([{ name: 'superuser' }]); + coreStart.http.post.mockResolvedValue({ id: '1D', api_key: 'AP1_K3Y' }); + + const { findByRole, findByDisplayValue } = render( + + + + ); + expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role'); - it('renders a callout indicating the user is an administrator', async () => { - const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]'; + const dialog = await findByRole('dialog'); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(calloutEl).length > 0; - }); + fireEvent.click(await findByRole('button', { name: 'Create API key' })); + + const alert = await findByRole('alert'); + within(alert).getByText(/Enter a name/i); - expect(wrapper.find(calloutEl).text()).toEqual('You are an API Key administrator.'); + fireEvent.change(await within(dialog).findByLabelText('Name'), { + target: { value: 'Test' }, }); - it('renders the correct description text', async () => { - const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; + fireEvent.click(await findByRole('button', { name: 'Create API key' })); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(descriptionEl).length > 0; + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { + body: JSON.stringify({ name: 'Test' }), }); - - expect(wrapper.find(descriptionEl).text()).toEqual( - 'View and invalidate API keys. An API key sends requests on behalf of a user.' - ); + expect(history.location.pathname).toBe('/'); }); + + await findByDisplayValue(btoa('1D:AP1_K3Y')); }); - describe('Non-admin view', () => { - let wrapper: ReactWrapper; - beforeEach(() => { - apiClientMock.checkPrivileges.mockResolvedValue({ - isAdmin: false, - canManage: true, - areApiKeysEnabled: true, - }); + it('creates API key with optional expiration, redirects back and displays base64', async () => { + const history = createMemoryHistory({ initialEntries: ['/create'] }); + coreStart.http.get.mockResolvedValue([{ name: 'superuser' }]); + coreStart.http.post.mockResolvedValue({ id: '1D', api_key: 'AP1_K3Y' }); + + const { findByRole, findByDisplayValue } = render( + + + + ); + expect(coreStart.http.get).toHaveBeenCalledWith('/api/security/role'); - wrapper = renderView(); + const dialog = await findByRole('dialog'); + + fireEvent.change(await within(dialog).findByLabelText('Name'), { + target: { value: 'Test' }, }); - it('does NOT render a callout indicating the user is an administrator', async () => { - const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; - const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]'; + fireEvent.click(await within(dialog).findByLabelText('Expire after time')); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(descriptionEl).length > 0; - }); + fireEvent.click(await findByRole('button', { name: 'Create API key' })); - expect(wrapper.find(calloutEl).length).toEqual(0); + const alert = await findByRole('alert'); + within(alert).getByText(/Enter a valid duration or disable this option\./i); + + fireEvent.change(await within(dialog).findByLabelText('Lifetime (days)'), { + target: { value: '12' }, }); - it('renders the correct description text', async () => { - const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]'; + fireEvent.click(await findByRole('button', { name: 'Create API key' })); - await waitForRender(wrapper, (updatedWrapper) => { - return updatedWrapper.find(descriptionEl).length > 0; + await waitFor(() => { + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { + body: JSON.stringify({ name: 'Test', expiration: '12d' }), }); + expect(history.location.pathname).toBe('/'); + }); + + await findByDisplayValue(btoa('1D:AP1_K3Y')); + }); + + it('deletes api key using cta button', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { findByRole, findAllByLabelText } = render( + + + + ); + + const [deleteButton] = await findAllByLabelText(/Delete/i); + fireEvent.click(deleteButton); + + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete API key' })); + + await waitFor(() => { + expect(apiClientMock.invalidateApiKeys).toHaveBeenLastCalledWith( + [{ id: '0QQZ2m0BO2XZwgJFuWTT', name: 'first-api-key' }], + true + ); + }); + }); + + it('deletes multiple api keys using bulk select', async () => { + const history = createMemoryHistory({ initialEntries: ['/'] }); + + const { findByRole, findAllByRole } = render( + + + + ); + + const deleteCheckboxes = await findAllByRole('checkbox', { name: 'Select this row' }); + deleteCheckboxes.forEach((checkbox) => fireEvent.click(checkbox)); + fireEvent.click(await findByRole('button', { name: 'Delete API keys' })); + + const dialog = await findByRole('dialog'); + fireEvent.click(await within(dialog).findByRole('button', { name: 'Delete API keys' })); - expect(wrapper.find(descriptionEl).text()).toEqual( - 'View and invalidate your API keys. An API key sends requests on your behalf.' + await waitFor(() => { + expect(apiClientMock.invalidateApiKeys).toHaveBeenLastCalledWith( + [ + { id: '0QQZ2m0BO2XZwgJFuWTT', name: 'first-api-key' }, + { id: 'BO2XZwgJFuWTT0QQZ2m0', name: 'second-api-key' }, + ], + true ); }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 62ca51be2ede8..442c1d910f814 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -9,10 +9,11 @@ import type { EuiBasicTableColumn, EuiInMemoryTableProps } from '@elastic/eui'; import { EuiBadge, EuiButton, - EuiButtonIcon, EuiCallOut, EuiFlexGroup, EuiFlexItem, + EuiHealth, + EuiIcon, EuiInMemoryTable, EuiPageContent, EuiPageContentBody, @@ -23,8 +24,10 @@ import { EuiTitle, EuiToolTip, } from '@elastic/eui'; +import type { History } from 'history'; import moment from 'moment-timezone'; import React, { Component } from 'react'; +import { Route } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -32,14 +35,20 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import type { NotificationsStart } from 'src/core/public'; import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; import type { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; -import type { APIKeysAPIClient } from '../api_keys_api_client'; -import { EmptyPrompt } from './empty_prompt'; +import { Breadcrumb } from '../../../components/breadcrumb'; +import { SelectableTokenField } from '../../../components/token_field'; +import type { APIKeysAPIClient, CreateApiKeyResponse } from '../api_keys_api_client'; +import { ApiKeysEmptyPrompt } from './api_keys_empty_prompt'; +import { CreateApiKeyFlyout } from './create_api_key_flyout'; +import type { InvalidateApiKeys } from './invalidate_provider'; import { InvalidateProvider } from './invalidate_provider'; import { NotEnabled } from './not_enabled'; import { PermissionDenied } from './permission_denied'; interface Props { + history: History; notifications: NotificationsStart; apiKeysAPIClient: PublicMethodsOf; } @@ -50,9 +59,10 @@ interface State { isAdmin: boolean; canManage: boolean; areApiKeysEnabled: boolean; - apiKeys: ApiKey[]; + apiKeys?: ApiKey[]; selectedItems: ApiKey[]; error: any; + createdApiKey?: CreateApiKeyResponse; } const DATE_FORMAT = 'MMMM Do YYYY HH:mm:ss'; @@ -66,7 +76,7 @@ export class APIKeysGridPage extends Component { isAdmin: false, canManage: false, areApiKeysEnabled: false, - apiKeys: [], + apiKeys: undefined, selectedItems: [], error: undefined, }; @@ -77,6 +87,31 @@ export class APIKeysGridPage extends Component { } public render() { + return ( +
+ + + { + this.props.history.push({ pathname: '/' }); + this.reloadApiKeys(); + this.setState({ createdApiKey: apiKey }); + }} + onCancel={() => this.props.history.push({ pathname: '/' })} + /> + + + {this.renderContent()} +
+ ); + } + + public renderContent() { const { isLoadingApp, isLoadingTable, @@ -87,104 +122,191 @@ export class APIKeysGridPage extends Component { apiKeys, } = this.state; - if (isLoadingApp) { - return ( - - - - - - ); - } - - if (!canManage) { - return ; - } - - if (error) { - const { - body: { error: errorTitle, message, statusCode }, - } = error; - - return ( - - + - } - color="danger" - iconType="alert" - data-test-subj="apiKeysError" - > - {statusCode}: {errorTitle} - {message} - - - ); - } + + + ); + } - if (!areApiKeysEnabled) { - return ( - - - - ); + if (!canManage) { + return ; + } + + if (error) { + return ( + + + + + + + + ); + } + + if (!areApiKeysEnabled) { + return ( + + + + ); + } } if (!isLoadingTable && apiKeys && apiKeys.length === 0) { return ( - + + + + + ); } - const description = ( - -

- {isAdmin ? ( - - ) : ( - - )} -

-
- ); + const concatenated = `${this.state.createdApiKey?.id}:${this.state.createdApiKey?.api_key}`; return ( -

+

-

+
- {description} + +

+ {isAdmin ? ( + + ) : ( + + )} +

+
+
+ + + +
+ {this.state.createdApiKey && !this.state.isLoadingTable && ( + <> + +

+ +

+ +
+ + + )} + {this.renderTable()}
); } private renderTable = () => { - const { apiKeys, selectedItems, isLoadingTable, isAdmin } = this.state; + const { apiKeys, selectedItems, isLoadingTable, isAdmin, error } = this.state; const message = isLoadingTable ? ( { const sorting = { sort: { - field: 'expiration', - direction: 'asc', + field: 'creation', + direction: 'desc', }, } as const; @@ -234,7 +356,7 @@ export class APIKeysGridPage extends Component { > { }} ) : undefined, - toolsRight: ( - this.reloadApiKeys()} - data-test-subj="reloadButton" - > - - - ), box: { incremental: true, }, @@ -270,14 +379,23 @@ export class APIKeysGridPage extends Component { }), multiSelect: false, options: Object.keys( - apiKeys.reduce((apiKeysMap: any, apiKey) => { + apiKeys?.reduce((apiKeysMap: any, apiKey) => { apiKeysMap[apiKey.username] = true; return apiKeysMap; - }, {}) + }, {}) ?? {} ).map((username) => { return { value: username, - view: username, + view: ( + + + + + + {username} + + + ), }; }), }, @@ -289,10 +407,10 @@ export class APIKeysGridPage extends Component { }), multiSelect: false, options: Object.keys( - apiKeys.reduce((apiKeysMap: any, apiKey) => { + apiKeys?.reduce((apiKeysMap: any, apiKey) => { apiKeysMap[apiKey.realm] = true; return apiKeysMap; - }, {}) + }, {}) ?? {} ).map((realm) => { return { value: realm, @@ -306,52 +424,58 @@ export class APIKeysGridPage extends Component { return ( <> - {isAdmin ? ( + {!isAdmin ? ( <> } - color="success" + color="primary" iconType="user" - size="s" - data-test-subj="apiKeyAdminDescriptionCallOut" /> - - + ) : undefined} - { - { - return { - 'data-test-subj': 'apiKeyRow', - }; - }} - /> - } + + {(invalidateApiKeyPrompt) => ( + + )} + ); }; - private getColumnConfig = () => { - const { isAdmin } = this.state; + private getColumnConfig = (invalidateApiKeyPrompt: InvalidateApiKeys) => { + const { isAdmin, createdApiKey } = this.state; + + let config: Array> = []; - let config: Array> = [ + config = config.concat([ { field: 'name', name: i18n.translate('xpack.security.management.apiKeys.table.nameColumnName', { @@ -359,7 +483,7 @@ export class APIKeysGridPage extends Component { }), sortable: true, }, - ]; + ]); if (isAdmin) { config = config.concat([ @@ -369,6 +493,16 @@ export class APIKeysGridPage extends Component { defaultMessage: 'User', }), sortable: true, + render: (username: string) => ( + + + + + + {username} + + + ), }, { field: 'realm', @@ -387,91 +521,83 @@ export class APIKeysGridPage extends Component { defaultMessage: 'Created', }), sortable: true, - render: (creationDateMs: number) => moment(creationDateMs).format(DATE_FORMAT), - }, - { - field: 'expiration', - name: i18n.translate('xpack.security.management.apiKeys.table.expirationDateColumnName', { - defaultMessage: 'Expires', - }), - sortable: true, - render: (expirationDateMs: number) => { - if (expirationDateMs === undefined) { - return ( - - {i18n.translate( - 'xpack.security.management.apiKeys.table.expirationDateNeverMessage', - { - defaultMessage: 'Never', - } - )} - - ); - } - - return moment(expirationDateMs).format(DATE_FORMAT); + mobileOptions: { + show: false, }, + render: (creation: string, item: ApiKey) => ( + + {item.id === createdApiKey?.id ? ( + + + + ) : ( + {moment(creation).fromNow()} + )} + + ), }, { name: i18n.translate('xpack.security.management.apiKeys.table.statusColumnName', { defaultMessage: 'Status', }), render: ({ expiration }: any) => { - const now = Date.now(); + if (!expiration) { + return ( + + + + ); + } - if (now > expiration) { - return Expired; + if (Date.now() > expiration) { + return ( + + + + ); } - return Active; + return ( + + + + + + ); }, }, { - name: i18n.translate('xpack.security.management.apiKeys.table.actionsColumnName', { - defaultMessage: 'Actions', - }), actions: [ { - render: ({ name, id }: any) => { - return ( - - - - {(invalidateApiKeyPrompt) => { - return ( - - - invalidateApiKeyPrompt([{ id, name }], this.onApiKeysInvalidated) - } - /> - - ); - }} - - - - ); - }, + name: i18n.translate('xpack.security.management.apiKeys.table.deleteAction', { + defaultMessage: 'Delete', + }), + description: i18n.translate( + 'xpack.security.management.apiKeys.table.deleteDescription', + { + defaultMessage: 'Delete this API key', + } + ), + icon: 'trash', + type: 'icon', + color: 'danger', + onClick: (item) => + invalidateApiKeyPrompt([{ id: item.id, name: item.name }], this.onApiKeysInvalidated), }, ], }, @@ -498,7 +624,7 @@ export class APIKeysGridPage extends Component { if (!canManage || !areApiKeysEnabled) { this.setState({ isLoadingApp: false }); } else { - this.initiallyLoadApiKeys(); + this.loadApiKeys(); } } catch (e) { this.props.notifications.toasts.addDanger( @@ -510,13 +636,13 @@ export class APIKeysGridPage extends Component { } } - private initiallyLoadApiKeys = () => { - this.setState({ isLoadingApp: true, isLoadingTable: false }); - this.loadApiKeys(); - }; - private reloadApiKeys = () => { - this.setState({ apiKeys: [], isLoadingApp: false, isLoadingTable: true }); + this.setState({ + isLoadingApp: false, + isLoadingTable: true, + createdApiKey: undefined, + error: undefined, + }); this.loadApiKeys(); }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx new file mode 100644 index 0000000000000..27385e4b29b00 --- /dev/null +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx @@ -0,0 +1,378 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + EuiCallOut, + EuiFieldNumber, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiFormFieldset, + EuiFormRow, + EuiIcon, + EuiLoadingContent, + EuiSpacer, + EuiSwitch, + EuiText, +} from '@elastic/eui'; +import type { FunctionComponent } from 'react'; +import React, { useEffect } from 'react'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { CodeEditorField, useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import type { ApiKeyRoleDescriptors } from '../../../../common/model'; +import { DocLink } from '../../../components/doc_link'; +import type { FormFlyoutProps } from '../../../components/form_flyout'; +import { FormFlyout } from '../../../components/form_flyout'; +import { useCurrentUser } from '../../../components/use_current_user'; +import { useForm } from '../../../components/use_form'; +import type { ValidationErrors } from '../../../components/use_form'; +import { useInitialFocus } from '../../../components/use_initial_focus'; +import { RolesAPIClient } from '../../roles/roles_api_client'; +import { APIKeysAPIClient } from '../api_keys_api_client'; +import type { CreateApiKeyRequest, CreateApiKeyResponse } from '../api_keys_api_client'; + +export interface ApiKeyFormValues { + name: string; + expiration: string; + customExpiration: boolean; + customPrivileges: boolean; + role_descriptors: string; +} + +export interface CreateApiKeyFlyoutProps { + defaultValues?: ApiKeyFormValues; + onSuccess?: (apiKey: CreateApiKeyResponse) => void; + onCancel: FormFlyoutProps['onCancel']; +} + +const defaultDefaultValues: ApiKeyFormValues = { + name: '', + expiration: '', + customExpiration: false, + customPrivileges: false, + role_descriptors: JSON.stringify( + { + 'role-a': { + cluster: ['all'], + indices: [ + { + names: ['index-a*'], + privileges: ['read'], + }, + ], + }, + 'role-b': { + cluster: ['all'], + indices: [ + { + names: ['index-b*'], + privileges: ['all'], + }, + ], + }, + }, + null, + 2 + ), +}; + +export const CreateApiKeyFlyout: FunctionComponent = ({ + onSuccess, + onCancel, + defaultValues = defaultDefaultValues, +}) => { + const { services } = useKibana(); + const { value: currentUser, loading: isLoadingCurrentUser } = useCurrentUser(); + const [{ value: roles, loading: isLoadingRoles }, getRoles] = useAsyncFn( + () => new RolesAPIClient(services.http!).getRoles(), + [services.http] + ); + const [form, eventHandlers] = useForm({ + onSubmit: async (values) => { + try { + const apiKey = await new APIKeysAPIClient(services.http!).createApiKey(mapValues(values)); + onSuccess?.(apiKey); + } catch (error) { + throw error; + } + }, + validate, + defaultValues, + }); + const isLoading = isLoadingCurrentUser || isLoadingRoles; + + useEffect(() => { + getRoles(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + if (currentUser && roles) { + const userPermissions = currentUser.roles.reduce( + (accumulator, roleName) => { + const role = roles.find((r) => r.name === roleName); + if (role) { + accumulator[role.name] = role.elasticsearch; + } + return accumulator; + }, + {} + ); + if (!form.touched.role_descriptors) { + form.setValue('role_descriptors', JSON.stringify(userPermissions, null, 2)); + } + } + }, [currentUser, roles]); // eslint-disable-line react-hooks/exhaustive-deps + + const firstFieldRef = useInitialFocus([isLoading]); + + return ( + + {form.submitError && ( + <> + + {(form.submitError as any).body?.message || form.submitError.message} + + + + )} + + {isLoading ? ( + + ) : ( + + + + + + + + + + {currentUser?.username} + + + + + + + + + + + + + form.setValue('customPrivileges', e.target.checked)} + /> + {form.values.customPrivileges && ( + <> + + + + + } + error={form.errors.role_descriptors} + isInvalid={form.touched.role_descriptors && !!form.errors.role_descriptors} + > + form.setValue('role_descriptors', value)} + languageId="xjson" + height={200} + /> + + + + )} + + + + + form.setValue('customExpiration', e.target.checked)} + /> + {form.values.customExpiration && ( + <> + + + + + + + )} + + + {/* Hidden submit button is required for enter key to trigger form submission */} + + + )} + + ); +}; + +export function validate(values: ApiKeyFormValues) { + const errors: ValidationErrors = {}; + + if (!values.name) { + errors.name = i18n.translate('xpack.security.management.apiKeys.createApiKey.nameRequired', { + defaultMessage: 'Enter a name.', + }); + } + + if (values.customExpiration) { + const parsedExpiration = parseFloat(values.expiration); + if (isNaN(parsedExpiration) || parsedExpiration <= 0) { + errors.expiration = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.expirationRequired', + { + defaultMessage: 'Enter a valid duration or disable this option.', + } + ); + } + } + + if (values.customPrivileges) { + if (!values.role_descriptors) { + errors.role_descriptors = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.roleDescriptorsRequired', + { + defaultMessage: 'Enter role descriptors or disable this option.', + } + ); + } else { + try { + JSON.parse(values.role_descriptors); + } catch (e) { + errors.role_descriptors = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.invalidJsonError', + { + defaultMessage: 'Enter valid JSON.', + } + ); + } + } + } + + return errors; +} + +export function mapValues(values: ApiKeyFormValues): CreateApiKeyRequest { + return { + name: values.name, + expiration: values.customExpiration && values.expiration ? `${values.expiration}d` : undefined, + role_descriptors: + values.customPrivileges && values.role_descriptors + ? JSON.parse(values.role_descriptors) + : undefined, + }; +} diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx deleted file mode 100644 index 0987f43a3d14d..0000000000000 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { EuiButton, EuiEmptyPrompt, EuiLink } from '@elastic/eui'; -import React, { Fragment } from 'react'; - -import { FormattedMessage } from '@kbn/i18n/react'; - -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; - -interface Props { - isAdmin: boolean; -} - -export const EmptyPrompt: React.FunctionComponent = ({ isAdmin }) => { - const { services } = useKibana(); - const application = services.application!; - const docLinks = services.docLinks!; - return ( - - {isAdmin ? ( - - ) : ( - - )} - - } - body={ - -

- - - - ), - }} - /> -

-
- } - actions={ - application.navigateToApp('dev_tools')} - data-test-subj="goToConsoleButton" - > - - - } - data-test-subj="emptyPrompt" - /> - ); -}; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts deleted file mode 100644 index c68b2c170df5b..0000000000000 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export { EmptyPrompt } from './empty_prompt'; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts index 4eab1c881c221..dc99861ce0a8d 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { InvalidateProvider } from './invalidate_provider'; +export { InvalidateProvider, InvalidateApiKeys } from './invalidate_provider'; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx index a68534db4fd85..26d1e1f72d31f 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/invalidate_provider/invalidate_provider.tsx @@ -41,7 +41,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ const invalidateApiKeyPrompt: InvalidateApiKeys = (keys, onSuccess = () => undefined) => { if (!keys || !keys.length) { - throw new Error('No API key IDs specified for invalidation'); + throw new Error('No API key IDs specified for deletion'); } setIsModalOpen(true); setApiKeys(keys); @@ -75,16 +75,16 @@ export const InvalidateProvider: React.FunctionComponent = ({ const hasMultipleSuccesses = itemsInvalidated.length > 1; const successMessage = hasMultipleSuccesses ? i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.successMultipleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.successMultipleNotificationTitle', { - defaultMessage: 'Invalidated {count} API keys', + defaultMessage: 'Deleted {count} API keys', values: { count: itemsInvalidated.length }, } ) : i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.successSingleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.successSingleNotificationTitle', { - defaultMessage: "Invalidated API key '{name}'", + defaultMessage: "Deleted API key '{name}'", values: { name: itemsInvalidated[0].name }, } ); @@ -102,7 +102,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ const hasMultipleErrors = (errors && errors.length > 1) || (error && apiKeys.length > 1); const errorMessage = hasMultipleErrors ? i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.errorMultipleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.errorMultipleNotificationTitle', { defaultMessage: 'Error deleting {count} apiKeys', values: { @@ -111,7 +111,7 @@ export const InvalidateProvider: React.FunctionComponent = ({ } ) : i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.errorSingleNotificationTitle', + 'xpack.security.management.apiKeys.deleteApiKey.errorSingleNotificationTitle', { defaultMessage: "Error deleting API key '{name}'", values: { name: (errors && errors[0].name) || apiKeys[0].name }, @@ -130,19 +130,20 @@ export const InvalidateProvider: React.FunctionComponent = ({ return ( = ({ onCancel={closeModal} onConfirm={invalidateApiKey} cancelButtonText={i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.cancelButtonLabel', + 'xpack.security.management.apiKeys.deleteApiKey.confirmModal.cancelButtonLabel', { defaultMessage: 'Cancel' } )} confirmButtonText={i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.confirmButtonLabel', + 'xpack.security.management.apiKeys.deleteApiKey.confirmModal.confirmButtonLabel', { - defaultMessage: 'Invalidate {count, plural, one {API key} other {API keys}}', + defaultMessage: 'Delete {count, plural, one {API key} other {API keys}}', values: { count: apiKeys.length }, } )} @@ -167,8 +168,8 @@ export const InvalidateProvider: React.FunctionComponent = ({

{i18n.translate( - 'xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription', - { defaultMessage: 'You are about to invalidate these API keys:' } + 'xpack.security.management.apiKeys.deleteApiKey.confirmModal.deleteMultipleListDescription', + { defaultMessage: 'You are about to delete these API keys:' } )}

    diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx index bada8c5c7ce4c..d2611864e77a2 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx @@ -6,29 +6,36 @@ */ jest.mock('./api_keys_grid', () => ({ - APIKeysGridPage: (props: any) => `Page: ${JSON.stringify(props)}`, + APIKeysGridPage: (props: any) => JSON.stringify(props, null, 2), })); + +import { act } from '@testing-library/react'; + import { coreMock, scopedHistoryMock } from 'src/core/public/mocks'; +import type { Unmount } from 'src/plugins/management/public/types'; +import { securityMock } from '../../mocks'; import { apiKeysManagementApp } from './api_keys_management_app'; describe('apiKeysManagementApp', () => { it('create() returns proper management app descriptor', () => { const { getStartServices } = coreMock.createSetup(); + const { authc } = securityMock.createSetup(); - expect(apiKeysManagementApp.create({ getStartServices: getStartServices as any })) + expect(apiKeysManagementApp.create({ authc, getStartServices: getStartServices as any })) .toMatchInlineSnapshot(` Object { "id": "api_keys", "mount": [Function], "order": 30, - "title": "API Keys", + "title": "API keys", } `); }); it('mount() works for the `grid` page', async () => { const { getStartServices } = coreMock.createSetup(); + const { authc } = securityMock.createSetup(); const startServices = await getStartServices(); const docTitle = startServices[0].chrome.docTitle; @@ -36,28 +43,54 @@ describe('apiKeysManagementApp', () => { const container = document.createElement('div'); const setBreadcrumbs = jest.fn(); - const unmount = await apiKeysManagementApp - .create({ getStartServices: () => Promise.resolve(startServices) as any }) - .mount({ - basePath: '/some-base-path', - element: container, - setBreadcrumbs, - history: scopedHistoryMock.create(), - }); + let unmount: Unmount; + await act(async () => { + unmount = await apiKeysManagementApp + .create({ authc, getStartServices: () => Promise.resolve(startServices) as any }) + .mount({ + basePath: '/some-base-path', + element: container, + setBreadcrumbs, + history: scopedHistoryMock.create(), + }); + }); expect(setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API Keys' }]); - expect(docTitle.change).toHaveBeenCalledWith('API Keys'); + expect(setBreadcrumbs).toHaveBeenCalledWith([{ href: '/', text: 'API keys' }]); + expect(docTitle.change).toHaveBeenCalledWith(['API keys']); expect(docTitle.reset).not.toHaveBeenCalled(); expect(container).toMatchInlineSnapshot(`
    - Page: {"notifications":{"toasts":{}},"apiKeysAPIClient":{"http":{"basePath":{"basePath":"","serverBasePath":""},"anonymousPaths":{},"externalUrl":{}}}} + { + "history": { + "action": "PUSH", + "length": 1, + "location": { + "pathname": "/", + "search": "", + "hash": "" + } + }, + "notifications": { + "toasts": {} + }, + "apiKeysAPIClient": { + "http": { + "basePath": { + "basePath": "", + "serverBasePath": "" + }, + "anonymousPaths": {}, + "externalUrl": {} + } + } + }
    `); - unmount(); - expect(docTitle.reset).toHaveBeenCalledTimes(1); + unmount!(); + expect(docTitle.reset).toHaveBeenCalledTimes(1); expect(container).toMatchInlineSnapshot(`
    `); }); }); diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx index 8fa52ba7e2edd..68e06d38db4c8 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx @@ -5,63 +5,101 @@ * 2.0. */ +import type { History } from 'history'; +import type { FunctionComponent } from 'react'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { Router } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; -import type { StartServicesAccessor } from 'src/core/public'; -import type { RegisterManagementAppArgs } from 'src/plugins/management/public'; +import { I18nProvider } from '@kbn/i18n/react'; +import type { CoreStart, StartServicesAccessor } from '../../../../../../src/core/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; +import type { RegisterManagementAppArgs } from '../../../../../../src/plugins/management/public'; +import type { AuthenticationServiceSetup } from '../../authentication'; +import type { BreadcrumbsChangeHandler } from '../../components/breadcrumb'; +import { + Breadcrumb, + BreadcrumbsProvider, + createBreadcrumbsChangeHandler, +} from '../../components/breadcrumb'; +import { AuthenticationProvider } from '../../components/use_current_user'; import type { PluginStartDependencies } from '../../plugin'; interface CreateParams { + authc: AuthenticationServiceSetup; getStartServices: StartServicesAccessor; } export const apiKeysManagementApp = Object.freeze({ id: 'api_keys', - create({ getStartServices }: CreateParams) { - const title = i18n.translate('xpack.security.management.apiKeysTitle', { - defaultMessage: 'API Keys', - }); + create({ authc, getStartServices }: CreateParams) { return { id: this.id, order: 30, - title, - async mount({ element, setBreadcrumbs }) { - setBreadcrumbs([ - { - text: title, - href: `/`, - }, - ]); - - const [[core], { APIKeysGridPage }, { APIKeysAPIClient }] = await Promise.all([ + title: i18n.translate('xpack.security.management.apiKeysTitle', { + defaultMessage: 'API keys', + }), + async mount({ element, setBreadcrumbs, history }) { + const [[coreStart], { APIKeysGridPage }, { APIKeysAPIClient }] = await Promise.all([ getStartServices(), import('./api_keys_grid'), import('./api_keys_api_client'), ]); - core.chrome.docTitle.change(title); - render( - - + + - - , + + , element ); return () => { - core.chrome.docTitle.reset(); unmountComponentAtNode(element); }; }, } as RegisterManagementAppArgs; }, }); + +export interface ProvidersProps { + services: CoreStart; + history: History; + authc: AuthenticationServiceSetup; + onChange?: BreadcrumbsChangeHandler; +} + +export const Providers: FunctionComponent = ({ + services, + history, + authc, + onChange, + children, +}) => ( + + + + + {children} + + + + +); diff --git a/x-pack/plugins/security/public/management/management_service.test.ts b/x-pack/plugins/security/public/management/management_service.test.ts index 694f3cc3880a2..b21897377d5eb 100644 --- a/x-pack/plugins/security/public/management/management_service.test.ts +++ b/x-pack/plugins/security/public/management/management_service.test.ts @@ -68,7 +68,7 @@ describe('ManagementService', () => { id: 'api_keys', mount: expect.any(Function), order: 30, - title: 'API Keys', + title: 'API keys', }); expect(mockSection.registerApp).toHaveBeenCalledWith({ id: 'role_mappings', diff --git a/x-pack/plugins/security/public/management/management_service.ts b/x-pack/plugins/security/public/management/management_service.ts index 7809a45db1660..af1b05e64e37c 100644 --- a/x-pack/plugins/security/public/management/management_service.ts +++ b/x-pack/plugins/security/public/management/management_service.ts @@ -47,7 +47,7 @@ export class ManagementService { this.securitySection.registerApp( rolesManagementApp.create({ fatalErrors, license, getStartServices }) ); - this.securitySection.registerApp(apiKeysManagementApp.create({ getStartServices })); + this.securitySection.registerApp(apiKeysManagementApp.create({ authc, getStartServices })); this.securitySection.registerApp(roleMappingsManagementApp.create({ getStartServices })); } diff --git a/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx b/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx index 01b387c9e1fc2..445d424adb388 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/change_password_flyout.tsx @@ -28,6 +28,7 @@ import { FormFlyout } from '../../../components/form_flyout'; import { useCurrentUser } from '../../../components/use_current_user'; import type { ValidationErrors } from '../../../components/use_form'; import { useForm } from '../../../components/use_form'; +import { useInitialFocus } from '../../../components/use_initial_focus'; import { UserAPIClient } from '../user_api_client'; export interface ChangePasswordFormValues { @@ -147,6 +148,8 @@ export const ChangePasswordFlyout: FunctionComponent defaultValues, }); + const firstFieldRef = useInitialFocus([isLoading]); + return ( defaultValue={form.values.current_password} isInvalid={form.touched.current_password && !!form.errors.current_password} autoComplete="current-password" + inputRef={firstFieldRef} /> ) : null} @@ -263,6 +267,7 @@ export const ChangePasswordFlyout: FunctionComponent defaultValue={form.values.password} isInvalid={form.touched.password && !!form.errors.password} autoComplete="new-password" + inputRef={isCurrentUser ? undefined : firstFieldRef} /> = ({ }, [services.http]); return ( - = ({ values: { count: usernames.length, isLoading: state.loading }, } )} - confirmButtonColor="danger" + buttonColor="danger" isLoading={state.loading} > @@ -94,6 +100,6 @@ export const ConfirmDeleteUsers: FunctionComponent = ({ />

    -
    + ); }; diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx index a3d36e19504e1..e8779a3bb59b9 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiText } from '@elastic/eui'; +import { EuiConfirmModal, EuiText } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; @@ -13,9 +13,8 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { UserAPIClient } from '..'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ConfirmModal } from '../../../components/confirm_modal'; -import { UserAPIClient } from '../user_api_client'; export interface ConfirmDisableUsersProps { usernames: string[]; @@ -58,13 +57,20 @@ export const ConfirmDisableUsers: FunctionComponent = }, [services.http]); return ( - = values: { count: usernames.length, isLoading: state.loading }, }) } - confirmButtonColor={isSystemUser ? 'danger' : undefined} + buttonColor={isSystemUser ? 'danger' : undefined} isLoading={state.loading} > {isSystemUser ? ( @@ -89,7 +95,7 @@ export const ConfirmDisableUsers: FunctionComponent =

    @@ -117,6 +123,6 @@ export const ConfirmDisableUsers: FunctionComponent = )} )} - + ); }; diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx index 24364d7b56d99..68c9a645eaa9a 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiText } from '@elastic/eui'; +import { EuiConfirmModal, EuiText } from '@elastic/eui'; import type { FunctionComponent } from 'react'; import React from 'react'; import useAsyncFn from 'react-use/lib/useAsyncFn'; @@ -13,9 +13,8 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { UserAPIClient } from '..'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; -import { ConfirmModal } from '../../../components/confirm_modal'; -import { UserAPIClient } from '../user_api_client'; export interface ConfirmEnableUsersProps { usernames: string[]; @@ -54,13 +53,20 @@ export const ConfirmEnableUsers: FunctionComponent = ({ }, [services.http]); return ( - = ({

)} - +
); }; diff --git a/x-pack/plugins/security/public/management/users/users_management_app.tsx b/x-pack/plugins/security/public/management/users/users_management_app.tsx index 3e18734cbf368..f6a2956c7ad43 100644 --- a/x-pack/plugins/security/public/management/users/users_management_app.tsx +++ b/x-pack/plugins/security/public/management/users/users_management_app.tsx @@ -20,7 +20,11 @@ import type { RegisterManagementAppArgs } from 'src/plugins/management/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import type { AuthenticationServiceSetup } from '../../authentication'; import type { BreadcrumbsChangeHandler } from '../../components/breadcrumb'; -import { Breadcrumb, BreadcrumbsProvider, getDocTitle } from '../../components/breadcrumb'; +import { + Breadcrumb, + BreadcrumbsProvider, + createBreadcrumbsChangeHandler, +} from '../../components/breadcrumb'; import { AuthenticationProvider } from '../../components/use_current_user'; import type { PluginStartDependencies } from '../../plugin'; import { tryDecodeURIComponent } from '../url_utils'; @@ -64,10 +68,7 @@ export const usersManagementApp = Object.freeze({ services={coreStart} history={history} authc={authc} - onChange={(breadcrumbs) => { - setBreadcrumbs(breadcrumbs); - coreStart.chrome.docTitle.change(getDocTitle(breadcrumbs)); - }} + onChange={createBreadcrumbsChangeHandler(coreStart.chrome, setBreadcrumbs)} > { + function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } + ) { + return ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as SecurityRequestHandlerContext; + } + + let routeHandler: RequestHandler; + let authc: DeeplyMockedKeys; + beforeEach(() => { + authc = authenticationServiceMock.createStart(); + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.getAuthenticationService.mockReturnValue(authc); + + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + + const [, apiKeyRouteHandler] = mockRouteDefinitionParams.router.post.mock.calls.find( + ([{ path }]) => path === '/internal/security/api_key' + )!; + routeHandler = apiKeyRouteHandler; + }); + + describe('failure', () => { + test('returns result of license checker', async () => { + const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); + const response = await routeHandler( + mockContext, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + }); + + test('returns error from cluster client', async () => { + const error = Boom.notAcceptable('test not acceptable message'); + authc.apiKeys.create.mockRejectedValue(error); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(406); + expect(response.payload).toEqual(error); + }); + }); + + describe('success', () => { + test('allows an API Key to be created', async () => { + authc.apiKeys.create.mockResolvedValue({ + api_key: 'abc123', + id: 'key_id', + name: 'my api key', + }); + + const payload = { + name: 'my api key', + expires: '12d', + role_descriptors: { + role_1: {}, + }, + }; + + const request = httpServerMock.createKibanaRequest({ + body: { + ...payload, + }, + }); + + const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + expect(authc.apiKeys.create).toHaveBeenCalledWith(request, payload); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ + api_key: 'abc123', + id: 'key_id', + name: 'my api key', + }); + }); + + test('returns a message if API Keys are disabled', async () => { + authc.apiKeys.create.mockResolvedValue(null); + + const payload = { + name: 'my api key', + expires: '12d', + role_descriptors: { + role_1: {}, + }, + }; + + const request = httpServerMock.createKibanaRequest({ + body: { + ...payload, + }, + }); + + const response = await routeHandler(getMockContext(), request, kibanaResponseFactory); + expect(authc.apiKeys.create).toHaveBeenCalledWith(request, payload); + + expect(response.status).toBe(400); + expect(response.payload).toEqual({ + message: 'API Keys are not available', + }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts new file mode 100644 index 0000000000000..a309d3a0e3edb --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/create.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 { schema } from '@kbn/config-schema'; + +import type { RouteDefinitionParams } from '..'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; + +export function defineCreateApiKeyRoutes({ + router, + getAuthenticationService, +}: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/api_key', + validate: { + body: schema.object({ + name: schema.string(), + expiration: schema.maybe(schema.string()), + role_descriptors: schema.recordOf( + schema.string(), + schema.object({}, { unknowns: 'allow' }), + { + defaultValue: {}, + } + ), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const apiKey = await getAuthenticationService().apiKeys.create(request, request.body); + + if (!apiKey) { + return response.badRequest({ body: { message: `API Keys are not available` } }); + } + + return response.ok({ body: apiKey }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/api_keys/index.ts b/x-pack/plugins/security/server/routes/api_keys/index.ts index e6a8711bdf19e..aa1e3b858ea58 100644 --- a/x-pack/plugins/security/server/routes/api_keys/index.ts +++ b/x-pack/plugins/security/server/routes/api_keys/index.ts @@ -6,6 +6,7 @@ */ import type { RouteDefinitionParams } from '../'; +import { defineCreateApiKeyRoutes } from './create'; import { defineEnabledApiKeysRoutes } from './enabled'; import { defineGetApiKeysRoutes } from './get'; import { defineInvalidateApiKeysRoutes } from './invalidate'; @@ -14,6 +15,7 @@ import { defineCheckPrivilegesRoutes } from './privileges'; export function defineApiKeysRoutes(params: RouteDefinitionParams) { defineEnabledApiKeysRoutes(params); defineGetApiKeysRoutes(params); + defineCreateApiKeyRoutes(params); defineCheckPrivilegesRoutes(params); defineInvalidateApiKeysRoutes(params); } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 527f32828979a..8f71353113f5f 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17385,7 +17385,6 @@ "xpack.security.components.sessionIdleTimeoutWarning.title": "警告", "xpack.security.components.sessionLifespanWarning.message": "セッションは最大時間制限{timeout}に達しました。もう一度ログインする必要があります。", "xpack.security.components.sessionLifespanWarning.title": "警告", - "xpack.security.confirmModal.cancelButton": "キャンセル", "xpack.security.conflictingSessionError": "申し訳ありません。すでに有効なKibanaセッションがあります。新しいセッションを開始する場合は、先に既存のセッションからログアウトしてください。", "xpack.security.formFlyout.cancelButton": "キャンセル", "xpack.security.loggedOut.login": "ログイン", @@ -17421,19 +17420,7 @@ "xpack.security.loginWithElasticsearchLabel": "Elasticsearchでログイン", "xpack.security.logoutAppTitle": "ログアウト", "xpack.security.management.apiKeys.deniedPermissionTitle": "API キーを管理するにはパーミッションが必要です", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.cancelButtonLabel": "キャンセル", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription": "これらの API キーを無効化しようとしています:", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleTitle": "{count} API キーを無効にしますか?", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateSingleTitle": "API キー「{name}」を無効にしますか?", - "xpack.security.management.apiKeys.invalidateApiKey.errorMultipleNotificationTitle": "{count} 件の API キーの削除中にエラーが発生", - "xpack.security.management.apiKeys.invalidateApiKey.errorSingleNotificationTitle": "API キー「{name}」の削除中にエラーが発生", - "xpack.security.management.apiKeys.invalidateApiKey.successMultipleNotificationTitle": "無効な {count} API キー", - "xpack.security.management.apiKeys.invalidateApiKey.successSingleNotificationTitle": "API キー「{name}」を無効にしました", "xpack.security.management.apiKeys.noPermissionToManageRolesDescription": "システム管理者にお問い合わせください。", - "xpack.security.management.apiKeys.table.actionDeleteAriaLabel": "「{name}」を無効にする", - "xpack.security.management.apiKeys.table.actionDeleteTooltip": "無効にする", - "xpack.security.management.apiKeys.table.actionsColumnName": "アクション", - "xpack.security.management.apiKeys.table.adminText": "あなたは API キー管理者です。", "xpack.security.management.apiKeys.table.apiKeysAllDescription": "API キーを表示して無効にします。API キーはユーザーの代わりにリクエストを送信します。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorDescription": "システム管理者に連絡し、{link}を参照して API キーを有効にしてください。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText": "ドキュメント", @@ -17442,20 +17429,10 @@ "xpack.security.management.apiKeys.table.apiKeysTableLoadingMessage": "API キーを読み込み中…", "xpack.security.management.apiKeys.table.apiKeysTitle": "API キー", "xpack.security.management.apiKeys.table.creationDateColumnName": "作成済み", - "xpack.security.management.apiKeys.table.emptyPromptAdminTitle": "API キーがありません", - "xpack.security.management.apiKeys.table.emptyPromptConsoleButtonMessage": "コンソールに移動してください", - "xpack.security.management.apiKeys.table.emptyPromptDescription": "コンソールで {link} を作成できます。", - "xpack.security.management.apiKeys.table.emptyPromptDocsLinkMessage": "API キー", - "xpack.security.management.apiKeys.table.emptyPromptNonAdminTitle": "まだ API キーがありません", - "xpack.security.management.apiKeys.table.expirationDateColumnName": "有効期限", - "xpack.security.management.apiKeys.table.expirationDateNeverMessage": "なし", "xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage": "権限の確認エラー:{message}", "xpack.security.management.apiKeys.table.loadingApiKeysDescription": "API キーを読み込み中…", - "xpack.security.management.apiKeys.table.loadingApiKeysErrorTitle": "API キーを読み込み中にエラーが発生", "xpack.security.management.apiKeys.table.nameColumnName": "名前", - "xpack.security.management.apiKeys.table.realmColumnName": "レルム", "xpack.security.management.apiKeys.table.realmFilterLabel": "レルム", - "xpack.security.management.apiKeys.table.reloadApiKeysButton": "再読み込み", "xpack.security.management.apiKeys.table.statusColumnName": "ステータス", "xpack.security.management.apiKeys.table.userFilterLabel": "ユーザー", "xpack.security.management.apiKeys.table.userNameColumnName": "ユーザー", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f8c8ee753942c..7269615c051db 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17625,7 +17625,6 @@ "xpack.security.components.sessionIdleTimeoutWarning.title": "警告", "xpack.security.components.sessionLifespanWarning.message": "您的会话将达到最大时间限制 {timeout}。您将需要重新登录。", "xpack.security.components.sessionLifespanWarning.title": "警告", - "xpack.security.confirmModal.cancelButton": "取消", "xpack.security.conflictingSessionError": "抱歉,您已有活动的 Kibana 会话。如果希望开始新的会话,请首先从现有会话注销。", "xpack.security.formFlyout.cancelButton": "取消", "xpack.security.loggedOut.login": "登录", @@ -17661,20 +17660,7 @@ "xpack.security.loginWithElasticsearchLabel": "通过 Elasticsearch 登录", "xpack.security.logoutAppTitle": "注销", "xpack.security.management.apiKeys.deniedPermissionTitle": "您需要管理 API 密钥的权限", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.cancelButtonLabel": "取消", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.confirmButtonLabel": "作废 {count, plural, other {API 密钥}}", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleListDescription": "您即将作废以下 API 密钥:", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateMultipleTitle": "作废 {count} 个 API 密钥?", - "xpack.security.management.apiKeys.invalidateApiKey.confirmModal.invalidateSingleTitle": "作废 API 密钥“{name}”?", - "xpack.security.management.apiKeys.invalidateApiKey.errorMultipleNotificationTitle": "删除 {count} 个 API 密钥时出错", - "xpack.security.management.apiKeys.invalidateApiKey.errorSingleNotificationTitle": "删除 API 密钥“{name}”时出错", - "xpack.security.management.apiKeys.invalidateApiKey.successMultipleNotificationTitle": "已作废 {count} 个 API 密钥", - "xpack.security.management.apiKeys.invalidateApiKey.successSingleNotificationTitle": "已作废 API 密钥“{name}”", "xpack.security.management.apiKeys.noPermissionToManageRolesDescription": "请联系您的系统管理员。", - "xpack.security.management.apiKeys.table.actionDeleteAriaLabel": "作废“{name}”", - "xpack.security.management.apiKeys.table.actionDeleteTooltip": "作废", - "xpack.security.management.apiKeys.table.actionsColumnName": "操作", - "xpack.security.management.apiKeys.table.adminText": "您是 API 密钥管理员。", "xpack.security.management.apiKeys.table.apiKeysAllDescription": "查看并作废 API 密钥。API 密钥代表用户发送请求。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorDescription": "请联系您的系统管理员并参阅{link}以启用 API 密钥。", "xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText": "文档", @@ -17683,21 +17669,11 @@ "xpack.security.management.apiKeys.table.apiKeysTableLoadingMessage": "正在加载 API 密钥……", "xpack.security.management.apiKeys.table.apiKeysTitle": "API 密钥", "xpack.security.management.apiKeys.table.creationDateColumnName": "已创建", - "xpack.security.management.apiKeys.table.emptyPromptAdminTitle": "无 API 密钥", - "xpack.security.management.apiKeys.table.emptyPromptConsoleButtonMessage": "前往 Console", - "xpack.security.management.apiKeys.table.emptyPromptDescription": "您可以从 Console 创建 {link}。", - "xpack.security.management.apiKeys.table.emptyPromptDocsLinkMessage": "API 密钥", - "xpack.security.management.apiKeys.table.emptyPromptNonAdminTitle": "您未有任何 API 密钥", - "xpack.security.management.apiKeys.table.expirationDateColumnName": "过期", - "xpack.security.management.apiKeys.table.expirationDateNeverMessage": "永不", "xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage": "检查权限时出错:{message}", "xpack.security.management.apiKeys.table.invalidateApiKeyButton": "作废 {count, plural, other {API 密钥}}", "xpack.security.management.apiKeys.table.loadingApiKeysDescription": "正在加载 API 密钥……", - "xpack.security.management.apiKeys.table.loadingApiKeysErrorTitle": "加载 API 密钥时出错", "xpack.security.management.apiKeys.table.nameColumnName": "名称", - "xpack.security.management.apiKeys.table.realmColumnName": "Realm", "xpack.security.management.apiKeys.table.realmFilterLabel": "Realm", - "xpack.security.management.apiKeys.table.reloadApiKeysButton": "重新加载", "xpack.security.management.apiKeys.table.statusColumnName": "状态", "xpack.security.management.apiKeys.table.userFilterLabel": "用户", "xpack.security.management.apiKeys.table.userNameColumnName": "用户", diff --git a/x-pack/test/api_integration/apis/security/api_keys.ts b/x-pack/test/api_integration/apis/security/api_keys.ts index 596a0b038cfb3..c6513fa800c1c 100644 --- a/x-pack/test/api_integration/apis/security/api_keys.ts +++ b/x-pack/test/api_integration/apis/security/api_keys.ts @@ -25,5 +25,27 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + + describe('POST /internal/security/api_key', () => { + it('should allow an API Key to be created', async () => { + await supertest + .post('/internal/security/api_key') + .set('kbn-xsrf', 'xxx') + .send({ + name: 'test_api_key', + expiration: '12d', + role_descriptors: { + role_1: { + cluster: ['monitor'], + }, + }, + }) + .expect(200) + .then((response: Record) => { + const { name } = response.body; + expect(name).to.eql('test_api_key'); + }); + }); + }); }); } diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index 6191a2b8dbcfc..be8f128359345 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -5,7 +5,6 @@ * 2.0. */ -import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { @@ -13,6 +12,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const log = getService('log'); const security = getService('security'); const testSubjects = getService('testSubjects'); + const find = getService('find'); describe('Home page', function () { before(async () => { @@ -31,17 +31,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('Loads the app', async () => { await security.testUser.setRoles(['test_api_keys']); - log.debug('Checking for section header'); - const headers = await testSubjects.findAll('noApiKeysHeader'); - if (headers.length > 0) { - expect(await headers[0].getVisibleText()).to.be('No API keys'); - const goToConsoleButton = await pageObjects.apiKeys.getGoToConsoleButton(); - expect(await goToConsoleButton.isDisplayed()).to.be(true); - } else { - // page may already contain EiTable with data, then check API Key Admin text - const description = await pageObjects.apiKeys.getApiKeyAdminDesc(); - expect(description).to.be('You are an API Key administrator.'); - } + log.debug('Checking for create API key call to action'); + await find.existsByLinkText('Create API key'); }); }); }; From 6ddc4bff069a80b9afe81f7555a81cc9ba72d315 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Tue, 13 Apr 2021 14:32:11 +0300 Subject: [PATCH 61/79] [TSVB] Wrong custom values formatting for the empty buckets (#96293) * Don't apply formatter for default value * Remove the logic to overwrite the default value because it is not being used * Fix remark Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/vis_type_timeseries/common/get_last_value.js | 9 +++++---- .../vis_type_timeseries/common/get_last_value.test.js | 4 ---- .../public/application/components/lib/tick_formatter.js | 6 ++++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/plugins/vis_type_timeseries/common/get_last_value.js b/src/plugins/vis_type_timeseries/common/get_last_value.js index 5a36a5e099f9d..80adf7098f24d 100644 --- a/src/plugins/vis_type_timeseries/common/get_last_value.js +++ b/src/plugins/vis_type_timeseries/common/get_last_value.js @@ -8,13 +8,14 @@ import { isArray, last } from 'lodash'; -const DEFAULT_VALUE = '-'; +export const DEFAULT_VALUE = '-'; + const extractValue = (data) => (data && data[1]) ?? null; -export const getLastValue = (data, defaultValue = DEFAULT_VALUE) => { +export const getLastValue = (data) => { if (!isArray(data)) { - return data ?? defaultValue; + return data ?? DEFAULT_VALUE; } - return extractValue(last(data)) ?? defaultValue; + return extractValue(last(data)) ?? DEFAULT_VALUE; }; diff --git a/src/plugins/vis_type_timeseries/common/get_last_value.test.js b/src/plugins/vis_type_timeseries/common/get_last_value.test.js index 122f037ddf3e4..794bbe17a1e7a 100644 --- a/src/plugins/vis_type_timeseries/common/get_last_value.test.js +++ b/src/plugins/vis_type_timeseries/common/get_last_value.test.js @@ -37,8 +37,4 @@ describe('getLastValue(data)', () => { ]) ).toBe('-'); }); - - test('should allows to override the default value', () => { - expect(getLastValue(null, 'default')).toBe('default'); - }); }); diff --git a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js index c9c0e0b3f43a3..ac4780e673e07 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js +++ b/src/plugins/vis_type_timeseries/public/application/components/lib/tick_formatter.js @@ -8,6 +8,7 @@ import handlebars from 'handlebars/dist/handlebars'; import { isNumber } from 'lodash'; +import { DEFAULT_VALUE } from '../../../../common/get_last_value'; import { inputFormats, outputFormats, isDuration } from '../lib/durations'; import { getFieldFormats } from '../../../services'; @@ -38,6 +39,11 @@ export const createTickFormatter = (format = '0,0.[00]', template, getConfig = n } return (val) => { let value; + + if (val === DEFAULT_VALUE) { + return val; + } + if (!isNumber(val)) { value = val; } else { From d8b4316783dea8450d6fbd298e88359f3de65002 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 13 Apr 2021 14:12:19 +0200 Subject: [PATCH 62/79] [Discover] Close inspector when switching app (#92994) --- .../public/application/components/discover.tsx | 17 ++++++++++++++++- .../application/components/discover_topnav.tsx | 1 - .../top_nav/get_top_nav_links.test.ts | 2 -- .../components/top_nav/get_top_nav_links.ts | 6 ------ 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 6b71bd892b520..0df921dc99ad7 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -43,6 +43,7 @@ import { DiscoverTopNav } from './discover_topnav'; import { ElasticSearchHit } from '../doc_views/doc_views_types'; import { setBreadcrumbsTitle } from '../helpers/breadcrumbs'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; +import { InspectorSession } from '../../../../inspector/public'; const DocTableLegacyMemoized = React.memo(DocTableLegacy); const SidebarMemoized = React.memo(DiscoverSidebarResponsive); @@ -71,6 +72,7 @@ export function Discover({ refreshAppState, }: DiscoverProps) { const [expandedDoc, setExpandedDoc] = useState(undefined); + const [inspectorSession, setInspectorSession] = useState(undefined); const scrollableDesktop = useRef(null); const collapseIcon = useRef(null); const isMobile = () => { @@ -131,7 +133,20 @@ export function Discover({ const onOpenInspector = useCallback(() => { // prevent overlapping setExpandedDoc(undefined); - }, [setExpandedDoc]); + const session = services.inspector.open(opts.inspectorAdapters, { + title: savedSearch.title, + }); + setInspectorSession(session); + }, [setExpandedDoc, opts.inspectorAdapters, savedSearch, services.inspector]); + + useEffect(() => { + return () => { + if (inspectorSession) { + // Close the inspector if this scope is destroyed (e.g. because the user navigates away). + inspectorSession.close(); + } + }; + }, [inspectorSession]); const onSort = useCallback( (sort: string[][]) => { diff --git a/src/plugins/discover/public/application/components/discover_topnav.tsx b/src/plugins/discover/public/application/components/discover_topnav.tsx index ee59ee13583bd..c5c0df6e6f74a 100644 --- a/src/plugins/discover/public/application/components/discover_topnav.tsx +++ b/src/plugins/discover/public/application/components/discover_topnav.tsx @@ -33,7 +33,6 @@ export const DiscoverTopNav = ({ getTopNavLinks({ getFieldCounts: opts.getFieldCounts, indexPattern, - inspectorAdapters: opts.inspectorAdapters, navigateTo: opts.navigateTo, savedSearch: opts.savedSearch, services: opts.services, diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts index 30edb102c420a..f6e9e70b337ba 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.test.ts @@ -8,7 +8,6 @@ import { ISearchSource } from 'src/plugins/data/public'; import { getTopNavLinks } from './get_top_nav_links'; -import { inspectorPluginMock } from '../../../../../inspector/public/mocks'; import { indexPatternMock } from '../../../__mocks__/index_pattern'; import { savedSearchMock } from '../../../__mocks__/saved_search'; import { DiscoverServices } from '../../../build_services'; @@ -28,7 +27,6 @@ test('getTopNavLinks result', () => { const topNavLinks = getTopNavLinks({ getFieldCounts: jest.fn(), indexPattern: indexPatternMock, - inspectorAdapters: inspectorPluginMock, navigateTo: jest.fn(), onOpenInspector: jest.fn(), savedSearch: savedSearchMock, diff --git a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts index 65fef2e4d030f..635684177e1e3 100644 --- a/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts +++ b/src/plugins/discover/public/application/components/top_nav/get_top_nav_links.ts @@ -11,7 +11,6 @@ import { showOpenSearchPanel } from './show_open_search_panel'; import { getSharingData, showPublicUrlSwitch } from '../../helpers/get_sharing_data'; import { unhashUrl } from '../../../../../kibana_utils/public'; import { DiscoverServices } from '../../../build_services'; -import { Adapters } from '../../../../../inspector/common/adapters'; import { SavedSearch } from '../../../saved_searches'; import { onSaveSearch } from './on_save_search'; import { GetStateReturn } from '../../angular/discover_state'; @@ -23,7 +22,6 @@ import { IndexPattern, ISearchSource } from '../../../kibana_services'; export const getTopNavLinks = ({ getFieldCounts, indexPattern, - inspectorAdapters, navigateTo, savedSearch, services, @@ -33,7 +31,6 @@ export const getTopNavLinks = ({ }: { getFieldCounts: () => Promise>; indexPattern: IndexPattern; - inspectorAdapters: Adapters; navigateTo: (url: string) => void; savedSearch: SavedSearch; services: DiscoverServices; @@ -127,9 +124,6 @@ export const getTopNavLinks = ({ testId: 'openInspectorButton', run: () => { onOpenInspector(); - services.inspector.open(inspectorAdapters, { - title: savedSearch.title, - }); }, }; From b9c4d248ae55f698ba375777bb22e15cb02101a4 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 13 Apr 2021 14:15:34 +0200 Subject: [PATCH 63/79] [ESUI] More robust handling of error responses (#96819) * more robust handling of error responses * added tests and further hardening of how we handle error values --- .../errors/handle_es_error.test.ts | 71 +++++++++++++++++++ .../errors/handle_es_error.ts | 8 ++- 2 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.test.ts diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.test.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.test.ts new file mode 100644 index 0000000000000..cff179f64ea08 --- /dev/null +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 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 { errors } from '@elastic/elasticsearch'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { kibanaResponseFactory as response } from 'src/core/server'; +import { handleEsError } from './handle_es_error'; + +const { ResponseError } = errors; + +const anyObject: any = {}; + +describe('handleEsError', () => { + test('top-level reason is an empty string', () => { + const emptyReasonError = new ResponseError({ + warnings: [], + meta: anyObject, + body: { + error: { + root_cause: [], + type: 'search_phase_execution_exception', + reason: '', // Empty reason + phase: 'fetch', + grouped: true, + failed_shards: [], + caused_by: { + type: 'too_many_buckets_exception', + reason: 'This is the nested reason', + max_buckets: 100, + }, + }, + }, + statusCode: 503, + headers: {}, + }); + + const { payload, status } = handleEsError({ error: emptyReasonError, response }); + + expect(payload.message).toEqual('This is the nested reason'); + expect(status).toBe(503); + }); + + test('empty error', () => { + const { payload, status } = handleEsError({ + error: new ResponseError({ + body: {}, + statusCode: 400, + headers: {}, + meta: anyObject, + warnings: [], + }), + response, + }); + + expect(payload).toEqual({ + attributes: { causes: undefined, error: undefined }, + message: 'Response Error', + }); + + expect(status).toBe(400); + }); + + test('unknown object', () => { + expect(() => handleEsError({ error: anyObject, response })).toThrow(); + }); +}); diff --git a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts index 6a308203fcc27..678c46f69d51f 100644 --- a/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts +++ b/src/plugins/es_ui_shared/__packages_do_not_import__/errors/handle_es_error.ts @@ -38,12 +38,14 @@ export const handleEsError = ({ return response.customError({ statusCode, body: { - message: body.error?.reason ?? error.message ?? 'Unknown error', + message: + // We use || instead of ?? as the switch here because reason could be an empty string + body?.error?.reason || body?.error?.caused_by?.reason || error.message || 'Unknown error', attributes: { // The full original ES error object - error: body.error, + error: body?.error, // We assume that this is an ES error object with a nested caused by chain if we can see the "caused_by" field at the top-level - causes: body.error?.caused_by ? getEsCause(body.error) : undefined, + causes: body?.error?.caused_by ? getEsCause(body.error) : undefined, }, }, }); From bfd5b7bda69fde9154b8fc2f955eef5c32f25e33 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Tue, 13 Apr 2021 14:34:32 +0200 Subject: [PATCH 64/79] Exclude non-persisted sessions from SO migration (#96938) --- .../migrations/core/elastic_index.test.ts | 16 ++ .../migrations/core/elastic_index.ts | 57 +++++-- .../saved_objects/migrations/core/index.ts | 1 + .../migrationsv2/actions/index.ts | 15 +- .../integration_tests/actions.test.ts | 10 +- .../migrations_state_action_machine.test.ts | 152 +++++++++++++++--- .../saved_objects/migrationsv2/model.test.ts | 50 +++++- .../saved_objects/migrationsv2/model.ts | 9 +- .../server/saved_objects/migrationsv2/next.ts | 4 +- .../saved_objects/migrationsv2/types.ts | 5 +- 10 files changed, 254 insertions(+), 65 deletions(-) diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts index 2fc78fc619cab..1d2ec6abc0dd1 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.test.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.test.ts @@ -425,6 +425,22 @@ describe('ElasticIndex', () => { type: 'tsvb-validation-telemetry', }, }, + { + bool: { + must: [ + { + match: { + type: 'search-session', + }, + }, + { + match: { + 'search-session.persisted': false, + }, + }, + ], + }, + }, ], }, }, diff --git a/src/core/server/saved_objects/migrations/core/elastic_index.ts b/src/core/server/saved_objects/migrations/core/elastic_index.ts index 462425ff6e3e0..460aabbc77415 100644 --- a/src/core/server/saved_objects/migrations/core/elastic_index.ts +++ b/src/core/server/saved_objects/migrations/core/elastic_index.ts @@ -29,6 +29,46 @@ export interface FullIndexInfo { mappings: IndexMapping; } +// When migrating from the outdated index we use a read query which excludes +// saved objects which are no longer used. These saved objects will still be +// kept in the outdated index for backup purposes, but won't be availble in +// the upgraded index. +export const excludeUnusedTypesQuery: estypes.QueryContainer = { + bool: { + must_not: [ + // https://github.com/elastic/kibana/issues/91869 + { + term: { + type: 'fleet-agent-events', + }, + }, + // https://github.com/elastic/kibana/issues/95617 + { + term: { + type: 'tsvb-validation-telemetry', + }, + }, + // https://github.com/elastic/kibana/issues/96131 + { + bool: { + must: [ + { + match: { + type: 'search-session', + }, + }, + { + match: { + 'search-session.persisted': false, + }, + }, + ], + }, + }, + ], + }, +}; + /** * A slight enhancement to indices.get, that adds indexName, and validates that the * index mappings are somewhat what we expect. @@ -69,23 +109,6 @@ export function reader( const scroll = scrollDuration; let scrollId: string | undefined; - // When migrating from the outdated index we use a read query which excludes - // saved object types which are no longer used. These saved objects will - // still be kept in the outdated index for backup purposes, but won't be - // availble in the upgraded index. - const EXCLUDE_UNUSED_TYPES = [ - 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 - 'tsvb-validation-telemetry', // https://github.com/elastic/kibana/issues/95617 - ]; - - const excludeUnusedTypesQuery = { - bool: { - must_not: EXCLUDE_UNUSED_TYPES.map((type) => ({ - term: { type }, - })), - }, - }; - const nextBatch = () => scrollId !== undefined ? client.scroll>({ diff --git a/src/core/server/saved_objects/migrations/core/index.ts b/src/core/server/saved_objects/migrations/core/index.ts index 322150e2b850e..1e51983a0ffbd 100644 --- a/src/core/server/saved_objects/migrations/core/index.ts +++ b/src/core/server/saved_objects/migrations/core/index.ts @@ -14,3 +14,4 @@ export type { LogFn, SavedObjectsMigrationLogger } from './migration_logger'; export type { MigrationResult, MigrationStatus } from './migration_coordinator'; export { createMigrationEsClient } from './migration_es_client'; export type { MigrationEsClient } from './migration_es_client'; +export { excludeUnusedTypesQuery } from './elastic_index'; diff --git a/src/core/server/saved_objects/migrationsv2/actions/index.ts b/src/core/server/saved_objects/migrationsv2/actions/index.ts index 9d6afbd3b0d87..02d3f8e21a510 100644 --- a/src/core/server/saved_objects/migrationsv2/actions/index.ts +++ b/src/core/server/saved_objects/migrationsv2/actions/index.ts @@ -14,7 +14,6 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors'; import { pipe } from 'fp-ts/lib/pipeable'; import { flow } from 'fp-ts/lib/function'; -import { QueryContainer } from '@elastic/eui/src/components/search_bar/query/ast_to_es_query_dsl'; import { ElasticsearchClient } from '../../../elasticsearch'; import { IndexMapping } from '../../mappings'; import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '../../serialization'; @@ -440,9 +439,9 @@ export const reindex = ( requireAlias: boolean, /* When reindexing we use a source query to exclude saved objects types which * are no longer used. These saved objects will still be kept in the outdated - * index for backup purposes, but won't be availble in the upgraded index. + * index for backup purposes, but won't be available in the upgraded index. */ - unusedTypesToExclude: Option.Option + unusedTypesQuery: Option.Option ): TaskEither.TaskEither => () => { return client .reindex({ @@ -457,14 +456,10 @@ export const reindex = ( // Set reindex batch size size: BATCH_SIZE, // Exclude saved object types - query: Option.fold( + query: Option.fold( () => undefined, - (types) => ({ - bool: { - must_not: types.map((type) => ({ term: { type } })), - }, - }) - )(unusedTypesToExclude), + (query) => query + )(unusedTypesQuery), }, dest: { index: targetIndex, diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts index 21c05d22b0581..3905044f04e2f 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/actions.test.ts @@ -416,14 +416,20 @@ describe('migration actions', () => { ] `); }); - it('resolves right and excludes all unusedTypesToExclude documents', async () => { + it('resolves right and excludes all documents not matching the unusedTypesQuery', async () => { const res = (await reindex( client, 'existing_index_with_docs', 'reindex_target_excluded_docs', Option.none, false, - Option.some(['f_agent_event', 'another_unused_type']) + Option.of({ + bool: { + must_not: ['f_agent_event', 'another_unused_type'].map((type) => ({ + term: { type }, + })), + }, + }) )()) as Either.Right; const task = waitForReindexTask(client, res.right.taskId, '10s'); await expect(task()).resolves.toMatchInlineSnapshot(` diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index 4d93abcc4018f..fa2e65f16bb2d 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -254,12 +254,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", @@ -322,12 +350,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", @@ -475,12 +531,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", @@ -538,12 +622,40 @@ describe('migrationsStateActionMachine', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".my-so-index_7.11.0", "versionIndex": ".my-so-index_7.11.0_001", diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index 8aad62f13b8fe..0267ae33dd157 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -70,7 +70,17 @@ describe('migrations v2 model', () => { versionAlias: '.kibana_7.11.0', versionIndex: '.kibana_7.11.0_001', tempIndex: '.kibana_7.11.0_reindex_temp', - unusedTypesToExclude: Option.some(['unused-fleet-agent-events']), + unusedTypesQuery: Option.of({ + bool: { + must_not: [ + { + term: { + type: 'unused-fleet-agent-events', + }, + }, + ], + }, + }), }; describe('exponential retry delays for retryable_es_client_error', () => { @@ -1177,12 +1187,40 @@ describe('migrations v2 model', () => { }, }, }, - "unusedTypesToExclude": Object { + "unusedTypesQuery": Object { "_tag": "Some", - "value": Array [ - "fleet-agent-events", - "tsvb-validation-telemetry", - ], + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", + }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], + }, + }, }, "versionAlias": ".kibana_task_manager_8.1.0", "versionIndex": ".kibana_task_manager_8.1.0_001", diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index ee78692a7044f..acf0f620136a2 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -16,6 +16,7 @@ import { IndexMapping } from '../mappings'; import { ResponseType } from './next'; import { SavedObjectsMigrationVersion } from '../types'; import { disableUnknownTypeMappingFields } from '../migrations/core/migration_context'; +import { excludeUnusedTypesQuery } from '../migrations/core'; import { SavedObjectsMigrationConfigType } from '../saved_objects_config'; /** @@ -74,6 +75,7 @@ function indexBelongsToLaterVersion(indexName: string, kibanaVersion: string): b const version = valid(indexVersion(indexName)); return version != null ? gt(version, kibanaVersion) : false; } + /** * Extracts the version number from a >= 7.11 index * @param indexName A >= v7.11 index name @@ -781,11 +783,6 @@ export const createInitialState = ({ }, }; - const unusedTypesToExclude = Option.some([ - 'fleet-agent-events', // https://github.com/elastic/kibana/issues/91869 - 'tsvb-validation-telemetry', // https://github.com/elastic/kibana/issues/95617 - ]); - const initialState: InitState = { controlState: 'INIT', indexPrefix, @@ -804,7 +801,7 @@ export const createInitialState = ({ retryAttempts: migrationsConfig.retryAttempts, batchSize: migrationsConfig.batchSize, logs: [], - unusedTypesToExclude, + unusedTypesQuery: Option.of(excludeUnusedTypesQuery), }; return initialState; }; diff --git a/src/core/server/saved_objects/migrationsv2/next.ts b/src/core/server/saved_objects/migrationsv2/next.ts index 5cbda741a0ce5..bb506cbca66fb 100644 --- a/src/core/server/saved_objects/migrationsv2/next.ts +++ b/src/core/server/saved_objects/migrationsv2/next.ts @@ -70,7 +70,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra state.tempIndex, Option.none, false, - state.unusedTypesToExclude + state.unusedTypesQuery ), SET_TEMP_WRITE_BLOCK: (state: SetTempWriteBlock) => Actions.setWriteBlock(client, state.tempIndex), @@ -115,7 +115,7 @@ export const nextActionMap = (client: ElasticsearchClient, transformRawDocs: Tra state.sourceIndex.value, state.preMigrationScript, false, - state.unusedTypesToExclude + state.unusedTypesQuery ), LEGACY_REINDEX_WAIT_FOR_TASK: (state: LegacyReindexWaitForTaskState) => Actions.waitForReindexTask(client, state.legacyReindexTaskId, '60s'), diff --git a/src/core/server/saved_objects/migrationsv2/types.ts b/src/core/server/saved_objects/migrationsv2/types.ts index e9b351c0152fc..5e84bc23b1d16 100644 --- a/src/core/server/saved_objects/migrationsv2/types.ts +++ b/src/core/server/saved_objects/migrationsv2/types.ts @@ -7,6 +7,7 @@ */ import * as Option from 'fp-ts/lib/Option'; +import { estypes } from '@elastic/elasticsearch'; import { ControlState } from './state_action_machine'; import { AliasAction } from './actions'; import { IndexMapping } from '../mappings'; @@ -91,9 +92,9 @@ export interface BaseState extends ControlState { readonly tempIndex: string; /* When reindexing we use a source query to exclude saved objects types which * are no longer used. These saved objects will still be kept in the outdated - * index for backup purposes, but won't be availble in the upgraded index. + * index for backup purposes, but won't be available in the upgraded index. */ - readonly unusedTypesToExclude: Option.Option; + readonly unusedTypesQuery: Option.Option; } export type InitState = BaseState & { From 451c5a6fae1f352702371e91a71638d6431e88aa Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 13 Apr 2021 09:20:11 -0400 Subject: [PATCH 65/79] [Maps] Enable filtering with spatial relationships on geo_point fields (#96849) --- .../elasticsearch_geo_utils.ts | 17 +--- .../geometry_filter_form.test.js.snap | 92 +++++++++++++++---- .../public/components/geometry_filter_form.js | 22 ++--- .../components/geometry_filter_form.test.js | 2 +- .../draw_filter_control.tsx | 9 +- .../feature_geometry_filter_form.js | 9 +- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 8 files changed, 89 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts index f2a8b95f7b643..197b7f49eda0a 100644 --- a/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts +++ b/x-pack/plugins/maps/common/elasticsearch_util/elasticsearch_geo_utils.ts @@ -369,7 +369,6 @@ export function createSpatialFilterWithGeometry({ geometryLabel, indexPatternId, geoFieldName, - geoFieldType, relation = ES_SPATIAL_RELATIONS.INTERSECTS, }: { preIndexedShape?: PreIndexedShape; @@ -377,32 +376,20 @@ export function createSpatialFilterWithGeometry({ geometryLabel: string; indexPatternId: string; geoFieldName: string; - geoFieldType: ES_GEO_FIELD_TYPE; relation: ES_SPATIAL_RELATIONS; }): GeoFilter { - ensureGeoField(geoFieldType); - - const isGeoPoint = geoFieldType === ES_GEO_FIELD_TYPE.GEO_POINT; - - const relationLabel = isGeoPoint - ? i18n.translate('xpack.maps.es_geo_utils.shapeFilter.geoPointRelationLabel', { - defaultMessage: 'in', - }) - : getEsSpatialRelationLabel(relation); const meta: FilterMeta = { type: SPATIAL_FILTER_TYPE, negate: false, index: indexPatternId, key: geoFieldName, - alias: `${geoFieldName} ${relationLabel} ${geometryLabel}`, + alias: `${geoFieldName} ${getEsSpatialRelationLabel(relation)} ${geometryLabel}`, disabled: false, }; const shapeQuery: GeoShapeQueryBody = { - // geo_shape query with geo_point field only supports intersects relation - relation: isGeoPoint ? ES_SPATIAL_RELATIONS.INTERSECTS : relation, + relation, }; - if (preIndexedShape) { shapeQuery.indexed_shape = preIndexedShape; } else { diff --git a/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap index 2d39a52dfe974..ccbe4667b78ea 100644 --- a/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap +++ b/x-pack/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should not render relation select when geo field is geo_point 1`] = ` +exports[`should not show "within" relation when filter geometry is not closed 1`] = ` + + + `; -exports[`should not show "within" relation when filter geometry is not closed 1`] = ` +exports[`should render error message 1`] = ` + + Simulated error + @@ -147,7 +177,7 @@ exports[`should not show "within" relation when filter geometry is not closed 1` `; -exports[`should render error message 1`] = ` +exports[`should render relation select when geo field is geo_shape 1`] = ` + + + - - Simulated error - @@ -210,7 +268,7 @@ exports[`should render error message 1`] = ` `; -exports[`should render relation select when geo field is geo_shape 1`] = ` +exports[`should render relation select without "within"-relation when geo field is geo_point 1`] = ` { - // can not filter by within relation when filtering geometry is not closed - return relation !== ES_SPATIAL_RELATIONS.WITHIN; - }); + const spatialRelations = + this.props.isFilterGeometryClosed && + this.state.selectedField.geoFieldType !== ES_GEO_FIELD_TYPE.GEO_POINT + ? Object.values(ES_SPATIAL_RELATIONS) + : Object.values(ES_SPATIAL_RELATIONS).filter((relation) => { + // - cannot filter by "within"-relation when filtering geometry is not closed + // - do not distinguish between intersects/within for filtering for points since they are equivalent + return relation !== ES_SPATIAL_RELATIONS.WITHIN; + }); + const options = spatialRelations.map((relation) => { return { value: relation, diff --git a/x-pack/plugins/maps/public/components/geometry_filter_form.test.js b/x-pack/plugins/maps/public/components/geometry_filter_form.test.js index f1876198f8b67..d981caf944ab9 100644 --- a/x-pack/plugins/maps/public/components/geometry_filter_form.test.js +++ b/x-pack/plugins/maps/public/components/geometry_filter_form.test.js @@ -16,7 +16,7 @@ const defaultProps = { onSubmit: () => {}, }; -test('should not render relation select when geo field is geo_point', async () => { +test('should render relation select without "within"-relation when geo field is geo_point', async () => { const component = shallow( { : geometry, indexPatternId: this.props.drawState.indexPatternId, geoFieldName: this.props.drawState.geoFieldName, - geoFieldType: this.props.drawState.geoFieldType - ? this.props.drawState.geoFieldType - : ES_GEO_FIELD_TYPE.GEO_POINT, geometryLabel: this.props.drawState.geometryLabel ? this.props.drawState.geometryLabel : '', relation: this.props.drawState.relation ? this.props.drawState.relation diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js index 3950c6ef124be..9d4cf78c98754 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/plugins/maps/public/connected_components/mb_map/features_tooltip/feature_geometry_filter_form.js @@ -52,13 +52,7 @@ export class FeatureGeometryFilterForm extends Component { return preIndexedShape; }; - _createFilter = async ({ - geometryLabel, - indexPatternId, - geoFieldName, - geoFieldType, - relation, - }) => { + _createFilter = async ({ geometryLabel, indexPatternId, geoFieldName, relation }) => { this.setState({ errorMsg: undefined }); const preIndexedShape = await this._loadPreIndexedShape(); if (!this._isMounted) { @@ -72,7 +66,6 @@ export class FeatureGeometryFilterForm extends Component { geometryLabel, indexPatternId, geoFieldName, - geoFieldType, relation, }); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8f71353113f5f..a0f535e93a8a6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -12424,7 +12424,6 @@ "xpack.maps.es_geo_utils.convert.invalidGeometryCollectionErrorMessage": "GeometryCollectionを convertESShapeToGeojsonGeometryに渡さないでください", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "{geometryType} ジオメトリから Geojson に変換できません。サポートされていません", "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} の {distanceKm}km 以内にある {geoFieldName}", - "xpack.maps.es_geo_utils.shapeFilter.geoPointRelationLabel": "in", "xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage": "サポートされていないフィールドタイプ、期待値:{expectedTypes}、提供された値:{fieldType}", "xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage": "サポートされていないジオメトリタイプ、期待値:{expectedTypes}、提供された値:{geometryType}", "xpack.maps.es_geo_utils.wkt.invalidWKTErrorMessage": "{wkt} を Geojson に変換できません。有効な WKT が必要です。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 7269615c051db..31bc197f2ea05 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -12591,7 +12591,6 @@ "xpack.maps.es_geo_utils.convert.invalidGeometryCollectionErrorMessage": "不应将 GeometryCollection 传递给 convertESShapeToGeojsonGeometry", "xpack.maps.es_geo_utils.convert.unsupportedGeometryTypeErrorMessage": "无法将 {geometryType} 几何图形转换成 geojson,不支持", "xpack.maps.es_geo_utils.distanceFilterAlias": "{pointLabel} {distanceKm}km 内的 {geoFieldName}", - "xpack.maps.es_geo_utils.shapeFilter.geoPointRelationLabel": "于", "xpack.maps.es_geo_utils.unsupportedFieldTypeErrorMessage": "字段类型不受支持,应为 {expectedTypes},而提供的是 {fieldType}", "xpack.maps.es_geo_utils.unsupportedGeometryTypeErrorMessage": "几何类型不受支持,应为 {expectedTypes},而提供的是 {geometryType}", "xpack.maps.es_geo_utils.wkt.invalidWKTErrorMessage": "无法将 {wkt} 转换成 geojson。需要有效的 WKT。", From 25000b40911de78dbfeeee2fe92b381ae05d4e43 Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Tue, 13 Apr 2021 09:21:21 -0400 Subject: [PATCH 66/79] [Maps] wrap flaky test in retry block (#96448) --- x-pack/test/functional/apps/maps/embeddable/dashboard.js | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/test/functional/apps/maps/embeddable/dashboard.js b/x-pack/test/functional/apps/maps/embeddable/dashboard.js index e1181119bee09..860273bc23cc1 100644 --- a/x-pack/test/functional/apps/maps/embeddable/dashboard.js +++ b/x-pack/test/functional/apps/maps/embeddable/dashboard.js @@ -35,6 +35,7 @@ export default function ({ getPageObjects, getService }) { }); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.loadSavedDashboard('map embeddable example'); + await PageObjects.dashboard.waitForRenderComplete(); }); after(async () => { From bc59d55d6759744cecd327a8a5551358a05153a7 Mon Sep 17 00:00:00 2001 From: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Tue, 13 Apr 2021 16:26:49 +0300 Subject: [PATCH 67/79] [TSVB] Fix annotation line doesn't work if no index pattern is applied (#96646) * [TSVB] fix annotation line doesnt work if no index pattern is applied * [TSVB] remove series from annotations, remove timeField placeholder Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/lib/vis_data/get_annotations.ts | 9 +-------- .../request_processors/annotations/date_histogram.js | 2 +- .../lib/vis_data/request_processors/annotations/query.js | 2 +- .../vis_data/request_processors/annotations/top_hits.js | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.ts index 6c19163a5ee20..1e2f6f39d00cf 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/get_annotations.ts @@ -19,14 +19,7 @@ import { getLastSeriesTimestamp } from './helpers/timestamp'; import { VisTypeTimeseriesVisDataRequest } from '../../types'; function validAnnotation(annotation: AnnotationItemsSchema) { - return ( - annotation.index_pattern && - annotation.time_field && - annotation.fields && - annotation.icon && - annotation.template && - !annotation.hidden - ); + return annotation.fields && annotation.icon && annotation.template && !annotation.hidden; } interface GetAnnotationsParams { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js index f3ee416be81a8..48b35d0db5086 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/date_histogram.js @@ -25,7 +25,7 @@ export function dateHistogram( ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const timeField = annotation.time_field; + const timeField = annotation.time_field || annotationIndex.indexPattern?.timeFieldName || ''; validateField(timeField, annotationIndex); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js index 46a3c369e548d..3be567dfe1f40 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/query.js @@ -22,7 +22,7 @@ export function query( ) { return (next) => async (doc) => { const barTargetUiSettings = await uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET); - const timeField = (annotation.time_field || annotationIndex.indexPattern?.timeField) ?? ''; + const timeField = (annotation.time_field || annotationIndex.indexPattern?.timeFieldName) ?? ''; validateField(timeField, annotationIndex); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js index 1b4434c4867c8..447cfdbc8c6e4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/annotations/top_hits.js @@ -12,7 +12,7 @@ import { validateField } from '../../../../../common/fields_utils'; export function topHits(req, panel, annotation, esQueryConfig, annotationIndex) { return (next) => (doc) => { const fields = (annotation.fields && annotation.fields.split(/[,\s]+/)) || []; - const timeField = annotation.time_field; + const timeField = annotation.time_field || annotationIndex.indexPattern?.timeFieldName || ''; validateField(timeField, annotationIndex); From 93e270e60ad165dd6e986c24fafb096498ead369 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Tue, 13 Apr 2021 08:31:00 -0500 Subject: [PATCH 68/79] [Enterprise Search] Design Pass: Role mappings (#96882) * Update shared button color and panel shading * Vertically align table cells to top * [App Search] Update panels to have backgrounds not borders * [Workplace Search] Update panels to have backgrounds not borders * re-align last cell to right Accidentally deleted it refactoring * Conditionally have border for App Search Requested to remove for empty state --- .../components/role_mappings/role_mapping.tsx | 4 ++-- .../components/role_mappings/role_mappings.tsx | 17 ++++++++++------- .../role_mapping/add_role_mapping_button.tsx | 2 +- .../shared/role_mapping/attribute_selector.tsx | 2 +- .../role_mapping/role_mappings_table.scss | 12 ++++++++++++ .../shared/role_mapping/role_mappings_table.tsx | 6 ++++-- .../views/role_mappings/role_mapping.tsx | 4 ++-- .../views/role_mappings/role_mappings.tsx | 16 +++++++++------- 8 files changed, 41 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx index ebd034caaedb3..47c0eb2483ec1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mapping.tsx @@ -166,7 +166,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { - +

{ROLE_TITLE}

@@ -189,7 +189,7 @@ export const RoleMapping: React.FC = ({ isNew }) => {
{hasAdvancedRoles && ( - +

{ENGINE_ACCESS_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx index 2ec2b93d1e24f..e8d9e06142ef8 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/role_mappings/role_mappings.tsx @@ -17,6 +17,7 @@ import { EuiPageContent, EuiPageContentBody, EuiPageHeader, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -78,12 +79,14 @@ export const RoleMappings: React.FC = () => { const addMappingButton = ; const roleMappingEmptyState = ( - {EMPTY_ROLE_MAPPINGS_TITLE}} - body={

{EMPTY_ROLE_MAPPINGS_BODY}

} - actions={addMappingButton} - /> + + {EMPTY_ROLE_MAPPINGS_TITLE}} + body={

{EMPTY_ROLE_MAPPINGS_BODY}

} + actions={addMappingButton} + /> +
); const roleMappingsTable = ( @@ -127,7 +130,7 @@ export const RoleMappings: React.FC = () => { pageTitle={ROLE_MAPPINGS_TITLE} description={ROLE_MAPPINGS_DESCRIPTION} /> - + 0}> {roleMappings.length === 0 ? roleMappingEmptyState : roleMappingsTable} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx index 0ae9f16ea2f9b..097302e0aa5f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/add_role_mapping_button.tsx @@ -16,7 +16,7 @@ interface Props { } export const AddRoleMappingButton: React.FC = ({ path }) => ( - + {ADD_ROLE_MAPPING_BUTTON} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx index 0417331be208d..0ee093ed934c9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/attribute_selector.tsx @@ -100,7 +100,7 @@ export const AttributeSelector: React.FC = ({ handleAuthProviderChange = () => null, }) => { return ( - +

{ATTRIBUTE_SELECTOR_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss new file mode 100644 index 0000000000000..6eaa3b9257936 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.scss @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +.roleMappingsTable { + td { + vertical-align: top; + } +} diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx index 6db62e4c10b6b..a5f6fb368c96f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/role_mappings_table.tsx @@ -29,6 +29,8 @@ import { MANAGE_BUTTON_LABEL } from '../constants'; import { EuiLinkTo } from '../react_router_helpers'; import { RoleRules } from '../types'; +import './role_mappings_table.scss'; + import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL, @@ -108,7 +110,7 @@ export const RoleMappingsTable: React.FC = ({
{filteredResults.length > 0 ? ( - + {EXTERNAL_ATTRIBUTE_LABEL} {ATTRIBUTE_VALUE_LABEL} @@ -152,7 +154,7 @@ export const RoleMappingsTable: React.FC = ({ {authProvider.map(getAuthProviderDisplayValue).join(', ')} )} - + {id && {MANAGE_BUTTON_LABEL}} {toolTip && } diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx index 7db1e82d29449..d69e94b20444e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mapping.tsx @@ -141,7 +141,7 @@ export const RoleMapping: React.FC = ({ isNew }) => { - +

{ROLE_LABEL}

@@ -158,7 +158,7 @@ export const RoleMapping: React.FC = ({ isNew }) => {
- +

{GROUP_ASSIGNMENT_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx index 842c59e683f06..0e3533d48a5a9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/role_mappings/role_mappings.tsx @@ -9,7 +9,7 @@ import React, { useEffect } from 'react'; import { useActions, useValues } from 'kea'; -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiPanel } from '@elastic/eui'; import { FlashMessages } from '../../../shared/flash_messages'; import { Loading } from '../../../shared/loading'; @@ -39,12 +39,14 @@ export const RoleMappings: React.FC = () => { const addMappingButton = ; const emptyPrompt = ( - {EMPTY_ROLE_MAPPINGS_TITLE}} - body={

{EMPTY_ROLE_MAPPINGS_BODY}

} - actions={addMappingButton} - /> + + {EMPTY_ROLE_MAPPINGS_TITLE}} + body={

{EMPTY_ROLE_MAPPINGS_BODY}

} + actions={addMappingButton} + /> +
); const roleMappingsTable = ( Date: Tue, 13 Apr 2021 09:31:18 -0400 Subject: [PATCH 69/79] [Telemetry] Fix Logstash telemetry collection for multi node clusters (#96831) Prior to this fix, each Logstash node was overwriting the collected list of ephemeral ids used to collect pipeline details. This meant that pipeline details were only being collected for the last Logstash node retrieved for each cluster. --- .../get_logstash_stats.test.ts | 140 ++++++++++++++++++ .../get_logstash_stats.ts | 6 +- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts index f2f0c37255d92..cf1574f8d3f0e 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.test.ts @@ -194,6 +194,117 @@ describe('Get Logstash Stats', () => { }); }); + it('should retrieve all ephemeral ids from all hits for the same cluster', () => { + const results = { + hits: { + hits: [ + { + _source: { + type: 'logstash_stats', + cluster_uuid: 'FlV4ckTxQ0a78hmBkzzc9A', + logstash_stats: { + logstash: { + uuid: '0000000-0000-0000-0000-000000000000', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + { + _source: { + type: 'logstash_stats', + cluster_uuid: 'FlV4ckTxQ0a78hmBkzzc9A', + logstash_stats: { + logstash: { + uuid: '11111111-1111-1111-1111-111111111111', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + { + _source: { + type: 'logstash_stats', + cluster_uuid: '3', + logstash_stats: { + logstash: { + uuid: '22222222-2222-2222-2222-222222222222', + }, + pipelines: [ + { + id: 'main', + ephemeral_id: 'cccccccc-cccc-cccc-cccc-cccccccccccc', + queue: { + type: 'memory', + }, + }, + ], + }, + }, + }, + ], + }, + }; + + const options = getBaseOptions(); + processStatsResults(results as any, options); + + expect(options.allEphemeralIds).toStrictEqual({ + FlV4ckTxQ0a78hmBkzzc9A: [ + 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', + 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', + ], + '3': ['cccccccc-cccc-cccc-cccc-cccccccccccc'], + }); + + expect(options.clusters).toStrictEqual({ + FlV4ckTxQ0a78hmBkzzc9A: { + count: 2, + cluster_stats: { + plugins: [], + collection_types: { + internal_collection: 2, + }, + pipelines: {}, + queues: { + memory: 2, + }, + }, + versions: [], + }, + '3': { + count: 1, + cluster_stats: { + plugins: [], + collection_types: { + internal_collection: 1, + }, + pipelines: {}, + queues: { + memory: 1, + }, + }, + versions: [], + }, + }); + }); + it('should summarize stats from hits across multiple result objects', () => { const options = getBaseOptions(); @@ -208,6 +319,35 @@ describe('Get Logstash Stats', () => { }); }); + expect(options.allEphemeralIds).toStrictEqual({ + '1n1p': ['cf37c6fa-2f1a-41e2-9a89-36b420a8b9a5'], + '1nmp': [ + '47a70feb-3cb5-4618-8670-2c0bada61acd', + '5a65d966-0330-4bd7-82f2-ee81040c13cf', + '8d33fe25-a2c0-4c54-9ecf-d218cb8dbfe4', + 'f4167a94-20a8-43e7-828e-4cf38d906187', + ], + mnmp: [ + '2fcd4161-e08f-4eea-818b-703ea3ec6389', + 'c6785d63-6e5f-42c2-839d-5edf139b7c19', + 'bc6ef6f2-ecce-4328-96a2-002de41a144d', + '72058ad1-68a1-45f6-a8e8-10621ffc7288', + '18593052-c021-4158-860d-d8122981a0ac', + '4207025c-9b00-4bea-a36c-6fbf2d3c215e', + '0ec4702d-b5e5-4c60-91e9-6fa6a836f0d1', + '41258219-b129-4fad-a629-f244826281f8', + 'e73bc63d-561a-4acd-a0c4-d5f70c4603df', + 'ddf882b7-be26-4a93-8144-0aeb35122651', + '602936f5-98a3-4f8c-9471-cf389a519f4b', + '8b300988-62cc-4bc6-9ee0-9194f3f78e27', + '6ab60531-fb6f-478c-9063-82f2b0af2bed', + '802a5994-a03c-44b8-a650-47c0f71c2e48', + '6070b400-5c10-4c5e-b5c5-a5bd9be6d321', + '3193df5f-2a34-4fe3-816e-6b05999aa5ce', + '994e68cd-d607-40e6-a54c-02a51caa17e0', + ], + }); + expect(options.clusters).toStrictEqual({ '1n1p': { count: 1, diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts index 93c69c644c064..f4f67a5582303 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_logstash_stats.ts @@ -147,8 +147,6 @@ export function processStatsResults( } clusterStats.collection_types![thisCollectionType] = (clusterStats.collection_types![thisCollectionType] || 0) + 1; - - const theseEphemeralIds: string[] = []; const pipelines = logstashStats.pipelines || []; pipelines.forEach((pipeline) => { @@ -162,10 +160,10 @@ export function processStatsResults( const ephemeralId = pipeline.ephemeral_id; if (ephemeralId !== undefined) { - theseEphemeralIds.push(ephemeralId); + allEphemeralIds[clusterUuid] = allEphemeralIds[clusterUuid] || []; + allEphemeralIds[clusterUuid].push(ephemeralId); } }); - allEphemeralIds[clusterUuid] = theseEphemeralIds; } }); } From 73ccf7844a64a4395b3059d4c98ca46026fca826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Kopyci=C5=84ski?= Date: Tue, 13 Apr 2021 15:38:12 +0200 Subject: [PATCH 70/79] [Fleet] Add support for long and double field type in multi_fields (#96834) --- .../elasticsearch/template/template.test.ts | 58 +++++++++++++++++++ .../epm/elasticsearch/template/template.ts | 6 ++ 2 files changed, 64 insertions(+) diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts index df82aa90b5a13..dcc685bb270b4 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.test.ts @@ -301,6 +301,64 @@ describe('EPM template', () => { expect(mappings).toEqual(keywordWithNormalizedMultiFieldsMapping); }); + it('tests processing keyword field with multi fields with long field', () => { + const keywordWithMultiFieldsLiteralYml = ` + - name: keywordWithMultiFields + type: keyword + multi_fields: + - name: number_memory_devices + type: long + normalizer: lowercase + `; + + const keywordWithMultiFieldsMapping = { + properties: { + keywordWithMultiFields: { + ignore_above: 1024, + type: 'keyword', + fields: { + number_memory_devices: { + type: 'long', + }, + }, + }, + }, + }; + const fields: Field[] = safeLoad(keywordWithMultiFieldsLiteralYml); + const processedFields = processFields(fields); + const mappings = generateMappings(processedFields); + expect(mappings).toEqual(keywordWithMultiFieldsMapping); + }); + + it('tests processing keyword field with multi fields with double field', () => { + const keywordWithMultiFieldsLiteralYml = ` + - name: keywordWithMultiFields + type: keyword + multi_fields: + - name: number + type: double + normalizer: lowercase + `; + + const keywordWithMultiFieldsMapping = { + properties: { + keywordWithMultiFields: { + ignore_above: 1024, + type: 'keyword', + fields: { + number: { + type: 'double', + }, + }, + }, + }, + }; + const fields: Field[] = safeLoad(keywordWithMultiFieldsLiteralYml); + const processedFields = processFields(fields); + const mappings = generateMappings(processedFields); + expect(mappings).toEqual(keywordWithMultiFieldsMapping); + }); + it('tests processing object field with no other attributes', () => { const objectFieldLiteralYml = ` - name: objectField diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index 0b95f8d76627a..f6ca1dfc99f4e 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -204,6 +204,12 @@ function generateMultiFields(fields: Fields): MultiFields { case 'keyword': multiFields[f.name] = { ...generateKeywordMapping(f), type: f.type }; break; + case 'long': + multiFields[f.name] = { type: f.type }; + break; + case 'double': + multiFields[f.name] = { type: f.type }; + break; } }); } From 8cce4805d4bf3d593c7c467f56572121f990718b Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 13 Apr 2021 15:54:42 +0200 Subject: [PATCH 71/79] [Discover][EuiDataGrid] Add document selector (#94804) Co-authored-by: Ryan Keairns --- .../discover_grid/discover_grid.test.tsx | 146 +++++++++++++++ .../discover_grid/discover_grid.tsx | 54 +++++- .../discover_grid_cell_actions.test.tsx | 4 + .../discover_grid/discover_grid_columns.tsx | 15 ++ .../discover_grid/discover_grid_context.tsx | 2 + .../discover_grid_document_selection.test.tsx | 143 +++++++++++++++ .../discover_grid_document_selection.tsx | 170 ++++++++++++++++++ .../discover_grid_expand_button.test.tsx | 6 + .../apps/dashboard/embeddable_data_grid.ts | 4 +- .../apps/discover/_data_grid_field_data.ts | 2 +- test/functional/services/data_grid.ts | 2 +- 11 files changed, 537 insertions(+), 11 deletions(-) create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx create mode 100644 src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx new file mode 100644 index 0000000000000..8037022085f02 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.test.tsx @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { ReactWrapper } from 'enzyme'; +import { EuiCopy } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { esHits } from '../../../__mocks__/es_hits'; +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { mountWithIntl } from '@kbn/test/jest'; +import { DiscoverGrid, DiscoverGridProps } from './discover_grid'; +import { uiSettingsMock } from '../../../__mocks__/ui_settings'; +import { DiscoverServices } from '../../../build_services'; +import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { getDocId } from './discover_grid_document_selection'; + +function getProps() { + const servicesMock = { + uiSettings: uiSettingsMock, + } as DiscoverServices; + return { + ariaLabelledBy: '', + columns: [], + indexPattern: indexPatternMock, + isLoading: false, + expandedDoc: undefined, + onAddColumn: jest.fn(), + onFilter: jest.fn(), + onRemoveColumn: jest.fn(), + onResize: jest.fn(), + onSetColumns: jest.fn(), + onSort: jest.fn(), + rows: esHits, + sampleSize: 30, + searchDescription: '', + searchTitle: '', + services: servicesMock, + setExpandedDoc: jest.fn(), + settings: {}, + showTimeCol: true, + sort: [], + useNewFieldsApi: true, + }; +} + +function getComponent() { + return mountWithIntl(); +} + +function getSelectedDocNr(component: ReactWrapper) { + const gridSelectionBtn = findTestSubject(component, 'dscGridSelectionBtn'); + if (!gridSelectionBtn.length) { + return 0; + } + const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-selected-documents'); + return Number(selectedNr); +} + +function getDisplayedDocNr(component: ReactWrapper) { + const gridSelectionBtn = findTestSubject(component, 'discoverDocTable'); + if (!gridSelectionBtn.length) { + return 0; + } + const selectedNr = gridSelectionBtn.getDOMNode().getAttribute('data-document-number'); + return Number(selectedNr); +} + +async function toggleDocSelection( + component: ReactWrapper, + document: ElasticSearchHit +) { + act(() => { + const docId = getDocId(document); + findTestSubject(component, `dscGridSelectDoc-${docId}`).simulate('change'); + }); + component.update(); +} + +describe('DiscoverGrid', () => { + describe('Document selection', () => { + let component: ReactWrapper; + beforeEach(() => { + component = getComponent(); + }); + + test('no documents are selected initially', async () => { + expect(getSelectedDocNr(component)).toBe(0); + expect(getDisplayedDocNr(component)).toBe(5); + }); + + test('Allows selection/deselection of multiple documents', async () => { + await toggleDocSelection(component, esHits[0]); + expect(getSelectedDocNr(component)).toBe(1); + await toggleDocSelection(component, esHits[1]); + expect(getSelectedDocNr(component)).toBe(2); + await toggleDocSelection(component, esHits[1]); + expect(getSelectedDocNr(component)).toBe(1); + }); + + test('deselection of all selected documents', async () => { + await toggleDocSelection(component, esHits[0]); + await toggleDocSelection(component, esHits[1]); + expect(getSelectedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridClearSelectedDocuments').simulate('click'); + expect(getSelectedDocNr(component)).toBe(0); + }); + + test('showing only selected documents and undo selection', async () => { + await toggleDocSelection(component, esHits[0]); + await toggleDocSelection(component, esHits[1]); + expect(getSelectedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(2); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + component.update(); + findTestSubject(component, 'dscGridShowAllDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(5); + }); + + test('showing only selected documents and remove filter deselecting each doc manually', async () => { + await toggleDocSelection(component, esHits[0]); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + findTestSubject(component, 'dscGridShowSelectedDocuments').simulate('click'); + expect(getDisplayedDocNr(component)).toBe(1); + await toggleDocSelection(component, esHits[0]); + expect(getDisplayedDocNr(component)).toBe(5); + await toggleDocSelection(component, esHits[0]); + expect(getDisplayedDocNr(component)).toBe(5); + }); + + test('copying selected documents to clipboard', async () => { + await toggleDocSelection(component, esHits[0]); + findTestSubject(component, 'dscGridSelectionBtn').simulate('click'); + expect(component.find(EuiCopy).prop('textToCopy')).toMatchInlineSnapshot( + `"[{\\"_index\\":\\"i\\",\\"_id\\":\\"1\\",\\"_score\\":1,\\"_type\\":\\"_doc\\",\\"_source\\":{\\"date\\":\\"2020-20-01T12:12:12.123\\",\\"message\\":\\"test1\\",\\"bytes\\":20}}]"` + ); + }); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx index 1888ae8562a37..300c40a28c662 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid.tsx @@ -37,6 +37,7 @@ import { defaultPageSize, gridStyle, pageSizeArr, toolbarVisibility } from './co import { DiscoverServices } from '../../../build_services'; import { getDisplayedColumns } from '../../helpers/columns'; import { KibanaContextProvider } from '../../../../../kibana_react/public'; +import { DiscoverGridDocumentToolbarBtn, getDocId } from './discover_grid_document_selection'; interface SortObj { id: string; @@ -158,14 +159,27 @@ export const DiscoverGrid = ({ sort, useNewFieldsApi, }: DiscoverGridProps) => { + const [selectedDocs, setSelectedDocs] = useState([]); + const [isFilterActive, setIsFilterActive] = useState(false); const displayedColumns = getDisplayedColumns(columns, indexPattern); const defaultColumns = displayedColumns.includes('_source'); + const displayedRows = useMemo(() => { + if (!rows) { + return []; + } + if (!isFilterActive || selectedDocs.length === 0) { + return rows; + } + return rows.filter((row) => { + return selectedDocs.includes(getDocId(row)); + }); + }, [rows, selectedDocs, isFilterActive]); /** * Pagination */ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: defaultPageSize }); - const rowCount = useMemo(() => (rows ? rows.length : 0), [rows]); + const rowCount = useMemo(() => (displayedRows ? displayedRows.length : 0), [displayedRows]); const pageCount = useMemo(() => Math.ceil(rowCount / pagination.pageSize), [ rowCount, pagination, @@ -207,11 +221,11 @@ export const DiscoverGrid = ({ () => getRenderCellValueFn( indexPattern, - rows, - rows ? rows.map((hit) => indexPattern.flattenHit(hit)) : [], + displayedRows, + displayedRows ? displayedRows.map((hit) => indexPattern.flattenHit(hit)) : [], useNewFieldsApi ), - [rows, indexPattern, useNewFieldsApi] + [displayedRows, indexPattern, useNewFieldsApi] ); /** @@ -240,6 +254,20 @@ export const DiscoverGrid = ({ ]); const lead = useMemo(() => getLeadControlColumns(), []); + const additionalControls = useMemo( + () => + selectedDocs.length ? ( + + ) : null, + [selectedDocs, isFilterActive, rows, setIsFilterActive] + ); + if (!rowCount) { return (
@@ -257,10 +285,17 @@ export const DiscoverGrid = ({ value={{ expanded: expandedDoc, setExpanded: setExpandedDoc, - rows: rows || [], + rows: displayedRows, onFilter, indexPattern, isDarkMode: services.uiSettings.get('theme:darkMode'), + selectedDocs, + setSelectedDocs: (newSelectedDocs) => { + setSelectedDocs(newSelectedDocs); + if (isFilterActive && newSelectedDocs.length === 0) { + setIsFilterActive(false); + } + }, }} > @@ -335,7 +375,7 @@ export const DiscoverGrid = ({ ( + + + {i18n.translate('discover.selectColumnHeader', { + defaultMessage: 'Select column', + })} + + + ), + }, ]; } diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx index 46169e1e1325f..e57d3fb8362ae 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_context.tsx @@ -17,6 +17,8 @@ export interface GridContext { onFilter: DocViewFilterFn; indexPattern: IndexPattern; isDarkMode: boolean; + selectedDocs: string[]; + setSelectedDocs: (selected: string[]) => void; } const defaultContext = ({} as unknown) as GridContext; diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx new file mode 100644 index 0000000000000..9ebe3ee95f797 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.test.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React from 'react'; +import { mountWithIntl } from '@kbn/test/jest'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { + DiscoverGridDocumentToolbarBtn, + getDocId, + SelectButton, +} from './discover_grid_document_selection'; +import { esHits } from '../../../__mocks__/es_hits'; +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { DiscoverGridContext } from './discover_grid_context'; + +describe('document selection', () => { + describe('getDocId', () => { + test('doc with custom routing', () => { + const doc = { + _id: 'test-id', + _index: 'test-indices', + _routing: 'why-not', + }; + expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::why-not"`); + }); + test('doc without custom routing', () => { + const doc = { + _id: 'test-id', + _index: 'test-indices', + }; + expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::"`); + }); + }); + + describe('SelectButton', () => { + test('is not checked', () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + expect(checkBox.props().checked).toBeFalsy(); + }); + + test('is checked', () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + selectedDocs: ['i::1::'], + setSelectedDocs: jest.fn(), + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + expect(checkBox.props().checked).toBeTruthy(); + }); + + test('adding a selection', () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + checkBox.simulate('change'); + expect(contextMock.setSelectedDocs).toHaveBeenCalledWith(['i::1::']); + }); + test('removing a selection', () => { + const contextMock = { + expanded: undefined, + setExpanded: jest.fn(), + rows: esHits, + onFilter: jest.fn(), + indexPattern: indexPatternMock, + isDarkMode: false, + selectedDocs: ['i::1::'], + setSelectedDocs: jest.fn(), + }; + + const component = mountWithIntl( + + + + ); + + const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); + checkBox.simulate('change'); + expect(contextMock.setSelectedDocs).toHaveBeenCalledWith([]); + }); + }); + describe('DiscoverGridDocumentToolbarBtn', () => { + test('it renders a button clickable button', () => { + const props = { + isFilterActive: false, + rows: esHits, + selectedDocs: ['i::1::'], + setIsFilterActive: jest.fn(), + setSelectedDocs: jest.fn(), + }; + const component = mountWithIntl(); + const button = findTestSubject(component, 'dscGridSelectionBtn'); + expect(button.length).toBe(1); + }); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx new file mode 100644 index 0000000000000..4aaefc99479c1 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_document_selection.tsx @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import React, { useCallback, useState, useContext, useMemo } from 'react'; +import { + EuiButtonEmpty, + EuiContextMenuItem, + EuiContextMenuPanel, + EuiCopy, + EuiPopover, + EuiCheckbox, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import classNames from 'classnames'; +import { ElasticSearchHit } from '../../doc_views/doc_views_types'; +import { DiscoverGridContext } from './discover_grid_context'; + +/** + * Returning a generated id of a given ES document, since `_id` can be the same + * when using different indices and shard routing + */ +export const getDocId = (doc: ElasticSearchHit & { _routing?: string }) => { + const routing = doc._routing ? doc._routing : ''; + return [doc._index, doc._id, routing].join('::'); +}; +export const SelectButton = ({ rowIndex }: { rowIndex: number }) => { + const ctx = useContext(DiscoverGridContext); + const doc = useMemo(() => ctx.rows[rowIndex], [ctx.rows, rowIndex]); + const id = useMemo(() => getDocId(doc), [doc]); + const checked = useMemo(() => ctx.selectedDocs.includes(id), [ctx.selectedDocs, id]); + + return ( + { + if (checked) { + const newSelection = ctx.selectedDocs.filter((docId) => docId !== id); + ctx.setSelectedDocs(newSelection); + } else { + ctx.setSelectedDocs([...ctx.selectedDocs, id]); + } + }} + /> + ); +}; + +export function DiscoverGridDocumentToolbarBtn({ + isFilterActive, + rows, + selectedDocs, + setIsFilterActive, + setSelectedDocs, +}: { + isFilterActive: boolean; + rows: ElasticSearchHit[]; + selectedDocs: string[]; + setIsFilterActive: (value: boolean) => void; + setSelectedDocs: (value: string[]) => void; +}) { + const [isSelectionPopoverOpen, setIsSelectionPopoverOpen] = useState(false); + + const getMenuItems = useCallback(() => { + return [ + isFilterActive ? ( + { + setIsSelectionPopoverOpen(false); + setIsFilterActive(false); + }} + > + + + ) : ( + { + setIsSelectionPopoverOpen(false); + setIsFilterActive(true); + }} + > + + + ), + + { + setIsSelectionPopoverOpen(false); + setSelectedDocs([]); + setIsFilterActive(false); + }} + > + + , + selectedDocs.includes(getDocId(row)))) : '' + } + > + {(copy) => ( + + + + )} + , + ]; + }, [ + isFilterActive, + rows, + selectedDocs, + setIsFilterActive, + setIsSelectionPopoverOpen, + setSelectedDocs, + ]); + + return ( + setIsSelectionPopoverOpen(false)} + isOpen={isSelectionPopoverOpen} + panelPaddingSize="none" + button={ + setIsSelectionPopoverOpen(true)} + data-selected-documents={selectedDocs.length} + data-test-subj="dscGridSelectionBtn" + isSelected={isFilterActive} + className={classNames({ + // eslint-disable-next-line @typescript-eslint/naming-convention + euiDataGrid__controlBtn: true, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'euiDataGrid__controlBtn--active': isFilterActive, + })} + > + + + } + > + {isSelectionPopoverOpen && } + + ); +} diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx index 98a1205483808..d1299b39a25b2 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_expand_button.test.tsx @@ -23,6 +23,8 @@ describe('Discover grid view button ', function () { onFilter: jest.fn(), indexPattern: indexPatternMock, isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), }; const component = mountWithIntl( @@ -49,6 +51,8 @@ describe('Discover grid view button ', function () { onFilter: jest.fn(), indexPattern: indexPatternMock, isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), }; const component = mountWithIntl( @@ -75,6 +79,8 @@ describe('Discover grid view button ', function () { onFilter: jest.fn(), indexPattern: indexPatternMock, isDarkMode: false, + selectedDocs: [], + setSelectedDocs: jest.fn(), }; const component = mountWithIntl( diff --git a/test/functional/apps/dashboard/embeddable_data_grid.ts b/test/functional/apps/dashboard/embeddable_data_grid.ts index 00a75baae4be7..a9e0039de1f79 100644 --- a/test/functional/apps/dashboard/embeddable_data_grid.ts +++ b/test/functional/apps/dashboard/embeddable_data_grid.ts @@ -47,12 +47,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('are added when a cell filter is clicked', async function () { - await find.clickByCssSelector(`[role="gridcell"]:nth-child(3)`); + await find.clickByCssSelector(`[role="gridcell"]:nth-child(4)`); // needs a short delay between becoming visible & being clickable await PageObjects.common.sleep(250); await find.clickByCssSelector(`[data-test-subj="filterOutButton"]`); await PageObjects.header.waitUntilLoadingHasFinished(); - await find.clickByCssSelector(`[role="gridcell"]:nth-child(3)`); + await find.clickByCssSelector(`[role="gridcell"]:nth-child(4)`); await PageObjects.common.sleep(250); await find.clickByCssSelector(`[data-test-subj="filterForButton"]`); const filterCount = await filterBar.getFilterCount(); diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index e8fcb06d06193..f41a98e2f3364 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -68,7 +68,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.discover.waitUntilSearchingHasFinished(); await retry.waitFor('first cell contains expected timestamp', async () => { - const cell = await dataGrid.getCellElement(1, 2); + const cell = await dataGrid.getCellElement(1, 3); const text = await cell.getVisibleText(); return text === expectedTimeStamp; }); diff --git a/test/functional/services/data_grid.ts b/test/functional/services/data_grid.ts index c0a7e0f82e692..87fa59b48a324 100644 --- a/test/functional/services/data_grid.ts +++ b/test/functional/services/data_grid.ts @@ -168,7 +168,7 @@ export function DataGridProvider({ getService, getPageObjects }: FtrProviderCont const textArr = []; let idx = 0; for (const cell of result) { - if (idx > 0) { + if (idx > 1) { textArr.push(await cell.getVisibleText()); } idx++; From 22dd61d919a2b3e04b85b3b1dc6dcd63c988a406 Mon Sep 17 00:00:00 2001 From: Tyler Smalley Date: Tue, 13 Apr 2021 06:55:50 -0700 Subject: [PATCH 72/79] [keystore] Fix openHandle in Jest tests (#96671) ``` [2021-04-07T00:19:27Z] Jest did not exit one second after the test run has completed. [2021-04-07T00:19:27Z] [2021-04-07T00:19:27Z] This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue. ``` Signed-off-by: Tyler Smalley Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/cli_keystore/utils/prompt.js | 1 + src/cli_keystore/utils/prompt.test.js | 36 +++++++++++---------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/cli_keystore/utils/prompt.js b/src/cli_keystore/utils/prompt.js index d681f7de2e32c..195f794db3e6e 100644 --- a/src/cli_keystore/utils/prompt.js +++ b/src/cli_keystore/utils/prompt.js @@ -75,6 +75,7 @@ export function question(question, options = {}) { }); rl.question(questionPrompt, (value) => { + rl.close(); resolve(value); }); }); diff --git a/src/cli_keystore/utils/prompt.test.js b/src/cli_keystore/utils/prompt.test.js index 306d4b2bd66df..e7ccac4e83e11 100644 --- a/src/cli_keystore/utils/prompt.test.js +++ b/src/cli_keystore/utils/prompt.test.js @@ -6,14 +6,11 @@ * Side Public License, v 1. */ -import sinon from 'sinon'; import { PassThrough } from 'stream'; import { confirm, question } from './prompt'; describe('prompt', () => { - const sandbox = sinon.createSandbox(); - let input; let output; @@ -23,30 +20,27 @@ describe('prompt', () => { }); afterEach(() => { - sandbox.restore(); + input.end(); + output.end(); }); describe('confirm', () => { it('prompts for question', async () => { - const onData = sandbox.stub(output, 'write'); - - confirm('my question', { output }); + const write = jest.spyOn(output, 'write'); - sinon.assert.calledOnce(onData); + process.nextTick(() => input.write('Y\n')); + await confirm('my question', { input, output }); - const { args } = onData.getCall(0); - expect(args[0]).toEqual('my question [y/N] '); + expect(write).toHaveBeenCalledWith('my question [y/N] '); }); it('prompts for question with default true', async () => { - const onData = sandbox.stub(output, 'write'); - - confirm('my question', { output, default: true }); + const write = jest.spyOn(output, 'write'); - sinon.assert.calledOnce(onData); + process.nextTick(() => input.write('Y\n')); + await confirm('my question', { input, output, default: true }); - const { args } = onData.getCall(0); - expect(args[0]).toEqual('my question [Y/n] '); + expect(write).toHaveBeenCalledWith('my question [Y/n] '); }); it('defaults to false', async () => { @@ -87,14 +81,12 @@ describe('prompt', () => { describe('question', () => { it('prompts for question', async () => { - const onData = sandbox.stub(output, 'write'); - - question('my question', { output }); + const write = jest.spyOn(output, 'write'); - sinon.assert.calledOnce(onData); + process.nextTick(() => input.write('my answer\n')); + await question('my question', { input, output }); - const { args } = onData.getCall(0); - expect(args[0]).toEqual('my question: '); + expect(write).toHaveBeenCalledWith('my question: '); }); it('can be answered', async () => { From ba091c00cf3ccf94f7dfe3c5e3effa36cda1233f Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Tue, 13 Apr 2021 15:04:07 +0100 Subject: [PATCH 73/79] [K8] [Maps] Fix toolbar overlay styles (#96352) * Fix toolbar overlay styles * More styles * Updating test * Better focus state for mapbox buttons * Mapbox buttons focus * Focus againa * Focus states again * no background only for focus not hover * Adding mixin for button group border radius Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../maps/public/{index.scss => _index.scss} | 1 + x-pack/plugins/maps/public/_mapbox_hacks.scss | 19 ++++++- x-pack/plugins/maps/public/_mixins.scss | 11 +++++ .../toolbar_overlay/_index.scss | 22 +-------- .../toolbar_overlay/_toolbar_overlay.scss | 49 +++++++++++++++++++ .../fit_to_data/fit_to_data.tsx | 30 ++++++------ .../set_view_control/set_view_control.tsx | 29 ++++++----- .../__snapshots__/tools_control.test.tsx.snap | 38 ++++++++------ .../tools_control/tools_control.tsx | 27 +++++----- .../public/lazy_load_bundle/lazy/index.ts | 2 +- 10 files changed, 152 insertions(+), 76 deletions(-) rename x-pack/plugins/maps/public/{index.scss => _index.scss} (94%) create mode 100644 x-pack/plugins/maps/public/_mixins.scss create mode 100644 x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss diff --git a/x-pack/plugins/maps/public/index.scss b/x-pack/plugins/maps/public/_index.scss similarity index 94% rename from x-pack/plugins/maps/public/index.scss rename to x-pack/plugins/maps/public/_index.scss index d2dd07b0f81f9..5332464ade9fb 100644 --- a/x-pack/plugins/maps/public/index.scss +++ b/x-pack/plugins/maps/public/_index.scss @@ -7,6 +7,7 @@ // mapChart__legend--small // mapChart__legend-isLoading +@import 'mixins'; @import 'main'; @import 'mapbox_hacks'; @import 'connected_components/index'; diff --git a/x-pack/plugins/maps/public/_mapbox_hacks.scss b/x-pack/plugins/maps/public/_mapbox_hacks.scss index 9b2d93986e426..480232007995d 100644 --- a/x-pack/plugins/maps/public/_mapbox_hacks.scss +++ b/x-pack/plugins/maps/public/_mapbox_hacks.scss @@ -10,8 +10,13 @@ .mapboxgl-ctrl-group:not(:empty) { @include euiBottomShadowLarge; + @include mapToolbarButtonGroupBorderRadius; background-color: $euiColorEmptyShade; - border-radius: $euiBorderRadius; + transition: transform $euiAnimSpeedNormal ease-in-out; + + &:hover { + transform: translateY(-1px); + } > button { @include size($euiSizeXL); @@ -21,6 +26,18 @@ } } } + + .mapboxgl-ctrl button:not(:disabled) { + transition: background $euiAnimSpeedNormal ease-in-out; + + &:hover { + background-color: transparentize($euiColorDarkShade, .9); + } + } + + .mapboxgl-ctrl-group button:focus:focus-visible { + box-shadow: none; + } } // Custom SVG as background for zoom controls based off of EUI glyphs plusInCircleFilled and minusInCircleFilled diff --git a/x-pack/plugins/maps/public/_mixins.scss b/x-pack/plugins/maps/public/_mixins.scss new file mode 100644 index 0000000000000..914bc23c1163c --- /dev/null +++ b/x-pack/plugins/maps/public/_mixins.scss @@ -0,0 +1,11 @@ +@mixin mapToolbarButtonGroupBorderRadius { + @include kbnThemeStyle($theme: 'v7') { + border-radius: $euiBorderRadius; + } + + @include kbnThemeStyle($theme: 'v8') { + border-radius: $euiBorderRadiusSmall; + } + + overflow: hidden; +} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss index e92e89b170370..a472f1b640f68 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_index.scss @@ -1,22 +1,2 @@ @import 'tools_control/index'; - -.mapToolbarOverlay { - position: absolute; - top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin - left: $euiSizeM; - z-index: 2; // Sit on top of mapbox controls shadow -} - -.mapToolbarOverlay__button { - @include size($euiSizeXL); - // sass-lint:disable-block no-important - background-color: $euiColorEmptyShade !important; - pointer-events: all; - position: relative; - - &:enabled, - &:enabled:hover, - &:enabled:focus { - @include euiBottomShadowLarge; - } -} +@import 'toolbar_overlay'; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss new file mode 100644 index 0000000000000..d95dd2504babc --- /dev/null +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/_toolbar_overlay.scss @@ -0,0 +1,49 @@ +.mapToolbarOverlay { + position: absolute; + top: ($euiSizeM + $euiSizeS) + ($euiSizeXL * 2); // Position and height of mapbox controls plus margin + left: $euiSizeM; + z-index: 2; // Sit on top of mapbox controls shadow +} + +.mapToolbarOverlay__button, +.mapToolbarOverlay__buttonGroup { + position: relative; + transition: transform $euiAnimSpeedNormal ease-in-out, background $euiAnimSpeedNormal ease-in-out; + + @include kbnThemeStyle($theme: 'v7') { + // Overrides the .euiPanel default border + // sass-lint:disable-block no-important + border: none !important; + + // Overrides the .euiPanel--hasShadow + &.euiPanel.euiPanel--hasShadow { + @include euiBottomShadowLarge; + } + } + + &:hover { + transform: translateY(-1px); + } + + // Removes the hover effect from the .euiButtonIcon because it would create a 1px bottom gap + // So we put this hover effect into the panel that wraps the button or buttons + .euiButtonIcon:hover { + transform: translateY(0); + } + + // Removes the focus background state because it can induce users to think these buttons are "enabled". + // The buttons functionality are just applied once, so they shouldn't stay highlighted. + .euiButtonIcon:focus:not(:hover) { + background: none; + } +} + +.mapToolbarOverlay__buttonGroup { + @include mapToolbarButtonGroupBorderRadius; + display: flex; + flex-direction: column; + + .euiButtonIcon { + border-radius: 0; + } +} \ No newline at end of file diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx index 9d074ac760612..64e163cd96a92 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx @@ -7,7 +7,7 @@ import React from 'react'; -import { EuiButtonIcon } from '@elastic/eui'; +import { EuiButtonIcon, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../classes/layers/layer'; @@ -56,19 +56,21 @@ export class FitToData extends React.Component { } return ( - + + + ); } } diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx index b657d6369f8aa..de37ec5e00877 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/set_view_control/set_view_control.tsx @@ -15,6 +15,7 @@ import { EuiPopover, EuiTextAlign, EuiSpacer, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -190,19 +191,21 @@ export class SetViewControl extends Component { anchorPosition="leftUp" panelPaddingSize="s" button={ - + + + } isOpen={this.state.isPopoverOpen} closePopover={this._closePopover} diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap index 456138e191810..b6d217d690764 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/__snapshots__/tools_control.test.tsx.snap @@ -8,14 +8,19 @@ exports[`Should render cancel button when drawing 1`] = ` + paddingSize="none" + > + + } closePopover={[Function]} display="inlineBlock" @@ -134,14 +139,19 @@ exports[`renders 1`] = ` + paddingSize="none" + > + + } closePopover={[Function]} display="inlineBlock" diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx index 1d2354ba3154a..6779fe945137e 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx @@ -13,6 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiButton, + EuiPanel, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -205,18 +206,20 @@ export class ToolsControl extends Component { _renderToolsButton() { return ( - + + + ); } diff --git a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts index e7f5df49527b7..4ccc19ae988da 100644 --- a/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts +++ b/x-pack/plugins/maps/public/lazy_load_bundle/lazy/index.ts @@ -5,7 +5,7 @@ * 2.0. */ -import '../../index.scss'; +import '../../_index.scss'; export * from '../../embeddable/map_embeddable'; export * from '../../kibana_services'; export { renderApp } from '../../render_app'; From 3acabf32b4df97a616eceaa43eb6ecf608018012 Mon Sep 17 00:00:00 2001 From: Melissa Alvarez Date: Tue, 13 Apr 2021 10:12:22 -0400 Subject: [PATCH 74/79] ensure ROC chart gets loaded correctly (#96890) --- .../application/data_frame_analytics/common/analytics.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index 505673f440ef2..61abf8476c632 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -523,6 +523,9 @@ export const loadEvalData = async ({ [jobType]: { actual_field: dependentVariable, predicted_field: predictedField, + ...(jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION + ? { top_classes_field: `${resultsField}.top_classes` } + : {}), metrics: metrics[jobType as keyof EvaluateMetrics], }, }, From 98f799953bbc93b99ae01c2e56e8414982fcf9ac Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Tue, 13 Apr 2021 16:13:25 +0200 Subject: [PATCH 75/79] [Search Sessions] Remove auto-refresh limitation (#96539) --- x-pack/plugins/data_enhanced/public/plugin.ts | 1 - ...onnected_search_session_indicator.test.tsx | 42 ------------------- .../connected_search_session_indicator.tsx | 20 +-------- .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 5 files changed, 1 insertion(+), 64 deletions(-) diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 439cae4f414f7..82f04d82ea2f8 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -84,7 +84,6 @@ export class DataEnhancedPlugin sessionService: plugins.data.search.session, application: core.application, basePath: core.http.basePath, - timeFilter: plugins.data.query.timefilter.timefilter, storage: this.storage, disableSaveAfterSessionCompletesTimeout: moment .duration(this.config.search.sessions.notTouchedTimeout) diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index c96d821641dd6..a16557b50700e 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -60,7 +60,6 @@ test("shouldn't show indicator in case no active search session", async () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -89,7 +88,6 @@ test("shouldn't show indicator in case app hasn't opt-in", async () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -120,7 +118,6 @@ test('should show indicator in case there is an active search session', async () const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -146,7 +143,6 @@ test('should be disabled in case uiConfig says so ', async () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -171,7 +167,6 @@ test('should be disabled in case not enough permissions', async () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$, hasAccess: () => false }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, basePath, @@ -191,38 +186,6 @@ test('should be disabled in case not enough permissions', async () => { expect(screen.getByRole('button', { name: 'Manage sessions' })).toBeDisabled(); }); -test('should be disabled during auto-refresh', async () => { - const state$ = new BehaviorSubject(SearchSessionState.Loading); - - const SearchSessionIndicator = createConnectedSearchSessionIndicator({ - sessionService: { ...sessionService, state$ }, - application, - timeFilter, - storage, - disableSaveAfterSessionCompletesTimeout, - usageCollector, - basePath, - }); - - render( - - - - ); - - await waitFor(() => screen.getByTestId('searchSessionIndicator')); - - await userEvent.click(screen.getByLabelText('Search session loading')); - - expect(screen.getByRole('button', { name: 'Save session' })).not.toBeDisabled(); - - act(() => { - refreshInterval$.next({ value: 0, pause: false }); - }); - - expect(screen.getByRole('button', { name: 'Save session' })).toBeDisabled(); -}); - describe('Completed inactivity', () => { beforeEach(() => { jest.useFakeTimers(); @@ -236,7 +199,6 @@ describe('Completed inactivity', () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -298,7 +260,6 @@ describe('tour steps', () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -340,7 +301,6 @@ describe('tour steps', () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -376,7 +336,6 @@ describe('tour steps', () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, @@ -404,7 +363,6 @@ describe('tour steps', () => { const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index 630aea417c84e..603df09e1c4c6 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -6,7 +6,7 @@ */ import React, { useCallback, useEffect, useState } from 'react'; -import { debounce, distinctUntilChanged, map, mapTo, switchMap, tap } from 'rxjs/operators'; +import { debounce, distinctUntilChanged, mapTo, switchMap, tap } from 'rxjs/operators'; import { merge, of, timer } from 'rxjs'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; @@ -14,7 +14,6 @@ import { SearchSessionIndicator, SearchSessionIndicatorRef } from '../search_ses import { ISessionService, SearchSessionState, - TimefilterContract, SearchUsageCollector, } from '../../../../../../../src/plugins/data/public'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; @@ -24,7 +23,6 @@ import { useSearchSessionTour } from './search_session_tour'; export interface SearchSessionIndicatorDeps { sessionService: ISessionService; - timeFilter: TimefilterContract; application: ApplicationStart; basePath: IBasePath; storage: IStorageWrapper; @@ -39,17 +37,12 @@ export interface SearchSessionIndicatorDeps { export const createConnectedSearchSessionIndicator = ({ sessionService, application, - timeFilter, storage, disableSaveAfterSessionCompletesTimeout, usageCollector, basePath, }: SearchSessionIndicatorDeps): React.FC => { const searchSessionsManagementUrl = basePath.prepend('/app/management/kibana/search_sessions'); - const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; - const isAutoRefreshEnabled$ = timeFilter - .getRefreshIntervalUpdate$() - .pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); const debouncedSessionServiceState$ = sessionService.state$.pipe( debounce((_state) => timer(_state === SearchSessionState.None ? 50 : 300)) // switch to None faster to quickly remove indicator when navigating away @@ -69,7 +62,6 @@ export const createConnectedSearchSessionIndicator = ({ return () => { const state = useObservable(debouncedSessionServiceState$, SearchSessionState.None); - const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); const isSaveDisabledByApp = sessionService.getSearchSessionIndicatorUiConfig().isDisabled(); const disableSaveAfterSessionCompleteTimedOut = useObservable( disableSaveAfterSessionCompleteTimedOut$, @@ -91,16 +83,6 @@ export const createConnectedSearchSessionIndicator = ({ let managementDisabled = false; let managementDisabledReasonText: string = ''; - if (autoRefreshEnabled) { - saveDisabled = true; - saveDisabledReasonText = i18n.translate( - 'xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage', - { - defaultMessage: 'Saving search session is not available when auto refresh is enabled.', - } - ); - } - if (disableSaveAfterSessionCompleteTimedOut) { saveDisabled = true; saveDisabledReasonText = i18n.translate( diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index a0f535e93a8a6..7eb1fb458351a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7344,7 +7344,6 @@ "xpack.data.searchSessionIndicator.canceledTitleText": "検索セッションが停止しました", "xpack.data.searchSessionIndicator.canceledTooltipText": "検索セッションが停止しました", "xpack.data.searchSessionIndicator.continueInBackgroundButtonText": "セッションの保存", - "xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage": "自動更新が有効な場合は、検索セッションの保存を使用できません。", "xpack.data.searchSessionIndicator.disabledDueToDisabledGloballyMessage": "検索セッションを管理するアクセス権がありません", "xpack.data.searchSessionIndicator.disabledDueToTimeoutMessage": "検索セッション結果が期限切れです。", "xpack.data.searchSessionIndicator.loadingInTheBackgroundDescriptionText": "管理から完了した結果に戻ることができます。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 31bc197f2ea05..7e80a52d229c4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7408,7 +7408,6 @@ "xpack.data.searchSessionIndicator.canceledTitleText": "搜索会话已停止", "xpack.data.searchSessionIndicator.canceledTooltipText": "搜索会话已停止", "xpack.data.searchSessionIndicator.continueInBackgroundButtonText": "保存会话", - "xpack.data.searchSessionIndicator.disabledDueToAutoRefreshMessage": "启用自动刷新时,保存搜索会话不可用。", "xpack.data.searchSessionIndicator.disabledDueToDisabledGloballyMessage": "您无权管理搜索会话", "xpack.data.searchSessionIndicator.disabledDueToTimeoutMessage": "搜索会话结果已过期。", "xpack.data.searchSessionIndicator.loadingInTheBackgroundDescriptionText": "可以从“管理”中返回至完成的结果。", From bedf92f0010c927f1f95c30227416b16ad2db37f Mon Sep 17 00:00:00 2001 From: Craig Chamberlain Date: Tue, 13 Apr 2021 10:35:01 -0400 Subject: [PATCH 76/79] Adds Network ML module with four ML jobs for ECS network data (#96480) * network module adds the network module with four ml jobs for the 7.13 release * Update datafeed_high_count_network_denies.json json formatting * update test added the security_network module to the list * renames module name change to security_network / Security: Network * formatting change hyphen char to underscores * fixes and name changes fixes to df queries, descriptions. created_by param * update tests tests need the security_network module added * formatting change hyphens to underscores * descriptions format descriptions * Update datafeed_high_count_network_events.json indentation fixes * Update x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json Co-authored-by: Lisa Cawley * Update x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json Co-authored-by: Lisa Cawley * Update datafeed_high_count_network_events.json change to a filter Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Lisa Cawley --- .../modules/security_network/logo.json | 3 + .../modules/security_network/manifest.json | 63 +++++++++++++++++++ ...eed_high_count_by_destination_country.json | 25 ++++++++ .../datafeed_high_count_network_denies.json | 25 ++++++++ .../datafeed_high_count_network_events.json | 20 ++++++ .../ml/datafeed_rare_destination_country.json | 25 ++++++++ .../ml/high_count_by_destination_country.json | 35 +++++++++++ .../ml/high_count_network_denies.json | 34 ++++++++++ .../ml/high_count_network_events.json | 34 ++++++++++ .../ml/rare_destination_country.json | 35 +++++++++++ .../apis/ml/modules/get_module.ts | 1 + .../apis/ml/modules/recognize_module.ts | 2 +- 12 files changed, 301 insertions(+), 1 deletion(-) create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json create mode 100755 x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json new file mode 100755 index 0000000000000..862f970b7405d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/logo.json @@ -0,0 +1,3 @@ +{ + "icon": "logoSecurity" +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json new file mode 100755 index 0000000000000..55f07ab077d40 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/manifest.json @@ -0,0 +1,63 @@ +{ + "id": "security_network", + "title": "Security: Network", + "description": "Detect anomalous network activity in your ECS-compatible network logs.", + "type": "network data", + "logoFile": "logo.json", + "defaultIndexPattern": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + } + ] + } + }, + "jobs": [ + { + "id": "high_count_by_destination_country", + "file": "high_count_by_destination_country.json" + }, + { + "id": "high_count_network_denies", + "file": "high_count_network_denies.json" + }, + { + "id": "high_count_network_events", + "file": "high_count_network_events.json" + }, + { + "id": "rare_destination_country", + "file": "rare_destination_country.json" + } + ], + "datafeeds": [ + { + "id": "datafeed_high_count_by_destination_country", + "file": "datafeed_high_count_by_destination_country.json", + "job_id": "high_count_by_destination_country" + }, + { + "id": "datafeed_high_count_network_denies", + "file": "datafeed_high_count_network_denies.json", + "job_id": "high_count_network_denies" + }, + { + "id": "datafeed_high_count_network_events", + "file": "datafeed_high_count_network_events.json", + "job_id": "high_count_network_events" + }, + { + "id": "datafeed_rare_destination_country", + "file": "datafeed_rare_destination_country.json", + "job_id": "rare_destination_country" + } + ] +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json new file mode 100755 index 0000000000000..48706c6ea6b5d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_by_destination_country.json @@ -0,0 +1,25 @@ +{ + "job_id": "high_count_by_destination_country", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + }, + { + "exists": { + "field": "destination.geo.country_name" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json new file mode 100755 index 0000000000000..a4412a6d732e9 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_denies.json @@ -0,0 +1,25 @@ +{ + "job_id": "high_count_network_denies", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + }, + { + "term": { + "event.outcome": "deny" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json new file mode 100755 index 0000000000000..1e3bbf92b8aed --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_high_count_network_events.json @@ -0,0 +1,20 @@ +{ + "job_id": "high_count_network_events", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json new file mode 100755 index 0000000000000..92431a6912faa --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/datafeed_rare_destination_country.json @@ -0,0 +1,25 @@ +{ + "job_id": "rare_destination_country", + "indices": [ + "logs-*", + "filebeat-*", + "packetbeat-*" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": [ + { + "term": { + "event.category": "network" + } + }, + { + "exists": { + "field": "destination.geo.country_name" + } + } + ] + } + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json new file mode 100755 index 0000000000000..aaee46d9cf80b --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_by_destination_country.json @@ -0,0 +1,35 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusually large spike in network activity to one destination country in the network logs. This could be due to unusually large amounts of reconnaissance or enumeration traffic. Data exfiltration activity may also produce such a surge in traffic to a destination country which does not normally appear in network traffic or business work-flows. Malware instances and persistence mechanisms may communicate with command-and-control (C2) infrastructure in their country of origin, which may be an unusual destination country for the source network.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high_non_zero_count by \"destination.geo.country_name\"", + "function": "high_non_zero_count", + "by_field_name": "destination.geo.country_name", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.ip" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json new file mode 100755 index 0000000000000..bc08aa21f3277 --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_denies.json @@ -0,0 +1,34 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusually large spike in network traffic that was denied by network ACLs or firewall rules. Such a burst of denied traffic is usually either 1) a misconfigured application or firewall or 2) suspicious or malicious activity. Unsuccessful attempts at network transit, in order to connect to command-and-control (C2), or engage in data exfiltration, may produce a burst of failed connections. This could also be due to unusually large amounts of reconnaissance or enumeration traffic. Denial-of-service attacks or traffic floods may also produce such a surge in traffic.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high_count", + "function": "high_count", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.port" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json new file mode 100755 index 0000000000000..d709eb21d7c6d --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/high_count_network_events.json @@ -0,0 +1,34 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusually large spike in network traffic. Such a burst of traffic, if not caused by a surge in business activity, can be due to suspicious or malicious activity. Large-scale data exfiltration may produce a burst of network traffic; this could also be due to unusually large amounts of reconnaissance or enumeration traffic. Denial-of-service attacks or traffic floods may also produce such a surge in traffic.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high_count", + "function": "high_count", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.ip" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json new file mode 100755 index 0000000000000..15571f89b81af --- /dev/null +++ b/x-pack/plugins/ml/server/models/data_recognizer/modules/security_network/ml/rare_destination_country.json @@ -0,0 +1,35 @@ +{ + "job_type": "anomaly_detector", + "description": "Security: Network - looks for an unusual destination country name in the network logs. This can be due to initial access, persistence, command-and-control, or exfiltration activity. For example, when a user clicks on a link in a phishing email or opens a malicious document, a request may be sent to download and run a payload from a server in a country which does not normally appear in network traffic or business work-flows. Malware instances and persistence mechanisms may communicate with command-and-control (C2) infrastructure in their country of origin, which may be an unusual destination country for the source network.", + "groups": [ + "security", + "network" + ], + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "rare by \"destination.geo.country_name\"", + "function": "rare", + "by_field_name": "destination.geo.country_name", + "detector_index": 0 + } + ], + "influencers": [ + "destination.geo.country_name", + "destination.as.organization.name", + "source.ip", + "destination.ip" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-security-network" + } +} diff --git a/x-pack/test/api_integration/apis/ml/modules/get_module.ts b/x-pack/test/api_integration/apis/ml/modules/get_module.ts index bd35bdddc3399..aade372374548 100644 --- a/x-pack/test/api_integration/apis/ml/modules/get_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/get_module.ts @@ -27,6 +27,7 @@ const moduleIds = [ 'sample_data_ecommerce', 'sample_data_weblogs', 'security_linux', + 'security_network', 'security_windows', 'siem_auditbeat', 'siem_auditbeat_auth', diff --git a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts index d7ba410dd5dd8..d6020e17801fd 100644 --- a/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts +++ b/x-pack/test/api_integration/apis/ml/modules/recognize_module.ts @@ -143,7 +143,7 @@ export default ({ getService }: FtrProviderContext) => { user: USER.ML_POWERUSER, expected: { responseCode: 200, - moduleIds: ['security_linux', 'security_windows'], + moduleIds: ['security_linux', 'security_network', 'security_windows'], }, }, ]; From 27c191d405db7f2a3096a269b7929f6adb1d86db Mon Sep 17 00:00:00 2001 From: Spencer Date: Tue, 13 Apr 2021 07:43:03 -0700 Subject: [PATCH 77/79] [plugin-generator] don't generate .eslintrc.js files for internal plugins (#96921) Co-authored-by: spalger --- packages/kbn-plugin-generator/src/render_template.ts | 2 +- x-pack/examples/reporting_example/.eslintrc.js | 7 ------- x-pack/examples/reporting_example/common/index.ts | 7 +++++++ x-pack/examples/reporting_example/public/application.tsx | 7 +++++++ .../examples/reporting_example/public/components/app.tsx | 7 +++++++ x-pack/examples/reporting_example/public/index.ts | 7 +++++++ x-pack/examples/reporting_example/public/plugin.ts | 7 +++++++ x-pack/examples/reporting_example/public/types.ts | 7 +++++++ x-pack/plugins/timelines/.eslintrc.js | 7 ------- x-pack/plugins/timelines/common/index.ts | 7 +++++++ x-pack/plugins/timelines/public/components/index.tsx | 7 +++++++ x-pack/plugins/timelines/public/index.ts | 7 +++++++ x-pack/plugins/timelines/public/plugin.ts | 7 +++++++ x-pack/plugins/timelines/public/types.ts | 7 +++++++ x-pack/plugins/timelines/server/config.ts | 5 +++-- x-pack/plugins/timelines/server/index.ts | 5 +++-- x-pack/plugins/timelines/server/plugin.ts | 7 +++++++ x-pack/plugins/timelines/server/routes/index.ts | 7 +++++++ x-pack/plugins/timelines/server/types.ts | 7 +++++++ 19 files changed, 105 insertions(+), 19 deletions(-) delete mode 100644 x-pack/examples/reporting_example/.eslintrc.js delete mode 100644 x-pack/plugins/timelines/.eslintrc.js diff --git a/packages/kbn-plugin-generator/src/render_template.ts b/packages/kbn-plugin-generator/src/render_template.ts index 282a547318d28..1a9716f1f1ba5 100644 --- a/packages/kbn-plugin-generator/src/render_template.ts +++ b/packages/kbn-plugin-generator/src/render_template.ts @@ -84,7 +84,7 @@ export async function renderTemplates({ answers.ui ? [] : 'public/**/*', answers.ui && !answers.internal ? [] : ['translations/**/*', 'i18nrc.json'], answers.server ? [] : 'server/**/*', - !answers.internal ? [] : ['eslintrc.js', 'tsconfig.json', 'package.json', '.gitignore'] + !answers.internal ? [] : ['.eslintrc.js', 'tsconfig.json', 'package.json', '.gitignore'] ) ), diff --git a/x-pack/examples/reporting_example/.eslintrc.js b/x-pack/examples/reporting_example/.eslintrc.js deleted file mode 100644 index b267018448ba6..0000000000000 --- a/x-pack/examples/reporting_example/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], - rules: { - '@kbn/eslint/require-license-header': 'off', - }, -}; diff --git a/x-pack/examples/reporting_example/common/index.ts b/x-pack/examples/reporting_example/common/index.ts index e47604bd7b823..f01f2673eff56 100644 --- a/x-pack/examples/reporting_example/common/index.ts +++ b/x-pack/examples/reporting_example/common/index.ts @@ -1,2 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + export const PLUGIN_ID = 'reportingExample'; export const PLUGIN_NAME = 'reportingExample'; diff --git a/x-pack/examples/reporting_example/public/application.tsx b/x-pack/examples/reporting_example/public/application.tsx index 1bb944faad3ea..25a1cc767f1f5 100644 --- a/x-pack/examples/reporting_example/public/application.tsx +++ b/x-pack/examples/reporting_example/public/application.tsx @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import React from 'react'; import ReactDOM from 'react-dom'; import { AppMountParameters, CoreStart } from '../../../../src/core/public'; diff --git a/x-pack/examples/reporting_example/public/components/app.tsx b/x-pack/examples/reporting_example/public/components/app.tsx index 8f7176675f2c2..fd4a85dd06779 100644 --- a/x-pack/examples/reporting_example/public/components/app.tsx +++ b/x-pack/examples/reporting_example/public/components/app.tsx @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { EuiCard, EuiCode, diff --git a/x-pack/examples/reporting_example/public/index.ts b/x-pack/examples/reporting_example/public/index.ts index a490cf96895be..f9f749e2b0cd0 100644 --- a/x-pack/examples/reporting_example/public/index.ts +++ b/x-pack/examples/reporting_example/public/index.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { ReportingExamplePlugin } from './plugin'; export function plugin() { diff --git a/x-pack/examples/reporting_example/public/plugin.ts b/x-pack/examples/reporting_example/public/plugin.ts index 95b4d917f549a..6ac1cbe01db92 100644 --- a/x-pack/examples/reporting_example/public/plugin.ts +++ b/x-pack/examples/reporting_example/public/plugin.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { AppMountParameters, AppNavLinkStatus, diff --git a/x-pack/examples/reporting_example/public/types.ts b/x-pack/examples/reporting_example/public/types.ts index d574053266fae..56e8c34d9dae4 100644 --- a/x-pack/examples/reporting_example/public/types.ts +++ b/x-pack/examples/reporting_example/public/types.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { DeveloperExamplesSetup } from '../../../../examples/developer_examples/public'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; import { ReportingStart } from '../../../plugins/reporting/public'; diff --git a/x-pack/plugins/timelines/.eslintrc.js b/x-pack/plugins/timelines/.eslintrc.js deleted file mode 100644 index b267018448ba6..0000000000000 --- a/x-pack/plugins/timelines/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], - rules: { - '@kbn/eslint/require-license-header': 'off', - }, -}; diff --git a/x-pack/plugins/timelines/common/index.ts b/x-pack/plugins/timelines/common/index.ts index 2354c513f73b8..c095b6c89627e 100644 --- a/x-pack/plugins/timelines/common/index.ts +++ b/x-pack/plugins/timelines/common/index.ts @@ -1,2 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + export const PLUGIN_ID = 'timelines'; export const PLUGIN_NAME = 'timelines'; diff --git a/x-pack/plugins/timelines/public/components/index.tsx b/x-pack/plugins/timelines/public/components/index.tsx index 3388b3c44baff..f44ad8052917f 100644 --- a/x-pack/plugins/timelines/public/components/index.tsx +++ b/x-pack/plugins/timelines/public/components/index.tsx @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import React from 'react'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; diff --git a/x-pack/plugins/timelines/public/index.ts b/x-pack/plugins/timelines/public/index.ts index b535def809de3..c3d24d49e2401 100644 --- a/x-pack/plugins/timelines/public/index.ts +++ b/x-pack/plugins/timelines/public/index.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import './index.scss'; import { PluginInitializerContext } from 'src/core/public'; diff --git a/x-pack/plugins/timelines/public/plugin.ts b/x-pack/plugins/timelines/public/plugin.ts index 7e90d9467fefd..76a692cf8ed10 100644 --- a/x-pack/plugins/timelines/public/plugin.ts +++ b/x-pack/plugins/timelines/public/plugin.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { CoreSetup, Plugin, PluginInitializerContext } from '../../../../src/core/public'; import { TimelinesPluginSetup, TimelineProps } from './types'; import { getTimelineLazy } from './methods'; diff --git a/x-pack/plugins/timelines/public/types.ts b/x-pack/plugins/timelines/public/types.ts index b199b45902718..1fa6d33a6af60 100644 --- a/x-pack/plugins/timelines/public/types.ts +++ b/x-pack/plugins/timelines/public/types.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { ReactElement } from 'react'; export interface TimelinesPluginSetup { diff --git a/x-pack/plugins/timelines/server/config.ts b/x-pack/plugins/timelines/server/config.ts index 633a95b8f91a7..31be256611803 100644 --- a/x-pack/plugins/timelines/server/config.ts +++ b/x-pack/plugins/timelines/server/config.ts @@ -1,7 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * 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 { TypeOf, schema } from '@kbn/config-schema'; diff --git a/x-pack/plugins/timelines/server/index.ts b/x-pack/plugins/timelines/server/index.ts index 32de97be2704a..65e2b6494c6f4 100644 --- a/x-pack/plugins/timelines/server/index.ts +++ b/x-pack/plugins/timelines/server/index.ts @@ -1,7 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { PluginInitializerContext } from '../../../../src/core/server'; diff --git a/x-pack/plugins/timelines/server/plugin.ts b/x-pack/plugins/timelines/server/plugin.ts index 3e330b19b7fdb..825d42994e096 100644 --- a/x-pack/plugins/timelines/server/plugin.ts +++ b/x-pack/plugins/timelines/server/plugin.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { PluginInitializerContext, CoreSetup, diff --git a/x-pack/plugins/timelines/server/routes/index.ts b/x-pack/plugins/timelines/server/routes/index.ts index edb10c579b30b..1c651469b795a 100644 --- a/x-pack/plugins/timelines/server/routes/index.ts +++ b/x-pack/plugins/timelines/server/routes/index.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + import { IRouter } from '../../../../../src/core/server'; export function defineRoutes(router: IRouter) { diff --git a/x-pack/plugins/timelines/server/types.ts b/x-pack/plugins/timelines/server/types.ts index cb544562b79b4..5bcc90b48f0b9 100644 --- a/x-pack/plugins/timelines/server/types.ts +++ b/x-pack/plugins/timelines/server/types.ts @@ -1,3 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface TimelinesPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface From 417776d9b693f5b0bb8c311b549cff6c40300ebc Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Tue, 13 Apr 2021 07:49:38 -0700 Subject: [PATCH 78/79] [DOCS] Adds concepts section for analysts (#96675) * [DOCS] Adds concepts section for analysts * [DOCS] Minor tweaks to concepts doc * Update docs/concepts/index.asciidoc Co-authored-by: Wylie Conlon * Update docs/concepts/save-query.asciidoc Co-authored-by: Wylie Conlon Co-authored-by: Wylie Conlon --- docs/concepts/images/add-filter-popup.png | Bin 0 -> 31465 bytes docs/concepts/images/global-search.png | Bin 0 -> 46460 bytes docs/concepts/images/refresh-every.png | Bin 0 -> 8560 bytes docs/concepts/images/save-icon.png | Bin 0 -> 841 bytes docs/concepts/images/top-bar.png | Bin 0 -> 64914 bytes docs/concepts/index.asciidoc | 149 ++++++++++++++++++++++ docs/concepts/save-query.asciidoc | 39 ++++++ docs/user/index.asciidoc | 2 + 8 files changed, 190 insertions(+) create mode 100644 docs/concepts/images/add-filter-popup.png create mode 100644 docs/concepts/images/global-search.png create mode 100644 docs/concepts/images/refresh-every.png create mode 100644 docs/concepts/images/save-icon.png create mode 100755 docs/concepts/images/top-bar.png create mode 100644 docs/concepts/index.asciidoc create mode 100644 docs/concepts/save-query.asciidoc diff --git a/docs/concepts/images/add-filter-popup.png b/docs/concepts/images/add-filter-popup.png new file mode 100644 index 0000000000000000000000000000000000000000..f1b5b1ff3f6ca6deef2aa256902642878ddc8a22 GIT binary patch literal 31465 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 literal 0 HcmV?d00001 diff --git a/docs/concepts/images/refresh-every.png b/docs/concepts/images/refresh-every.png new file mode 100644 index 0000000000000000000000000000000000000000..a0930a6c56a653849d6dbc39f95c0cf94d93bb1e GIT binary patch literal 8560 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 P*F#1^Q5-C4;QxOB6{%)W literal 0 HcmV?d00001 diff --git a/docs/concepts/images/save-icon.png b/docs/concepts/images/save-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..959c7ef8e1bb977c49e667902392f8d02ae1c910 GIT binary patch literal 841 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%0AO;aZK=^_BVZzDJ7e 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 NLR40yOi1VZ{{aydX3zit literal 0 HcmV?d00001 diff --git a/docs/concepts/index.asciidoc b/docs/concepts/index.asciidoc new file mode 100644 index 0000000000000..70b8a5265ce8a --- /dev/null +++ b/docs/concepts/index.asciidoc @@ -0,0 +1,149 @@ +[[kibana-concepts-analysts]] +== {kib} concepts for analysts +**_Learn the shared concepts for analyzing and visualizing your data_** + +As an analyst, you will use a combination of {kib} apps to analyze and +visualize your data. {kib} contains both general-purpose apps and apps for the +https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*], +{observability-guide}/observability-introduction.html[*Elastic Observability*], +and {security-guide}/es-overview.html[*Elastic Security*] solutions. +These apps share a common set of concepts. + +[float] +=== Three things to know about {es} + +You don't need to know everything about {es} to use {kib}, but the most important concepts follow: + +* *{es} makes JSON documents searchable and aggregatable.* The documents are +stored in an {ref}/documents-indices.html[index] or {ref}/data-streams.html[data stream], which represent one type of data. + +* **_Searchable_ means that you can filter the documents for conditions.** +For example, you can filter for data "within the last 7 days" or data that "contains the word {kib}". +{kib} provides many ways for you to construct filters, which are also called queries or search terms. + +* **_Aggregatable_ means that you can extract summaries from matching documents.** +The simplest aggregation is *count*, and it is frequently used in combination +with the *date histogram*, to see count over time. The *terms* aggregation shows the most frequent values. + +[float] +=== Finding your apps and objects + +{kib} offers a <> on every page that you can use to find any app or saved object. +Open the search bar using the keyboard shortcut Ctrl+/ on Windows and Linux, Command+/ on MacOS. + +[role="screenshot"] +image:concepts/images/global-search.png["Global search showing matches to apps and saved objects for the word visualize"] + +[float] +=== Accessing data with index patterns + +{kib} requires an index pattern to tell it which {es} data you want to access, +and whether the data is time-based. An index pattern can point to one or more {es} +data streams, indices, or index aliases by name. +For example, `logs-elasticsearch-prod-*` is an index pattern, +and it is time-based with a time field of `@timestamp`. The time field is not editable. + +Index patterns are typically created by an administrator when sending data to {es}. +You can <> in *Stack Management*, or by using a script +that accesses the {kib} API. + +{kib} uses the index pattern to show you a list of fields, such as +`event.duration`. You can customize the display name and format for each field. +For example, you can tell Kibana to display `event.duration` in seconds. +{kib} has <> for strings, +dates, geopoints, +and numbers. + +[float] +=== Searching your data + +{kib} provides you several ways to build search queries, +which will reduce the number of document matches that you get from {es}. +Each app in {kib} provides a time filter, and most apps also include semi-structured search and extra filters. + +[role="screenshot"] +image:concepts/images/top-bar.png["Time filter, semi-structured search, and filters in a {kib} app"] + +If you frequently use any of the search options, you can click the +save icon +image:concepts/images/save-icon.png["save icon"] next to the +semi-structured search to save or load a previously saved query. +The saved query will always contain the semi-structured search query, +and can optionally contain the time filter and extra filters. + +[float] +==== Time filter + +The <> limits the time range of data displayed. +In most cases, the time filter applies to the time field in the index pattern, +but some apps allow you to use a different time field. + +Using the time filter, you can configure a refresh rate to periodically +resubmit your searches. You can also click *Refresh* to resubmit the search. +This might be useful if you use {kib} to monitor the underlying data. + +[role="screenshot"] +image:concepts/images/refresh-every.png["section of time filter where you can configure a refresh rate"] + + +[float] +==== Semi-structured search + +Combine free text search with field-based search using the Kibana Query Language (KQL). +Type a search term to match across all fields, or start typing a field name to +get suggestions for field names and operators that you can use to build a structured query. +The semi-structured search will filter documents for matches, and only return matching documents. + +Following are some example KQL queries. For more detailed examples, refer to <>. + +[cols=2*] +|=== +| Exact phrase query +| `http.response.body.content.text:"quick brown fox"` + +| Terms query +| http.response.status_code:400 401 404 + +| Boolean query +| `response:200 or extension:php` + +| Range query +| `account_number >= 100 and items_sold <= 200` + +| Wildcard query +| `machine.os:win*` +|=== + +[float] +==== Additional filters with AND + +Structured filters are a more interactive way to create {es} queries, +and are commonly used when building dashboards that are shared by multiple analysts. +Each filter can be disabled, inverted, or pinned across all apps. +The structured filters are the only way to use the {es} Query DSL in JSON form, +or to target a specific index pattern for filtering. Each of the structured +filters is combined with AND logic on the rest of the query. + +[role="screenshot"] +image:concepts/images/add-filter-popup.png["Add filter popup"] + +[float] +=== Saving objects +{kib} lets you save objects for your own future use or for sharing with others. +Each <> type has different abilities. For example, you can save +your search queries made with *Discover*, which lets you: + +* Share a link to your search +* Download the full search results in CSV form +* Start an aggregated visualization using the same search query +* Embed the *Discover* search results into a dashboard +* Embed the *Discover* search results into a Canvas workpad + +For organization, every saved object can have a name, <>, and type. +Use the global search to quickly open a saved object. + +[float] +=== What's next? + +* Try the {kib} <>, which shows you how to put these concepts into action. +* Go to <> for instructions on searching your data. diff --git a/docs/concepts/save-query.asciidoc b/docs/concepts/save-query.asciidoc new file mode 100644 index 0000000000000..4f049d121bbef --- /dev/null +++ b/docs/concepts/save-query.asciidoc @@ -0,0 +1,39 @@ +[[save-load-delete-query]] +== Save a query +A saved query is a collection of query text and filters that you can +reuse in any app with a query bar, like <> and <>. Save a query when you want to: + +* Retrieve results from the same query at a later time without having to reenter the query text, add the filters or set the time filter +* View the results of the same query in multiple apps +* Share your query + +Saved queries don't include information specific to *Discover*, +such as the currently selected columns in the document table, the sort order, and the index pattern. +To save your current view of *Discover* for later retrieval and reuse, +create a <> instead. + +NOTE:: + +If you have insufficient privileges to save queries, the *Save current query* +button isn't visible in the saved query management popover. +For more information, see <> + +. Click *#* in the query bar. +. In the popover, click *Save current query*. ++ +[role="screenshot"] +image::discover/images/saved-query-management-component-all-privileges.png["Example of the saved query management popover with a list of saved queries with write access",width="80%"] ++ +. Enter a name, a description, and then select the filter options. +By default, filters are automatically included, but the time filter is not. ++ +[role="screenshot"] +image::discover/images/saved-query-save-form-default-filters.png["Example of the saved query management save form with the filters option included and the time filter option excluded",width="80%"] +. Click *Save*. +. To load a saved query into *Discover* or *Dashboard*, open the *Saved search* popover, and select the query. +. To manage your saved queries, use these actions in the popover: ++ +* Save as new: Save changes to the current query. +* Clear. Clear a query that is currently loaded in an app. +* Delete. You can’t recover a deleted query. +. To import and export saved queries, go to <>. diff --git a/docs/user/index.asciidoc b/docs/user/index.asciidoc index d7e15258bf29b..81ded1e54d8fd 100644 --- a/docs/user/index.asciidoc +++ b/docs/user/index.asciidoc @@ -2,6 +2,8 @@ include::introduction.asciidoc[] include::whats-new.asciidoc[] +include::{kib-repo-dir}/concepts/index.asciidoc[] + include::{kib-repo-dir}/getting-started/quick-start-guide.asciidoc[] include::setup.asciidoc[] From f67f0e80e73d95c72701be5186d14428843951b9 Mon Sep 17 00:00:00 2001 From: Tim Sullivan Date: Tue, 13 Apr 2021 08:03:09 -0700 Subject: [PATCH 79/79] Reporting: Fix _index and _id columns in CSV export (#96097) * Reporting: Fix _index and _id columns in CSV export * optimize - cache _columns and run getColumns once per execution * Update x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.ts Co-authored-by: Michael Dokolin * feedback * fix typescripts * fix plugin list test * fix plugin list * take away the export interface to test CI build stats Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Michael Dokolin --- .../helpers/get_sharing_data.test.ts | 69 ++++++--- .../application/helpers/get_sharing_data.ts | 17 +-- .../get_csv_panel_action.test.ts | 65 +++++++-- .../panel_actions/get_csv_panel_action.tsx | 28 ++-- .../register_csv_reporting.tsx | 14 +- .../register_pdf_png_reporting.tsx | 14 +- .../__snapshots__/generate_csv.test.ts.snap | 24 +++- .../generate_csv/generate_csv.test.ts | 136 +++++++++++++++++- .../generate_csv/generate_csv.ts | 69 +++++---- .../export_types/csv_searchsource/types.d.ts | 6 +- .../csv_searchsource_immediate/types.d.ts | 5 +- .../routes/csv_searchsource_immediate.ts | 1 + .../apps/dashboard/reporting/download_csv.ts | 3 +- .../csv_searchsource_immediate.ts | 9 +- 14 files changed, 342 insertions(+), 118 deletions(-) diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index ebb1946b524cd..6a51c085ebbc9 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -6,13 +6,12 @@ * Side Public License, v 1. */ -import { Capabilities } from 'kibana/public'; -import { getSharingData, showPublicUrlSwitch } from './get_sharing_data'; -import { IUiSettingsClient } from 'kibana/public'; +import { Capabilities, IUiSettingsClient } from 'kibana/public'; +import { IndexPattern } from 'src/plugins/data/public'; import { createSearchSourceMock } from '../../../../data/common/search/search_source/mocks'; -import { indexPatternMock } from '../../__mocks__/index_pattern'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; -import { IndexPattern } from 'src/plugins/data/public'; +import { indexPatternMock } from '../../__mocks__/index_pattern'; +import { getSharingData, showPublicUrlSwitch } from './get_sharing_data'; describe('getSharingData', () => { let mockConfig: IUiSettingsClient; @@ -36,6 +35,32 @@ describe('getSharingData', () => { const result = await getSharingData(searchSourceMock, { columns: [] }, mockConfig); expect(result).toMatchInlineSnapshot(` Object { + "columns": Array [], + "searchSource": Object { + "index": "the-index-pattern-id", + "sort": Array [ + Object { + "_score": "desc", + }, + ], + }, + } + `); + }); + + test('returns valid data for sharing when columns are selected', async () => { + const searchSourceMock = createSearchSourceMock({ index: indexPatternMock }); + const result = await getSharingData( + searchSourceMock, + { columns: ['column_a', 'column_b'] }, + mockConfig + ); + expect(result).toMatchInlineSnapshot(` + Object { + "columns": Array [ + "column_a", + "column_b", + ], "searchSource": Object { "index": "the-index-pattern-id", "sort": Array [ @@ -69,16 +94,16 @@ describe('getSharingData', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "columns": Array [ + "cool-timefield", + "cool-field-1", + "cool-field-2", + "cool-field-3", + "cool-field-4", + "cool-field-5", + "cool-field-6", + ], "searchSource": Object { - "fields": Array [ - "cool-timefield", - "cool-field-1", - "cool-field-2", - "cool-field-3", - "cool-field-4", - "cool-field-5", - "cool-field-6", - ], "index": "the-index-pattern-id", "sort": Array [ Object { @@ -120,15 +145,15 @@ describe('getSharingData', () => { ); expect(result).toMatchInlineSnapshot(` Object { + "columns": Array [ + "cool-field-1", + "cool-field-2", + "cool-field-3", + "cool-field-4", + "cool-field-5", + "cool-field-6", + ], "searchSource": Object { - "fields": Array [ - "cool-field-1", - "cool-field-2", - "cool-field-3", - "cool-field-4", - "cool-field-5", - "cool-field-6", - ], "index": "the-index-pattern-id", "sort": Array [ Object { diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.ts index f0e07ccc38deb..47be4b8037152 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.ts @@ -7,11 +7,11 @@ */ import type { Capabilities, IUiSettingsClient } from 'kibana/public'; -import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; -import { getSortForSearchSource } from '../angular/doc_table'; import { ISearchSource } from '../../../../data/common'; -import { AppState } from '../angular/discover_state'; +import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; import type { SavedSearch, SortOrder } from '../../saved_searches/types'; +import { AppState } from '../angular/discover_state'; +import { getSortForSearchSource } from '../angular/doc_table'; /** * Preparing data to share the current state as link or CSV/Report @@ -23,10 +23,6 @@ export async function getSharingData( ) { const searchSource = currentSearchSource.createCopy(); const index = searchSource.getField('index')!; - const fields = { - fields: searchSource.getField('fields'), - fieldsFromSource: searchSource.getField('fieldsFromSource'), - }; searchSource.setField( 'sort', @@ -37,7 +33,7 @@ export async function getSharingData( searchSource.removeField('aggs'); searchSource.removeField('size'); - // fields get re-set to match the saved search columns + // Columns that the user has selected in the saved search let columns = state.columns || []; if (columns && columns.length > 0) { @@ -50,14 +46,11 @@ export async function getSharingData( if (timeFieldName && !columns.includes(timeFieldName)) { columns = [timeFieldName, ...columns]; } - - // if columns were selected in the saved search, use them for the searchSource's fields - const fieldsKey = fields.fieldsFromSource ? 'fieldsFromSource' : 'fields'; - searchSource.setField(fieldsKey, columns); } return { searchSource: searchSource.getSerializedFields(true), + columns, }; } diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts index 4e1b9ccd2642f..06d626a4c4044 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.test.ts @@ -16,6 +16,7 @@ describe('GetCsvReportPanelAction', () => { let core: any; let context: any; let mockLicense$: any; + let mockSearchSource: any; beforeAll(() => { if (typeof window.URL.revokeObjectURL === 'undefined') { @@ -49,22 +50,19 @@ describe('GetCsvReportPanelAction', () => { }, } as any; + mockSearchSource = { + createCopy: () => mockSearchSource, + removeField: jest.fn(), + setField: jest.fn(), + getField: jest.fn(), + getSerializedFields: jest.fn().mockImplementation(() => ({})), + }; + context = { embeddable: { type: 'search', getSavedSearch: () => { - const searchSource = { - createCopy: () => searchSource, - removeField: jest.fn(), - setField: jest.fn(), - getField: jest.fn().mockImplementation((key: string) => { - if (key === 'index') { - return 'my-test-index-*'; - } - }), - getSerializedFields: jest.fn().mockImplementation(() => ({})), - }; - return { searchSource }; + return { searchSource: mockSearchSource }; }, getTitle: () => `The Dude`, getInspectorAdapters: () => null, @@ -79,6 +77,49 @@ describe('GetCsvReportPanelAction', () => { } as any; }); + it('translates empty embeddable context into job params', async () => { + const panel = new GetCsvReportPanelAction(core, mockLicense$()); + + await panel.execute(context); + + expect(core.http.post).toHaveBeenCalledWith( + '/api/reporting/v1/generate/immediate/csv_searchsource', + { + body: '{"searchSource":{},"columns":[],"browserTimezone":"America/New_York"}', + } + ); + }); + + it('translates embeddable context into job params', async () => { + // setup + mockSearchSource = { + createCopy: () => mockSearchSource, + removeField: jest.fn(), + setField: jest.fn(), + getField: jest.fn(), + getSerializedFields: jest.fn().mockImplementation(() => ({ testData: 'testDataValue' })), + }; + context.embeddable.getSavedSearch = () => { + return { + searchSource: mockSearchSource, + columns: ['column_a', 'column_b'], + }; + }; + + const panel = new GetCsvReportPanelAction(core, mockLicense$()); + + // test + await panel.execute(context); + + expect(core.http.post).toHaveBeenCalledWith( + '/api/reporting/v1/generate/immediate/csv_searchsource', + { + body: + '{"searchSource":{"testData":"testDataValue"},"columns":["column_a","column_b"],"browserTimezone":"America/New_York"}', + } + ); + }); + it('allows downloading for valid licenses', async () => { const panel = new GetCsvReportPanelAction(core, mockLicense$()); diff --git a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx index d440edc3f3fe9..95d193880975c 100644 --- a/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx +++ b/x-pack/plugins/reporting/public/panel_actions/get_csv_panel_action.tsx @@ -7,21 +7,19 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; -import { CoreSetup } from 'src/core/public'; +import type { CoreSetup } from 'src/core/public'; +import type { ISearchEmbeddable, SavedSearch } from '../../../../../src/plugins/discover/public'; import { loadSharingDataHelpers, - ISearchEmbeddable, - SavedSearch, SEARCH_EMBEDDABLE_TYPE, } from '../../../../../src/plugins/discover/public'; -import { IEmbeddable, ViewMode } from '../../../../../src/plugins/embeddable/public'; -import { - IncompatibleActionError, - UiActionsActionDefinition as ActionDefinition, -} from '../../../../../src/plugins/ui_actions/public'; -import { LicensingPluginSetup } from '../../../licensing/public'; +import type { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; +import { ViewMode } from '../../../../../src/plugins/embeddable/public'; +import type { UiActionsActionDefinition as ActionDefinition } from '../../../../../src/plugins/ui_actions/public'; +import { IncompatibleActionError } from '../../../../../src/plugins/ui_actions/public'; +import type { LicensingPluginSetup } from '../../../licensing/public'; import { API_GENERATE_IMMEDIATE, CSV_REPORTING_ACTION } from '../../common/constants'; -import { JobParamsDownloadCSV } from '../../server/export_types/csv_searchsource_immediate/types'; +import type { JobParamsDownloadCSV } from '../../server/export_types/csv_searchsource_immediate/types'; import { checkLicense } from '../lib/license_check'; function isSavedSearchEmbeddable( @@ -64,14 +62,11 @@ export class GetCsvReportPanelAction implements ActionDefinition public async getSearchSource(savedSearch: SavedSearch, embeddable: ISearchEmbeddable) { const { getSharingData } = await loadSharingDataHelpers(); - const searchSource = savedSearch.searchSource.createCopy(); - const { searchSource: serializedSearchSource } = await getSharingData( - searchSource, + return await getSharingData( + savedSearch.searchSource, savedSearch, // TODO: get unsaved state (using embeddale.searchScope): https://github.com/elastic/kibana/issues/43977 this.core.uiSettings ); - - return serializedSearchSource; } public isCompatible = async (context: ActionContext) => { @@ -96,12 +91,13 @@ export class GetCsvReportPanelAction implements ActionDefinition } const savedSearch = embeddable.getSavedSearch(); - const searchSource = await this.getSearchSource(savedSearch, embeddable); + const { columns, searchSource } = await this.getSearchSource(savedSearch, embeddable); const kibanaTimezone = this.core.uiSettings.get('dateFormat:tz'); const browserTimezone = kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; const immediateJobParams: JobParamsDownloadCSV = { searchSource, + columns, browserTimezone, title: savedSearch.title, }; diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx index 97433f7a4f0c1..8995ef4739b09 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_csv_reporting.tsx @@ -8,14 +8,15 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; import React from 'react'; -import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; -import { ShareContext } from '../../../../../src/plugins/share/public'; -import { LicensingPluginSetup } from '../../../licensing/public'; +import type { IUiSettingsClient, ToastsSetup } from 'src/core/public'; +import type { SearchSourceFields } from 'src/plugins/data/common'; +import type { ShareContext } from '../../../../../src/plugins/share/public'; +import type { LicensingPluginSetup } from '../../../licensing/public'; import { CSV_JOB_TYPE } from '../../common/constants'; -import { JobParamsCSV } from '../../server/export_types/csv_searchsource/types'; +import type { JobParamsCSV } from '../../server/export_types/csv_searchsource/types'; import { ReportingPanelContent } from '../components/reporting_panel_content_lazy'; import { checkLicense } from '../lib/license_check'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import type { ReportingAPIClient } from '../lib/reporting_api_client'; interface ReportingProvider { apiClient: ReportingAPIClient; @@ -65,7 +66,8 @@ export const csvReportingProvider = ({ browserTimezone, title: sharingData.title as string, objectType, - searchSource: sharingData.searchSource, + searchSource: sharingData.searchSource as SearchSourceFields, + columns: sharingData.columns as string[] | undefined, }; const getJobParams = () => jobParams; diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 87011cc918587..00ba167c50ae6 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -8,15 +8,15 @@ import { i18n } from '@kbn/i18n'; import moment from 'moment-timezone'; import React from 'react'; -import { IUiSettingsClient, ToastsSetup } from 'src/core/public'; -import { ShareContext } from '../../../../../src/plugins/share/public'; -import { LicensingPluginSetup } from '../../../licensing/public'; -import { LayoutParams } from '../../common/types'; -import { JobParamsPNG } from '../../server/export_types/png/types'; -import { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; +import type { IUiSettingsClient, ToastsSetup } from 'src/core/public'; +import type { ShareContext } from '../../../../../src/plugins/share/public'; +import type { LicensingPluginSetup } from '../../../licensing/public'; +import type { LayoutParams } from '../../common/types'; +import type { JobParamsPNG } from '../../server/export_types/png/types'; +import type { JobParamsPDF } from '../../server/export_types/printable_pdf/types'; import { ScreenCapturePanelContent } from '../components/screen_capture_panel_content_lazy'; import { checkLicense } from '../lib/license_check'; -import { ReportingAPIClient } from '../lib/reporting_api_client'; +import type { ReportingAPIClient } from '../lib/reporting_api_client'; interface ReportingPDFPNGProvider { apiClient: ReportingAPIClient; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap index 62c9ecff830ff..789b68a25ac42 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/__snapshots__/generate_csv.test.ts.snap @@ -1,18 +1,36 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`fields cells can be multi-value 1`] = ` +exports[`fields from job.columns (7.13+ generated) cells can be multi-value 1`] = ` +"product,category +coconut,\\"cool, rad\\" +" +`; + +exports[`fields from job.columns (7.13+ generated) columns can be top-level fields such as _id and _index 1`] = ` +"\\"_id\\",\\"_index\\",product,category +\\"my-cool-id\\",\\"my-cool-index\\",coconut,\\"cool, rad\\" +" +`; + +exports[`fields from job.columns (7.13+ generated) empty columns defaults to using searchSource.getFields() 1`] = ` +"product +coconut +" +`; + +exports[`fields from job.searchSource.getFields() (7.12 generated) cells can be multi-value 1`] = ` "\\"_id\\",sku \\"my-cool-id\\",\\"This is a cool SKU., This is also a cool SKU.\\" " `; -exports[`fields provides top-level underscored fields as columns 1`] = ` +exports[`fields from job.searchSource.getFields() (7.12 generated) provides top-level underscored fields as columns 1`] = ` "\\"_id\\",\\"_index\\",date,message \\"my-cool-id\\",\\"my-cool-index\\",\\"2020-12-31T00:14:28.000Z\\",\\"it's nice to see you\\" " `; -exports[`fields sorts the fields when they are to be used as table column names 1`] = ` +exports[`fields from job.searchSource.getFields() (7.12 generated) sorts the fields when they are to be used as table column names 1`] = ` "\\"_id\\",\\"_index\\",\\"_score\\",\\"_type\\",date,\\"message_t\\",\\"message_u\\",\\"message_v\\",\\"message_w\\",\\"message_x\\",\\"message_y\\",\\"message_z\\" \\"my-cool-id\\",\\"my-cool-index\\",\\"'-\\",\\"'-\\",\\"2020-12-31T00:14:28.000Z\\",\\"test field T\\",\\"test field U\\",\\"test field V\\",\\"test field W\\",\\"test field X\\",\\"test field Y\\",\\"test field Z\\" " diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts index 0193eaaff2c8d..8694eddce7967 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/generate_csv/generate_csv.test.ts @@ -326,7 +326,7 @@ it('uses the scrollId to page all the data', async () => { expect(csvResult.content).toMatchSnapshot(); }); -describe('fields', () => { +describe('fields from job.searchSource.getFields() (7.12 generated)', () => { it('cells can be multi-value', async () => { searchSourceMock.getField = jest.fn().mockImplementation((key: string) => { if (key === 'fields') { @@ -497,6 +497,140 @@ describe('fields', () => { }); }); +describe('fields from job.columns (7.13+ generated)', () => { + it('cells can be multi-value', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => + Rx.of({ + rawResponse: { + hits: { + hits: [ + { + _id: 'my-cool-id', + _index: 'my-cool-index', + _version: 4, + fields: { + product: 'coconut', + category: [`cool`, `rad`], + }, + }, + ], + total: 1, + }, + }, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ searchSource: {}, columns: ['product', 'category'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger + ); + const csvResult = await generateCsv.generateData(); + + expect(csvResult.content).toMatchSnapshot(); + }); + + it('columns can be top-level fields such as _id and _index', async () => { + mockDataClient.search = jest.fn().mockImplementation(() => + Rx.of({ + rawResponse: { + hits: { + hits: [ + { + _id: 'my-cool-id', + _index: 'my-cool-index', + _version: 4, + fields: { + product: 'coconut', + category: [`cool`, `rad`], + }, + }, + ], + total: 1, + }, + }, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ searchSource: {}, columns: ['_id', '_index', 'product', 'category'] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger + ); + const csvResult = await generateCsv.generateData(); + + expect(csvResult.content).toMatchSnapshot(); + }); + + it('empty columns defaults to using searchSource.getFields()', async () => { + searchSourceMock.getField = jest.fn().mockImplementation((key: string) => { + if (key === 'fields') { + return ['product']; + } + return mockSearchSourceGetFieldDefault(key); + }); + mockDataClient.search = jest.fn().mockImplementation(() => + Rx.of({ + rawResponse: { + hits: { + hits: [ + { + _id: 'my-cool-id', + _index: 'my-cool-index', + _version: 4, + fields: { + product: 'coconut', + category: [`cool`, `rad`], + }, + }, + ], + total: 1, + }, + }, + }) + ); + + const generateCsv = new CsvGenerator( + createMockJob({ searchSource: {}, columns: [] }), + mockConfig, + { + es: mockEsClient, + data: mockDataClient, + uiSettings: uiSettingsClient, + }, + { + searchSourceStart: mockSearchSourceService, + fieldFormatsRegistry: mockFieldFormatsRegistry, + }, + new CancellationToken(), + logger + ); + const csvResult = await generateCsv.generateData(); + + expect(csvResult.content).toMatchSnapshot(); + }); +}); + describe('formulas', () => { const TEST_FORMULA = '=SUM(A1:A2)'; 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 01959ed08036d..7517396961c00 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 @@ -20,6 +20,7 @@ import { ISearchSource, ISearchStartSearchSource, SearchFieldValue, + SearchSourceFields, tabifyDocs, } from '../../../../../../../src/plugins/data/common'; import { KbnServerError } from '../../../../../../../src/plugins/kibana_utils/server'; @@ -60,7 +61,8 @@ function isPlainStringArray( } export class CsvGenerator { - private _formatters: Record | null = null; + private _columns?: string[]; + private _formatters?: Record; private csvContainsFormulas = false; private maxSizeReached = false; private csvRowCount = 0; @@ -135,27 +137,36 @@ export class CsvGenerator { }; } - // use fields/fieldsFromSource from the searchSource to get the ordering of columns - // otherwise use the table columns as they are - private getFields(searchSource: ISearchSource, table: Datatable): string[] { - const fieldValues: Record = { - fields: searchSource.getField('fields'), - fieldsFromSource: searchSource.getField('fieldsFromSource'), - }; - const fieldSource = fieldValues.fieldsFromSource ? 'fieldsFromSource' : 'fields'; - this.logger.debug(`Getting search source fields from: '${fieldSource}'`); - - const fields = fieldValues[fieldSource]; - // Check if field name values are string[] and if the fields are user-defined - if (isPlainStringArray(fields)) { - return fields; + private getColumns(searchSource: ISearchSource, table: Datatable) { + if (this._columns != null) { + return this._columns; } - // Default to using the table column IDs as the fields - const columnIds = table.columns.map((c) => c.id); - // Fields in the API response don't come sorted - they need to be sorted client-side - columnIds.sort(); - return columnIds; + // if columns is not provided in job params, + // default to use fields/fieldsFromSource from the searchSource to get the ordering of columns + const getFromSearchSource = (): string[] => { + const fieldValues: Pick = { + fields: searchSource.getField('fields'), + fieldsFromSource: searchSource.getField('fieldsFromSource'), + }; + const fieldSource = fieldValues.fieldsFromSource ? 'fieldsFromSource' : 'fields'; + this.logger.debug(`Getting columns from '${fieldSource}' in search source.`); + + const fields = fieldValues[fieldSource]; + // Check if field name values are string[] and if the fields are user-defined + if (isPlainStringArray(fields)) { + return fields; + } + + // Default to using the table column IDs as the fields + const columnIds = table.columns.map((c) => c.id); + // Fields in the API response don't come sorted - they need to be sorted client-side + columnIds.sort(); + return columnIds; + }; + this._columns = this.job.columns?.length ? this.job.columns : getFromSearchSource(); + + return this._columns; } private formatCellValues(formatters: Record) { @@ -202,16 +213,16 @@ export class CsvGenerator { } /* - * Use the list of fields to generate the header row + * Use the list of columns to generate the header row */ private generateHeader( - fields: string[], + columns: string[], table: Datatable, builder: MaxSizeStringBuilder, settings: CsvExportSettings ) { this.logger.debug(`Building CSV header row...`); - const header = fields.map(this.escapeValues(settings)).join(settings.separator) + '\n'; + const header = columns.map(this.escapeValues(settings)).join(settings.separator) + '\n'; if (!builder.tryAppend(header)) { return { @@ -227,7 +238,7 @@ export class CsvGenerator { * Format a Datatable into rows of CSV content */ private generateRows( - fields: string[], + columns: string[], table: Datatable, builder: MaxSizeStringBuilder, formatters: Record, @@ -240,7 +251,7 @@ export class CsvGenerator { } const row = - fields + columns .map((f) => ({ column: f, data: dataTableRow[f] })) .map(this.formatCellValues(formatters)) .map(this.escapeValues(settings)) @@ -338,11 +349,13 @@ export class CsvGenerator { break; } - const fields = this.getFields(searchSource, table); + // If columns exists in the job params, use it to order the CSV columns + // otherwise, get the ordering from the searchSource's fields / fieldsFromSource + const columns = this.getColumns(searchSource, table); if (first) { first = false; - this.generateHeader(fields, table, builder, settings); + this.generateHeader(columns, table, builder, settings); } if (table.rows.length < 1) { @@ -350,7 +363,7 @@ export class CsvGenerator { } const formatters = this.getFormatters(table); - this.generateRows(fields, table, builder, formatters, settings); + this.generateRows(columns, table, builder, formatters, settings); // update iterator currentRecord += table.rows.length; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts index f0ad4e00ebd5c..d2a9e2b5bf783 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource/types.d.ts @@ -5,13 +5,15 @@ * 2.0. */ -import { BaseParams, BasePayload } from '../../types'; +import type { SearchSourceFields } from 'src/plugins/data/common'; +import type { BaseParams, BasePayload } from '../../types'; export type RawValue = string | object | null | undefined; interface BaseParamsCSV { browserTimezone: string; - searchSource: any; + searchSource: SearchSourceFields; + columns?: string[]; } export type JobParamsCSV = BaseParamsCSV & BaseParams; diff --git a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts index 276016dd61233..cb1dd659ee2c2 100644 --- a/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts +++ b/x-pack/plugins/reporting/server/export_types/csv_searchsource_immediate/types.d.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { TimeRangeParams } from '../common'; +import type { SearchSourceFields } from 'src/plugins/data/common'; export interface FakeRequest { headers: Record; @@ -14,7 +14,8 @@ export interface FakeRequest { export interface JobParamsDownloadCSV { browserTimezone: string; title: string; - searchSource: any; + searchSource: SearchSourceFields; + columns?: string[]; } export interface SavedObjectServiceError { diff --git a/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts b/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts index 55092b5236ce6..5d2b77c082ca5 100644 --- a/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts +++ b/x-pack/plugins/reporting/server/routes/csv_searchsource_immediate.ts @@ -44,6 +44,7 @@ export function registerGenerateCsvFromSavedObjectImmediate( path: `${API_BASE_GENERATE_V1}/immediate/csv_searchsource`, validate: { body: schema.object({ + columns: schema.maybe(schema.arrayOf(schema.string())), searchSource: schema.object({}, { unknowns: 'allow' }), browserTimezone: schema.string({ defaultValue: 'UTC' }), title: schema.string(), diff --git a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts index c437cfaa8f5dc..d4a909f6a0474 100644 --- a/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts +++ b/x-pack/test/functional/apps/dashboard/reporting/download_csv.ts @@ -50,8 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.existOrFail('csvDownloadStarted'); // validate toast panel }; - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/96000 - describe.skip('Download CSV', () => { + describe('Download CSV', () => { before('initialize tests', async () => { log.debug('ReportingPage:initTests'); await browser.setWindowSize(1600, 850); diff --git a/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts b/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts index ebc7badd88f42..f381bc1edd28e 100644 --- a/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts +++ b/x-pack/test/reporting_api_integration/reporting_and_security/csv_searchsource_immediate.ts @@ -10,9 +10,9 @@ import supertest from 'supertest'; import { JobParamsDownloadCSV } from '../../../plugins/reporting/server/export_types/csv_searchsource_immediate/types'; import { FtrProviderContext } from '../ftr_provider_context'; -const getMockJobParams = (obj: Partial): JobParamsDownloadCSV => ({ +const getMockJobParams = (obj: any): JobParamsDownloadCSV => ({ title: `Mock CSV Title`, - ...(obj as any), + ...obj, }); // eslint-disable-next-line import/no-default-export @@ -31,8 +31,7 @@ export default function ({ getService }: FtrProviderContext) { }, }; - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/96000 - describe.skip('CSV Generation from SearchSource', () => { + describe('CSV Generation from SearchSource', () => { before(async () => { await kibanaServer.uiSettings.update({ 'csv:quoteValues': false, @@ -387,9 +386,9 @@ export default function ({ getService }: FtrProviderContext) { version: true, index: '907bc200-a294-11e9-a900-ef10e0ac769e', sort: [{ date: 'desc' }], - fields: ['date', 'message', '_id', '_index'], filter: [], }, + columns: ['date', 'message', '_id', '_index'], }) ); const { status: resStatus, text: resText, type: resType } = res;