From 3b1921163c10acd03ba116b9d4fbceb427c01633 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 26 Feb 2020 12:05:47 +0100 Subject: [PATCH 01/22] satisfy proptype check (#58538) --- .../kibana/public/discover/np_ready/angular/discover.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index bf5049cd976a3..1ac54ad5dabee 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -556,8 +556,9 @@ function discoverController( $scope.opts = { // number of records to fetch, then paginate through sampleSize: config.get('discover:sampleSize'), - timefield: - indexPatternsUtils.isDefault($scope.indexPattern) && $scope.indexPattern.timeFieldName, + timefield: indexPatternsUtils.isDefault($scope.indexPattern) + ? $scope.indexPattern.timeFieldName + : undefined, savedSearch: savedSearch, indexPatternList: $route.current.locals.savedObjects.ip.list, }; From 342e50183d74774e36c275b429cd9afc54d45508 Mon Sep 17 00:00:00 2001 From: Uladzislau Lasitsa Date: Wed, 26 Feb 2020 16:04:58 +0300 Subject: [PATCH 02/22] =?UTF-8?q?Converted=20brush=20event=20to=20TS.=20Mi?= =?UTF-8?q?grated=20tests=20to=20"jest"=20way=20and=20optimiz=E2=80=A6=20(?= =?UTF-8?q?#58206)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Converted brush event to TS. Migrated tests to "jest" way and optimiz * Removed unused definition in interface. Revert changes related to import deserializeAggConfig. Co-authored-by: Alexey Antonov Co-authored-by: Elastic Machine --- .../public/actions/filters/brush_event.js | 71 ------ .../actions/filters/brush_event.test.js | 199 ----------------- .../actions/filters/brush_event.test.ts | 208 ++++++++++++++++++ .../public/actions/filters/brush_event.ts | 80 +++++++ .../public/actions/select_range_action.ts | 21 +- 5 files changed, 296 insertions(+), 283 deletions(-) delete mode 100644 src/legacy/core_plugins/data/public/actions/filters/brush_event.js delete mode 100644 src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js create mode 100644 src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts create mode 100644 src/legacy/core_plugins/data/public/actions/filters/brush_event.ts diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.js b/src/legacy/core_plugins/data/public/actions/filters/brush_event.js deleted file mode 100644 index 67711bd4599a2..0000000000000 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import moment from 'moment'; -import { esFilters } from '../../../../../../plugins/data/public'; -import { deserializeAggConfig } from '../../search/expressions/utils'; - -export async function onBrushEvent(event, getIndexPatterns) { - const isNumber = event.data.ordered; - const isDate = isNumber && event.data.ordered.date; - - const xRaw = _.get(event.data, 'series[0].values[0].xRaw'); - if (!xRaw) return []; - const column = xRaw.table.columns[xRaw.column]; - if (!column) return []; - if (!column.meta) return []; - const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId); - const aggConfig = deserializeAggConfig({ - ...column.meta, - indexPattern, - }); - const field = aggConfig.params.field; - if (!field) return []; - - if (event.range.length <= 1) return []; - - const min = event.range[0]; - const max = event.range[event.range.length - 1]; - if (min === max) return []; - - let range; - - if (isDate) { - range = { - gte: moment(min).toISOString(), - lt: moment(max).toISOString(), - format: 'strict_date_optional_time', - }; - } else { - range = { - gte: min, - lt: max, - }; - } - - const newFilter = esFilters.buildRangeFilter( - field, - range, - indexPattern, - event.data.xAxisFormatter - ); - - return [newFilter]; -} diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js deleted file mode 100644 index 743f6caee4edd..0000000000000 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import moment from 'moment'; -import expect from '@kbn/expect'; - -jest.mock('../../search/aggs', () => ({ - AggConfigs: function AggConfigs() { - return { - createAggConfig: ({ params }) => ({ - params, - getIndexPattern: () => ({ - timeFieldName: 'time', - }), - }), - }; - }, -})); - -import { onBrushEvent } from './brush_event'; - -describe('brushEvent', () => { - const DAY_IN_MS = 24 * 60 * 60 * 1000; - const JAN_01_2014 = 1388559600000; - - const aggConfigs = [ - { - params: {}, - getIndexPattern: () => ({ - timeFieldName: 'time', - }), - }, - ]; - - const baseEvent = { - data: { - fieldFormatter: _.constant({}), - series: [ - { - values: [ - { - xRaw: { - column: 0, - table: { - columns: [ - { - id: '1', - meta: { - type: 'histogram', - indexPatternId: 'indexPatternId', - aggConfigParams: aggConfigs[0].params, - }, - }, - ], - }, - }, - }, - ], - }, - ], - }, - }; - - beforeEach(() => { - baseEvent.data.indexPattern = { - id: 'logstash-*', - timeFieldName: 'time', - }; - }); - - test('should be a function', () => { - expect(onBrushEvent).to.be.a(Function); - }); - - test('ignores event when data.xAxisField not provided', async () => { - const event = _.cloneDeep(baseEvent); - const filters = await onBrushEvent(event, () => ({ - get: () => baseEvent.data.indexPattern, - })); - expect(filters.length).to.equal(0); - }); - - describe('handles an event when the x-axis field is a date field', () => { - describe('date field is index pattern timefield', () => { - let dateEvent; - const dateField = { - name: 'time', - type: 'date', - }; - - beforeEach(() => { - aggConfigs[0].params.field = dateField; - dateEvent = _.cloneDeep(baseEvent); - dateEvent.data.ordered = { date: true }; - }); - - test('by ignoring the event when range spans zero time', async () => { - const event = _.cloneDeep(dateEvent); - event.range = [JAN_01_2014, JAN_01_2014]; - const filters = await onBrushEvent(event, () => ({ - get: () => dateEvent.data.indexPattern, - })); - expect(filters.length).to.equal(0); - }); - - test('by updating the timefilter', async () => { - const event = _.cloneDeep(dateEvent); - event.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS]; - const filters = await onBrushEvent(event, () => ({ - get: async () => dateEvent.data.indexPattern, - })); - expect(filters[0].range.time.gte).to.be(new Date(JAN_01_2014).toISOString()); - // Set to a baseline timezone for comparison. - expect(filters[0].range.time.lt).to.be(new Date(JAN_01_2014 + DAY_IN_MS).toISOString()); - }); - }); - - describe('date field is not index pattern timefield', () => { - let dateEvent; - const dateField = { - name: 'anotherTimeField', - type: 'date', - }; - - beforeEach(() => { - aggConfigs[0].params.field = dateField; - dateEvent = _.cloneDeep(baseEvent); - dateEvent.data.ordered = { date: true }; - }); - - test('creates a new range filter', async () => { - const event = _.cloneDeep(dateEvent); - const rangeBegin = JAN_01_2014; - const rangeEnd = rangeBegin + DAY_IN_MS; - event.range = [rangeBegin, rangeEnd]; - const filters = await onBrushEvent(event, () => ({ - get: () => dateEvent.data.indexPattern, - })); - expect(filters.length).to.equal(1); - expect(filters[0].range.anotherTimeField.gte).to.equal(moment(rangeBegin).toISOString()); - expect(filters[0].range.anotherTimeField.lt).to.equal(moment(rangeEnd).toISOString()); - expect(filters[0].range.anotherTimeField).to.have.property('format'); - expect(filters[0].range.anotherTimeField.format).to.equal('strict_date_optional_time'); - }); - }); - }); - - describe('handles an event when the x-axis field is a number', () => { - let numberEvent; - const numberField = { - name: 'numberField', - type: 'number', - }; - - beforeEach(() => { - aggConfigs[0].params.field = numberField; - numberEvent = _.cloneDeep(baseEvent); - numberEvent.data.ordered = { date: false }; - }); - - test('by ignoring the event when range does not span at least 2 values', async () => { - const event = _.cloneDeep(numberEvent); - event.range = [1]; - const filters = await onBrushEvent(event, () => ({ - get: () => numberEvent.data.indexPattern, - })); - expect(filters.length).to.equal(0); - }); - - test('by creating a new filter', async () => { - const event = _.cloneDeep(numberEvent); - event.range = [1, 2, 3, 4]; - const filters = await onBrushEvent(event, () => ({ - get: () => numberEvent.data.indexPattern, - })); - expect(filters.length).to.equal(1); - expect(filters[0].range.numberField.gte).to.equal(1); - expect(filters[0].range.numberField.lt).to.equal(4); - expect(filters[0].range.numberField).not.to.have.property('format'); - }); - }); -}); diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts new file mode 100644 index 0000000000000..0e18c7c707fa3 --- /dev/null +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.ts @@ -0,0 +1,208 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import moment from 'moment'; + +jest.mock('../../search/aggs', () => ({ + AggConfigs: function AggConfigs() { + return { + createAggConfig: ({ params }: Record) => ({ + params, + getIndexPattern: () => ({ + timeFieldName: 'time', + }), + }), + }; + }, +})); + +jest.mock('../../../../../../plugins/data/public/services', () => ({ + getIndexPatterns: () => { + return { + get: async () => { + return { + id: 'logstash-*', + timeFieldName: 'time', + }; + }, + }; + }, +})); + +import { onBrushEvent, BrushEvent } from './brush_event'; + +describe('brushEvent', () => { + const DAY_IN_MS = 24 * 60 * 60 * 1000; + const JAN_01_2014 = 1388559600000; + let baseEvent: BrushEvent; + + const aggConfigs = [ + { + params: { + field: {}, + }, + getIndexPattern: () => ({ + timeFieldName: 'time', + }), + }, + ]; + + beforeEach(() => { + baseEvent = { + data: { + ordered: { + date: false, + }, + series: [ + { + values: [ + { + xRaw: { + column: 0, + table: { + columns: [ + { + id: '1', + meta: { + type: 'histogram', + indexPatternId: 'indexPatternId', + aggConfigParams: aggConfigs[0].params, + }, + }, + ], + }, + }, + }, + ], + }, + ], + }, + range: [], + }; + }); + + test('should be a function', () => { + expect(typeof onBrushEvent).toBe('function'); + }); + + test('ignores event when data.xAxisField not provided', async () => { + const filter = await onBrushEvent(baseEvent); + expect(filter).toBeUndefined(); + }); + + describe('handles an event when the x-axis field is a date field', () => { + describe('date field is index pattern timefield', () => { + beforeEach(() => { + aggConfigs[0].params.field = { + name: 'time', + type: 'date', + }; + baseEvent.data.ordered = { date: true }; + }); + + afterAll(() => { + baseEvent.range = []; + baseEvent.data.ordered = { date: false }; + }); + + test('by ignoring the event when range spans zero time', async () => { + baseEvent.range = [JAN_01_2014, JAN_01_2014]; + const filter = await onBrushEvent(baseEvent); + expect(filter).toBeUndefined(); + }); + + test('by updating the timefilter', async () => { + baseEvent.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS]; + const filter = await onBrushEvent(baseEvent); + expect(filter).toBeDefined(); + + if (filter) { + expect(filter.range.time.gte).toBe(new Date(JAN_01_2014).toISOString()); + // Set to a baseline timezone for comparison. + expect(filter.range.time.lt).toBe(new Date(JAN_01_2014 + DAY_IN_MS).toISOString()); + } + }); + }); + + describe('date field is not index pattern timefield', () => { + beforeEach(() => { + aggConfigs[0].params.field = { + name: 'anotherTimeField', + type: 'date', + }; + baseEvent.data.ordered = { date: true }; + }); + + afterAll(() => { + baseEvent.range = []; + baseEvent.data.ordered = { date: false }; + }); + + test('creates a new range filter', async () => { + const rangeBegin = JAN_01_2014; + const rangeEnd = rangeBegin + DAY_IN_MS; + baseEvent.range = [rangeBegin, rangeEnd]; + const filter = await onBrushEvent(baseEvent); + + expect(filter).toBeDefined(); + + if (filter) { + expect(filter.range.anotherTimeField.gte).toBe(moment(rangeBegin).toISOString()); + expect(filter.range.anotherTimeField.lt).toBe(moment(rangeEnd).toISOString()); + expect(filter.range.anotherTimeField).toHaveProperty( + 'format', + 'strict_date_optional_time' + ); + } + }); + }); + }); + + describe('handles an event when the x-axis field is a number', () => { + beforeAll(() => { + aggConfigs[0].params.field = { + name: 'numberField', + type: 'number', + }; + }); + + afterAll(() => { + baseEvent.range = []; + }); + + test('by ignoring the event when range does not span at least 2 values', async () => { + baseEvent.range = [1]; + const filter = await onBrushEvent(baseEvent); + expect(filter).toBeUndefined(); + }); + + test('by creating a new filter', async () => { + baseEvent.range = [1, 2, 3, 4]; + const filter = await onBrushEvent(baseEvent); + + expect(filter).toBeDefined(); + + if (filter) { + expect(filter.range.numberField.gte).toBe(1); + expect(filter.range.numberField.lt).toBe(4); + expect(filter.range.numberField).not.toHaveProperty('format'); + } + }); + }); +}); diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.ts new file mode 100644 index 0000000000000..00990d21ccf37 --- /dev/null +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.ts @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get, last } from 'lodash'; +import moment from 'moment'; +import { esFilters, IFieldType, RangeFilterParams } from '../../../../../../plugins/data/public'; +import { deserializeAggConfig } from '../../search/expressions/utils'; +// should be removed after moving into new platform plugins data folder +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIndexPatterns } from '../../../../../../plugins/data/public/services'; + +export interface BrushEvent { + data: { + ordered: { + date: boolean; + }; + series: Array>; + }; + range: number[]; +} + +export async function onBrushEvent(event: BrushEvent) { + const isDate = get(event.data, 'ordered.date'); + const xRaw: Record = get(event.data, 'series[0].values[0].xRaw'); + + if (!xRaw) { + return; + } + + const column: Record = xRaw.table.columns[xRaw.column]; + + if (!column || !column.meta) { + return; + } + + const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId); + const aggConfig = deserializeAggConfig({ + ...column.meta, + indexPattern, + }); + const field: IFieldType = aggConfig.params.field; + + if (!field || event.range.length <= 1) { + return; + } + + const min = event.range[0]; + const max = last(event.range); + + if (min === max) { + return; + } + + const range: RangeFilterParams = { + gte: isDate ? moment(min).toISOString() : min, + lt: isDate ? moment(max).toISOString() : max, + }; + + if (isDate) { + range.format = 'strict_date_optional_time'; + } + + return esFilters.buildRangeFilter(field, range, indexPattern); +} diff --git a/src/legacy/core_plugins/data/public/actions/select_range_action.ts b/src/legacy/core_plugins/data/public/actions/select_range_action.ts index 8d0b74be50535..7f1c5d78ab800 100644 --- a/src/legacy/core_plugins/data/public/actions/select_range_action.ts +++ b/src/legacy/core_plugins/data/public/actions/select_range_action.ts @@ -23,16 +23,8 @@ import { createAction, IncompatibleActionError, } from '../../../../../plugins/ui_actions/public'; -// @ts-ignore import { onBrushEvent } from './filters/brush_event'; -import { - Filter, - FilterManager, - TimefilterContract, - esFilters, -} from '../../../../../plugins/data/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getIndexPatterns } from '../../../../../plugins/data/public/services'; +import { FilterManager, TimefilterContract, esFilters } from '../../../../../plugins/data/public'; export const SELECT_RANGE_ACTION = 'SELECT_RANGE_ACTION'; @@ -43,8 +35,7 @@ interface ActionContext { async function isCompatible(context: ActionContext) { try { - const filters: Filter[] = (await onBrushEvent(context.data, getIndexPatterns)) || []; - return filters.length > 0; + return Boolean(await onBrushEvent(context.data)); } catch { return false; } @@ -68,9 +59,13 @@ export function selectRangeAction( throw new IncompatibleActionError(); } - const filters: Filter[] = (await onBrushEvent(data, getIndexPatterns)) || []; + const filter = await onBrushEvent(data); + + if (!filter) { + return; + } - const selectedFilters: Filter[] = esFilters.mapAndFlattenFilters(filters); + const selectedFilters = esFilters.mapAndFlattenFilters([filter]); if (timeFieldName) { const { timeRangeFilter, restOfFilters } = esFilters.extractTimeFilter( From 2ea4bdfe0dc61e303fa555a12788a3868e7510db Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 26 Feb 2020 07:14:08 -0600 Subject: [PATCH 03/22] Server-side license check for APM service map (#58547) * Factor our the license checking logic and messaging to common * Add licensing plugin as a dependency of the APM plugin * Throw a forbidden error on the server if trying to access the service map routes --- .../app/ServiceMap/PlatinumLicensePrompt.tsx | 18 ++++++------------ .../public/components/app/ServiceMap/index.tsx | 13 +++++++------ x-pack/plugins/apm/common/service_map.ts | 18 ++++++++++++++++++ x-pack/plugins/apm/kibana.json | 2 +- x-pack/plugins/apm/server/plugin.ts | 2 ++ .../plugins/apm/server/routes/service_map.ts | 17 ++++++++++++++--- 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx index c5771995daa24..9213349a1492b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { - EuiEmptyPrompt, EuiButton, - EuiPanel, + EuiEmptyPrompt, EuiFlexGroup, - EuiFlexItem + EuiFlexItem, + EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { invalidLicenseMessage } from '../../../../../../../plugins/apm/common/service_map'; import { useKibanaUrl } from '../../../hooks/useKibanaUrl'; export function PlatinumLicensePrompt() { @@ -43,14 +44,7 @@ export function PlatinumLicensePrompt() { )} ]} - body={ -

- {i18n.translate('xpack.apm.serviceMap.licensePromptBody', { - defaultMessage: - "In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data." - })} -

- } + body={

{invalidLicenseMessage}

} title={

{i18n.translate('xpack.apm.serviceMap.licensePromptTitle', { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx index 5fea4be9ca0da..b14ecaa803f6d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -17,12 +17,14 @@ import React, { useState } from 'react'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; +import { isValidPlatinumLicense } from '../../../../../../../plugins/apm/common/service_map'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceMapAPIResponse } from '../../../../../../../plugins/apm/server/lib/service_map/get_service_map'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../hooks/useCallApmApi'; import { useDeepObjectIdentity } from '../../../hooks/useDeepObjectIdentity'; import { useLicense } from '../../../hooks/useLicense'; +import { useLoadingIndicator } from '../../../hooks/useLoadingIndicator'; import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { Controls } from './Controls'; @@ -31,7 +33,6 @@ import { getCytoscapeElements } from './get_cytoscape_elements'; import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; import { Popover } from './Popover'; import { useRefHeight } from './useRefHeight'; -import { useLoadingIndicator } from '../../../hooks/useLoadingIndicator'; interface ServiceMapProps { serviceName?: string; @@ -195,13 +196,13 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [elements]); - const isValidPlatinumLicense = - license?.isActive && - (license?.type === 'platinum' || license?.type === 'trial'); - const [wrapperRef, height] = useRefHeight(); - return isValidPlatinumLicense ? ( + if (!license) { + return null; + } + + return isValidPlatinumLicense(license) ? (
{ plugins: { apm_oss: APMOSSPlugin extends Plugin ? TSetup : never; home: HomeServerPluginSetup; + licensing: LicensingPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; } diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 584598805f8b3..bead0445d6ccc 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -4,13 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as t from 'io-ts'; import Boom from 'boom'; +import * as t from 'io-ts'; +import { + invalidLicenseMessage, + isValidPlatinumLicense +} from '../../common/service_map'; import { setupRequest } from '../lib/helpers/setup_request'; -import { createRoute } from './create_route'; -import { uiFiltersRt, rangeRt } from './default_api_types'; import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; +import { createRoute } from './create_route'; +import { rangeRt, uiFiltersRt } from './default_api_types'; export const serviceMapRoute = createRoute(() => ({ path: '/api/apm/service-map', @@ -26,6 +30,10 @@ export const serviceMapRoute = createRoute(() => ({ if (!context.config['xpack.apm.serviceMapEnabled']) { throw Boom.notFound(); } + if (!isValidPlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(invalidLicenseMessage); + } + const setup = await setupRequest(context, request); const { query: { serviceName, environment, after } @@ -51,6 +59,9 @@ export const serviceMapServiceNodeRoute = createRoute(() => ({ if (!context.config['xpack.apm.serviceMapEnabled']) { throw Boom.notFound(); } + if (!isValidPlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(invalidLicenseMessage); + } const setup = await setupRequest(context, request); const { From 3212754e62a73f75dd055d5c9e8eb1e43a5dec29 Mon Sep 17 00:00:00 2001 From: Nathan Reese Date: Wed, 26 Feb 2020 07:50:11 -0700 Subject: [PATCH 04/22] [Maps] add Top term aggregation (#57875) * [Maps] add Top term aggregation * update pew-pew source to handle terms agg * make helper function for pulling values from bucket * update terms source * better join labels * categoricla meta * remove unused constant * remove unused changes * remove unused constant METRIC_SCHEMA_CONFIG * update jest expect * fix auto complete suggestions for top term * get category autocomplete working with style props from joins * pluck categorical style meta with real field name * mock MetricsEditor to fix jest test * review feedback * es_agg_utils.js to es_agg_utils.ts * typing updates * use composit agg to avoid search.buckets limit * i18n update and functional test fix * stop paging through results when request is aborted * remove unused file * do not use composite agg when no terms sub-aggregations * clean up * pass indexPattern to getValueAggsDsl * review feedback * more review feedback * ts-ignore for untyped imports in tests * more review feedback * add bucket.hasOwnProperty check Co-authored-by: Elastic Machine --- .../legacy/plugins/maps/common/constants.ts | 4 +- .../maps/public/actions/map_actions.js | 22 +- .../maps/public/components/metric_editor.js | 13 +- .../maps/public/components/metric_select.js | 20 +- .../maps/public/components/metrics_editor.js | 4 +- .../resources/metrics_expression.js | 8 +- .../resources/metrics_expression.test.js | 6 + .../connected_components/map/mb/view.js | 15 +- .../maps/public/elasticsearch_geo_utils.js | 18 ++ .../maps/public/layers/fields/es_agg_field.js | 33 ++- .../public/layers/fields/es_agg_field.test.js | 14 +- .../public/layers/sources/es_agg_source.js | 38 ++-- .../es_geo_grid_source/convert_to_geojson.js | 89 ++++---- .../convert_to_geojson.test.ts | 159 ++++++++++++++ .../es_geo_grid_source/es_geo_grid_source.js | 201 ++++++++++++++---- .../es_geo_grid_source/geo_tile_utils.js | 3 +- .../es_geo_grid_source/geo_tile_utils.test.js | 2 + .../es_pew_pew_source/convert_to_lines.js | 19 +- .../convert_to_lines.test.ts | 68 ++++++ .../es_pew_pew_source/es_pew_pew_source.js | 8 +- .../es_search_source/es_search_source.js | 26 +-- .../maps/public/layers/sources/es_source.js | 11 +- .../public/layers/sources/es_term_source.js | 91 +++----- .../layers/sources/es_term_source.test.js | 91 +------- .../properties/dynamic_size_property.js | 12 +- .../properties/dynamic_style_property.js | 27 ++- .../layers/styles/vector/vector_style.js | 10 +- .../tooltips/es_aggmetric_tooltip_property.js | 6 +- .../public/layers/util/es_agg_utils.test.ts | 37 ++++ .../maps/public/layers/util/es_agg_utils.ts | 51 +++++ .../public/layers/util/is_metric_countable.js | 4 +- .../maps/public/layers/vector_layer.js | 15 +- .../maps/public/selectors/map_selectors.js | 15 ++ .../translations/translations/ja-JP.json | 1 - .../translations/translations/zh-CN.json | 1 - 35 files changed, 726 insertions(+), 416 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts diff --git a/x-pack/legacy/plugins/maps/common/constants.ts b/x-pack/legacy/plugins/maps/common/constants.ts index ab9a696fa3a17..542abfb004d0d 100644 --- a/x-pack/legacy/plugins/maps/common/constants.ts +++ b/x-pack/legacy/plugins/maps/common/constants.ts @@ -117,16 +117,16 @@ export const DRAW_TYPE = { POLYGON: 'POLYGON', }; -export const METRIC_TYPE = { +export const AGG_TYPE = { AVG: 'avg', COUNT: 'count', MAX: 'max', MIN: 'min', SUM: 'sum', + TERMS: 'terms', UNIQUE_COUNT: 'cardinality', }; -export const COUNT_AGG_TYPE = METRIC_TYPE.COUNT; export const COUNT_PROP_LABEL = i18n.translate('xpack.maps.aggs.defaultCountLabel', { defaultMessage: 'count', }); diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index 2c6c60db9a012..cfca044ea759a 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -18,6 +18,7 @@ import { getTransientLayerId, getOpenTooltips, getQuery, + getDataRequestDescriptor, } from '../selectors/map_selectors'; import { FLYOUT_STATE } from '../reducers/ui'; import { @@ -76,7 +77,7 @@ export const HIDE_LAYER_CONTROL = 'HIDE_LAYER_CONTROL'; export const HIDE_VIEW_CONTROL = 'HIDE_VIEW_CONTROL'; export const SET_WAITING_FOR_READY_HIDDEN_LAYERS = 'SET_WAITING_FOR_READY_HIDDEN_LAYERS'; -function getLayerLoadingCallbacks(dispatch, layerId) { +function getLayerLoadingCallbacks(dispatch, getState, layerId) { return { startLoading: (dataId, requestToken, meta) => dispatch(startDataLoad(layerId, dataId, requestToken, meta)), @@ -87,6 +88,13 @@ function getLayerLoadingCallbacks(dispatch, layerId) { updateSourceData: newData => { dispatch(updateSourceDataRequest(layerId, newData)); }, + isRequestStillActive: (dataId, requestToken) => { + const dataRequest = getDataRequestDescriptor(getState(), layerId, dataId); + if (!dataRequest) { + return false; + } + return dataRequest.dataRequestToken === requestToken; + }, registerCancelCallback: (requestToken, callback) => dispatch(registerCancelCallback(requestToken, callback)), }; @@ -98,11 +106,11 @@ function getLayerById(layerId, state) { }); } -async function syncDataForAllLayers(getState, dispatch, dataFilters) { +async function syncDataForAllLayers(dispatch, getState, dataFilters) { const state = getState(); const layerList = getLayerList(state); const syncs = layerList.map(layer => { - const loadingFunctions = getLayerLoadingCallbacks(dispatch, layer.getId()); + const loadingFunctions = getLayerLoadingCallbacks(dispatch, getState, layer.getId()); return layer.syncData({ ...loadingFunctions, dataFilters }); }); await Promise.all(syncs); @@ -412,7 +420,7 @@ export function mapExtentChanged(newMapConstants) { }, }); const newDataFilters = { ...dataFilters, ...newMapConstants }; - await syncDataForAllLayers(getState, dispatch, newDataFilters); + await syncDataForAllLayers(dispatch, getState, newDataFilters); }; } @@ -653,7 +661,7 @@ export function syncDataForLayer(layerId) { const targetLayer = getLayerById(layerId, getState()); if (targetLayer) { const dataFilters = getDataFilters(getState()); - const loadingFunctions = getLayerLoadingCallbacks(dispatch, layerId); + const loadingFunctions = getLayerLoadingCallbacks(dispatch, getState, layerId); await targetLayer.syncData({ ...loadingFunctions, dataFilters, @@ -773,7 +781,7 @@ export function setQuery({ query, timeFilters, filters = [], refresh = false }) }); const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(getState, dispatch, dataFilters); + await syncDataForAllLayers(dispatch, getState, dataFilters); }; } @@ -792,7 +800,7 @@ export function triggerRefreshTimer() { }); const dataFilters = getDataFilters(getState()); - await syncDataForAllLayers(getState, dispatch, dataFilters); + await syncDataForAllLayers(dispatch, getState, dataFilters); }; } diff --git a/x-pack/legacy/plugins/maps/public/components/metric_editor.js b/x-pack/legacy/plugins/maps/public/components/metric_editor.js index e60c2ac0dd7ab..530f402592b2b 100644 --- a/x-pack/legacy/plugins/maps/public/components/metric_editor.js +++ b/x-pack/legacy/plugins/maps/public/components/metric_editor.js @@ -12,17 +12,16 @@ import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import { MetricSelect, METRIC_AGGREGATION_VALUES } from './metric_select'; import { SingleFieldSelect } from './single_field_select'; -import { METRIC_TYPE } from '../../common/constants'; +import { AGG_TYPE } from '../../common/constants'; +import { getTermsFields } from '../index_pattern_util'; function filterFieldsForAgg(fields, aggType) { if (!fields) { return []; } - if (aggType === METRIC_TYPE.UNIQUE_COUNT) { - return fields.filter(field => { - return field.aggregatable; - }); + if (aggType === AGG_TYPE.UNIQUE_COUNT || aggType === AGG_TYPE.TERMS) { + return getTermsFields(fields); } return fields.filter(field => { @@ -38,7 +37,7 @@ export function MetricEditor({ fields, metricsFilter, metric, onChange, removeBu }; // unset field when new agg type does not support currently selected field. - if (metric.field && metricAggregationType !== METRIC_TYPE.COUNT) { + if (metric.field && metricAggregationType !== AGG_TYPE.COUNT) { const fieldsForNewAggType = filterFieldsForAgg(fields, metricAggregationType); const found = fieldsForNewAggType.find(field => { return field.name === metric.field; @@ -64,7 +63,7 @@ export function MetricEditor({ fields, metricsFilter, metric, onChange, removeBu }; let fieldSelect; - if (metric.type && metric.type !== METRIC_TYPE.COUNT) { + if (metric.type && metric.type !== AGG_TYPE.COUNT) { fieldSelect = ( { - if (type === METRIC_TYPE.COUNT) { + if (type === AGG_TYPE.COUNT) { return true; } @@ -70,7 +70,7 @@ export class MetricsExpression extends Component { }) .map(({ type, field }) => { // do not use metric label so field and aggregation are not obscured. - if (type === METRIC_TYPE.COUNT) { + if (type === AGG_TYPE.COUNT) { return 'count'; } @@ -130,5 +130,5 @@ MetricsExpression.propTypes = { }; MetricsExpression.defaultProps = { - metrics: [{ type: METRIC_TYPE.COUNT }], + metrics: [{ type: AGG_TYPE.COUNT }], }; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js index e0e1556ecde06..e4e3776c8e92c 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../../../../components/metrics_editor', () => ({ + MetricsEditor: () => { + return
mockMetricsEditor
; + }, +})); + import React from 'react'; import { shallow } from 'enzyme'; import { MetricsExpression } from './metrics_expression'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 1e44c7225a564..fdc8ad2176d08 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -23,6 +23,7 @@ import sprites1 from '@elastic/maki/dist/sprite@1.png'; import sprites2 from '@elastic/maki/dist/sprite@2.png'; import { DrawControl } from './draw_control'; import { TooltipControl } from './tooltip_control'; +import { clampToLatBounds, clampToLonBounds } from '../../../elasticsearch_geo_utils'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); @@ -234,12 +235,12 @@ export class MBMapContainer extends React.Component { //clamping ot -89/89 latitudes since Mapboxgl does not seem to handle bounds that contain the poles (logs errors to the console when using -90/90) const lnLatBounds = new mapboxgl.LngLatBounds( new mapboxgl.LngLat( - clamp(goto.bounds.min_lon, -180, 180), - clamp(goto.bounds.min_lat, -89, 89) + clampToLonBounds(goto.bounds.min_lon), + clampToLatBounds(goto.bounds.min_lat) ), new mapboxgl.LngLat( - clamp(goto.bounds.max_lon, -180, 180), - clamp(goto.bounds.max_lat, -89, 89) + clampToLonBounds(goto.bounds.max_lon), + clampToLatBounds(goto.bounds.max_lat) ) ); //maxZoom ensure we're not zooming in too far on single points or small shapes @@ -306,9 +307,3 @@ export class MBMapContainer extends React.Component { ); } } - -function clamp(val, min, max) { - if (val > max) val = max; - else if (val < min) val = min; - return val; -} diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js index ec0ae4161b3f2..9b33d3036785c 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js +++ b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js @@ -433,3 +433,21 @@ export function convertMapExtentToPolygon({ maxLat, maxLon, minLat, minLon }) { return formatEnvelopeAsPolygon({ maxLat, maxLon, minLat, minLon }); } + +export function clampToLatBounds(lat) { + return clamp(lat, -89, 89); +} + +export function clampToLonBounds(lon) { + return clamp(lon, -180, 180); +} + +export function clamp(val, min, max) { + if (val > max) { + return max; + } else if (val < min) { + return min; + } else { + return val; + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js index 65109cb99809f..28c199b64d3ef 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.js @@ -5,9 +5,10 @@ */ import { AbstractField } from './field'; -import { COUNT_AGG_TYPE } from '../../../common/constants'; +import { AGG_TYPE } from '../../../common/constants'; import { isMetricCountable } from '../util/is_metric_countable'; import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property'; +import { getField, addFieldToDSL } from '../util/es_agg_utils'; export class ESAggMetricField extends AbstractField { static type = 'ES_AGG'; @@ -34,12 +35,11 @@ export class ESAggMetricField extends AbstractField { } isValid() { - return this.getAggType() === COUNT_AGG_TYPE ? true : !!this._esDocField; + return this.getAggType() === AGG_TYPE.COUNT ? true : !!this._esDocField; } async getDataType() { - // aggregations only provide numerical data - return 'number'; + return this.getAggType() === AGG_TYPE.TERMS ? 'string' : 'number'; } getESDocFieldName() { @@ -47,9 +47,9 @@ export class ESAggMetricField extends AbstractField { } getRequestDescription() { - return this.getAggType() !== COUNT_AGG_TYPE + return this.getAggType() !== AGG_TYPE.COUNT ? `${this.getAggType()} ${this.getESDocFieldName()}` - : COUNT_AGG_TYPE; + : AGG_TYPE.COUNT; } async createTooltipProperty(value) { @@ -63,18 +63,13 @@ export class ESAggMetricField extends AbstractField { ); } - makeMetricAggConfig() { - const metricAggConfig = { - id: this.getName(), - enabled: true, - type: this.getAggType(), - schema: 'metric', - params: {}, + getValueAggDsl(indexPattern) { + const field = getField(indexPattern, this.getESDocFieldName()); + const aggType = this.getAggType(); + const aggBody = aggType === AGG_TYPE.TERMS ? { size: 1, shard_size: 1 } : {}; + return { + [aggType]: addFieldToDSL(aggBody, field), }; - if (this.getAggType() !== COUNT_AGG_TYPE) { - metricAggConfig.params = { field: this.getESDocFieldName() }; - } - return metricAggConfig; } supportsFieldMeta() { @@ -85,4 +80,8 @@ export class ESAggMetricField extends AbstractField { async getOrdinalFieldMetaRequest(config) { return this._esDocField.getOrdinalFieldMetaRequest(config); } + + async getCategoricalFieldMetaRequest() { + return this._esDocField.getCategoricalFieldMetaRequest(); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js index 2f18987513d92..aeeffd63607ee 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.js @@ -5,24 +5,24 @@ */ import { ESAggMetricField } from './es_agg_field'; -import { METRIC_TYPE } from '../../../common/constants'; +import { AGG_TYPE } from '../../../common/constants'; describe('supportsFieldMeta', () => { test('Non-counting aggregations should support field meta', () => { - const avgMetric = new ESAggMetricField({ aggType: METRIC_TYPE.AVG }); + const avgMetric = new ESAggMetricField({ aggType: AGG_TYPE.AVG }); expect(avgMetric.supportsFieldMeta()).toBe(true); - const maxMetric = new ESAggMetricField({ aggType: METRIC_TYPE.MAX }); + const maxMetric = new ESAggMetricField({ aggType: AGG_TYPE.MAX }); expect(maxMetric.supportsFieldMeta()).toBe(true); - const minMetric = new ESAggMetricField({ aggType: METRIC_TYPE.MIN }); + const minMetric = new ESAggMetricField({ aggType: AGG_TYPE.MIN }); expect(minMetric.supportsFieldMeta()).toBe(true); }); test('Counting aggregations should not support field meta', () => { - const countMetric = new ESAggMetricField({ aggType: METRIC_TYPE.COUNT }); + const countMetric = new ESAggMetricField({ aggType: AGG_TYPE.COUNT }); expect(countMetric.supportsFieldMeta()).toBe(false); - const sumMetric = new ESAggMetricField({ aggType: METRIC_TYPE.SUM }); + const sumMetric = new ESAggMetricField({ aggType: AGG_TYPE.SUM }); expect(sumMetric.supportsFieldMeta()).toBe(false); - const uniqueCountMetric = new ESAggMetricField({ aggType: METRIC_TYPE.UNIQUE_COUNT }); + const uniqueCountMetric = new ESAggMetricField({ aggType: AGG_TYPE.UNIQUE_COUNT }); expect(uniqueCountMetric.supportsFieldMeta()).toBe(false); }); }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js index 967a3c41aec26..bee35216f59da 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js @@ -8,8 +8,7 @@ import { AbstractESSource } from './es_source'; import { ESAggMetricField } from '../fields/es_agg_field'; import { ESDocField } from '../fields/es_doc_field'; import { - METRIC_TYPE, - COUNT_AGG_TYPE, + AGG_TYPE, COUNT_PROP_LABEL, COUNT_PROP_NAME, FIELD_ORIGIN, @@ -18,23 +17,6 @@ import { export const AGG_DELIMITER = '_of_'; export class AbstractESAggSource extends AbstractESSource { - static METRIC_SCHEMA_CONFIG = { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: Infinity, - aggFilter: [ - METRIC_TYPE.AVG, - METRIC_TYPE.COUNT, - METRIC_TYPE.MAX, - METRIC_TYPE.MIN, - METRIC_TYPE.SUM, - METRIC_TYPE.UNIQUE_COUNT, - ], - defaults: [{ schema: 'metric', type: METRIC_TYPE.COUNT }], - }; - constructor(descriptor, inspectorAdapters) { super(descriptor, inspectorAdapters); this._metricFields = this._descriptor.metrics @@ -81,7 +63,7 @@ export class AbstractESAggSource extends AbstractESSource { if (metrics.length === 0) { metrics.push( new ESAggMetricField({ - aggType: COUNT_AGG_TYPE, + aggType: AGG_TYPE.COUNT, source: this, origin: this.getOriginForField(), }) @@ -91,15 +73,23 @@ export class AbstractESAggSource extends AbstractESSource { } formatMetricKey(aggType, fieldName) { - return aggType !== COUNT_AGG_TYPE ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME; + return aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME; } formatMetricLabel(aggType, fieldName) { - return aggType !== COUNT_AGG_TYPE ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL; + return aggType !== AGG_TYPE.COUNT ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL; } - createMetricAggConfigs() { - return this.getMetricFields().map(esAggMetric => esAggMetric.makeMetricAggConfig()); + getValueAggsDsl(indexPattern) { + const valueAggsDsl = {}; + this.getMetricFields() + .filter(esAggMetric => { + return esAggMetric.getAggType() !== AGG_TYPE.COUNT; + }) + .forEach(esAggMetric => { + valueAggsDsl[esAggMetric.getName()] = esAggMetric.getValueAggDsl(indexPattern); + }); + return valueAggsDsl; } async getNumberFields() { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js index 4e15d1c927c36..bb9bf1b508f94 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js @@ -4,68 +4,63 @@ * you may not use this file except in compliance with the Elastic License. */ +import _ from 'lodash'; import { RENDER_AS } from './render_as'; import { getTileBoundingBox } from './geo_tile_utils'; -import { EMPTY_FEATURE_COLLECTION } from '../../../../common/constants'; +import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; +import { clamp } from '../../../elasticsearch_geo_utils'; -export function convertToGeoJson({ table, renderAs }) { - if (!table || !table.rows) { - return EMPTY_FEATURE_COLLECTION; - } +const GRID_BUCKET_KEYS_TO_IGNORE = ['key', 'gridCentroid']; - const geoGridColumn = table.columns.find( - column => column.aggConfig.type.dslName === 'geotile_grid' +export function convertCompositeRespToGeoJson(esResponse, renderAs) { + return convertToGeoJson( + esResponse, + renderAs, + esResponse => { + return _.get(esResponse, 'aggregations.compositeSplit.buckets', []); + }, + gridBucket => { + return gridBucket.key.gridSplit; + } ); - if (!geoGridColumn) { - return EMPTY_FEATURE_COLLECTION; - } +} - const metricColumns = table.columns.filter(column => { - return ( - column.aggConfig.type.type === 'metrics' && column.aggConfig.type.dslName !== 'geo_centroid' - ); - }); - const geocentroidColumn = table.columns.find( - column => column.aggConfig.type.dslName === 'geo_centroid' +export function convertRegularRespToGeoJson(esResponse, renderAs) { + return convertToGeoJson( + esResponse, + renderAs, + esResponse => { + return _.get(esResponse, 'aggregations.gridSplit.buckets', []); + }, + gridBucket => { + return gridBucket.key; + } ); - if (!geocentroidColumn) { - return EMPTY_FEATURE_COLLECTION; - } +} +function convertToGeoJson(esResponse, renderAs, pluckGridBuckets, pluckGridKey) { const features = []; - table.rows.forEach(row => { - const gridKey = row[geoGridColumn.id]; - if (!gridKey) { - return; - } - - const properties = {}; - metricColumns.forEach(metricColumn => { - properties[metricColumn.aggConfig.id] = row[metricColumn.id]; - }); + const gridBuckets = pluckGridBuckets(esResponse); + for (let i = 0; i < gridBuckets.length; i++) { + const gridBucket = gridBuckets[i]; + const gridKey = pluckGridKey(gridBucket); features.push({ type: 'Feature', geometry: rowToGeometry({ - row, gridKey, - geocentroidColumn, + gridCentroid: gridBucket.gridCentroid, renderAs, }), id: gridKey, - properties, + properties: extractPropertiesFromBucket(gridBucket, GRID_BUCKET_KEYS_TO_IGNORE), }); - }); + } - return { - featureCollection: { - type: 'FeatureCollection', - features: features, - }, - }; + return features; } -function rowToGeometry({ row, gridKey, geocentroidColumn, renderAs }) { +function rowToGeometry({ gridKey, gridCentroid, renderAs }) { const { top, bottom, right, left } = getTileBoundingBox(gridKey); if (renderAs === RENDER_AS.GRID) { @@ -83,10 +78,10 @@ function rowToGeometry({ row, gridKey, geocentroidColumn, renderAs }) { }; } - // see https://github.com/elastic/elasticsearch/issues/24694 for why clampGrid is used + // see https://github.com/elastic/elasticsearch/issues/24694 for why clamp is used const pointCoordinates = [ - clampGrid(row[geocentroidColumn.id].lon, left, right), - clampGrid(row[geocentroidColumn.id].lat, bottom, top), + clamp(gridCentroid.location.lon, left, right), + clamp(gridCentroid.location.lat, bottom, top), ]; return { @@ -94,9 +89,3 @@ function rowToGeometry({ row, gridKey, geocentroidColumn, renderAs }) { coordinates: pointCoordinates, }; } - -function clampGrid(val, min, max) { - if (val > max) val = max; - else if (val < min) val = min; - return val; -} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts new file mode 100644 index 0000000000000..ba79464a01a9b --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../../../kibana_services', () => {}); + +// @ts-ignore +import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; +// @ts-ignore +import { RENDER_AS } from './render_as'; + +describe('convertCompositeRespToGeoJson', () => { + const esResponse = { + aggregations: { + compositeSplit: { + after_key: { + gridSplit: '10/327/460', + }, + buckets: [ + { + key: { gridSplit: '4/4/6' }, + doc_count: 65, + avg_of_bytes: { value: 5359.2307692307695 }, + 'terms_of_machine.os.keyword': { + buckets: [ + { + key: 'win xp', + doc_count: 16, + }, + ], + }, + gridCentroid: { + location: { lat: 36.62813963153614, lon: -81.94552666092149 }, + count: 65, + }, + }, + ], + }, + }, + }; + + it('Should convert elasticsearch aggregation response into feature collection of points', () => { + const features = convertCompositeRespToGeoJson(esResponse, RENDER_AS.POINT); + expect(features.length).toBe(1); + expect(features[0]).toEqual({ + geometry: { + coordinates: [-81.94552666092149, 36.62813963153614], + type: 'Point', + }, + id: '4/4/6', + properties: { + avg_of_bytes: 5359.2307692307695, + doc_count: 65, + 'terms_of_machine.os.keyword': 'win xp', + }, + type: 'Feature', + }); + }); + + it('Should convert elasticsearch aggregation response into feature collection of Polygons', () => { + const features = convertCompositeRespToGeoJson(esResponse, RENDER_AS.GRID); + expect(features.length).toBe(1); + expect(features[0]).toEqual({ + geometry: { + coordinates: [ + [ + [-67.5, 40.9799], + [-90, 40.9799], + [-90, 21.94305], + [-67.5, 21.94305], + [-67.5, 40.9799], + ], + ], + type: 'Polygon', + }, + id: '4/4/6', + properties: { + avg_of_bytes: 5359.2307692307695, + doc_count: 65, + 'terms_of_machine.os.keyword': 'win xp', + }, + type: 'Feature', + }); + }); +}); + +describe('convertRegularRespToGeoJson', () => { + const esResponse = { + aggregations: { + gridSplit: { + buckets: [ + { + key: '4/4/6', + doc_count: 65, + avg_of_bytes: { value: 5359.2307692307695 }, + 'terms_of_machine.os.keyword': { + buckets: [ + { + key: 'win xp', + doc_count: 16, + }, + ], + }, + gridCentroid: { + location: { lat: 36.62813963153614, lon: -81.94552666092149 }, + count: 65, + }, + }, + ], + }, + }, + }; + + it('Should convert elasticsearch aggregation response into feature collection of points', () => { + const features = convertRegularRespToGeoJson(esResponse, RENDER_AS.POINT); + expect(features.length).toBe(1); + expect(features[0]).toEqual({ + geometry: { + coordinates: [-81.94552666092149, 36.62813963153614], + type: 'Point', + }, + id: '4/4/6', + properties: { + avg_of_bytes: 5359.2307692307695, + doc_count: 65, + 'terms_of_machine.os.keyword': 'win xp', + }, + type: 'Feature', + }); + }); + + it('Should convert elasticsearch aggregation response into feature collection of Polygons', () => { + const features = convertRegularRespToGeoJson(esResponse, RENDER_AS.GRID); + expect(features.length).toBe(1); + expect(features[0]).toEqual({ + geometry: { + coordinates: [ + [ + [-67.5, 40.9799], + [-90, 40.9799], + [-90, 21.94305], + [-67.5, 21.94305], + [-67.5, 40.9799], + ], + ], + type: 'Polygon', + }, + id: '4/4/6', + properties: { + avg_of_bytes: 5359.2307692307695, + doc_count: 65, + 'terms_of_machine.os.keyword': 'win xp', + }, + type: 'Feature', + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index 0912e5a9f1283..a0ddf584bcebc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -10,9 +10,7 @@ import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; import { HeatmapLayer } from '../../heatmap_layer'; import { VectorLayer } from '../../vector_layer'; -import { AggConfigs, Schemas } from 'ui/agg_types'; -import { tabifyAggResponse } from '../../../../../../../../src/legacy/core_plugins/data/public'; -import { convertToGeoJson } from './convert_to_geojson'; +import { convertCompositeRespToGeoJson, convertRegularRespToGeoJson } from './convert_to_geojson'; import { VectorStyle } from '../../styles/vector/vector_style'; import { getDefaultDynamicProperties, @@ -24,6 +22,8 @@ import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { GRID_RESOLUTION } from '../../grid_resolution'; import { + AGG_TYPE, + DEFAULT_MAX_BUCKETS_LIMIT, SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID, COUNT_PROP_NAME, @@ -34,21 +34,10 @@ import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { StaticStyleProperty } from '../../styles/vector/properties/static_style_property'; +import { DataRequestAbortError } from '../../util/data_request'; const MAX_GEOTILE_LEVEL = 29; -const aggSchemas = new Schemas([ - AbstractESAggSource.METRIC_SCHEMA_CONFIG, - { - group: 'buckets', - name: 'segment', - title: 'Geo Grid', - aggFilter: 'geotile_grid', - min: 1, - max: 1, - }, -]); - export class ESGeoGridSource extends AbstractESAggSource { static type = ES_GEO_GRID; static title = i18n.translate('xpack.maps.source.esGridTitle', { @@ -175,15 +164,120 @@ export class ESGeoGridSource extends AbstractESAggSource { ); } - async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) { - const indexPattern = await this.getIndexPattern(); - const searchSource = await this._makeSearchSource(searchFilters, 0); - const aggConfigs = new AggConfigs( - indexPattern, - this._makeAggConfigs(searchFilters.geogridPrecision), - aggSchemas.all - ); - searchSource.setField('aggs', aggConfigs.toDsl()); + async _compositeAggRequest({ + searchSource, + indexPattern, + precision, + layerName, + registerCancelCallback, + bucketsPerGrid, + isRequestStillActive, + }) { + const gridsPerRequest = Math.floor(DEFAULT_MAX_BUCKETS_LIMIT / bucketsPerGrid); + const aggs = { + compositeSplit: { + composite: { + size: gridsPerRequest, + sources: [ + { + gridSplit: { + geotile_grid: { + field: this._descriptor.geoField, + precision, + }, + }, + }, + ], + }, + aggs: { + gridCentroid: { + geo_centroid: { + field: this._descriptor.geoField, + }, + }, + ...this.getValueAggsDsl(indexPattern), + }, + }, + }; + + const features = []; + let requestCount = 0; + let afterKey = null; + while (true) { + if (!isRequestStillActive()) { + // Stop paging through results if request is obsolete + throw new DataRequestAbortError(); + } + + requestCount++; + + // circuit breaker to ensure reasonable number of requests + if (requestCount > 5) { + throw new Error( + i18n.translate('xpack.maps.source.esGrid.compositePaginationErrorMessage', { + defaultMessage: `{layerName} is causing too many requests. Reduce "Grid resolution" and/or reduce the number of top term "Metrics".`, + values: { layerName }, + }) + ); + } + + if (afterKey) { + aggs.compositeSplit.composite.after = afterKey; + } + searchSource.setField('aggs', aggs); + const requestId = afterKey ? `${this.getId()} afterKey ${afterKey.geoSplit}` : this.getId(); + const esResponse = await this._runEsQuery({ + requestId, + requestName: `${layerName} (${requestCount})`, + searchSource, + registerCancelCallback, + requestDescription: i18n.translate( + 'xpack.maps.source.esGrid.compositeInspectorDescription', + { + defaultMessage: 'Elasticsearch geo grid aggregation request: {requestId}', + values: { requestId }, + } + ), + }); + + features.push(...convertCompositeRespToGeoJson(esResponse, this._descriptor.requestType)); + + afterKey = esResponse.aggregations.compositeSplit.after_key; + if (esResponse.aggregations.compositeSplit.buckets.length < gridsPerRequest) { + // Finished because request did not get full resultset back + break; + } + } + + return features; + } + + // Do not use composite aggregation when there are no terms sub-aggregations + // see https://github.com/elastic/kibana/pull/57875#issuecomment-590515482 for explanation on using separate code paths + async _nonCompositeAggRequest({ + searchSource, + indexPattern, + precision, + layerName, + registerCancelCallback, + }) { + searchSource.setField('aggs', { + gridSplit: { + geotile_grid: { + field: this._descriptor.geoField, + precision, + }, + aggs: { + gridCentroid: { + geo_centroid: { + field: this._descriptor.geoField, + }, + }, + ...this.getValueAggsDsl(indexPattern), + }, + }, + }); + const esResponse = await this._runEsQuery({ requestId: this.getId(), requestName: layerName, @@ -194,14 +288,45 @@ export class ESGeoGridSource extends AbstractESAggSource { }), }); - const tabifiedResp = tabifyAggResponse(aggConfigs, esResponse); - const { featureCollection } = convertToGeoJson({ - table: tabifiedResp, - renderAs: this._descriptor.requestType, + return convertRegularRespToGeoJson(esResponse, this._descriptor.requestType); + } + + async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback, isRequestStillActive) { + const indexPattern = await this.getIndexPattern(); + const searchSource = await this._makeSearchSource(searchFilters, 0); + + let bucketsPerGrid = 1; + this.getMetricFields().forEach(metricField => { + if (metricField.getAggType() === AGG_TYPE.TERMS) { + // each terms aggregation increases the overall number of buckets per grid + bucketsPerGrid++; + } }); + const features = + bucketsPerGrid === 1 + ? await this._nonCompositeAggRequest({ + searchSource, + indexPattern, + precision: searchFilters.geogridPrecision, + layerName, + registerCancelCallback, + }) + : await this._compositeAggRequest({ + searchSource, + indexPattern, + precision: searchFilters.geogridPrecision, + layerName, + registerCancelCallback, + bucketsPerGrid, + isRequestStillActive, + }); + return { - data: featureCollection, + data: { + type: 'FeatureCollection', + features: features, + }, meta: { areResultsTrimmed: false, }, @@ -212,24 +337,6 @@ export class ESGeoGridSource extends AbstractESAggSource { return true; } - _makeAggConfigs(precision) { - const metricAggConfigs = this.createMetricAggConfigs(); - return [ - ...metricAggConfigs, - { - id: 'grid', - enabled: true, - type: 'geotile_grid', - schema: 'segment', - params: { - field: this._descriptor.geoField, - useGeocentroid: true, - precision: precision, - }, - }, - ]; - } - _createHeatmapLayerDescriptor(options) { return HeatmapLayer.createDescriptor({ sourceDescriptor: this._descriptor, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js index da0bc1685f223..251e33b9579cb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js @@ -6,6 +6,7 @@ import _ from 'lodash'; import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; +import { clampToLatBounds } from '../../../elasticsearch_geo_utils'; const ZOOM_TILE_KEY_INDEX = 0; const X_TILE_KEY_INDEX = 1; @@ -87,7 +88,7 @@ function sec(value) { } function latitudeToTile(lat, tileCount) { - const radians = (lat * Math.PI) / 180; + const radians = (clampToLatBounds(lat) * Math.PI) / 180; const y = ((1 - Math.log(Math.tan(radians) + sec(radians)) / Math.PI) / 2) * tileCount; return Math.floor(y); } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js index ae2623e168766..88a6ce048a178 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +jest.mock('../../../kibana_services', () => {}); + import { parseTileKey, getTileBoundingBox, expandToTileBoundaries } from './geo_tile_utils'; it('Should parse tile key', () => { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js index 2057949c30c88..96a7f50cdf523 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js @@ -5,9 +5,11 @@ */ import _ from 'lodash'; +import { extractPropertiesFromBucket } from '../../util/es_agg_utils'; const LAT_INDEX = 0; const LON_INDEX = 1; +const PEW_PEW_BUCKET_KEYS_TO_IGNORE = ['key', 'sourceCentroid']; function parsePointFromKey(key) { const split = key.split(','); @@ -25,25 +27,16 @@ export function convertToLines(esResponse) { const dest = parsePointFromKey(destBucket.key); const sourceBuckets = _.get(destBucket, 'sourceGrid.buckets', []); for (let j = 0; j < sourceBuckets.length; j++) { - const { key, sourceCentroid, ...rest } = sourceBuckets[j]; - - // flatten metrics - Object.keys(rest).forEach(key => { - if (_.has(rest[key], 'value')) { - rest[key] = rest[key].value; - } - }); - + const sourceBucket = sourceBuckets[j]; + const sourceCentroid = sourceBucket.sourceCentroid; lineFeatures.push({ type: 'Feature', geometry: { type: 'LineString', coordinates: [[sourceCentroid.location.lon, sourceCentroid.location.lat], dest], }, - id: `${dest.join()},${key}`, - properties: { - ...rest, - }, + id: `${dest.join()},${sourceBucket.key}`, + properties: extractPropertiesFromBucket(sourceBucket, PEW_PEW_BUCKET_KEYS_TO_IGNORE), }); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts new file mode 100644 index 0000000000000..5fbd5a3ad20c0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +import { convertToLines } from './convert_to_lines'; + +const esResponse = { + aggregations: { + destSplit: { + buckets: [ + { + key: '43.68389896117151, 10.39269994944334', + doc_count: 2, + sourceGrid: { + buckets: [ + { + key: '4/9/3', + doc_count: 1, + terms_of_Carrier: { + buckets: [ + { + key: 'ES-Air', + doc_count: 1, + }, + ], + }, + sourceCentroid: { + location: { + lat: 68.15180202014744, + lon: 33.46390150487423, + }, + count: 1, + }, + avg_of_FlightDelayMin: { + value: 3, + }, + }, + ], + }, + }, + ], + }, + }, +}; + +it('Should convert elasticsearch aggregation response into feature collection of lines', () => { + const geoJson = convertToLines(esResponse); + expect(geoJson.featureCollection.features.length).toBe(1); + expect(geoJson.featureCollection.features[0]).toEqual({ + geometry: { + coordinates: [ + [33.46390150487423, 68.15180202014744], + [10.39269994944334, 43.68389896117151], + ], + type: 'LineString', + }, + id: '10.39269994944334,43.68389896117151,4/9/3', + properties: { + avg_of_FlightDelayMin: 3, + doc_count: 1, + terms_of_Carrier: 'ES-Air', + }, + type: 'Feature', + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 176ab62baf98c..53536b11aaca6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW, COUNT_PROP_NAME } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; -import { AggConfigs, Schemas } from 'ui/agg_types'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { COLOR_GRADIENTS } from '../../styles/color_utils'; @@ -28,8 +27,6 @@ import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; const MAX_GEOTILE_LEVEL = 29; -const aggSchemas = new Schemas([AbstractESAggSource.METRIC_SCHEMA_CONFIG]); - export class ESPewPewSource extends AbstractESAggSource { static type = ES_PEW_PEW; static title = i18n.translate('xpack.maps.source.pewPewTitle', { @@ -170,9 +167,6 @@ export class ESPewPewSource extends AbstractESAggSource { async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) { const indexPattern = await this.getIndexPattern(); - const metricAggConfigs = this.createMetricAggConfigs(); - const aggConfigs = new AggConfigs(indexPattern, metricAggConfigs, aggSchemas.all); - const searchSource = await this._makeSearchSource(searchFilters, 0); searchSource.setField('aggs', { destSplit: { @@ -199,7 +193,7 @@ export class ESPewPewSource extends AbstractESAggSource { field: this._descriptor.sourceGeoField, }, }, - ...aggConfigs.toDsl(), + ...this.getValueAggsDsl(indexPattern), }, }, }, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js index 288dd117da137..3533282436139 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js @@ -28,31 +28,7 @@ import { loadIndexSettings } from './load_index_settings'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; import { ESDocField } from '../../fields/es_doc_field'; - -function getField(indexPattern, fieldName) { - const field = indexPattern.fields.getByName(fieldName); - if (!field) { - throw new Error( - i18n.translate('xpack.maps.source.esSearch.fieldNotFoundMsg', { - defaultMessage: `Unable to find '{fieldName}' in index-pattern '{indexPatternTitle}'.`, - values: { fieldName, indexPatternTitle: indexPattern.title }, - }) - ); - } - return field; -} - -function addFieldToDSL(dsl, field) { - return !field.scripted - ? { ...dsl, field: field.name } - : { - ...dsl, - script: { - source: field.script, - lang: field.lang, - }, - }; -} +import { getField, addFieldToDSL } from '../../util/es_agg_utils'; function getDocValueAndSourceFields(indexPattern, fieldNames) { const docValueFields = []; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index d78d3038f870d..782f2845ceeff 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -18,7 +18,7 @@ import { AggConfigs } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; import { copyPersistentState } from '../../reducers/util'; -import { ES_GEO_FIELD_TYPE, METRIC_TYPE } from '../../../common/constants'; +import { ES_GEO_FIELD_TYPE, AGG_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils'; @@ -270,7 +270,7 @@ export class AbstractESSource extends AbstractVectorSource { // Do not use field formatters for counting metrics if ( metricField && - (metricField.type === METRIC_TYPE.COUNT || metricField.type === METRIC_TYPE.UNIQUE_COUNT) + (metricField.type === AGG_TYPE.COUNT || metricField.type === AGG_TYPE.UNIQUE_COUNT) ) { return null; } @@ -347,13 +347,16 @@ export class AbstractESSource extends AbstractVectorSource { } getValueSuggestions = async (fieldName, query) => { - if (!fieldName) { + // fieldName could be an aggregation so it needs to be unpacked to expose raw field. + const metricField = this.getMetricFields().find(field => field.getName() === fieldName); + const realFieldName = metricField ? metricField.getESDocFieldName() : fieldName; + if (!realFieldName) { return []; } try { const indexPattern = await this.getIndexPattern(); - const field = indexPattern.fields.getByName(fieldName); + const field = indexPattern.fields.getByName(realFieldName); return await autocompleteService.getValueSuggestions({ indexPattern, field, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index 7d7a2e159d128..9cc2919404a94 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -6,46 +6,25 @@ import _ from 'lodash'; -import { AggConfigs, Schemas } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; -import { - COUNT_PROP_LABEL, - DEFAULT_MAX_BUCKETS_LIMIT, - FIELD_ORIGIN, - METRIC_TYPE, -} from '../../../common/constants'; +import { DEFAULT_MAX_BUCKETS_LIMIT, FIELD_ORIGIN, AGG_TYPE } from '../../../common/constants'; import { ESDocField } from '../fields/es_doc_field'; import { AbstractESAggSource, AGG_DELIMITER } from './es_agg_source'; +import { getField, addFieldToDSL, extractPropertiesFromBucket } from '../util/es_agg_utils'; const TERMS_AGG_NAME = 'join'; const FIELD_NAME_PREFIX = '__kbnjoin__'; const GROUP_BY_DELIMITER = '_groupby_'; +const TERMS_BUCKET_KEYS_TO_IGNORE = ['key', 'doc_count']; -const aggSchemas = new Schemas([ - AbstractESAggSource.METRIC_SCHEMA_CONFIG, - { - group: 'buckets', - name: 'segment', - title: 'Terms', - aggFilter: 'terms', - min: 1, - max: 1, - }, -]); - -export function extractPropertiesMap(rawEsData, propertyNames, countPropertyName) { +export function extractPropertiesMap(rawEsData, countPropertyName) { const propertiesMap = new Map(); _.get(rawEsData, ['aggregations', TERMS_AGG_NAME, 'buckets'], []).forEach(termBucket => { - const properties = {}; + const properties = extractPropertiesFromBucket(termBucket, TERMS_BUCKET_KEYS_TO_IGNORE); if (countPropertyName) { properties[countPropertyName] = termBucket.doc_count; } - propertyNames.forEach(propertyName => { - if (_.has(termBucket, [propertyName, 'value'])) { - properties[propertyName] = _.get(termBucket, [propertyName, 'value']); - } - }); propertiesMap.set(termBucket.key.toString(), properties); }); return propertiesMap; @@ -90,15 +69,27 @@ export class ESTermSource extends AbstractESAggSource { formatMetricKey(aggType, fieldName) { const metricKey = - aggType !== METRIC_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : aggType; + aggType !== AGG_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : aggType; return `${FIELD_NAME_PREFIX}${metricKey}${GROUP_BY_DELIMITER}${ this._descriptor.indexPatternTitle }.${this._termField.getName()}`; } formatMetricLabel(type, fieldName) { - const metricLabel = type !== METRIC_TYPE.COUNT ? `${type} ${fieldName}` : COUNT_PROP_LABEL; - return `${metricLabel} of ${this._descriptor.indexPatternTitle}:${this._termField.getName()}`; + switch (type) { + case AGG_TYPE.COUNT: + return i18n.translate('xpack.maps.source.esJoin.countLabel', { + defaultMessage: `Count of {indexPatternTitle}`, + values: { indexPatternTitle: this._descriptor.indexPatternTitle }, + }); + case AGG_TYPE.TERMS: + return i18n.translate('xpack.maps.source.esJoin.topTermLabel', { + defaultMessage: `Top {fieldName}`, + values: { fieldName }, + }); + default: + return `${type} ${fieldName}`; + } } async getPropertiesMap(searchFilters, leftSourceName, leftFieldName, registerCancelCallback) { @@ -108,9 +99,14 @@ export class ESTermSource extends AbstractESAggSource { const indexPattern = await this.getIndexPattern(); const searchSource = await this._makeSearchSource(searchFilters, 0); - const configStates = this._makeAggConfigs(); - const aggConfigs = new AggConfigs(indexPattern, configStates, aggSchemas.all); - searchSource.setField('aggs', aggConfigs.toDsl()); + const termsField = getField(indexPattern, this._termField.getName()); + const termsAgg = { size: DEFAULT_MAX_BUCKETS_LIMIT }; + searchSource.setField('aggs', { + [TERMS_AGG_NAME]: { + terms: addFieldToDSL(termsAgg, termsField), + aggs: { ...this.getValueAggsDsl(indexPattern) }, + }, + }); const rawEsData = await this._runEsQuery({ requestId: this.getId(), @@ -120,19 +116,9 @@ export class ESTermSource extends AbstractESAggSource { requestDescription: this._getRequestDescription(leftSourceName, leftFieldName), }); - const metricPropertyNames = configStates - .filter(configState => { - return configState.schema === 'metric' && configState.type !== METRIC_TYPE.COUNT; - }) - .map(configState => { - return configState.id; - }); - const countConfigState = configStates.find(configState => { - return configState.type === METRIC_TYPE.COUNT; - }); - const countPropertyName = _.get(countConfigState, 'id'); + const countPropertyName = this.formatMetricKey(AGG_TYPE.COUNT); return { - propertiesMap: extractPropertiesMap(rawEsData, metricPropertyNames, countPropertyName), + propertiesMap: extractPropertiesMap(rawEsData, countPropertyName), }; } @@ -164,23 +150,6 @@ export class ESTermSource extends AbstractESAggSource { }); } - _makeAggConfigs() { - const metricAggConfigs = this.createMetricAggConfigs(); - return [ - ...metricAggConfigs, - { - id: TERMS_AGG_NAME, - enabled: true, - type: 'terms', - schema: 'segment', - params: { - field: this._termField.getName(), - size: DEFAULT_MAX_BUCKETS_LIMIT, - }, - }, - ]; - } - async getDisplayName() { //no need to localize. this is never rendered. return `es_table ${this._descriptor.indexPatternId}`; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js index ffaaf2d705b5c..39cc301d458cb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js @@ -8,9 +8,6 @@ import { ESTermSource, extractPropertiesMap } from './es_term_source'; jest.mock('ui/new_platform'); jest.mock('../vector_layer', () => {}); -jest.mock('ui/agg_types', () => ({ - Schemas: function() {}, -})); jest.mock('ui/timefilter', () => {}); const indexPatternTitle = 'myIndex'; @@ -44,7 +41,7 @@ describe('getMetricFields', () => { expect(metrics[0].getAggType()).toEqual('count'); expect(metrics[0].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField'); - expect(await metrics[0].getLabel()).toEqual('count of myIndex:myTermField'); + expect(await metrics[0].getLabel()).toEqual('Count of myIndex'); }); it('should remove incomplete metric configurations', async () => { @@ -65,84 +62,13 @@ describe('getMetricFields', () => { expect(metrics[1].getAggType()).toEqual('count'); expect(metrics[1].getName()).toEqual('__kbnjoin__count_groupby_myIndex.myTermField'); - expect(await metrics[1].getLabel()).toEqual('count of myIndex:myTermField'); - }); -}); - -describe('_makeAggConfigs', () => { - describe('no metrics', () => { - let aggConfigs; - beforeAll(() => { - const source = new ESTermSource({ - indexPatternTitle: indexPatternTitle, - term: termFieldName, - }); - aggConfigs = source._makeAggConfigs(); - }); - - it('should make default "count" metric agg config', () => { - expect(aggConfigs.length).toBe(2); - expect(aggConfigs[0]).toEqual({ - id: '__kbnjoin__count_groupby_myIndex.myTermField', - enabled: true, - type: 'count', - schema: 'metric', - params: {}, - }); - }); - - it('should make "terms" buckets agg config', () => { - expect(aggConfigs.length).toBe(2); - expect(aggConfigs[1]).toEqual({ - id: 'join', - enabled: true, - type: 'terms', - schema: 'segment', - params: { - field: termFieldName, - size: 10000, - }, - }); - }); - }); - - describe('metrics', () => { - let aggConfigs; - beforeAll(() => { - const source = new ESTermSource({ - indexPatternTitle: indexPatternTitle, - term: 'myTermField', - metrics: metricExamples, - }); - aggConfigs = source._makeAggConfigs(); - }); - - it('should ignore invalid metrics configs', () => { - expect(aggConfigs.length).toBe(3); - }); - - it('should make agg config for each valid metric', () => { - expect(aggConfigs[0]).toEqual({ - id: '__kbnjoin__sum_of_myFieldGettingSummed_groupby_myIndex.myTermField', - enabled: true, - type: 'sum', - schema: 'metric', - params: { - field: sumFieldName, - }, - }); - expect(aggConfigs[1]).toEqual({ - id: '__kbnjoin__count_groupby_myIndex.myTermField', - enabled: true, - type: 'count', - schema: 'metric', - params: {}, - }); - }); + expect(await metrics[1].getLabel()).toEqual('Count of myIndex'); }); }); describe('extractPropertiesMap', () => { + const minPropName = + '__kbnjoin__min_of_avlAirTemp_groupby_kibana_sample_data_ky_avl.kytcCountyNmbr'; const responseWithNumberTypes = { aggregations: { join: { @@ -150,14 +76,14 @@ describe('extractPropertiesMap', () => { { key: 109, doc_count: 1130, - '__kbnjoin__min_of_avlAirTemp_groupby_kibana_sample_data_ky_avl.kytcCountyNmbr': { + [minPropName]: { value: 36, }, }, { key: 62, doc_count: 448, - '__kbnjoin__min_of_avlAirTemp_groupby_kibana_sample_data_ky_avl.kytcCountyNmbr': { + [minPropName]: { value: 0, }, }, @@ -166,11 +92,10 @@ describe('extractPropertiesMap', () => { }, }; const countPropName = '__kbnjoin__count_groupby_kibana_sample_data_ky_avl.kytcCountyNmbr'; - const minPropName = - '__kbnjoin__min_of_avlAirTemp_groupby_kibana_sample_data_ky_avl.kytcCountyNmbr'; + let propertiesMap; beforeAll(() => { - propertiesMap = extractPropertiesMap(responseWithNumberTypes, [minPropName], countPropName); + propertiesMap = extractPropertiesMap(responseWithNumberTypes, countPropName); }); it('should create key for each join term', () => { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index e137e15730827..dfc5c530cc90f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -43,16 +43,8 @@ function getSymbolSizeIcons() { } export class DynamicSizeProperty extends DynamicStyleProperty { - constructor( - options, - styleName, - field, - getFieldMeta, - getFieldFormatter, - getValueSuggestions, - isSymbolizedAsIcon - ) { - super(options, styleName, field, getFieldMeta, getFieldFormatter, getValueSuggestions); + constructor(options, styleName, field, getFieldMeta, getFieldFormatter, isSymbolizedAsIcon) { + super(options, styleName, field, getFieldMeta, getFieldFormatter); this._isSymbolizedAsIcon = isSymbolizedAsIcon; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js index ef19e9b23b10d..af78c4c0e461e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js @@ -13,21 +13,22 @@ import React from 'react'; import { OrdinalLegend } from './components/ordinal_legend'; import { CategoricalLegend } from './components/categorical_legend'; import { OrdinalFieldMetaOptionsPopover } from '../components/ordinal_field_meta_options_popover'; +import { ESAggMetricField } from '../../../fields/es_agg_field'; export class DynamicStyleProperty extends AbstractStyleProperty { static type = STYLE_TYPE.DYNAMIC; - constructor(options, styleName, field, getFieldMeta, getFieldFormatter, source) { + constructor(options, styleName, field, getFieldMeta, getFieldFormatter) { super(options, styleName); this._field = field; this._getFieldMeta = getFieldMeta; this._getFieldFormatter = getFieldFormatter; - this._source = source; } getValueSuggestions = query => { const fieldName = this.getFieldName(); - return this._source && fieldName ? this._source.getValueSuggestions(fieldName, query) : []; + const fieldSource = this.getFieldSource(); + return fieldSource && fieldName ? fieldSource.getValueSuggestions(fieldName, query) : []; }; getFieldMeta() { @@ -38,6 +39,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return this._field; } + getFieldSource() { + return this._field ? this._field.getSource() : null; + } + getFieldName() { return this._field ? this._field.getName() : ''; } @@ -180,9 +185,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } _pluckOrdinalStyleMetaFromFieldMetaData(fieldMetaData) { - const realFieldName = this._field.getESDocFieldName - ? this._field.getESDocFieldName() - : this._field.getName(); + const realFieldName = + this._field instanceof ESAggMetricField + ? this._field.getESDocFieldName() + : this._field.getName(); const stats = fieldMetaData[realFieldName]; if (!stats) { return null; @@ -203,12 +209,15 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } _pluckCategoricalStyleMetaFromFieldMetaData(fieldMetaData) { - const name = this.getField().getName(); - if (!fieldMetaData[name] || !fieldMetaData[name].buckets) { + const realFieldName = + this._field instanceof ESAggMetricField + ? this._field.getESDocFieldName() + : this._field.getName(); + if (!fieldMetaData[realFieldName] || !fieldMetaData[realFieldName].buckets) { return null; } - const ordered = fieldMetaData[name].buckets.map(bucket => { + const ordered = fieldMetaData[realFieldName].buckets.map(bucket => { return { key: bucket.key, count: bucket.doc_count, diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 62651fdd702d6..053aa114d94ae 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -625,7 +625,6 @@ export class VectorStyle extends AbstractStyle { field, this._getFieldMeta, this._getFieldFormatter, - this._source, isSymbolizedAsIcon ); } else { @@ -645,8 +644,7 @@ export class VectorStyle extends AbstractStyle { styleName, field, this._getFieldMeta, - this._getFieldFormatter, - this._source + this._getFieldFormatter ); } else { throw new Error(`${descriptor} not implemented`); @@ -678,8 +676,7 @@ export class VectorStyle extends AbstractStyle { VECTOR_STYLES.LABEL_TEXT, field, this._getFieldMeta, - this._getFieldFormatter, - this._source + this._getFieldFormatter ); } else { throw new Error(`${descriptor} not implemented`); @@ -698,8 +695,7 @@ export class VectorStyle extends AbstractStyle { VECTOR_STYLES.ICON, field, this._getFieldMeta, - this._getFieldFormatter, - this._source + this._getFieldFormatter ); } else { throw new Error(`${descriptor} not implemented`); diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js index 7cfb60910c155..229c84fe234bd 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/tooltips/es_aggmetric_tooltip_property.js @@ -5,7 +5,7 @@ */ import { ESTooltipProperty } from './es_tooltip_property'; -import { METRIC_TYPE } from '../../../common/constants'; +import { AGG_TYPE } from '../../../common/constants'; export class ESAggMetricTooltipProperty extends ESTooltipProperty { constructor(propertyKey, propertyName, rawValue, indexPattern, metricField) { @@ -22,8 +22,8 @@ export class ESAggMetricTooltipProperty extends ESTooltipProperty { return '-'; } if ( - this._metricField.getAggType() === METRIC_TYPE.COUNT || - this._metricField.getAggType() === METRIC_TYPE.UNIQUE_COUNT + this._metricField.getAggType() === AGG_TYPE.COUNT || + this._metricField.getAggType() === AGG_TYPE.UNIQUE_COUNT ) { return this._rawValue; } diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts b/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts new file mode 100644 index 0000000000000..201d6907981a2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts @@ -0,0 +1,37 @@ +/* + * 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. + */ + +import { extractPropertiesFromBucket } from './es_agg_utils'; + +describe('extractPropertiesFromBucket', () => { + test('Should ignore specified keys', () => { + const properties = extractPropertiesFromBucket({ key: '4/4/6' }, ['key']); + expect(properties).toEqual({}); + }); + + test('Should extract metric aggregation values', () => { + const properties = extractPropertiesFromBucket({ avg_of_bytes: { value: 5359 } }); + expect(properties).toEqual({ + avg_of_bytes: 5359, + }); + }); + + test('Should extract bucket aggregation values', () => { + const properties = extractPropertiesFromBucket({ + 'terms_of_machine.os.keyword': { + buckets: [ + { + key: 'win xp', + doc_count: 16, + }, + ], + }, + }); + expect(properties).toEqual({ + 'terms_of_machine.os.keyword': 'win xp', + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts b/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts new file mode 100644 index 0000000000000..7af176acfaf46 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import _ from 'lodash'; +import { IndexPattern, IFieldType } from '../../../../../../../src/plugins/data/public'; + +export function getField(indexPattern: IndexPattern, fieldName: string) { + const field = indexPattern.fields.getByName(fieldName); + if (!field) { + throw new Error( + i18n.translate('xpack.maps.source.esSearch.fieldNotFoundMsg', { + defaultMessage: `Unable to find '{fieldName}' in index-pattern '{indexPatternTitle}'.`, + values: { fieldName, indexPatternTitle: indexPattern.title }, + }) + ); + } + return field; +} + +export function addFieldToDSL(dsl: object, field: IFieldType) { + return !field.scripted + ? { ...dsl, field: field.name } + : { + ...dsl, + script: { + source: field.script, + lang: field.lang, + }, + }; +} + +export function extractPropertiesFromBucket(bucket: any, ignoreKeys: string[] = []) { + const properties: Record = {}; + for (const key in bucket) { + if (ignoreKeys.includes(key) || !bucket.hasOwnProperty(key)) { + continue; + } + + if (_.has(bucket[key], 'value')) { + properties[key] = bucket[key].value; + } else if (_.has(bucket[key], 'buckets')) { + properties[key] = _.get(bucket[key], 'buckets[0].key'); + } else { + properties[key] = bucket[key]; + } + } + return properties; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js b/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js index 54d8794b1e3cf..69ccb8890d10c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js +++ b/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.js @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { METRIC_TYPE } from '../../../common/constants'; +import { AGG_TYPE } from '../../../common/constants'; export function isMetricCountable(aggType) { - return [METRIC_TYPE.COUNT, METRIC_TYPE.SUM, METRIC_TYPE.UNIQUE_COUNT].includes(aggType); + return [AGG_TYPE.COUNT, AGG_TYPE.SUM, AGG_TYPE.UNIQUE_COUNT].includes(aggType); } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 1698d52ea4406..e1a30c8aef1d3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -365,8 +365,10 @@ export class VectorLayer extends AbstractLayer { onLoadError, registerCancelCallback, dataFilters, + isRequestStillActive, }) { - const requestToken = Symbol(`layer-${this.getId()}-${SOURCE_DATA_ID_ORIGIN}`); + const dataRequestId = SOURCE_DATA_ID_ORIGIN; + const requestToken = Symbol(`layer-${this.getId()}-${dataRequestId}`); const searchFilters = this._getSearchFilters(dataFilters); const prevDataRequest = this.getSourceDataRequest(); const canSkipFetch = await canSkipSourceUpdate({ @@ -382,22 +384,25 @@ export class VectorLayer extends AbstractLayer { } try { - startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, searchFilters); + startLoading(dataRequestId, requestToken, searchFilters); const layerName = await this.getDisplayName(); const { data: sourceFeatureCollection, meta } = await this._source.getGeoJsonWithMeta( layerName, searchFilters, - registerCancelCallback.bind(null, requestToken) + registerCancelCallback.bind(null, requestToken), + () => { + return isRequestStillActive(dataRequestId, requestToken); + } ); const layerFeatureCollection = assignFeatureIds(sourceFeatureCollection); - stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, layerFeatureCollection, meta); + stopLoading(dataRequestId, requestToken, layerFeatureCollection, meta); return { refreshed: true, featureCollection: layerFeatureCollection, }; } catch (error) { if (!(error instanceof DataRequestAbortError)) { - onLoadError(SOURCE_DATA_ID_ORIGIN, requestToken, error.message); + onLoadError(dataRequestId, requestToken, error.message); } return { refreshed: false, diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index d1048a759beca..4074344916390 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -125,6 +125,21 @@ export const getRefreshConfig = ({ map }) => { export const getRefreshTimerLastTriggeredAt = ({ map }) => map.mapState.refreshTimerLastTriggeredAt; +function getLayerDescriptor(state = {}, layerId) { + const layerListRaw = getLayerListRaw(state); + return layerListRaw.find(layer => layer.id === layerId); +} + +export function getDataRequestDescriptor(state = {}, layerId, dataId) { + const layerDescriptor = getLayerDescriptor(state, layerId); + if (!layerDescriptor || !layerDescriptor.__dataRequests) { + return; + } + return _.get(layerDescriptor, '__dataRequests', []).find(dataRequest => { + return dataRequest.dataId === dataId; + }); +} + export const getDataFilters = createSelector( getMapExtent, getMapBuffer, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4a627d48c3cf0..3c7d0ce47acb7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -7263,7 +7263,6 @@ "xpack.maps.source.esGrid.finestDropdownOption": "最も細かい", "xpack.maps.source.esGrid.geospatialFieldLabel": "地理空間フィールド", "xpack.maps.source.esGrid.indexPatternLabel": "インデックスパターン", - "xpack.maps.source.esGrid.inspectorDescription": "Elasticsearch ジオグリッド集約リクエスト", "xpack.maps.source.esGrid.metricsLabel": "メトリック", "xpack.maps.source.esGrid.noIndexPatternErrorMessage": "インデックスパターン {id} が見つかりません", "xpack.maps.source.esGrid.resolutionParamErrorMessage": "グリッド解像度パラメーターが認識されません: {resolution}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e0ef4a7a1ebdb..b262be626aa53 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -7263,7 +7263,6 @@ "xpack.maps.source.esGrid.finestDropdownOption": "最精致化", "xpack.maps.source.esGrid.geospatialFieldLabel": "地理空间字段", "xpack.maps.source.esGrid.indexPatternLabel": "索引模式", - "xpack.maps.source.esGrid.inspectorDescription": "Elasticsearch 地理网格聚合请求", "xpack.maps.source.esGrid.metricsLabel": "指标", "xpack.maps.source.esGrid.noIndexPatternErrorMessage": "找不到索引模式 {id}", "xpack.maps.source.esGrid.resolutionParamErrorMessage": "无法识别网格分辨率参数:{resolution}", From 47aa5b46df1dcafe0cf26043e00cb113da6bad79 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 26 Feb 2020 15:59:22 +0100 Subject: [PATCH 05/22] Saved query management: Discard pending listing requests (#58433) * discard pending listing requests * consolidate requests --- .../saved_query/saved_query_service.test.ts | 27 +++++++++++++++---- .../query/saved_query/saved_query_service.ts | 13 +++++---- .../data/public/query/saved_query/types.ts | 2 +- .../saved_query_management_component.tsx | 20 +++++++++++--- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts index a6b8de32a00bd..c983cc4ea8fc5 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -169,15 +169,27 @@ describe('saved query service', () => { it('should find and return saved queries without search text or pagination parameters', async () => { mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], + total: 5, }); const response = await findSavedQueries(); - expect(response).toEqual([{ id: 'foo', attributes: savedQueryAttributes }]); + expect(response.queries).toEqual([{ id: 'foo', attributes: savedQueryAttributes }]); + }); + + it('should return the total count along with the requested queries', async () => { + mockSavedObjectsClient.find.mockReturnValue({ + savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], + total: 5, + }); + + const response = await findSavedQueries(); + expect(response.total).toEqual(5); }); it('should find and return saved queries with search text matching the title field', async () => { mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], + total: 5, }); const response = await findSavedQueries('foo'); expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ @@ -188,7 +200,7 @@ describe('saved query service', () => { sortField: '_score', type: 'query', }); - expect(response).toEqual([{ id: 'foo', attributes: savedQueryAttributes }]); + expect(response.queries).toEqual([{ id: 'foo', attributes: savedQueryAttributes }]); }); it('should find and return parsed filters and timefilters items', async () => { const serializedSavedQueryAttributesWithFilters = { @@ -198,16 +210,20 @@ describe('saved query service', () => { }; mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: serializedSavedQueryAttributesWithFilters }], + total: 5, }); const response = await findSavedQueries('bar'); - expect(response).toEqual([{ id: 'foo', attributes: savedQueryAttributesWithFilters }]); + expect(response.queries).toEqual([ + { id: 'foo', attributes: savedQueryAttributesWithFilters }, + ]); }); it('should return an array of saved queries', async () => { mockSavedObjectsClient.find.mockReturnValue({ savedObjects: [{ id: 'foo', attributes: savedQueryAttributes }], + total: 5, }); const response = await findSavedQueries(); - expect(response).toEqual( + expect(response.queries).toEqual( expect.objectContaining([ { attributes: { @@ -226,6 +242,7 @@ describe('saved query service', () => { { id: 'foo', attributes: savedQueryAttributes }, { id: 'bar', attributes: savedQueryAttributesBar }, ], + total: 5, }); const response = await findSavedQueries(undefined, 2, 1); expect(mockSavedObjectsClient.find).toHaveBeenCalledWith({ @@ -236,7 +253,7 @@ describe('saved query service', () => { sortField: '_score', type: 'query', }); - expect(response).toEqual( + expect(response.queries).toEqual( expect.objectContaining([ { attributes: { diff --git a/src/plugins/data/public/query/saved_query/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts index 80dec1c9373ea..4d3a8f441ce5e 100644 --- a/src/plugins/data/public/query/saved_query/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -95,7 +95,7 @@ export const createSavedQueryService = ( searchText: string = '', perPage: number = 50, activePage: number = 1 - ): Promise => { + ): Promise<{ total: number; queries: SavedQuery[] }> => { const response = await savedObjectsClient.find({ type: 'query', search: searchText, @@ -105,10 +105,13 @@ export const createSavedQueryService = ( page: activePage, }); - return response.savedObjects.map( - (savedObject: { id: string; attributes: SerializedSavedQueryAttributes }) => - parseSavedQueryObject(savedObject) - ); + return { + total: response.total, + queries: response.savedObjects.map( + (savedObject: { id: string; attributes: SerializedSavedQueryAttributes }) => + parseSavedQueryObject(savedObject) + ), + }; }; const getSavedQuery = async (id: string): Promise => { diff --git a/src/plugins/data/public/query/saved_query/types.ts b/src/plugins/data/public/query/saved_query/types.ts index d05eada7b29e6..6ac5e51d5c312 100644 --- a/src/plugins/data/public/query/saved_query/types.ts +++ b/src/plugins/data/public/query/saved_query/types.ts @@ -46,7 +46,7 @@ export interface SavedQueryService { searchText?: string, perPage?: number, activePage?: number - ) => Promise; + ) => Promise<{ total: number; queries: SavedQuery[] }>; getSavedQuery: (id: string) => Promise; deleteSavedQuery: (id: string) => Promise<{}>; getSavedQueryCount: () => Promise; diff --git a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx index 2a11531ee336d..9347ef5974261 100644 --- a/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx +++ b/src/plugins/data/public/ui/saved_query_management/saved_query_management_component.tsx @@ -33,7 +33,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { FunctionComponent, useEffect, useState, Fragment } from 'react'; +import React, { FunctionComponent, useEffect, useState, Fragment, useRef } from 'react'; import { sortBy } from 'lodash'; import { SavedQuery, SavedQueryService } from '../..'; import { SavedQueryListItem } from './saved_query_list_item'; @@ -62,14 +62,25 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ const [savedQueries, setSavedQueries] = useState([] as SavedQuery[]); const [count, setTotalCount] = useState(0); const [activePage, setActivePage] = useState(0); + const cancelPendingListingRequest = useRef<() => void>(() => {}); useEffect(() => { const fetchCountAndSavedQueries = async () => { - const savedQueryCount = await savedQueryService.getSavedQueryCount(); - setTotalCount(savedQueryCount); + cancelPendingListingRequest.current(); + let requestGotCancelled = false; + cancelPendingListingRequest.current = () => { + requestGotCancelled = true; + }; + + const { + total: savedQueryCount, + queries: savedQueryItems, + } = await savedQueryService.findSavedQueries('', perPage, activePage + 1); + + if (requestGotCancelled) return; - const savedQueryItems = await savedQueryService.findSavedQueries('', perPage, activePage + 1); const sortedSavedQueryItems = sortBy(savedQueryItems, 'attributes.title'); + setTotalCount(savedQueryCount); setSavedQueries(sortedSavedQueryItems); }; if (isOpen) { @@ -103,6 +114,7 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ ); const onDeleteSavedQuery = async (savedQuery: SavedQuery) => { + cancelPendingListingRequest.current(); setSavedQueries( savedQueries.filter(currentSavedQuery => currentSavedQuery.id !== savedQuery.id) ); From 8524303b6e5cac434353d57700a31e907ab93f5a Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Wed, 26 Feb 2020 16:22:35 +0100 Subject: [PATCH 06/22] Allow savedObjects types registration from NP (#57430) * expose `registerType` API * expose `getTypeRegistry` API * change SavedObjectMigrationFn signature to add context * fix exported types * update generated doc * update migration documentation * fix legacy service test * fix typings * update service setup description * add saved_objects server folder convention * fix unit test * documentation NITs * add typeRegistry to SavedObjectClientWrapperOptions --- ...-plugin-server.isavedobjecttyperegistry.md | 13 ++ .../core/server/kibana-plugin-server.md | 7 +- ...-server.savedobjectmigrationcontext.log.md | 13 ++ ...ugin-server.savedobjectmigrationcontext.md | 20 ++ ...na-plugin-server.savedobjectmigrationfn.md | 21 +- ...server.savedobjectsclientwrapperoptions.md | 1 + ...bjectsclientwrapperoptions.typeregistry.md | 11 ++ ...-plugin-server.savedobjectsservicesetup.md | 24 ++- ...r.savedobjectsservicesetup.registertype.md | 60 ++++++ ...avedobjectsservicestart.gettyperegistry.md | 13 ++ ...-plugin-server.savedobjectsservicestart.md | 1 + ...ver.savedobjecttyperegistry.getalltypes.md | 17 ++ ...server.savedobjecttyperegistry.getindex.md | 24 +++ ...-server.savedobjecttyperegistry.gettype.md | 24 +++ ...server.savedobjecttyperegistry.ishidden.md | 24 +++ ...dobjecttyperegistry.isnamespaceagnostic.md | 24 +++ ...a-plugin-server.savedobjecttyperegistry.md | 25 +++ ...er.savedobjecttyperegistry.registertype.md | 24 +++ src/core/CONVENTIONS.md | 43 +++++ src/core/MIGRATION.md | 3 + src/core/MIGRATION_EXAMPLES.md | 181 ++++++++++++++++++ src/core/server/index.ts | 2 + src/core/server/legacy/legacy_service.ts | 3 +- src/core/server/mocks.ts | 8 +- src/core/server/plugins/plugin_context.ts | 2 + .../__snapshots__/utils.test.ts.snap | 6 +- src/core/server/saved_objects/index.ts | 6 +- .../migrations/core/document_migrator.test.ts | 2 +- .../migrations/core/document_migrator.ts | 3 +- .../server/saved_objects/migrations/index.ts | 6 +- .../server/saved_objects/migrations/types.ts | 33 +++- .../saved_objects_service.mock.ts | 5 +- .../saved_objects_service.test.ts | 13 +- .../saved_objects/saved_objects_service.ts | 95 ++++++--- .../saved_objects_type_registry.mock.ts | 5 +- .../saved_objects_type_registry.ts | 13 +- .../lib/scoped_client_provider.test.js | 12 ++ .../service/lib/scoped_client_provider.ts | 15 +- src/core/server/saved_objects/types.ts | 21 +- src/core/server/saved_objects/utils.test.ts | 47 ++++- src/core/server/saved_objects/utils.ts | 20 +- src/core/server/server.api.md | 20 +- src/legacy/server/kbn_server.d.ts | 1 - .../saved_objects/saved_objects_mixin.js | 2 +- .../saved_objects/saved_objects_mixin.test.js | 7 +- 45 files changed, 841 insertions(+), 79 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-server.isavedobjecttyperegistry.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.log.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.typeregistry.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.gettyperegistry.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getalltypes.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getindex.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.gettype.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.ishidden.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.isnamespaceagnostic.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.md create mode 100644 docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.registertype.md diff --git a/docs/development/core/server/kibana-plugin-server.isavedobjecttyperegistry.md b/docs/development/core/server/kibana-plugin-server.isavedobjecttyperegistry.md new file mode 100644 index 0000000000000..bbcba50c81027 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.isavedobjecttyperegistry.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ISavedObjectTypeRegistry](./kibana-plugin-server.isavedobjecttyperegistry.md) + +## ISavedObjectTypeRegistry type + +See [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) for documentation. + +Signature: + +```typescript +export declare type ISavedObjectTypeRegistry = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 9ec443d6482e8..15a1fd0506256 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -27,6 +27,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | | [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | | [SavedObjectsSerializer](./kibana-plugin-server.savedobjectsserializer.md) | A serializer that can be used to manually convert [raw](./kibana-plugin-server.savedobjectsrawdoc.md) or [sanitized](./kibana-plugin-server.savedobjectsanitizeddoc.md) documents to the other kind. | +| [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) | Registry holding information about all the registered [saved object types](./kibana-plugin-server.savedobjectstype.md). | | [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | ## Enumerations @@ -108,6 +109,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RouteValidatorOptions](./kibana-plugin-server.routevalidatoroptions.md) | Additional options for the RouteValidator class to modify its default behaviour. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | +| [SavedObjectMigrationContext](./kibana-plugin-server.savedobjectmigrationcontext.md) | Migration context provided when invoking a [migration handler](./kibana-plugin-server.savedobjectmigrationfn.md) | | [SavedObjectMigrationMap](./kibana-plugin-server.savedobjectmigrationmap.md) | A map of [migration functions](./kibana-plugin-server.savedobjectmigrationfn.md) to be used for a given type. The map's keys must be valid semver versions.For a given document, only migrations with a higher version number than that of the document will be applied. Migrations are executed in order, starting from the lowest version and ending with the highest one. | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | | [SavedObjectsBaseOptions](./kibana-plugin-server.savedobjectsbaseoptions.md) | | @@ -143,7 +145,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | | [SavedObjectsRepositoryFactory](./kibana-plugin-server.savedobjectsrepositoryfactory.md) | Factory provided when invoking a [client factory provider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) See [SavedObjectsServiceSetup.setClientFactoryProvider](./kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md) | | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | -| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | +| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for registering Saved Object types, creating and registering Saved Object client wrappers and factories. | | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | | [SavedObjectsType](./kibana-plugin-server.savedobjectstype.md) | | | [SavedObjectsTypeMappingDefinition](./kibana-plugin-server.savedobjectstypemappingdefinition.md) | Describe a saved object type mapping. | @@ -195,6 +197,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ICustomClusterClient](./kibana-plugin-server.icustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Returns authentication status for a request. | | [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | +| [ISavedObjectTypeRegistry](./kibana-plugin-server.isavedobjecttyperegistry.md) | See [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) for documentation. | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | | [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | | [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | @@ -226,7 +229,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RouteValidatorFullConfig](./kibana-plugin-server.routevalidatorfullconfig.md) | Route validations config and options merged into one object | | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | -| [SavedObjectMigrationFn](./kibana-plugin-server.savedobjectmigrationfn.md) | A migration function defined for a [saved objects type](./kibana-plugin-server.savedobjectstype.md) used to migrate it's | +| [SavedObjectMigrationFn](./kibana-plugin-server.savedobjectmigrationfn.md) | A migration function for a [saved object type](./kibana-plugin-server.savedobjectstype.md) used to migrate it to a given version | | [SavedObjectSanitizedDoc](./kibana-plugin-server.savedobjectsanitizeddoc.md) | | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.log.md b/docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.log.md new file mode 100644 index 0000000000000..4e4eaa3ca91e6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.log.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectMigrationContext](./kibana-plugin-server.savedobjectmigrationcontext.md) > [log](./kibana-plugin-server.savedobjectmigrationcontext.log.md) + +## SavedObjectMigrationContext.log property + +logger instance to be used by the migration handler + +Signature: + +```typescript +log: SavedObjectsMigrationLogger; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.md b/docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.md new file mode 100644 index 0000000000000..77698b37cd3c9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectmigrationcontext.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectMigrationContext](./kibana-plugin-server.savedobjectmigrationcontext.md) + +## SavedObjectMigrationContext interface + +Migration context provided when invoking a [migration handler](./kibana-plugin-server.savedobjectmigrationfn.md) + +Signature: + +```typescript +export interface SavedObjectMigrationContext +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [log](./kibana-plugin-server.savedobjectmigrationcontext.log.md) | SavedObjectsMigrationLogger | logger instance to be used by the migration handler | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectmigrationfn.md b/docs/development/core/server/kibana-plugin-server.savedobjectmigrationfn.md index 629d748083737..838fa55a7f089 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectmigrationfn.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectmigrationfn.md @@ -4,10 +4,27 @@ ## SavedObjectMigrationFn type -A migration function defined for a [saved objects type](./kibana-plugin-server.savedobjectstype.md) used to migrate it's +A migration function for a [saved object type](./kibana-plugin-server.savedobjectstype.md) used to migrate it to a given version Signature: ```typescript -export declare type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, log: SavedObjectsMigrationLogger) => SavedObjectUnsanitizedDoc; +export declare type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => SavedObjectUnsanitizedDoc; ``` + +## Example + + +```typescript +const migrateProperty: SavedObjectMigrationFn = (doc, { log }) => { + if(doc.attributes.someProp === null) { + log.warn('Skipping migration'); + } else { + doc.attributes.someProp = migrateProperty(doc.attributes.someProp); + } + + return doc; +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md index dfff863898a2b..67746126e79b4 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.md @@ -18,4 +18,5 @@ export interface SavedObjectsClientWrapperOptions | --- | --- | --- | | [client](./kibana-plugin-server.savedobjectsclientwrapperoptions.client.md) | SavedObjectsClientContract | | | [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md) | KibanaRequest | | +| [typeRegistry](./kibana-plugin-server.savedobjectsclientwrapperoptions.typeregistry.md) | ISavedObjectTypeRegistry | | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.typeregistry.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.typeregistry.md new file mode 100644 index 0000000000000..afd6898699384 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientwrapperoptions.typeregistry.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) > [typeRegistry](./kibana-plugin-server.savedobjectsclientwrapperoptions.typeregistry.md) + +## SavedObjectsClientWrapperOptions.typeRegistry property + +Signature: + +```typescript +typeRegistry: ISavedObjectTypeRegistry; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md index 9981bfee0cb7d..b6f2e7320c48a 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -4,7 +4,7 @@ ## SavedObjectsServiceSetup interface -Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. +Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for registering Saved Object types, creating and registering Saved Object client wrappers and factories. Signature: @@ -14,11 +14,9 @@ export interface SavedObjectsServiceSetup ## Remarks -Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. +When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. -When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor. - -## Example +## Example 1 ```ts @@ -34,10 +32,26 @@ export class Plugin() { ``` +## Example 2 + + +```ts +import { SavedObjectsClient, CoreSetup } from 'src/core/server'; +import { mySoType } from './saved_objects' + +export class Plugin() { + setup: (core: CoreSetup) => { + core.savedObjects.registerType(mySoType); + } +} + +``` + ## Properties | Property | Type | Description | | --- | --- | --- | | [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void | Add a [client wrapper factory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) with the given priority. | +| [registerType](./kibana-plugin-server.savedobjectsservicesetup.registertype.md) | (type: SavedObjectsType) => void | Register a [savedObjects type](./kibana-plugin-server.savedobjectstype.md) definition.See the [mappings format](./kibana-plugin-server.savedobjectstypemappingdefinition.md) and [migration format](./kibana-plugin-server.savedobjectmigrationmap.md) for more details about these. | | [setClientFactoryProvider](./kibana-plugin-server.savedobjectsservicesetup.setclientfactoryprovider.md) | (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void | Set the default [factory provider](./kibana-plugin-server.savedobjectsclientfactoryprovider.md) for creating Saved Objects clients. Only one provider can be set, subsequent calls to this method will fail. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md new file mode 100644 index 0000000000000..89102d292d634 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md @@ -0,0 +1,60 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [registerType](./kibana-plugin-server.savedobjectsservicesetup.registertype.md) + +## SavedObjectsServiceSetup.registerType property + +Register a [savedObjects type](./kibana-plugin-server.savedobjectstype.md) definition. + +See the [mappings format](./kibana-plugin-server.savedobjectstypemappingdefinition.md) and [migration format](./kibana-plugin-server.savedobjectmigrationmap.md) for more details about these. + +Signature: + +```typescript +registerType: (type: SavedObjectsType) => void; +``` + +## Remarks + +The type definition is an aggregation of the legacy savedObjects `schema`, `mappings` and `migration` concepts. This API is the single entry point to register saved object types in the new platform. + +## Example + + +```ts +// src/plugins/my_plugin/server/saved_objects/my_type.ts +import { SavedObjectsType } from 'src/core/server'; +import * as migrations from './migrations'; + +export const myType: SavedObjectsType = { + name: 'MyType', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + textField: { + type: 'text', + }, + boolField: { + type: 'boolean', + }, + }, + }, + migrations: { + '2.0.0': migrations.migrateToV2, + '2.1.0': migrations.migrateToV2_1 + }, +}; + +// src/plugins/my_plugin/server/plugin.ts +import { SavedObjectsClient, CoreSetup } from 'src/core/server'; +import { myType } from './saved_objects'; + +export class Plugin() { + setup: (core: CoreSetup) => { + core.savedObjects.registerType(myType); + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.gettyperegistry.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.gettyperegistry.md new file mode 100644 index 0000000000000..82e67bb307588 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.gettyperegistry.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [getTypeRegistry](./kibana-plugin-server.savedobjectsservicestart.gettyperegistry.md) + +## SavedObjectsServiceStart.getTypeRegistry property + +Returns the [registry](./kibana-plugin-server.isavedobjecttyperegistry.md) containing all registered [saved object types](./kibana-plugin-server.savedobjectstype.md) + +Signature: + +```typescript +getTypeRegistry: () => ISavedObjectTypeRegistry; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md index ad34d76bb33f4..293255bb33c2a 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md @@ -20,4 +20,5 @@ export interface SavedObjectsServiceStart | [createScopedRepository](./kibana-plugin-server.savedobjectsservicestart.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | | [createSerializer](./kibana-plugin-server.savedobjectsservicestart.createserializer.md) | () => SavedObjectsSerializer | Creates a [serializer](./kibana-plugin-server.savedobjectsserializer.md) that is aware of all registered types. | | [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). | +| [getTypeRegistry](./kibana-plugin-server.savedobjectsservicestart.gettyperegistry.md) | () => ISavedObjectTypeRegistry | Returns the [registry](./kibana-plugin-server.isavedobjecttyperegistry.md) containing all registered [saved object types](./kibana-plugin-server.savedobjectstype.md) | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getalltypes.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getalltypes.md new file mode 100644 index 0000000000000..d71b392c40840 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getalltypes.md @@ -0,0 +1,17 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) > [getAllTypes](./kibana-plugin-server.savedobjecttyperegistry.getalltypes.md) + +## SavedObjectTypeRegistry.getAllTypes() method + +Return all [types](./kibana-plugin-server.savedobjectstype.md) currently registered. + +Signature: + +```typescript +getAllTypes(): SavedObjectsType[]; +``` +Returns: + +`SavedObjectsType[]` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getindex.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getindex.md new file mode 100644 index 0000000000000..3479600456c47 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.getindex.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) > [getIndex](./kibana-plugin-server.savedobjecttyperegistry.getindex.md) + +## SavedObjectTypeRegistry.getIndex() method + +Returns the `indexPattern` property for given type, or `undefined` if the type is not registered. + +Signature: + +```typescript +getIndex(type: string): string | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | + +Returns: + +`string | undefined` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.gettype.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.gettype.md new file mode 100644 index 0000000000000..b32301a253731 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.gettype.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) > [getType](./kibana-plugin-server.savedobjecttyperegistry.gettype.md) + +## SavedObjectTypeRegistry.getType() method + +Return the [type](./kibana-plugin-server.savedobjectstype.md) definition for given type name. + +Signature: + +```typescript +getType(type: string): SavedObjectsType | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | + +Returns: + +`SavedObjectsType | undefined` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.ishidden.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.ishidden.md new file mode 100644 index 0000000000000..956ba2cbc1dbd --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.ishidden.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) > [isHidden](./kibana-plugin-server.savedobjecttyperegistry.ishidden.md) + +## SavedObjectTypeRegistry.isHidden() method + +Returns the `hidden` property for given type, or `false` if the type is not registered. + +Signature: + +```typescript +isHidden(type: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | + +Returns: + +`boolean` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.isnamespaceagnostic.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.isnamespaceagnostic.md new file mode 100644 index 0000000000000..e6e578d893648 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.isnamespaceagnostic.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) > [isNamespaceAgnostic](./kibana-plugin-server.savedobjecttyperegistry.isnamespaceagnostic.md) + +## SavedObjectTypeRegistry.isNamespaceAgnostic() method + +Returns the `namespaceAgnostic` property for given type, or `false` if the type is not registered. + +Signature: + +```typescript +isNamespaceAgnostic(type: string): boolean; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | + +Returns: + +`boolean` + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.md new file mode 100644 index 0000000000000..3daad35808624 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) + +## SavedObjectTypeRegistry class + +Registry holding information about all the registered [saved object types](./kibana-plugin-server.savedobjectstype.md). + +Signature: + +```typescript +export declare class SavedObjectTypeRegistry +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [getAllTypes()](./kibana-plugin-server.savedobjecttyperegistry.getalltypes.md) | | Return all [types](./kibana-plugin-server.savedobjectstype.md) currently registered. | +| [getIndex(type)](./kibana-plugin-server.savedobjecttyperegistry.getindex.md) | | Returns the indexPattern property for given type, or undefined if the type is not registered. | +| [getType(type)](./kibana-plugin-server.savedobjecttyperegistry.gettype.md) | | Return the [type](./kibana-plugin-server.savedobjectstype.md) definition for given type name. | +| [isHidden(type)](./kibana-plugin-server.savedobjecttyperegistry.ishidden.md) | | Returns the hidden property for given type, or false if the type is not registered. | +| [isNamespaceAgnostic(type)](./kibana-plugin-server.savedobjecttyperegistry.isnamespaceagnostic.md) | | Returns the namespaceAgnostic property for given type, or false if the type is not registered. | +| [registerType(type)](./kibana-plugin-server.savedobjecttyperegistry.registertype.md) | | Register a [type](./kibana-plugin-server.savedobjectstype.md) inside the registry. A type can only be registered once. subsequent calls with the same type name will throw an error. | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.registertype.md b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.registertype.md new file mode 100644 index 0000000000000..4e6d62ccd28d0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjecttyperegistry.registertype.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectTypeRegistry](./kibana-plugin-server.savedobjecttyperegistry.md) > [registerType](./kibana-plugin-server.savedobjecttyperegistry.registertype.md) + +## SavedObjectTypeRegistry.registerType() method + +Register a [type](./kibana-plugin-server.savedobjectstype.md) inside the registry. A type can only be registered once. subsequent calls with the same type name will throw an error. + +Signature: + +```typescript +registerType(type: SavedObjectsType): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | SavedObjectsType | | + +Returns: + +`void` + diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index dd83ab2daca82..2769079757bc3 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -7,6 +7,7 @@ - [Applications](#applications) - [Services](#services) - [Usage Collection](#usage-collection) + - [Saved Objects Types](#saved-objects-types) ## Plugin Structure @@ -31,6 +32,9 @@ my_plugin/ │ └── index.ts ├── collectors │ └── register.ts + ├── saved_objects + │ ├── index.ts + │ └── my_type.ts    ├── services    │   ├── my_service    │   │ └── index.ts @@ -259,6 +263,45 @@ export function registerMyPluginUsageCollector(usageCollection?: UsageCollection } ``` +### Saved Objects Types + +Saved object type definitions should be defined in their own `server/saved_objects` directory. + +The folder should contain a file per type, named after the snake_case name of the type, and an `index.ts` file exporting all the types. + +```typescript +// src/plugins/my-plugin/server/saved_objects/my_type.ts +import { SavedObjectsType } from 'src/core/server'; + +export const myType: SavedObjectsType = { + name: 'my-type', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + someField: { + type: 'text', + }, + anotherField: { + type: 'text', + }, + }, + }, + migrations: { + '1.0.0': migrateFirstTypeToV1, + '2.0.0': migrateFirstTypeToV2, + }, +}; +``` + +```typescript +// src/plugins/my-plugin/server/saved_objects/index.ts + +export { myType } from './my_type'; +``` + +Migration example from the legacy format is available in `src/core/MIGRATION_EXAMPLES.md#saved-objects-types` + ### Naming conventions Export start and setup contracts as `MyPluginStart` and `MyPluginSetup`. diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index d33fd9bcce7a0..6ee432635a947 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1207,6 +1207,9 @@ In server code, `core` can be accessed from either `server.newPlatform` or `kbnS | `request.getSavedObjectsClient` | [`context.core.savedObjects.client`](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.core.md) | | | `request.getUiSettingsService` | [`context.uiSettings.client`](/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md) | | | `kibana.Plugin.deprecations` | [Handle plugin configuration deprecations](#handle-plugin-config-deprecations) and [`PluginConfigDescriptor.deprecations`](docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md) | Deprecations from New Platform are not applied to legacy configuration | +| `kibana.Plugin.savedObjectSchemas` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | +| `kibana.Plugin.mappings` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | +| `kibana.Plugin.migrations` | [`core.savedObjects.registerType`](docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.registertype.md) | [Examples](./MIGRATION_EXAMPLES.md#saved-objects-types) | _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_ diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md index 5517dfa7f9a23..def83ba177fc9 100644 --- a/src/core/MIGRATION_EXAMPLES.md +++ b/src/core/MIGRATION_EXAMPLES.md @@ -19,6 +19,7 @@ APIs to their New Platform equivalents. - [Updating an application navlink](#updating-application-navlink) - [Chromeless Applications](#chromeless-applications) - [Render HTML Content](#render-html-content) + - [Saved Objects types](#saved-objects-types) ## Configuration @@ -737,3 +738,183 @@ router.get( } ); ``` + +## Saved Objects types + +In the legacy platform, saved object types were registered using static definitions in the `uiExports` part of +the plugin manifest. + +In the new platform, all these registration are to be performed programmatically during your plugin's `setup` phase, +using the core `savedObjects`'s `registerType` setup API. + +The most notable difference is that in the new platform, the type registration is performed in a single call to +`registerType`, passing a new `SavedObjectsType` structure that is a superset of the legacy `schema`, `migrations` +and `mappings`. + +### Concrete example + +Let say we have the following in a legacy plugin: + +```js +// src/legacy/core_plugins/my_plugin/index.js +import mappings from './mappings.json'; +import { migrations } from './migrations'; + +new kibana.Plugin({ + init(server){ + // [...] + }, + uiExports: { + mappings, + migrations, + savedObjectSchemas: { + 'first-type': { + isNamespaceAgnostic: true, + }, + 'second-type': { + isHidden: true, + }, + }, + }, +}) +``` + +```json +// src/legacy/core_plugins/my_plugin/mappings.json +{ + "first-type": { + "properties": { + "someField": { + "type": "text" + }, + "anotherField": { + "type": "text" + } + } + }, + "second-type": { + "properties": { + "textField": { + "type": "text" + }, + "boolField": { + "type": "boolean" + } + } + } +} +``` + +```js +// src/legacy/core_plugins/my_plugin/migrations.js +export const migrations = { + 'first-type': { + '1.0.0': migrateFirstTypeToV1, + '2.0.0': migrateFirstTypeToV2, + }, + 'second-type': { + '1.5.0': migrateSecondTypeToV15, + } +} +``` + +To migrate this, we will have to regroup the declaration per-type. That would become: + +First type: + +```typescript +// src/plugins/my_plugin/server/saved_objects/first_type.ts +import { SavedObjectsType } from 'src/core/server'; + +export const firstType: SavedObjectsType = { + name: 'first-type', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + someField: { + type: 'text', + }, + anotherField: { + type: 'text', + }, + }, + }, + migrations: { + '1.0.0': migrateFirstTypeToV1, + '2.0.0': migrateFirstTypeToV2, + }, +}; +``` + +Second type: + +```typescript +// src/plugins/my_plugin/server/saved_objects/second_type.ts +import { SavedObjectsType } from 'src/core/server'; + +export const secondType: SavedObjectsType = { + name: 'second-type', + hidden: true, + namespaceAgnostic: false, + mappings: { + properties: { + textField: { + type: 'text', + }, + boolField: { + type: 'boolean', + }, + }, + }, + migrations: { + '1.5.0': migrateSecondTypeToV15, + }, +}; +``` + +Registration in the plugin's setup phase: + +```typescript +// src/plugins/my_plugin/server/plugin.ts +import { firstType, secondType } from './saved_objects'; + +export class MyPlugin implements Plugin { + setup({ savedObjects }) { + savedObjects.registerType(firstType); + savedObjects.registerType(secondType); + } +} +``` + +### Changes in structure compared to legacy + +The NP `registerType` expected input is very close to the legacy format. However, there are some minor changes: + +- The `schema.isNamespaceAgnostic` property has been renamed: `SavedObjectsType.namespaceAgnostic` + +- The `schema.indexPattern` was accepting either a `string` or a `(config: LegacyConfig) => string`. `SavedObjectsType.indexPattern` only accepts a string, as you can access the configuration during your plugin's setup phase. + +- The migration function signature has changed: +In legacy, it was `(doc: SavedObjectUnsanitizedDoc, log: SavedObjectsMigrationLogger) => SavedObjectUnsanitizedDoc;` +In new platform, it is now `(doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => SavedObjectUnsanitizedDoc;` + +With context being: + +```typescript +export interface SavedObjectMigrationContext { + log: SavedObjectsMigrationLogger; +} +``` + +The changes is very minor though. The legacy migration: + +```js +const migration = (doc, log) => {...} +``` + +Would be converted to: + +```typescript +const migration: SavedObjectMigrationFn = (doc, { log }) => {...} +``` \ No newline at end of file diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 52827b72ee0cc..e45d4f28edcc3 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -201,6 +201,7 @@ export { SavedObjectsImportRetry, SavedObjectsImportUnknownError, SavedObjectsImportUnsupportedTypeError, + SavedObjectMigrationContext, SavedObjectsMigrationLogger, SavedObjectsRawDoc, SavedObjectSanitizedDoc, @@ -224,6 +225,7 @@ export { SavedObjectsTypeMappingDefinition, SavedObjectsMappingProperties, SavedObjectTypeRegistry, + ISavedObjectTypeRegistry, SavedObjectsType, SavedObjectMigrationMap, SavedObjectMigrationFn, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index b2501496d87ef..44f77b5ad215e 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -263,6 +263,7 @@ export class LegacyService implements CoreService { createScopedRepository: startDeps.core.savedObjects.createScopedRepository, createInternalRepository: startDeps.core.savedObjects.createInternalRepository, createSerializer: startDeps.core.savedObjects.createSerializer, + getTypeRegistry: startDeps.core.savedObjects.getTypeRegistry, }, uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient }, }; @@ -298,6 +299,7 @@ export class LegacyService implements CoreService { savedObjects: { setClientFactoryProvider: setupDeps.core.savedObjects.setClientFactoryProvider, addClientWrapper: setupDeps.core.savedObjects.addClientWrapper, + registerType: setupDeps.core.savedObjects.registerType, }, uiSettings: { register: setupDeps.core.uiSettings.register, @@ -329,7 +331,6 @@ export class LegacyService implements CoreService { __internals: { hapiServer: setupDeps.core.http.server, kibanaMigrator: startDeps.core.savedObjects.migrator, - typeRegistry: startDeps.core.savedObjects.typeRegistry, uiPlugins: setupDeps.core.plugins.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, rendering: setupDeps.core.rendering, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index d6554babab53e..b8380a3045962 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -114,18 +114,12 @@ function createCoreSetupMock() { register: uiSettingsServiceMock.createSetupContract().register, }; - const savedObjectsService = savedObjectsServiceMock.createSetupContract(); - const savedObjectMock: jest.Mocked = { - addClientWrapper: savedObjectsService.addClientWrapper, - setClientFactoryProvider: savedObjectsService.setClientFactoryProvider, - }; - const mock: CoreSetupMockType = { capabilities: capabilitiesServiceMock.createSetupContract(), context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetup(), http: httpMock, - savedObjects: savedObjectMock, + savedObjects: savedObjectsServiceMock.createInternalSetupContract(), uiSettings: uiSettingsMock, uuid: uuidServiceMock.createSetupContract(), getStartServices: jest diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index a7b555a9eba01..a8a16713f69a4 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -169,6 +169,7 @@ export function createPluginSetupContext( savedObjects: { setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, addClientWrapper: deps.savedObjects.addClientWrapper, + registerType: deps.savedObjects.registerType, }, uiSettings: { register: deps.uiSettings.register, @@ -206,6 +207,7 @@ export function createPluginStartContext( createInternalRepository: deps.savedObjects.createInternalRepository, createScopedRepository: deps.savedObjects.createScopedRepository, createSerializer: deps.savedObjects.createSerializer, + getTypeRegistry: deps.savedObjects.getTypeRegistry, }, uiSettings: { asScopedToClient: deps.uiSettings.asScopedToClient, diff --git a/src/core/server/saved_objects/__snapshots__/utils.test.ts.snap b/src/core/server/saved_objects/__snapshots__/utils.test.ts.snap index 7846e7f1a802a..89ff2b542c60f 100644 --- a/src/core/server/saved_objects/__snapshots__/utils.test.ts.snap +++ b/src/core/server/saved_objects/__snapshots__/utils.test.ts.snap @@ -64,8 +64,8 @@ Array [ }, }, "migrations": Object { - "1.0.0": [MockFunction], - "2.0.4": [MockFunction], + "1.0.0": [Function], + "2.0.4": [Function], }, "name": "typeA", "namespaceAgnostic": true, @@ -100,7 +100,7 @@ Array [ }, }, "migrations": Object { - "1.5.3": [MockFunction], + "1.5.3": [Function], }, "name": "typeC", "namespaceAgnostic": false, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 5be4458bdf2af..9bfe658028258 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -64,7 +64,11 @@ export { SavedObjectsTypeMappingDefinitions, } from './mappings'; -export { SavedObjectMigrationMap, SavedObjectMigrationFn } from './migrations'; +export { + SavedObjectMigrationMap, + SavedObjectMigrationFn, + SavedObjectMigrationContext, +} from './migrations'; export { SavedObjectsType } from './types'; diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 0e3a4780e12b6..ef3f546b5e574 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -585,7 +585,7 @@ describe('DocumentMigrator', () => { typeRegistry: createRegistry({ name: 'dog', migrations: { - '1.2.3': (doc, log) => { + '1.2.3': (doc, { log }) => { log.info(logTestMsg); log.warning(logTestMsg); return doc; diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index b5019b2874bec..0284f513a361c 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -309,7 +309,8 @@ function wrapWithTry( ) { return function tryTransformDoc(doc: SavedObjectUnsanitizedDoc) { try { - const result = migrationFn(doc, new MigrationLogger(log)); + const context = { log: new MigrationLogger(log) }; + const result = migrationFn(doc, context); // A basic sanity check to help migration authors detect basic errors // (e.g. forgetting to return the transformed doc) diff --git a/src/core/server/saved_objects/migrations/index.ts b/src/core/server/saved_objects/migrations/index.ts index e96986bd702e6..dc966f0797822 100644 --- a/src/core/server/saved_objects/migrations/index.ts +++ b/src/core/server/saved_objects/migrations/index.ts @@ -18,4 +18,8 @@ */ export { KibanaMigrator, IKibanaMigrator } from './kibana'; -export { SavedObjectMigrationFn, SavedObjectMigrationMap } from './types'; +export { + SavedObjectMigrationFn, + SavedObjectMigrationMap, + SavedObjectMigrationContext, +} from './types'; diff --git a/src/core/server/saved_objects/migrations/types.ts b/src/core/server/saved_objects/migrations/types.ts index 01741dd2ded1a..6bc085dde872e 100644 --- a/src/core/server/saved_objects/migrations/types.ts +++ b/src/core/server/saved_objects/migrations/types.ts @@ -21,14 +21,41 @@ import { SavedObjectUnsanitizedDoc } from '../serialization'; import { SavedObjectsMigrationLogger } from './core/migration_logger'; /** - * A migration function defined for a {@link SavedObjectsType | saved objects type} - * used to migrate it's {@link SavedObjectUnsanitizedDoc | documents} + * A migration function for a {@link SavedObjectsType | saved object type} + * used to migrate it to a given version + * + * @example + * ```typescript + * const migrateProperty: SavedObjectMigrationFn = (doc, { log }) => { + * if(doc.attributes.someProp === null) { + * log.warn('Skipping migration'); + * } else { + * doc.attributes.someProp = migrateProperty(doc.attributes.someProp); + * } + * + * return doc; + * } + * ``` + * + * @public */ export type SavedObjectMigrationFn = ( doc: SavedObjectUnsanitizedDoc, - log: SavedObjectsMigrationLogger + context: SavedObjectMigrationContext ) => SavedObjectUnsanitizedDoc; +/** + * Migration context provided when invoking a {@link SavedObjectMigrationFn | migration handler} + * + * @public + */ +export interface SavedObjectMigrationContext { + /** + * logger instance to be used by the migration handler + */ + log: SavedObjectsMigrationLogger; +} + /** * A map of {@link SavedObjectMigrationFn | migration functions} to be used for a given type. * The map's keys must be valid semver versions. diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 70f3d5a5b18e4..cbdff16324536 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -38,11 +38,13 @@ const createStartContractMock = () => { createInternalRepository: jest.fn(), createScopedRepository: jest.fn(), createSerializer: jest.fn(), + getTypeRegistry: jest.fn(), }; startContrat.getScopedClient.mockReturnValue(savedObjectsClientMock.create()); startContrat.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create()); startContrat.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + startContrat.getTypeRegistry.mockReturnValue(typeRegistryMock.create()); return startContrat; }; @@ -52,7 +54,6 @@ const createInternalStartContractMock = () => { ...createStartContractMock(), clientProvider: savedObjectsClientProviderMock.create(), migrator: mockKibanaMigrator.create(), - typeRegistry: typeRegistryMock.create(), }; return internalStartContract; @@ -62,6 +63,7 @@ const createSetupContractMock = () => { const setupContract: jest.Mocked = { setClientFactoryProvider: jest.fn(), addClientWrapper: jest.fn(), + registerType: jest.fn(), }; return setupContract; @@ -70,7 +72,6 @@ const createSetupContractMock = () => { const createInternalSetupContractMock = () => { const internalSetupContract: jest.Mocked = { ...createSetupContractMock(), - registerType: jest.fn(), }; return internalSetupContract; }; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 0c7bedecf39f5..a1e2c1e8dbf26 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -130,7 +130,7 @@ describe('SavedObjectsService', () => { }); }); - describe('registerType', () => { + describe('#registerType', () => { it('registers the type to the internal typeRegistry', async () => { const coreContext = createCoreContext(); const soService = new SavedObjectsService(coreContext); @@ -231,5 +231,16 @@ describe('SavedObjectsService', () => { expect(startContract.migrator).toBe(migratorInstanceMock); expect(migratorInstanceMock.runMigrations).toHaveBeenCalledTimes(1); }); + + describe('#getTypeRegistry', () => { + it('returns the internal type registry of the service', async () => { + const coreContext = createCoreContext({ skipMigration: false }); + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + const { getTypeRegistry } = await soService.start({}); + + expect(getTypeRegistry()).toBe(typeRegistryInstanceMock); + }); + }); }); }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index fa2b67a3e43b2..da8f7ab96d689 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -53,26 +53,13 @@ import { registerRoutes } from './routes'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to - * use Elasticsearch for storing and querying state. The - * SavedObjectsServiceSetup API exposes methods for creating and registering - * Saved Object client wrappers. + * use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods + * for registering Saved Object types, creating and registering Saved Object client wrappers and factories. * * @remarks - * Note: The Saved Object setup API's should only be used for creating and - * registering client wrappers. Constructing a Saved Objects client or - * repository for use within your own plugin won't have any of the registered - * wrappers applied and is considered an anti-pattern. Use the Saved Objects - * client from the - * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } - * method or the {@link RequestHandlerContext | route handler context} instead. - * * When plugins access the Saved Objects client, a new client is created using * the factory provided to `setClientFactory` and wrapped by all wrappers - * registered through `addClientWrapper`. To create a factory or wrapper, - * plugins will have to construct a Saved Objects client. First create a - * repository by calling `scopedRepository` or `internalRepository` and then - * use this repository as the argument to the {@link SavedObjectsClient} - * constructor. + * registered through `addClientWrapper`. * * @example * ```ts @@ -87,6 +74,18 @@ import { registerRoutes } from './routes'; * } * ``` * + * @example + * ```ts + * import { SavedObjectsClient, CoreSetup } from 'src/core/server'; + * import { mySoType } from './saved_objects' + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.registerType(mySoType); + * } + * } + * ``` + * * @public */ export interface SavedObjectsServiceSetup { @@ -104,14 +103,60 @@ export interface SavedObjectsServiceSetup { id: string, factory: SavedObjectsClientWrapperFactory ) => void; + + /** + * Register a {@link SavedObjectsType | savedObjects type} definition. + * + * See the {@link SavedObjectsTypeMappingDefinition | mappings format} and + * {@link SavedObjectMigrationMap | migration format} for more details about these. + * + * @example + * ```ts + * // src/plugins/my_plugin/server/saved_objects/my_type.ts + * import { SavedObjectsType } from 'src/core/server'; + * import * as migrations from './migrations'; + * + * export const myType: SavedObjectsType = { + * name: 'MyType', + * hidden: false, + * namespaceAgnostic: true, + * mappings: { + * properties: { + * textField: { + * type: 'text', + * }, + * boolField: { + * type: 'boolean', + * }, + * }, + * }, + * migrations: { + * '2.0.0': migrations.migrateToV2, + * '2.1.0': migrations.migrateToV2_1 + * }, + * }; + * + * // src/plugins/my_plugin/server/plugin.ts + * import { SavedObjectsClient, CoreSetup } from 'src/core/server'; + * import { myType } from './saved_objects'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.registerType(myType); + * } + * } + * ``` + * + * @remarks The type definition is an aggregation of the legacy savedObjects `schema`, `mappings` and `migration` concepts. + * This API is the single entry point to register saved object types in the new platform. + */ + registerType: (type: SavedObjectsType) => void; } /** * @internal */ -export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup { - registerType: (type: SavedObjectsType) => void; -} +export type InternalSavedObjectsServiceSetup = SavedObjectsServiceSetup; /** * Saved Objects is Kibana's data persisentence mechanism allowing plugins to @@ -159,6 +204,11 @@ export interface SavedObjectsServiceStart { * Creates a {@link SavedObjectsSerializer | serializer} that is aware of all registered types. */ createSerializer: () => SavedObjectsSerializer; + /** + * Returns the {@link ISavedObjectTypeRegistry | registry} containing all registered + * {@link SavedObjectsType | saved object types} + */ + getTypeRegistry: () => ISavedObjectTypeRegistry; } export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart { @@ -170,10 +220,6 @@ export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceSta * @deprecated Exposed only for injecting into Legacy */ clientProvider: ISavedObjectsClientProvider; - /** - * @deprecated Exposed only for injecting into Legacy - */ - typeRegistry: ISavedObjectTypeRegistry; } /** @@ -359,6 +405,7 @@ export class SavedObjectsService const repository = repositoryFactory.createScopedRepository(request); return new SavedObjectsClient(repository); }, + typeRegistry: this.typeRegistry, }); if (this.clientFactoryProvider) { const clientFactory = this.clientFactoryProvider(repositoryFactory); @@ -371,11 +418,11 @@ export class SavedObjectsService return { migrator, clientProvider, - typeRegistry: this.typeRegistry, getScopedClient: clientProvider.getClient.bind(clientProvider), createScopedRepository: repositoryFactory.createScopedRepository, createInternalRepository: repositoryFactory.createInternalRepository, createSerializer: () => new SavedObjectsSerializer(this.typeRegistry), + getTypeRegistry: () => this.typeRegistry, }; } diff --git a/src/core/server/saved_objects/saved_objects_type_registry.mock.ts b/src/core/server/saved_objects/saved_objects_type_registry.mock.ts index 6e11920db6b7d..435e352335ecf 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.mock.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.mock.ts @@ -17,9 +17,10 @@ * under the License. */ -import { ISavedObjectTypeRegistry } from './saved_objects_type_registry'; +import { ISavedObjectTypeRegistry, SavedObjectTypeRegistry } from './saved_objects_type_registry'; -const createRegistryMock = (): jest.Mocked => { +const createRegistryMock = (): jest.Mocked> => { const mock = { registerType: jest.fn(), getType: jest.fn(), diff --git a/src/core/server/saved_objects/saved_objects_type_registry.ts b/src/core/server/saved_objects/saved_objects_type_registry.ts index 3f26d696831fd..b73c80ad9dff7 100644 --- a/src/core/server/saved_objects/saved_objects_type_registry.ts +++ b/src/core/server/saved_objects/saved_objects_type_registry.ts @@ -23,14 +23,17 @@ import { SavedObjectsType } from './types'; /** * See {@link SavedObjectTypeRegistry} for documentation. * - * @internal - * */ -export type ISavedObjectTypeRegistry = PublicMethodsOf; + * @public + */ +export type ISavedObjectTypeRegistry = Pick< + SavedObjectTypeRegistry, + 'getType' | 'getAllTypes' | 'getIndex' | 'isNamespaceAgnostic' | 'isHidden' +>; /** - * Registry holding information about all the registered {@link SavedObjectsType | savedObject types}. + * Registry holding information about all the registered {@link SavedObjectsType | saved object types}. * - * @internal + * @public */ export class SavedObjectTypeRegistry { private readonly types = new Map(); diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js b/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js index eb210b6843de0..aa9448e61009d 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js @@ -18,6 +18,7 @@ */ import { SavedObjectsClientProvider } from './scoped_client_provider'; +import { typeRegistryMock } from '../../saved_objects_type_registry.mock'; test(`uses default client factory when one isn't set`, () => { const returnValue = Symbol(); @@ -26,6 +27,7 @@ test(`uses default client factory when one isn't set`, () => { const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, + typeRegistry: typeRegistryMock.create(), }); const result = clientProvider.getClient(request); @@ -44,6 +46,7 @@ test(`uses custom client factory when one is set`, () => { const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, + typeRegistry: typeRegistryMock.create(), }); clientProvider.setClientFactory(customClientFactoryMock); const result = clientProvider.getClient(request); @@ -68,6 +71,7 @@ test(`throws error when registering a wrapper with a duplicate id`, () => { const defaultClientFactoryMock = jest.fn(); const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, + typeRegistry: typeRegistryMock.create(), }); const firstClientWrapperFactoryMock = jest.fn(); const secondClientWrapperFactoryMock = jest.fn(); @@ -80,9 +84,11 @@ test(`throws error when registering a wrapper with a duplicate id`, () => { test(`invokes and uses wrappers in specified order`, () => { const defaultClient = Symbol(); + const typeRegistry = typeRegistryMock.create(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, + typeRegistry, }); const firstWrappedClient = Symbol('first client'); const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient); @@ -98,18 +104,22 @@ test(`invokes and uses wrappers in specified order`, () => { expect(firstClientWrapperFactoryMock).toHaveBeenCalledWith({ request, client: secondWrapperClient, + typeRegistry, }); expect(secondClientWrapperFactoryMock).toHaveBeenCalledWith({ request, client: defaultClient, + typeRegistry, }); }); test(`does not invoke or use excluded wrappers`, () => { const defaultClient = Symbol(); + const typeRegistry = typeRegistryMock.create(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, + typeRegistry, }); const firstWrappedClient = Symbol('first client'); const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient); @@ -128,6 +138,7 @@ test(`does not invoke or use excluded wrappers`, () => { expect(firstClientWrapperFactoryMock).toHaveBeenCalledWith({ request, client: defaultClient, + typeRegistry, }); expect(secondClientWrapperFactoryMock).not.toHaveBeenCalled(); }); @@ -137,6 +148,7 @@ test(`allows all wrappers to be excluded`, () => { const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); const clientProvider = new SavedObjectsClientProvider({ defaultClientFactory: defaultClientFactoryMock, + typeRegistry: typeRegistryMock.create(), }); const firstWrappedClient = Symbol('first client'); const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient); diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 8aadc4f57317c..24813cd8d9ab8 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -19,6 +19,7 @@ import { PriorityCollection } from './priority_collection'; import { SavedObjectsClientContract } from '../../types'; import { SavedObjectsRepositoryFactory } from '../../saved_objects_service'; +import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { KibanaRequest } from '../../../http'; /** @@ -27,6 +28,7 @@ import { KibanaRequest } from '../../../http'; */ export interface SavedObjectsClientWrapperOptions { client: SavedObjectsClientContract; + typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; } @@ -84,9 +86,17 @@ export class SavedObjectsClientProvider { }>(); private _clientFactory: SavedObjectsClientFactory; private readonly _originalClientFactory: SavedObjectsClientFactory; - - constructor({ defaultClientFactory }: { defaultClientFactory: SavedObjectsClientFactory }) { + private readonly _typeRegistry: ISavedObjectTypeRegistry; + + constructor({ + defaultClientFactory, + typeRegistry, + }: { + defaultClientFactory: SavedObjectsClientFactory; + typeRegistry: ISavedObjectTypeRegistry; + }) { this._originalClientFactory = this._clientFactory = defaultClientFactory; + this._typeRegistry = typeRegistry; } addClientWrapperFactory( @@ -129,6 +139,7 @@ export class SavedObjectsClientProvider { return factory({ request, client: clientToWrap, + typeRegistry: this._typeRegistry, }); }, client); } diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 9c204784b0aeb..495d896ad12cd 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -34,6 +34,8 @@ export { } from './import/types'; import { LegacyConfig } from '../legacy'; +import { SavedObjectUnsanitizedDoc } from './serialization'; +import { SavedObjectsMigrationLogger } from './migrations/core/migration_logger'; export { SavedObjectAttributes, SavedObjectAttribute, @@ -273,9 +275,26 @@ export interface SavedObjectsLegacyMapping { * @deprecated */ export interface SavedObjectsLegacyMigrationDefinitions { - [type: string]: SavedObjectMigrationMap; + [type: string]: SavedObjectLegacyMigrationMap; } +/** + * @internal + * @deprecated + */ +export interface SavedObjectLegacyMigrationMap { + [version: string]: SavedObjectLegacyMigrationFn; +} + +/** + * @internal + * @deprecated + */ +export type SavedObjectLegacyMigrationFn = ( + doc: SavedObjectUnsanitizedDoc, + log: SavedObjectsMigrationLogger +) => SavedObjectUnsanitizedDoc; + /** * @internal * @deprecated diff --git a/src/core/server/saved_objects/utils.test.ts b/src/core/server/saved_objects/utils.test.ts index 1e2b9f6a0f694..0a56535ac8509 100644 --- a/src/core/server/saved_objects/utils.test.ts +++ b/src/core/server/saved_objects/utils.test.ts @@ -20,7 +20,8 @@ import { legacyServiceMock } from '../legacy/legacy_service.mock'; import { convertLegacyTypes, convertTypesToLegacySchema } from './utils'; import { SavedObjectsLegacyUiExports, SavedObjectsType } from './types'; -import { LegacyConfig } from 'kibana/server'; +import { LegacyConfig, SavedObjectMigrationContext } from 'kibana/server'; +import { SavedObjectUnsanitizedDoc } from './serialization'; describe('convertLegacyTypes', () => { let legacyConfig: ReturnType; @@ -190,8 +191,48 @@ describe('convertLegacyTypes', () => { const converted = convertLegacyTypes(uiExports, legacyConfig); expect(converted.length).toEqual(2); - expect(converted[0].migrations).toEqual(migrationsA); - expect(converted[1].migrations).toEqual(migrationsB); + expect(Object.keys(converted[0]!.migrations!)).toEqual(Object.keys(migrationsA)); + expect(Object.keys(converted[1]!.migrations!)).toEqual(Object.keys(migrationsB)); + }); + + it('converts the migration to the new format', () => { + const legacyMigration = jest.fn(); + const migrationsA = { + '1.0.0': legacyMigration, + }; + + const uiExports: SavedObjectsLegacyUiExports = { + savedObjectMappings: [ + { + pluginId: 'pluginA', + properties: { + typeA: { + properties: { + fieldA: { type: 'text' }, + }, + }, + }, + }, + ], + savedObjectMigrations: { + typeA: migrationsA, + }, + savedObjectSchemas: {}, + savedObjectValidations: {}, + savedObjectsManagement: {}, + }; + + const converted = convertLegacyTypes(uiExports, legacyConfig); + expect(Object.keys(converted[0]!.migrations!)).toEqual(['1.0.0']); + + const migration = converted[0]!.migrations!['1.0.0']!; + + const doc = {} as SavedObjectUnsanitizedDoc; + const context = { log: {} } as SavedObjectMigrationContext; + migration(doc, context); + + expect(legacyMigration).toHaveBeenCalledTimes(1); + expect(legacyMigration).toHaveBeenCalledWith(doc, context.log); }); it('merges everything when all are present', () => { diff --git a/src/core/server/saved_objects/utils.ts b/src/core/server/saved_objects/utils.ts index 5c4d0ccb84b25..bb2c42c6a362c 100644 --- a/src/core/server/saved_objects/utils.ts +++ b/src/core/server/saved_objects/utils.ts @@ -18,7 +18,12 @@ */ import { LegacyConfig } from '../legacy'; -import { SavedObjectsType, SavedObjectsLegacyUiExports } from './types'; +import { SavedObjectMigrationMap } from './migrations'; +import { + SavedObjectsType, + SavedObjectsLegacyUiExports, + SavedObjectLegacyMigrationMap, +} from './types'; import { SavedObjectsSchemaDefinition } from './schema'; /** @@ -49,7 +54,7 @@ export const convertLegacyTypes = ( ? schema.indexPattern(legacyConfig) : schema?.indexPattern, convertToAliasScript: schema?.convertToAliasScript, - migrations: migrations ?? {}, + migrations: convertLegacyMigrations(migrations ?? {}), }; }), ]; @@ -74,3 +79,14 @@ export const convertTypesToLegacySchema = ( }; }, {} as SavedObjectsSchemaDefinition); }; + +const convertLegacyMigrations = ( + legacyMigrations: SavedObjectLegacyMigrationMap +): SavedObjectMigrationMap => { + return Object.entries(legacyMigrations).reduce((migrated, [version, migrationFn]) => { + return { + ...migrated, + [version]: (doc, context) => migrationFn(doc, context.log), + }; + }, {} as SavedObjectMigrationMap); +}; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index f717f30fdb0cf..8f4feb7169651 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -929,6 +929,9 @@ export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolea // @public export type ISavedObjectsRepository = Pick; +// @public +export type ISavedObjectTypeRegistry = Pick; + // @public export type IScopedClusterClient = Pick; @@ -1489,12 +1492,15 @@ export interface SavedObjectAttributes { // @public export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; +// @public +export interface SavedObjectMigrationContext { + log: SavedObjectsMigrationLogger; +} + // Warning: (ae-forgotten-export) The symbol "SavedObjectUnsanitizedDoc" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "SavedObjectMigrationFn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "SavedObjectUnsanitizedDoc" // // @public -export type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, log: SavedObjectsMigrationLogger) => SavedObjectUnsanitizedDoc; +export type SavedObjectMigrationFn = (doc: SavedObjectUnsanitizedDoc, context: SavedObjectMigrationContext) => SavedObjectUnsanitizedDoc; // @public export interface SavedObjectMigrationMap { @@ -1619,6 +1625,8 @@ export interface SavedObjectsClientWrapperOptions { client: SavedObjectsClientContract; // (undocumented) request: KibanaRequest; + // (undocumented) + typeRegistry: ISavedObjectTypeRegistry; } // @public @@ -2013,8 +2021,6 @@ export class SavedObjectsSchema { // @public export class SavedObjectsSerializer { - // Warning: (ae-forgotten-export) The symbol "ISavedObjectTypeRegistry" needs to be exported by the entry point index.d.ts - // // @internal constructor(registry: ISavedObjectTypeRegistry); generateRawId(namespace: string | undefined, type: string, id?: string): string; @@ -2026,6 +2032,7 @@ export class SavedObjectsSerializer { // @public export interface SavedObjectsServiceSetup { addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; + registerType: (type: SavedObjectsType) => void; setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; } @@ -2035,6 +2042,7 @@ export interface SavedObjectsServiceStart { createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; createSerializer: () => SavedObjectsSerializer; getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; + getTypeRegistry: () => ISavedObjectTypeRegistry; } // @public (undocumented) @@ -2069,7 +2077,7 @@ export interface SavedObjectsUpdateResponse extends Omit { }, }, }; + + const coreStart = coreMock.createStart(); + coreStart.savedObjects.getTypeRegistry.mockReturnValue(typeRegistry); + mockKbnServer = { newPlatform: { __internals: { kibanaMigrator: migrator, savedObjectsClientProvider: clientProvider, - typeRegistry, }, setup: { core: coreMock.createSetup(), }, start: { - core: coreMock.createStart(), + core: coreStart, }, }, server: mockServer, From 457783e8395b622de80f7c176c95172d5ce21c3b Mon Sep 17 00:00:00 2001 From: Spencer Date: Wed, 26 Feb 2020 08:25:03 -0700 Subject: [PATCH 07/22] [kbn/ui-shared-deps] load base css file (#58520) Co-authored-by: Elastic Machine --- packages/kbn-ui-shared-deps/index.d.ts | 5 +++++ packages/kbn-ui-shared-deps/index.js | 1 + packages/kbn-ui-shared-deps/scripts/build.js | 7 +++++-- src/legacy/ui/ui_render/ui_render_mixin.js | 1 + tasks/config/karma.js | 1 + 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/kbn-ui-shared-deps/index.d.ts b/packages/kbn-ui-shared-deps/index.d.ts index 132445bbde745..7ee96050a1248 100644 --- a/packages/kbn-ui-shared-deps/index.d.ts +++ b/packages/kbn-ui-shared-deps/index.d.ts @@ -27,6 +27,11 @@ export const distDir: string; */ export const distFilename: string; +/** + * Filename of the unthemed css file in the distributable directory + */ +export const baseCssDistFilename: string; + /** * Filename of the dark-theme css file in the distributable directory */ diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index c7c004bd55794..d1bb93ddecd0a 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -21,6 +21,7 @@ const Path = require('path'); exports.distDir = Path.resolve(__dirname, 'target'); exports.distFilename = 'kbn-ui-shared-deps.js'; +exports.baseCssDistFilename = 'kbn-ui-shared-deps.css'; exports.lightCssDistFilename = 'kbn-ui-shared-deps.light.css'; exports.darkCssDistFilename = 'kbn-ui-shared-deps.dark.css'; exports.externals = { diff --git a/packages/kbn-ui-shared-deps/scripts/build.js b/packages/kbn-ui-shared-deps/scripts/build.js index 8b7c22dac24ff..e45b3dbed1748 100644 --- a/packages/kbn-ui-shared-deps/scripts/build.js +++ b/packages/kbn-ui-shared-deps/scripts/build.js @@ -64,8 +64,11 @@ run( }); compiler.hooks.watchRun.tap('report on start', () => { - process.stdout.cursorTo(0, 0); - process.stdout.clearScreenDown(); + if (process.stdout.isTTY) { + process.stdout.cursorTo(0, 0); + process.stdout.clearScreenDown(); + } + log.info('Running webpack compilation...'); }); diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 21c10bb20962f..0a1b95c23450b 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -112,6 +112,7 @@ export function uiRenderMixin(kbnServer, server, config) { ); const styleSheetPaths = [ ...dllStyleChunks, + `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, ...(darkMode ? [ `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`, diff --git a/tasks/config/karma.js b/tasks/config/karma.js index 9992dafed41c5..24e97aa081e51 100644 --- a/tasks/config/karma.js +++ b/tasks/config/karma.js @@ -64,6 +64,7 @@ module.exports = function(grunt) { ? `http://localhost:5610/bundles/tests.bundle.js` : `http://localhost:5610/bundles/tests.bundle.js?shards=${TOTAL_CI_SHARDS}&shard_num=${shardNum}`, + `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`, // this causes tilemap tests to fail, probably because the eui styles haven't been // included in the karma harness a long some time, if ever // `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, From 4b8b9a42181e9ea8db0532f4827f5b98a687a7d8 Mon Sep 17 00:00:00 2001 From: Justin Juno <50022106+justinjunodev@users.noreply.github.com> Date: Wed, 26 Feb 2020 10:13:53 -0600 Subject: [PATCH 08/22] [DOCS] Add Homebrew start + stop instructions for Kibana (#58495) * add brew start and stop instructions to docs * add float to start-stop doc * Update start-stop.asciidoc Co-authored-by: Elastic Machine --- docs/setup/install/brew-running.asciidoc | 9 +++++++++ docs/setup/start-stop.asciidoc | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 docs/setup/install/brew-running.asciidoc diff --git a/docs/setup/install/brew-running.asciidoc b/docs/setup/install/brew-running.asciidoc new file mode 100644 index 0000000000000..ba78dd1659d04 --- /dev/null +++ b/docs/setup/install/brew-running.asciidoc @@ -0,0 +1,9 @@ +==== Running Kibana with `brew services` + +With Homebrew, Kibana can be started and stopped as follows: + +[source,sh] +-------------------------------------------------- +brew services start elastic/tap/kibana-full +brew services stop elastic/tap/kibana-full +-------------------------------------------------- diff --git a/docs/setup/start-stop.asciidoc b/docs/setup/start-stop.asciidoc index 2bbc49d9e2ae2..2fcc440680f12 100644 --- a/docs/setup/start-stop.asciidoc +++ b/docs/setup/start-stop.asciidoc @@ -46,4 +46,12 @@ include::install/init-systemd.asciidoc[] include::install/rpm-init.asciidoc[] [float] -include::install/systemd.asciidoc[] \ No newline at end of file +include::install/systemd.asciidoc[] + +[float] +=== Homebrew packages + +If you installed {kib} with the Elastic Homebrew formulae, you can start and stop {kib} from the command line using `brew services`. + +[float] +include::install/brew-running.asciidoc[] From 32fb5c15325b27fe98b6cbace708d6bbb04bdc60 Mon Sep 17 00:00:00 2001 From: Pete Harverson Date: Wed, 26 Feb 2020 16:30:10 +0000 Subject: [PATCH 09/22] [ML] Fixes creation of a watch for an anomaly detection job (#58597) --- x-pack/legacy/plugins/ml/public/application/app.tsx | 2 -- .../create_watch_flyout/create_watch_service.js | 12 +++++++++--- .../create_watch_flyout/select_severity.tsx | 4 ++-- .../ml/public/application/util/dependency_cache.ts | 10 ---------- x-pack/legacy/plugins/ml/public/legacy.ts | 2 -- 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/x-pack/legacy/plugins/ml/public/application/app.tsx b/x-pack/legacy/plugins/ml/public/application/app.tsx index add27193deb77..3acb24ac6e173 100644 --- a/x-pack/legacy/plugins/ml/public/application/app.tsx +++ b/x-pack/legacy/plugins/ml/public/application/app.tsx @@ -24,7 +24,6 @@ export interface MlDependencies extends AppMountParameters { security: SecurityPluginSetup; __LEGACY: { XSRF: string; - APP_URL: string; }; } @@ -48,7 +47,6 @@ const App: FC = ({ coreStart, deps }) => { basePath: coreStart.http.basePath, savedObjectsClient: coreStart.savedObjects.client, XSRF: deps.__LEGACY.XSRF, - APP_URL: deps.__LEGACY.APP_URL, application: coreStart.application, http: coreStart.http, security: deps.security, diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js index 887afeb3ba818..89589c98b52c2 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/create_watch_service.js @@ -9,9 +9,10 @@ import { http } from '../../../../services/http_service'; import emailBody from './email.html'; import emailInfluencersBody from './email_influencers.html'; +import { DEFAULT_WATCH_SEVERITY } from './select_severity'; import { watch } from './watch.js'; import { i18n } from '@kbn/i18n'; -import { getBasePath, getAppUrl } from '../../../../util/dependency_cache'; +import { getBasePath, getApplication } from '../../../../util/dependency_cache'; const compiledEmailBody = template(emailBody); const compiledEmailInfluencersBody = template(emailInfluencersBody); @@ -75,7 +76,10 @@ class CreateWatchService { this.config.interval = '20m'; this.config.watcherEditURL = ''; this.config.includeInfluencers = false; - this.config.threshold = { display: 'critical', val: 75 }; + + // Current implementation means that default needs to match that of the select severity control. + const { display, val } = DEFAULT_WATCH_SEVERITY; + this.config.threshold = { display, val }; } createNewWatch = function(jobId) { @@ -91,12 +95,13 @@ class CreateWatchService { watch.input.search.request.body.aggs.bucket_results.filter.range.anomaly_score.gte = this.config.threshold.val; if (this.config.includeEmail && this.config.email !== '') { + const { getUrlForApp } = getApplication(); const emails = this.config.email.split(','); emailSection.send_email.email.to = emails; // create the html by adding the variables to the compiled email body. emailSection.send_email.email.body.html = compiledEmailBody({ - serverAddress: getAppUrl(), + serverAddress: getUrlForApp('ml', { absolute: true }), influencersSection: this.config.includeInfluencers === true ? compiledEmailInfluencersBody({ @@ -153,6 +158,7 @@ class CreateWatchService { upstreamJSON: { id, type: 'json', + isNew: false, // Set to false, as we want to allow watches to be overwritten. watch, }, }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx index 8b0e7da2a5637..727830a58bb41 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/jobs_list/components/create_watch_flyout/select_severity.tsx @@ -78,7 +78,7 @@ function optionValueToThreshold(value: number) { return threshold; } -const TABLE_SEVERITY_DEFAULT = SEVERITY_OPTIONS[0]; +export const DEFAULT_WATCH_SEVERITY = SEVERITY_OPTIONS[3]; const getSeverityOptions = () => SEVERITY_OPTIONS.map(({ color, display, val }) => ({ @@ -114,7 +114,7 @@ interface Props { } export const SelectSeverity: FC = ({ onChange }) => { - const [severity, setSeverity] = useState(TABLE_SEVERITY_DEFAULT); + const [severity, setSeverity] = useState(DEFAULT_WATCH_SEVERITY); const onSeverityChange = (valueDisplay: string) => { const option = optionValueToThreshold(optionsMap[valueDisplay]); diff --git a/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts index f837d90dba8fe..6d1dfa96ca03e 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/dependency_cache.ts @@ -36,7 +36,6 @@ export interface DependencyCache { basePath: IBasePath | null; savedObjectsClient: SavedObjectsClientContract | null; XSRF: string | null; - APP_URL: string | null; application: ApplicationStart | null; http: HttpStart | null; security: SecurityPluginSetup | null; @@ -56,7 +55,6 @@ const cache: DependencyCache = { basePath: null, savedObjectsClient: null, XSRF: null, - APP_URL: null, application: null, http: null, security: null, @@ -76,7 +74,6 @@ export function setDependencyCache(deps: Partial) { cache.basePath = deps.basePath || null; cache.savedObjectsClient = deps.savedObjectsClient || null; cache.XSRF = deps.XSRF || null; - cache.APP_URL = deps.APP_URL || null; cache.application = deps.application || null; cache.http = deps.http || null; } @@ -171,13 +168,6 @@ export function getXSRF() { return cache.XSRF; } -export function getAppUrl() { - if (cache.APP_URL === null) { - throw new Error("app url hasn't been initialized"); - } - return cache.APP_URL; -} - export function getApplication() { if (cache.application === null) { throw new Error("application hasn't been initialized"); diff --git a/x-pack/legacy/plugins/ml/public/legacy.ts b/x-pack/legacy/plugins/ml/public/legacy.ts index 40a1afa06b5a6..7dfcf6a99c213 100644 --- a/x-pack/legacy/plugins/ml/public/legacy.ts +++ b/x-pack/legacy/plugins/ml/public/legacy.ts @@ -18,8 +18,6 @@ export const setup = pluginInstance.setup(npSetup.core, { security: ((npSetup.plugins as unknown) as { security: SecurityPluginSetup }).security, // security isn't in the PluginsSetup interface, but does exist __LEGACY: { XSRF: chrome.getXsrfToken(), - // @ts-ignore getAppUrl is missing from chrome's definition - APP_URL: chrome.getAppUrl(), }, }); export const start = pluginInstance.start(npStart.core, npStart.plugins); From 613e4b9b15dcdd42db456655635daa0d1247e5a7 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Wed, 26 Feb 2020 11:33:42 -0500 Subject: [PATCH 10/22] Median label shows "Median" instead of "50th percentile of" (#58521) * Median label shows "Median" instead of "50th percentile of" * Update test Co-authored-by: Elastic Machine --- .../public/search/aggs/metrics/median.test.ts | 18 +++++++++++++----- .../data/public/search/aggs/metrics/median.ts | 14 +++++--------- .../aggs/metrics/percentiles_get_value.ts | 2 +- .../functional/apps/visualize/_metric_chart.js | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts index 9affb0e3b2814..4755a873e6977 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts @@ -47,7 +47,6 @@ describe('AggTypeMetricMedianProvider class', () => { schema: 'metric', params: { field: 'bytes', - percents: [70], }, }, ], @@ -58,12 +57,21 @@ describe('AggTypeMetricMedianProvider class', () => { it('requests the percentiles aggregation in the Elasticsearch query DSL', () => { const dsl: Record = aggConfigs.toDsl(); - expect(dsl.median.percentiles.percents).toEqual([70]); + expect(dsl.median.percentiles.field).toEqual('bytes'); + expect(dsl.median.percentiles.percents).toEqual([50]); }); - it('asks Elasticsearch for array-based values in the aggregation response', () => { - const dsl: Record = aggConfigs.toDsl(); + it('converts the response', () => { + const agg = aggConfigs.getResponseAggs()[0]; - expect(dsl.median.percentiles.keyed).toBeFalsy(); + expect( + agg.getValue({ + [agg.id]: { + values: { + '50.0': 10, + }, + }, + }) + ).toEqual(10); }); }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts index be080aaa5ee6f..53a5ffff418f1 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts @@ -43,17 +43,13 @@ export const medianMetricAgg = new MetricAggType({ name: 'field', type: 'field', filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], - }, - { - name: 'percents', - default: [50], - }, - { write(agg, output) { - output.params.keyed = false; + output.params.field = agg.getParam('field').name; + output.params.percents = [50]; }, }, ], - getResponseAggs: percentilesMetricAgg.getResponseAggs, - getValue: percentilesMetricAgg.getValue, + getValue(agg, bucket) { + return bucket[agg.id].values['50.0']; + }, }); diff --git a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles_get_value.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles_get_value.ts index c357d7bb0a903..980d969a8ea0c 100644 --- a/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles_get_value.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles_get_value.ts @@ -24,7 +24,7 @@ export const getPercentileValue = ( agg: TAggConfig, bucket: any ) => { - const { values } = bucket[agg.parentId] && bucket[agg.parentId]; + const { values } = bucket[agg.parentId]; const percentile: any = find(values, ({ key }) => key === agg.key); diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js index 6a95f7553943c..dab9d2213b764 100644 --- a/test/functional/apps/visualize/_metric_chart.js +++ b/test/functional/apps/visualize/_metric_chart.js @@ -78,7 +78,7 @@ export default function({ getService, getPageObjects }) { }); it('should show Median', async function() { - const medianBytes = ['5,565.263', '50th percentile of bytes']; + const medianBytes = ['5,565.263', 'Median bytes']; // For now, only comparing the text label part of the metric log.debug('Aggregation = Median'); await PageObjects.visEditor.selectAggregation('Median', 'metrics'); From 8021fd887ab5f86ed17489e580d2c3cdc0cb9e84 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 26 Feb 2020 09:36:43 -0700 Subject: [PATCH 11/22] [Metrics UI] Custom Metrics for Inventory View (#58072) * [Metrics UI] Add custom metrics interface to Inventory View * WIP * Adding workflows for editing custom metrics * Polishing visual design * Removing extra text * Fixing types and return values * fixing i18n * Adding aria labels for clearity * Changing custom group by to match same width as custom metric * updating integration test for custom metrics * Fixing type def --- .../infra/common/http_api/snapshot_api.ts | 34 ++- .../metrics_and_groupby_toolbar_items.tsx | 11 +- .../infra/common/inventory_models/types.ts | 1 + .../common/saved_objects/inventory_view.ts | 32 +++ .../components/inventory/toolbars/toolbar.tsx | 10 +- .../inventory/toolbars/toolbar_wrapper.tsx | 6 +- .../components/nodes_overview/index.tsx | 7 +- .../components/waffle/custom_field_panel.tsx | 5 +- .../metric_control/custom_metric_form.tsx | 247 ++++++++++++++++++ .../metric_control/get_custom_metric_label.ts | 30 +++ .../waffle/metric_control/index.tsx | 199 ++++++++++++++ .../metric_control/metrics_context_menu.tsx | 75 ++++++ .../metric_control/metrics_edit_mode.tsx | 73 ++++++ .../waffle/metric_control/mode_switcher.tsx | 113 ++++++++ .../components/waffle/metric_control/types.ts | 7 + .../waffle/waffle_group_by_controls.tsx | 1 + .../waffle/waffle_inventory_switcher.tsx | 19 +- .../waffle/waffle_metric_controls.tsx | 97 ------- .../containers/waffle/with_waffle_options.tsx | 28 +- .../waffle/with_waffle_view_state.tsx | 9 +- .../pages/infrastructure/snapshot/toolbar.tsx | 2 + .../store/local/waffle_options/actions.ts | 9 +- .../store/local/waffle_options/reducer.ts | 14 +- .../store/local/waffle_options/selector.ts | 1 + .../server/lib/snapshot/query_helpers.ts | 31 ++- .../test/api_integration/apis/infra/waffle.ts | 45 +++- 26 files changed, 986 insertions(+), 120 deletions(-) create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/custom_metric_form.tsx create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/get_custom_metric_label.ts create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/index.tsx create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/metrics_context_menu.tsx create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/metrics_edit_mode.tsx create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/mode_switcher.tsx create mode 100644 x-pack/plugins/infra/public/components/waffle/metric_control/types.ts delete mode 100644 x-pack/plugins/infra/public/components/waffle/waffle_metric_controls.tsx diff --git a/x-pack/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/plugins/infra/common/http_api/snapshot_api.ts index c7c15fd8af161..6b666b39b0094 100644 --- a/x-pack/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/plugins/infra/common/http_api/snapshot_api.ts @@ -54,16 +54,41 @@ export const SnapshotGroupByRT = rt.array( }) ); -export const SnapshotMetricInputRT = rt.type({ +export const SnapshotNamedMetricInputRT = rt.type({ type: SnapshotMetricTypeRT, }); +export const SNAPSHOT_CUSTOM_AGGREGATIONS = ['avg', 'max', 'min', 'rate'] as const; + +export type SnapshotCustomAggregation = typeof SNAPSHOT_CUSTOM_AGGREGATIONS[number]; + +const snapshotCustomAggregationKeys = SNAPSHOT_CUSTOM_AGGREGATIONS.reduce< + Record +>((acc, agg) => ({ ...acc, [agg]: null }), {} as Record); + +export const SnapshotCustomAggregationRT = rt.keyof(snapshotCustomAggregationKeys); + +export const SnapshotCustomMetricInputRT = rt.intersection([ + rt.type({ + type: rt.literal('custom'), + field: rt.string, + aggregation: SnapshotCustomAggregationRT, + id: rt.string, + }), + rt.partial({ + label: rt.string, + }), +]); + +export const SnapshotMetricInputRT = rt.union([ + SnapshotNamedMetricInputRT, + SnapshotCustomMetricInputRT, +]); + export const SnapshotRequestRT = rt.intersection([ rt.type({ timerange: InfraTimerangeInputRT, - metric: rt.type({ - type: SnapshotMetricTypeRT, - }), + metric: SnapshotMetricInputRT, groupBy: SnapshotGroupByRT, nodeType: ItemTypeRT, sourceId: rt.string, @@ -77,6 +102,7 @@ export const SnapshotRequestRT = rt.intersection([ export type SnapshotNodePath = rt.TypeOf; export type SnapshotMetricInput = rt.TypeOf; +export type SnapshotCustomMetricInput = rt.TypeOf; export type InfraTimerangeInput = rt.TypeOf; export type SnapshotNodeMetric = rt.TypeOf; export type SnapshotGroupBy = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/inventory_models/shared/compontents/metrics_and_groupby_toolbar_items.tsx b/x-pack/plugins/infra/common/inventory_models/shared/compontents/metrics_and_groupby_toolbar_items.tsx index fcaedcdd080b8..738fce45ee99f 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/compontents/metrics_and_groupby_toolbar_items.tsx +++ b/x-pack/plugins/infra/common/inventory_models/shared/compontents/metrics_and_groupby_toolbar_items.tsx @@ -9,7 +9,7 @@ import { EuiFlexItem } from '@elastic/eui'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ToolbarProps } from '../../../../public/components/inventory/toolbars/toolbar'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { WaffleMetricControls } from '../../../../public/components/waffle/waffle_metric_controls'; +import { WaffleMetricControls } from '../../../../public/components/waffle/metric_control'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { WaffleGroupByControls } from '../../../../public/components/waffle/waffle_group_by_controls'; import { @@ -25,7 +25,11 @@ interface Props extends ToolbarProps { } export const MetricsAndGroupByToolbarItems = (props: Props) => { - const metricOptions = useMemo(() => props.metricTypes.map(toMetricOpt), [props.metricTypes]); + const metricOptions = useMemo( + () => + props.metricTypes.map(toMetricOpt).filter(v => v) as Array<{ text: string; value: string }>, + [props.metricTypes] + ); const groupByOptions = useMemo(() => props.groupByFields.map(toGroupByOpt), [ props.groupByFields, @@ -35,9 +39,12 @@ export const MetricsAndGroupByToolbarItems = (props: Props) => { <> diff --git a/x-pack/plugins/infra/common/inventory_models/types.ts b/x-pack/plugins/infra/common/inventory_models/types.ts index 2f61b16fb3df8..a6773d0a07450 100644 --- a/x-pack/plugins/infra/common/inventory_models/types.ts +++ b/x-pack/plugins/infra/common/inventory_models/types.ts @@ -305,6 +305,7 @@ export const SnapshotMetricTypeRT = rt.keyof({ sqsMessagesSent: null, sqsMessagesEmpty: null, sqsOldestMessage: null, + custom: null, }); export type SnapshotMetricType = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/saved_objects/inventory_view.ts b/x-pack/plugins/infra/common/saved_objects/inventory_view.ts index 3ab9042947f97..bccffadc0a1ba 100644 --- a/x-pack/plugins/infra/common/saved_objects/inventory_view.ts +++ b/x-pack/plugins/infra/common/saved_objects/inventory_view.ts @@ -26,6 +26,18 @@ export const inventoryViewSavedObjectMappings: { type: { type: 'keyword', }, + field: { + type: 'keyword', + }, + aggregation: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + label: { + type: 'keyword', + }, }, }, groupBy: { @@ -56,6 +68,26 @@ export const inventoryViewSavedObjectMappings: { }, }, }, + customMetrics: { + type: 'nested', + properties: { + type: { + type: 'keyword', + }, + field: { + type: 'keyword', + }, + aggregation: { + type: 'keyword', + }, + id: { + type: 'keyword', + }, + label: { + type: 'keyword', + }, + }, + }, boundsOverride: { properties: { max: { diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx index a2882a3cd3124..c59ab994a018c 100644 --- a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar.tsx @@ -7,7 +7,11 @@ import React, { FunctionComponent } from 'react'; import { Action } from 'typescript-fsa'; import { EuiFlexItem } from '@elastic/eui'; -import { SnapshotMetricInput, SnapshotGroupBy } from '../../../../common/http_api/snapshot_api'; +import { + SnapshotMetricInput, + SnapshotGroupBy, + SnapshotCustomMetricInput, +} from '../../../../common/http_api/snapshot_api'; import { InventoryCloudAccount } from '../../../../common/http_api/inventory_meta_api'; import { findToolbar } from '../../../../common/inventory_models/toolbars'; import { ToolbarWrapper } from './toolbar_wrapper'; @@ -35,6 +39,10 @@ export interface ToolbarProps { region: ReturnType; accounts: InventoryCloudAccount[]; regions: string[]; + customMetrics: ReturnType; + changeCustomMetrics: ( + payload: SnapshotCustomMetricInput[] + ) => Action; } const wrapToolbarItems = ( diff --git a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx index 231030362438f..735539d063b01 100644 --- a/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx +++ b/x-pack/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx @@ -37,6 +37,8 @@ export const ToolbarWrapper = (props: Props) => { nodeType, accountId, region, + customMetrics, + changeCustomMetrics, }) => props.children({ createDerivedIndexPattern, @@ -51,6 +53,8 @@ export const ToolbarWrapper = (props: Props) => { nodeType, region, accountId, + customMetrics, + changeCustomMetrics, }) } @@ -146,7 +150,7 @@ export const toGroupByOpt = (field: string) => ({ export const toMetricOpt = ( metric: SnapshotMetricType -): { text: string; value: SnapshotMetricType } => { +): { text: string; value: SnapshotMetricType } | undefined => { switch (metric) { case 'cpu': return { diff --git a/x-pack/plugins/infra/public/components/nodes_overview/index.tsx b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx index 8cd3faabd1e12..4d61568a63b9f 100644 --- a/x-pack/plugins/infra/public/components/nodes_overview/index.tsx +++ b/x-pack/plugins/infra/public/components/nodes_overview/index.tsx @@ -19,9 +19,10 @@ import { InfraLoadingPanel } from '../loading'; import { Map } from '../waffle/map'; import { ViewSwitcher } from '../waffle/view_switcher'; import { TableView } from './table'; -import { SnapshotNode } from '../../../common/http_api/snapshot_api'; +import { SnapshotNode, SnapshotCustomMetricInputRT } from '../../../common/http_api/snapshot_api'; import { convertIntervalToString } from '../../utils/convert_interval_to_string'; import { InventoryItemType } from '../../../common/inventory_models/types'; +import { createFormatterForMetric } from '../metrics_explorer/helpers/create_formatter_for_metric'; interface Props { options: InfraWaffleMapOptions; @@ -211,6 +212,10 @@ export const NodesOverview = class extends React.Component { // TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example. private formatter = (val: string | number) => { const { metric } = this.props.options; + if (SnapshotCustomMetricInputRT.is(metric)) { + const formatter = createFormatterForMetric(metric); + return formatter(val); + } const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count); if (val == null) { return ''; diff --git a/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx b/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx index 15d8b8b0e42b8..d2dc535f6d6b3 100644 --- a/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx +++ b/x-pack/plugins/infra/public/components/waffle/custom_field_panel.tsx @@ -50,10 +50,10 @@ export const CustomFieldPanel = class extends React.PureComponent helpText={i18n.translate('xpack.infra.waffle.customGroupByHelpText', { defaultMessage: 'This is the field used for the terms aggregation', })} - compressed + display="rowCompressed" + fullWidth > selectedOptions={this.state.selectedOptions} options={options} onChange={this.handleFieldSelection} + fullWidth isClearable={false} />
diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/custom_metric_form.tsx b/x-pack/plugins/infra/public/components/waffle/metric_control/custom_metric_form.tsx new file mode 100644 index 0000000000000..26e42061ed10b --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/custom_metric_form.tsx @@ -0,0 +1,247 @@ +/* + * 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. + */ +import React, { useState, useCallback } from 'react'; +import uuid from 'uuid'; +import { + EuiForm, + EuiButton, + EuiButtonEmpty, + EuiFormRow, + EuiFieldText, + EuiComboBox, + EuiSelect, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiPopoverTitle, +} from '@elastic/eui'; +import { IFieldType } from 'src/plugins/data/public'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + SnapshotCustomAggregation, + SnapshotCustomMetricInput, + SNAPSHOT_CUSTOM_AGGREGATIONS, + SnapshotCustomAggregationRT, +} from '../../../../common/http_api/snapshot_api'; +import { EuiTheme, withTheme } from '../../../../../../legacy/common/eui_styled_components'; + +interface SelectedOption { + label: string; +} + +const AGGREGATION_LABELS = { + ['avg']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.avg', { + defaultMessage: 'Average', + }), + ['max']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.max', { + defaultMessage: 'Max', + }), + ['min']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.min', { + defaultMessage: 'Min', + }), + ['rate']: i18n.translate('xpack.infra.waffle.customMetrics.aggregationLables.rate', { + defaultMessage: 'Rate', + }), +}; + +interface Props { + theme: EuiTheme; + metric?: SnapshotCustomMetricInput; + fields: IFieldType[]; + customMetrics: SnapshotCustomMetricInput[]; + onChange: (metric: SnapshotCustomMetricInput) => void; + onCancel: () => void; +} + +export const CustomMetricForm = withTheme( + ({ theme, onCancel, fields, onChange, metric }: Props) => { + const [label, setLabel] = useState(metric ? metric.label : void 0); + const [aggregation, setAggregation] = useState( + metric ? metric.aggregation : 'avg' + ); + const [field, setField] = useState(metric ? metric.field : void 0); + + const handleSubmit = useCallback(() => { + if (metric && aggregation && field) { + onChange({ + ...metric, + label, + aggregation, + field, + }); + } else if (aggregation && field) { + const newMetric: SnapshotCustomMetricInput = { + type: 'custom', + id: uuid.v1(), + label, + aggregation, + field, + }; + onChange(newMetric); + } + }, [metric, aggregation, field, onChange, label]); + + const handleLabelChange = useCallback( + e => { + setLabel(e.target.value); + }, + [setLabel] + ); + + const handleFieldChange = useCallback( + (selectedOptions: SelectedOption[]) => { + setField(selectedOptions[0].label); + }, + [setField] + ); + + const handleAggregationChange = useCallback( + e => { + const value = e.target.value; + const aggValue: SnapshotCustomAggregation = SnapshotCustomAggregationRT.is(value) + ? value + : 'avg'; + setAggregation(aggValue); + }, + [setAggregation] + ); + + const fieldOptions = fields + .filter(f => f.aggregatable && f.type === 'number' && !(field && field === f.name)) + .map(f => ({ label: f.name })); + + const aggregationOptions = SNAPSHOT_CUSTOM_AGGREGATIONS.map(k => ({ + text: AGGREGATION_LABELS[k as SnapshotCustomAggregation], + value: k, + })); + + const isSubmitDisabled = !field || !aggregation; + + const title = metric + ? i18n.translate('xpack.infra.waffle.customMetricPanelLabel.edit', { + defaultMessage: 'Edit custom metric', + }) + : i18n.translate('xpack.infra.waffle.customMetricPanelLabel.add', { + defaultMessage: 'Add custom metric', + }); + + const titleAriaLabel = metric + ? i18n.translate('xpack.infra.waffle.customMetricPanelLabel.editAriaLabel', { + defaultMessage: 'Back to custom metrics edit mode', + }) + : i18n.translate('xpack.infra.waffle.customMetricPanelLabel.addAriaLabel', { + defaultMessage: 'Back to metric picker', + }); + + return ( +
+ + + + {title} + + +
+ + + + + + + + of + + + + + + + + + + +
+
+ + + + + + +
+
+
+ ); + } +); diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/get_custom_metric_label.ts b/x-pack/plugins/infra/public/components/waffle/metric_control/get_custom_metric_label.ts new file mode 100644 index 0000000000000..4f88c1b29c1f2 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/get_custom_metric_label.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { SnapshotCustomMetricInput } from '../../../../common/http_api/snapshot_api'; + +export const getCustomMetricLabel = (metric: SnapshotCustomMetricInput) => { + const METRIC_LABELS = { + avg: i18n.translate('xpack.infra.waffle.aggregationNames.avg', { + defaultMessage: 'Avg of {field}', + values: { field: metric.field }, + }), + max: i18n.translate('xpack.infra.waffle.aggregationNames.max', { + defaultMessage: 'Max of {field}', + values: { field: metric.field }, + }), + min: i18n.translate('xpack.infra.waffle.aggregationNames.min', { + defaultMessage: 'Min of {field}', + values: { field: metric.field }, + }), + rate: i18n.translate('xpack.infra.waffle.aggregationNames.rate', { + defaultMessage: 'Rate of {field}', + values: { field: metric.field }, + }), + }; + return metric.label ? metric.label : METRIC_LABELS[metric.aggregation]; +}; diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/index.tsx b/x-pack/plugins/infra/public/components/waffle/metric_control/index.tsx new file mode 100644 index 0000000000000..0f2034fe9cb25 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/index.tsx @@ -0,0 +1,199 @@ +/* + * 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. + */ + +import { EuiFilterButton, EuiFilterGroup, EuiPopover } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState, useCallback } from 'react'; +import { IFieldType } from 'src/plugins/data/public'; +import { + SnapshotMetricInput, + SnapshotCustomMetricInput, + SnapshotCustomMetricInputRT, +} from '../../../../common/http_api/snapshot_api'; +import { CustomMetricForm } from './custom_metric_form'; +import { getCustomMetricLabel } from './get_custom_metric_label'; +import { MetricsContextMenu } from './metrics_context_menu'; +import { ModeSwitcher } from './mode_switcher'; +import { MetricsEditMode } from './metrics_edit_mode'; +import { CustomMetricMode } from './types'; +import { SnapshotMetricType } from '../../../../common/inventory_models/types'; + +interface Props { + options: Array<{ text: string; value: string }>; + metric: SnapshotMetricInput; + fields: IFieldType[]; + onChange: (metric: SnapshotMetricInput) => void; + onChangeCustomMetrics: (metrics: SnapshotCustomMetricInput[]) => void; + customMetrics: SnapshotCustomMetricInput[]; +} + +export const WaffleMetricControls = ({ + fields, + onChange, + onChangeCustomMetrics, + metric, + options, + customMetrics, +}: Props) => { + const [isPopoverOpen, setPopoverState] = useState(false); + const [mode, setMode] = useState('pick'); + const [editModeCustomMetrics, setEditModeCustomMetrics] = useState( + [] + ); + const [editCustomMetric, setEditCustomMetric] = useState(); + const handleClose = useCallback(() => { + setPopoverState(false); + }, [setPopoverState]); + + const handleToggle = useCallback(() => { + setPopoverState(!isPopoverOpen); + }, [isPopoverOpen]); + + const handleCustomMetric = useCallback( + (newMetric: SnapshotCustomMetricInput) => { + onChangeCustomMetrics([...customMetrics, newMetric]); + onChange(newMetric); + setMode('pick'); + }, + [customMetrics, onChange, onChangeCustomMetrics, setMode] + ); + + const setModeToEdit = useCallback(() => { + setMode('edit'); + setEditModeCustomMetrics(customMetrics); + }, [customMetrics]); + + const setModeToAdd = useCallback(() => { + setMode('addMetric'); + }, [setMode]); + + const setModeToPick = useCallback(() => { + setMode('pick'); + setEditModeCustomMetrics([]); + }, [setMode]); + + const handleDeleteCustomMetric = useCallback( + (m: SnapshotCustomMetricInput) => { + // If the metric we are deleting is the currently selected metric + // we need to change to the default. + if (SnapshotCustomMetricInputRT.is(metric) && m.id === metric.id) { + onChange({ type: options[0].value as SnapshotMetricType }); + } + // Filter out the deleted metric from the editbale. + const newMetrics = editModeCustomMetrics.filter(v => v.id !== m.id); + setEditModeCustomMetrics(newMetrics); + }, + [editModeCustomMetrics, metric, onChange, options] + ); + + const handleEditCustomMetric = useCallback( + (currentMetric: SnapshotCustomMetricInput) => { + const newMetrics = customMetrics.map(m => (m.id === currentMetric.id && currentMetric) || m); + onChangeCustomMetrics(newMetrics); + setModeToPick(); + setEditCustomMetric(void 0); + setEditModeCustomMetrics([]); + }, + [customMetrics, onChangeCustomMetrics, setModeToPick] + ); + + const handleSelectMetricToEdit = useCallback( + (currentMetric: SnapshotCustomMetricInput) => { + setEditCustomMetric(currentMetric); + setMode('editMetric'); + }, + [setMode, setEditCustomMetric] + ); + + const handleSaveEdit = useCallback(() => { + onChangeCustomMetrics(editModeCustomMetrics); + setMode('pick'); + }, [editModeCustomMetrics, onChangeCustomMetrics]); + + if (!options.length || !metric.type) { + throw Error( + i18n.translate('xpack.infra.waffle.unableToSelectMetricErrorTitle', { + defaultMessage: 'Unable to select options or value for metric.', + }) + ); + } + + const id = SnapshotCustomMetricInputRT.is(metric) && metric.id ? metric.id : metric.type; + const currentLabel = SnapshotCustomMetricInputRT.is(metric) + ? getCustomMetricLabel(metric) + : options.find(o => o.value === id)?.text; + + if (!currentLabel) { + return null; + } + + const button = ( + + + + ); + + return ( + + + {mode === 'pick' ? ( + + ) : null} + {mode === 'addMetric' ? ( + + ) : null} + {mode === 'editMetric' ? ( + + ) : null} + {mode === 'edit' ? ( + + ) : null} + + + + ); +}; diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/metrics_context_menu.tsx b/x-pack/plugins/infra/public/components/waffle/metric_control/metrics_context_menu.tsx new file mode 100644 index 0000000000000..1aacf54244c37 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/metrics_context_menu.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useCallback } from 'react'; +import { EuiContextMenuPanelDescriptor, EuiContextMenu } from '@elastic/eui'; +import { + SnapshotMetricInput, + SnapshotCustomMetricInput, + SnapshotCustomMetricInputRT, +} from '../../../../common/http_api/snapshot_api'; +import { + SnapshotMetricTypeRT, + SnapshotMetricType, +} from '../../../../common/inventory_models/types'; +import { getCustomMetricLabel } from './get_custom_metric_label'; + +interface Props { + options: Array<{ text: string; value: string }>; + metric: SnapshotMetricInput; + onChange: (metric: SnapshotMetricInput) => void; + onClose: () => void; + customMetrics: SnapshotCustomMetricInput[]; +} + +export const MetricsContextMenu = ({ + onClose, + onChange, + metric, + options, + customMetrics, +}: Props) => { + const id = SnapshotCustomMetricInputRT.is(metric) && metric.id ? metric.id : metric.type; + + const handleClick = useCallback( + (val: string) => { + if (!SnapshotMetricTypeRT.is(val)) { + const selectedMetric = customMetrics.find(m => m.id === val); + if (selectedMetric) { + onChange(selectedMetric); + } + } else { + onChange({ type: val as SnapshotMetricType }); + } + onClose(); + }, + [customMetrics, onChange, onClose] + ); + + const panels: EuiContextMenuPanelDescriptor[] = [ + { + id: 0, + title: '', + items: [ + ...options.map(o => { + const icon = o.value === id ? 'check' : 'empty'; + const panel = { name: o.text, onClick: () => handleClick(o.value), icon }; + return panel; + }), + ...customMetrics.map(m => { + const icon = m.id === id ? 'check' : 'empty'; + const panel = { + name: getCustomMetricLabel(m), + onClick: () => handleClick(m.id), + icon, + }; + return panel; + }), + ], + }, + ]; + + return ; +}; diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/metrics_edit_mode.tsx b/x-pack/plugins/infra/public/components/waffle/metric_control/metrics_edit_mode.tsx new file mode 100644 index 0000000000000..ba1f46815db20 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/metrics_edit_mode.tsx @@ -0,0 +1,73 @@ +/* + * 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. + */ +import React from 'react'; +import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SnapshotCustomMetricInput } from '../../../../common/http_api/snapshot_api'; +import { getCustomMetricLabel } from './get_custom_metric_label'; +import { EuiTheme, withTheme } from '../../../../../../legacy/common/eui_styled_components'; + +interface Props { + theme: EuiTheme; + customMetrics: SnapshotCustomMetricInput[]; + options: Array<{ text: string; value: string }>; + onEdit: (metric: SnapshotCustomMetricInput) => void; + onDelete: (metric: SnapshotCustomMetricInput) => void; +} +const ICON_WIDTH = 36; + +export const MetricsEditMode = withTheme( + ({ theme, customMetrics, options, onEdit, onDelete }: Props) => { + return ( +
+ {options.map(option => ( +
+ {option.text} +
+ ))} + {customMetrics.map(metric => ( + + + onEdit(metric)} + aria-label={i18n.translate( + 'xpack.infra.waffle.customMetrics.editMode.editButtonAriaLabel', + { + defaultMessage: 'Edit custom metric for {name}', + values: { name: getCustomMetricLabel(metric) }, + } + )} + /> + + + {getCustomMetricLabel(metric)} + + + onDelete(metric)} + aria-label={i18n.translate( + 'xpack.infra.waffle.customMetrics.editMode.deleteAriaLabel', + { + defaultMessage: 'Delete custom metric for {name}', + values: { name: getCustomMetricLabel(metric) }, + } + )} + /> + + + ))} +
+ ); + } +); diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/mode_switcher.tsx b/x-pack/plugins/infra/public/components/waffle/metric_control/mode_switcher.tsx new file mode 100644 index 0000000000000..43bb904594c68 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/mode_switcher.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/eui'; +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CustomMetricMode } from './types'; +import { SnapshotCustomMetricInput } from '../../../../common/http_api/snapshot_api'; +import { EuiTheme, withTheme } from '../../../../../../legacy/common/eui_styled_components'; + +interface Props { + theme: EuiTheme; + onEdit: () => void; + onAdd: () => void; + onSave: () => void; + onEditCancel: () => void; + mode: CustomMetricMode; + customMetrics: SnapshotCustomMetricInput[]; +} + +export const ModeSwitcher = withTheme( + ({ onSave, onEditCancel, onEdit, onAdd, mode, customMetrics, theme }: Props) => { + if (['editMetric', 'addMetric'].includes(mode)) { + return null; + } + return ( +
+ + {mode === 'edit' ? ( + <> + + + + + + + + + + + + ) : ( + <> + + + + + + + + + + + + )} + +
+ ); + } +); diff --git a/x-pack/plugins/infra/public/components/waffle/metric_control/types.ts b/x-pack/plugins/infra/public/components/waffle/metric_control/types.ts new file mode 100644 index 0000000000000..79e42b12f9976 --- /dev/null +++ b/x-pack/plugins/infra/public/components/waffle/metric_control/types.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export type CustomMetricMode = 'pick' | 'addMetric' | 'editMetric' | 'edit'; diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx index 81f82ec27b4a3..1a3cef591bc07 100644 --- a/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx +++ b/x-pack/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx @@ -104,6 +104,7 @@ export const WaffleGroupByControls = class extends React.PureComponent void; changeGroupBy: (groupBy: SnapshotGroupBy) => void; changeMetric: (metric: SnapshotMetricInput) => void; + changeCustomMetrics: (metrics: SnapshotCustomMetricInput[]) => void; changeAccount: (id: string) => void; changeRegion: (name: string) => void; } @@ -38,6 +43,7 @@ export const WaffleInventorySwitcher: React.FC = ( changeMetric, changeAccount, changeRegion, + changeCustomMetrics, nodeType, }) => { const [isOpen, setIsOpen] = useState(false); @@ -48,6 +54,7 @@ export const WaffleInventorySwitcher: React.FC = ( closePopover(); changeNodeType(targetNodeType); changeGroupBy([]); + changeCustomMetrics([]); changeAccount(''); changeRegion(''); const inventoryModel = findInventoryModel(targetNodeType); @@ -55,7 +62,15 @@ export const WaffleInventorySwitcher: React.FC = ( type: inventoryModel.metrics.defaultSnapshot, }); }, - [closePopover, changeNodeType, changeGroupBy, changeMetric, changeAccount, changeRegion] + [ + closePopover, + changeNodeType, + changeGroupBy, + changeCustomMetrics, + changeAccount, + changeRegion, + changeMetric, + ] ); const goToHost = useCallback(() => goToNodeType('host'), [goToNodeType]); const goToK8 = useCallback(() => goToNodeType('pod'), [goToNodeType]); diff --git a/x-pack/plugins/infra/public/components/waffle/waffle_metric_controls.tsx b/x-pack/plugins/infra/public/components/waffle/waffle_metric_controls.tsx deleted file mode 100644 index f9e48730eaaf2..0000000000000 --- a/x-pack/plugins/infra/public/components/waffle/waffle_metric_controls.tsx +++ /dev/null @@ -1,97 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - EuiContextMenu, - EuiContextMenuPanelDescriptor, - EuiFilterButton, - EuiFilterGroup, - EuiPopover, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; -import { SnapshotMetricInput } from '../../../common/http_api/snapshot_api'; -import { SnapshotMetricType } from '../../../common/inventory_models/types'; - -interface Props { - options: Array<{ text: string; value: SnapshotMetricType }>; - metric: SnapshotMetricInput; - onChange: (metric: SnapshotMetricInput) => void; -} - -const initialState = { - isPopoverOpen: false, -}; -type State = Readonly; - -export const WaffleMetricControls = class extends React.PureComponent { - public static displayName = 'WaffleMetricControls'; - public readonly state: State = initialState; - public render() { - const { metric, options } = this.props; - const value = metric.type; - - if (!options.length || !value) { - throw Error( - i18n.translate('xpack.infra.waffle.unableToSelectMetricErrorTitle', { - defaultMessage: 'Unable to select options or value for metric.', - }) - ); - } - const currentLabel = options.find(o => o.value === metric.type); - if (!currentLabel) { - return null; - } - const panels: EuiContextMenuPanelDescriptor[] = [ - { - id: 0, - title: '', - items: options.map(o => { - const icon = o.value === metric.type ? 'check' : 'empty'; - const panel = { name: o.text, onClick: this.handleClick(o.value), icon }; - return panel; - }), - }, - ]; - const button = ( - - - - ); - - return ( - - - - - - ); - } - private handleClose = () => { - this.setState({ isPopoverOpen: false }); - }; - - private handleToggle = () => { - this.setState(state => ({ isPopoverOpen: !state.isPopoverOpen })); - }; - - private handleClick = (value: SnapshotMetricType) => () => { - this.props.onChange({ type: value }); - this.handleClose(); - }; -}; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx index 45222b32aa51f..47dd6a5a73a73 100644 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx +++ b/x-pack/plugins/infra/public/containers/waffle/with_waffle_options.tsx @@ -14,7 +14,11 @@ import { State, waffleOptionsActions, waffleOptionsSelectors } from '../../store import { asChildFunctionRenderer } from '../../utils/typed_react'; import { bindPlainActionCreators } from '../../utils/typed_redux'; import { UrlStateContainer } from '../../utils/url_state'; -import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; +import { + SnapshotMetricInput, + SnapshotGroupBy, + SnapshotCustomMetricInputRT, +} from '../../../common/http_api/snapshot_api'; import { SnapshotMetricTypeRT, InventoryItemType, @@ -31,6 +35,7 @@ const selectOptionsUrlState = createSelector( waffleOptionsSelectors.selectAutoBounds, waffleOptionsSelectors.selectAccountId, waffleOptionsSelectors.selectRegion, + waffleOptionsSelectors.selectCustomMetrics, ( metric, view, @@ -40,7 +45,8 @@ const selectOptionsUrlState = createSelector( boundsOverride, autoBounds, accountId, - region + region, + customMetrics ) => ({ metric, groupBy, @@ -51,6 +57,7 @@ const selectOptionsUrlState = createSelector( autoBounds, accountId, region, + customMetrics, }) ); @@ -66,6 +73,7 @@ export const withWaffleOptions = connect( accountId: waffleOptionsSelectors.selectAccountId(state), region: waffleOptionsSelectors.selectRegion(state), urlState: selectOptionsUrlState(state), + customMetrics: waffleOptionsSelectors.selectCustomMetrics(state), }), bindPlainActionCreators({ changeMetric: waffleOptionsActions.changeMetric, @@ -77,6 +85,7 @@ export const withWaffleOptions = connect( changeAutoBounds: waffleOptionsActions.changeAutoBounds, changeAccount: waffleOptionsActions.changeAccount, changeRegion: waffleOptionsActions.changeRegion, + changeCustomMetrics: waffleOptionsActions.changeCustomMetrics, }) ); @@ -96,6 +105,7 @@ interface WaffleOptionsUrlState { auto?: ReturnType; accountId?: ReturnType; region?: ReturnType; + customMetrics?: ReturnType; } export const WithWaffleOptionsUrlState = () => ( @@ -111,6 +121,7 @@ export const WithWaffleOptionsUrlState = () => ( changeBoundsOverride, changeAccount, changeRegion, + changeCustomMetrics, }) => ( urlState={urlState} @@ -144,6 +155,9 @@ export const WithWaffleOptionsUrlState = () => ( if (newUrlState && newUrlState.region) { changeRegion(newUrlState.region); } + if (newUrlState && newUrlState.customMetrics) { + changeCustomMetrics(newUrlState.customMetrics); + } }} onInitialize={initialUrlState => { if (initialUrlState && initialUrlState.metric) { @@ -173,6 +187,9 @@ export const WithWaffleOptionsUrlState = () => ( if (initialUrlState && initialUrlState.region) { changeRegion(initialUrlState.region); } + if (initialUrlState && initialUrlState.customMetrics) { + changeCustomMetrics(initialUrlState.customMetrics); + } }} /> )} @@ -191,6 +208,7 @@ const mapToUrlState = (value: any): WaffleOptionsUrlState | undefined => auto: mapToAutoBoundsUrlState(value.autoBounds), accountId: value.accountId, region: value.region, + customMetrics: mapToCustomMetricsUrlState(value.customMetrics), } : undefined; @@ -232,6 +250,12 @@ const mapToCustomOptionsUrlState = (subject: any) => { : undefined; }; +const mapToCustomMetricsUrlState = (subject: any) => { + return subject && Array.isArray(subject) && subject.every(s => SnapshotCustomMetricInputRT.is(s)) + ? subject + : []; +}; + const mapToBoundsOverideUrlState = (subject: any) => { return subject != null && isNumber(subject.max) && isNumber(subject.min) ? subject : undefined; }; diff --git a/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx b/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx index 4e8a051bf8697..421c506166d04 100644 --- a/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx +++ b/x-pack/plugins/infra/public/containers/waffle/with_waffle_view_state.tsx @@ -30,6 +30,7 @@ const selectViewState = createSelector( waffleTimeSelectors.selectCurrentTime, waffleTimeSelectors.selectIsAutoReloading, waffleFilterSelectors.selectWaffleFilterQuery, + waffleOptionsSelectors.selectCustomMetrics, ( metric, view, @@ -40,7 +41,8 @@ const selectViewState = createSelector( autoBounds, time, autoReload, - filterQuery + filterQuery, + customMetrics ) => ({ time, autoReload, @@ -52,6 +54,7 @@ const selectViewState = createSelector( boundsOverride, autoBounds, filterQuery, + customMetrics, }) ); @@ -90,6 +93,9 @@ export const withWaffleViewState = connect( if (viewState.customOptions) { dispatch(waffleOptionsActions.changeCustomOptions(viewState.customOptions)); } + if (viewState.customMetrics) { + dispatch(waffleOptionsActions.changeCustomMetrics(viewState.customMetrics)); + } if (viewState.boundsOverride) { dispatch(waffleOptionsActions.changeBoundsOverride(viewState.boundsOverride)); } @@ -130,6 +136,7 @@ export interface WaffleViewState { nodeType?: ReturnType; view?: ReturnType; customOptions?: ReturnType; + customMetrics?: ReturnType; boundsOverride?: ReturnType; autoBounds?: ReturnType; time?: ReturnType; diff --git a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx index a5780f44050e1..3606580e86504 100644 --- a/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx +++ b/x-pack/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx @@ -29,6 +29,7 @@ export const SnapshotToolbar = () => ( changeGroupBy, changeAccount, changeRegion, + changeCustomMetrics, nodeType, }) => ( ( changeGroupBy={changeGroupBy} changeAccount={changeAccount} changeRegion={changeRegion} + changeCustomMetrics={changeCustomMetrics} /> )} diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts b/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts index 4a1b45084b08a..88229c31b2056 100644 --- a/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts +++ b/x-pack/plugins/infra/public/store/local/waffle_options/actions.ts @@ -5,7 +5,11 @@ */ import actionCreatorFactory from 'typescript-fsa'; -import { SnapshotGroupBy, SnapshotMetricInput } from '../../../../common/http_api/snapshot_api'; +import { + SnapshotGroupBy, + SnapshotMetricInput, + SnapshotCustomMetricInput, +} from '../../../../common/http_api/snapshot_api'; import { InventoryItemType } from '../../../../common/inventory_models/types'; import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib'; @@ -20,3 +24,6 @@ export const changeBoundsOverride = actionCreator('CHANGE_ export const changeAutoBounds = actionCreator('CHANGE_AUTO_BOUNDS'); export const changeAccount = actionCreator('CHANGE_ACCOUNT'); export const changeRegion = actionCreator('CHANGE_REGION'); +export const changeCustomMetrics = actionCreator( + 'CHANGE_CUSTOM_METRICS' +); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts b/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts index 9d86ffe612a28..3789228a7c16b 100644 --- a/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts +++ b/x-pack/plugins/infra/public/store/local/waffle_options/reducer.ts @@ -6,7 +6,11 @@ import { combineReducers } from 'redux'; import { reducerWithInitialState } from 'typescript-fsa-reducers'; -import { SnapshotMetricInput, SnapshotGroupBy } from '../../../../common/http_api/snapshot_api'; +import { + SnapshotMetricInput, + SnapshotGroupBy, + SnapshotCustomMetricInput, +} from '../../../../common/http_api/snapshot_api'; import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib'; import { changeAutoBounds, @@ -18,6 +22,7 @@ import { changeView, changeAccount, changeRegion, + changeCustomMetrics, } from './actions'; import { InventoryItemType } from '../../../../common/inventory_models/types'; @@ -31,6 +36,7 @@ export interface WaffleOptionsState { autoBounds: boolean; accountId: string; region: string; + customMetrics: SnapshotCustomMetricInput[]; } export const initialWaffleOptionsState: WaffleOptionsState = { @@ -43,6 +49,7 @@ export const initialWaffleOptionsState: WaffleOptionsState = { autoBounds: true, accountId: '', region: '', + customMetrics: [], }; const currentMetricReducer = reducerWithInitialState(initialWaffleOptionsState.metric).case( @@ -88,6 +95,10 @@ const currentRegionReducer = reducerWithInitialState(initialWaffleOptionsState.r (current, target) => target ); +const currentCustomMetricsReducer = reducerWithInitialState( + initialWaffleOptionsState.customMetrics +).case(changeCustomMetrics, (current, target) => target); + export const waffleOptionsReducer = combineReducers({ metric: currentMetricReducer, groupBy: currentGroupByReducer, @@ -98,4 +109,5 @@ export const waffleOptionsReducer = combineReducers({ autoBounds: currentAutoBoundsReducer, accountId: currentAccountIdReducer, region: currentRegionReducer, + customMetrics: currentCustomMetricsReducer, }); diff --git a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts b/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts index 255fbd5ec4266..4487af156df97 100644 --- a/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts +++ b/x-pack/plugins/infra/public/store/local/waffle_options/selector.ts @@ -15,3 +15,4 @@ export const selectBoundsOverride = (state: WaffleOptionsState) => state.boundsO export const selectAutoBounds = (state: WaffleOptionsState) => state.autoBounds; export const selectAccountId = (state: WaffleOptionsState) => state.accountId; export const selectRegion = (state: WaffleOptionsState) => state.region; +export const selectCustomMetrics = (state: WaffleOptionsState) => state.customMetrics; diff --git a/x-pack/plugins/infra/server/lib/snapshot/query_helpers.ts b/x-pack/plugins/infra/server/lib/snapshot/query_helpers.ts index 44d32c7b915a8..383dc9a773abe 100644 --- a/x-pack/plugins/infra/server/lib/snapshot/query_helpers.ts +++ b/x-pack/plugins/infra/server/lib/snapshot/query_helpers.ts @@ -8,7 +8,16 @@ import { i18n } from '@kbn/i18n'; import { findInventoryModel, findInventoryFields } from '../../../common/inventory_models/index'; import { InfraSnapshotRequestOptions } from './types'; import { getIntervalInSeconds } from '../../utils/get_interval_in_seconds'; -import { SnapshotModelRT, SnapshotModel } from '../../../common/inventory_models/types'; +import { + SnapshotModelRT, + SnapshotModel, + InventoryItemType, +} from '../../../common/inventory_models/types'; +import { + SnapshotMetricInput, + SnapshotCustomMetricInputRT, +} from '../../../common/http_api/snapshot_api'; +import { networkTraffic } from '../../../common/inventory_models/shared/metrics/snapshot/network_traffic'; interface GroupBySource { [id: string]: { @@ -45,9 +54,25 @@ export const getMetricsSources = (options: InfraSnapshotRequestOptions) => { return [{ id: { terms: { field: fields.id } } }]; }; +export const metricToAggregation = (nodeType: InventoryItemType, metric: SnapshotMetricInput) => { + const inventoryModel = findInventoryModel(nodeType); + if (SnapshotCustomMetricInputRT.is(metric)) { + if (metric.aggregation === 'rate') { + return networkTraffic(metric.type, metric.field); + } + return { + custom: { + [metric.aggregation]: { + field: metric.field, + }, + }, + }; + } + return inventoryModel.metrics.snapshot?.[metric.type]; +}; + export const getMetricsAggregations = (options: InfraSnapshotRequestOptions): SnapshotModel => { - const inventoryModel = findInventoryModel(options.nodeType); - const aggregation = inventoryModel.metrics.snapshot?.[options.metric.type]; + const aggregation = metricToAggregation(options.nodeType, options.metric); if (!SnapshotModelRT.is(aggregation)) { throw new Error( i18n.translate('xpack.infra.snapshot.missingSnapshotMetricError', { diff --git a/x-pack/test/api_integration/apis/infra/waffle.ts b/x-pack/test/api_integration/apis/infra/waffle.ts index 80fea1cdcd295..3413fc283556c 100644 --- a/x-pack/test/api_integration/apis/infra/waffle.ts +++ b/x-pack/test/api_integration/apis/infra/waffle.ts @@ -14,12 +14,15 @@ import { InfraSnapshotGroupbyInput, } from '../../../../plugins/infra/server/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { SnapshotNodeResponse } from '../../../../plugins/infra/common/http_api/snapshot_api'; +import { + SnapshotNodeResponse, + SnapshotMetricInput, +} from '../../../../plugins/infra/common/http_api/snapshot_api'; import { DATES } from './constants'; interface SnapshotRequest { filterQuery?: string | null; - metric: InfraSnapshotMetricInput; + metric: SnapshotMetricInput; groupBy: InfraSnapshotGroupbyInput[]; nodeType: InfraNodeType; sourceId: string; @@ -197,6 +200,44 @@ export default function({ getService }: FtrProviderContext) { }); }); + it('should work with custom metrics', async () => { + const data = await fetchSnapshot({ + sourceId: 'default', + timerange: { + to: max, + from: min, + interval: '1m', + }, + metric: { + type: 'custom', + field: 'system.cpu.user.pct', + aggregation: 'avg', + id: '1', + } as SnapshotMetricInput, + nodeType: 'host' as InfraNodeType, + groupBy: [], + }); + + const snapshot = data; + expect(snapshot).to.have.property('nodes'); + if (snapshot) { + const { nodes } = snapshot; + expect(nodes.length).to.equal(1); + const firstNode = first(nodes); + expect(firstNode).to.have.property('path'); + expect(firstNode.path.length).to.equal(1); + expect(first(firstNode.path)).to.have.property('value', 'demo-stack-mysql-01'); + expect(first(firstNode.path)).to.have.property('label', 'demo-stack-mysql-01'); + expect(firstNode).to.have.property('metric'); + expect(firstNode.metric).to.eql({ + name: 'custom', + value: 0.0041964285714285714, + max: 0.0041964285714285714, + avg: 0.0006994047619047619, + }); + } + }); + it('should basically work with 1 grouping', () => { const resp = fetchSnapshot({ sourceId: 'default', From e97b451cc186e47a4621b85cf2bd814a8529c0b6 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Wed, 26 Feb 2020 16:42:15 +0000 Subject: [PATCH 12/22] [SIEM] add custom reputation link (#57814) * add custom reputation link * fix unit test * add number of limitation to reputation links * fix dependency * apply defaultFieldRendererOverflow to reputation link * fix unit test * fix url template * fix display links * fix types * fix for review * update test case * update snapshot * add icons and tooltips * fix style * update test * add external link component * update test * fix types * fix style * update snapshot * remove useMemo * update description * code review * review II * code review * update description * fix unit test * fix unit test * fix unit test * fix unit test * fix types * fix styles * fix style * fix style * fix review Co-authored-by: Elastic Machine --- .../legacy/plugins/siem/common/constants.ts | 9 + x-pack/legacy/plugins/siem/index.ts | 15 + .../field_renderers.test.tsx.snap | 20 +- .../field_renderers/field_renderers.tsx | 111 +- .../public/components/links/index.test.tsx | 365 +- .../siem/public/components/links/index.tsx | 203 +- .../__snapshots__/zeek_details.test.tsx.snap | 3261 +++++++++++++++++ .../body/renderers/zeek/zeek_details.test.tsx | 5 +- .../renderers/zeek/zeek_signature.test.tsx | 11 +- .../body/renderers/zeek/zeek_signature.tsx | 10 +- .../step_about_rule/helpers.test.ts | 18 + 11 files changed, 3876 insertions(+), 152 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.test.ts diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 0d9e092e21d82..2a30293c244af 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -38,6 +38,15 @@ export const NEWS_FEED_URL_SETTING = 'siem:newsFeedUrl'; /** The default value for News feed widget */ export const NEWS_FEED_URL_SETTING_DEFAULT = 'https://feeds.elastic.co/security-solution'; +/** This Kibana Advanced Setting specifies the URLs of `IP Reputation Links`*/ +export const IP_REPUTATION_LINKS_SETTING = 'siem:ipReputationLinks'; + +/** The default value for `IP Reputation Links` */ +export const IP_REPUTATION_LINKS_SETTING_DEFAULT = `[ + { "name": "virustotal.com", "url_template": "https://www.virustotal.com/gui/search/{{ip}}" }, + { "name": "talosIntelligence.com", "url_template": "https://talosintelligence.com/reputation_center/lookup?search={{ip}}" } +]`; + /** * Id for the signals alerting type */ diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index 731ef10aa225e..db398821aecfd 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -28,6 +28,8 @@ import { NEWS_FEED_URL_SETTING, NEWS_FEED_URL_SETTING_DEFAULT, SIGNALS_INDEX_KEY, + IP_REPUTATION_LINKS_SETTING, + IP_REPUTATION_LINKS_SETTING_DEFAULT, } from './common/constants'; import { defaultIndexPattern } from './default_index_pattern'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; @@ -144,6 +146,19 @@ export const siem = (kibana: any) => { category: ['siem'], requiresPageReload: true, }, + [IP_REPUTATION_LINKS_SETTING]: { + name: i18n.translate('xpack.siem.uiSettings.ipReputationLinks', { + defaultMessage: 'IP Reputation Links', + }), + value: IP_REPUTATION_LINKS_SETTING_DEFAULT, + type: 'json', + description: i18n.translate('xpack.siem.uiSettings.ipReputationLinksDescription', { + defaultMessage: + 'Array of URL templates to build the list of reputation URLs to be displayed on the IP Details page.', + }), + category: ['siem'], + requiresPageReload: true, + }, }, mappings: savedObjectMappings, }, diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap index 2ff93b2ecada4..59010ab80af63 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/__snapshots__/field_renderers.test.tsx.snap @@ -122,25 +122,17 @@ exports[`Field Renderers #reputationRenderer it renders correctly against snapsh - - virustotal.com - - , - - talosIntelligence.com - + /> `; exports[`Field Renderers #whoisRenderer it renders correctly against snapshot 1`] = ` - iana.org - + `; diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx index 80d68dfe1b731..222eef515958c 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.tsx @@ -15,7 +15,7 @@ import { escapeDataProviderId } from '../drag_and_drop/helpers'; import { DefaultDraggable } from '../draggables'; import { getEmptyTagValue } from '../empty_value'; import { FormattedRelativePreferenceDate } from '../formatted_date'; -import { HostDetailsLink, ReputationLink, VirusTotalLink, WhoIsLink } from '../links'; +import { HostDetailsLink, ReputationLink, WhoIsLink, ReputationLinkSetting } from '../links'; import { Spacer } from '../page'; import * as i18n from '../page/network/ip_overview/translations'; @@ -132,11 +132,7 @@ export const hostNameRenderer = (host: HostEcsFields, ipFilter?: string): React. export const whoisRenderer = (ip: string) => {i18n.VIEW_WHOIS}; export const reputationRenderer = (ip: string): React.ReactElement => ( - <> - {i18n.VIEW_VIRUS_TOTAL} - {', '} - {i18n.VIEW_TALOS_INTELLIGENCE} - + ); interface DefaultFieldRendererProps { @@ -148,73 +144,78 @@ interface DefaultFieldRendererProps { moreMaxHeight?: string; } +type OverflowRenderer = (item: string | ReputationLinkSetting) => JSX.Element; + // TODO: This causes breaks between elements until the ticket below is fixed // https://github.com/elastic/ingest-dev/issues/474 -export const DefaultFieldRenderer = React.memo( - ({ - attrName, - displayCount = 1, - idPrefix, - moreMaxHeight = DEFAULT_MORE_MAX_HEIGHT, - render, - rowItems, - }) => { - if (rowItems != null && rowItems.length > 0) { - const draggables = rowItems.slice(0, displayCount).map((rowItem, index) => { - const id = escapeDataProviderId( - `default-field-renderer-default-draggable-${idPrefix}-${attrName}-${rowItem}` - ); - return ( - - {index !== 0 && ( - <> - {','} - - - )} +export const DefaultFieldRendererComponent: React.FC = ({ + attrName, + displayCount = 1, + idPrefix, + moreMaxHeight = DEFAULT_MORE_MAX_HEIGHT, + render, + rowItems, +}) => { + if (rowItems != null && rowItems.length > 0) { + const draggables = rowItems.slice(0, displayCount).map((rowItem, index) => { + const id = escapeDataProviderId( + `default-field-renderer-default-draggable-${idPrefix}-${attrName}-${rowItem}` + ); + return ( + + {index !== 0 && ( + <> + {','} + + + )} + {typeof rowItem === 'string' && ( {render ? render(rowItem) : rowItem} - - ); - }); - - return draggables.length > 0 ? ( - - {draggables}{' '} - { - - } - - ) : ( - getEmptyTagValue() + )} + ); - } else { - return getEmptyTagValue(); - } + }); + + return draggables.length > 0 ? ( + + {draggables} + + + + + ) : ( + getEmptyTagValue() + ); + } else { + return getEmptyTagValue(); } -); +}; + +export const DefaultFieldRenderer = React.memo(DefaultFieldRendererComponent); DefaultFieldRenderer.displayName = 'DefaultFieldRenderer'; +type RowItemTypes = string | ReputationLinkSetting; interface DefaultFieldRendererOverflowProps { - rowItems: string[]; + rowItems: string[] | ReputationLinkSetting[]; idPrefix: string; - render?: (item: string) => React.ReactNode; + render?: (item: RowItemTypes) => React.ReactNode; overflowIndexStart?: number; moreMaxHeight: string; } interface MoreContainerProps { idPrefix: string; - render?: (item: string) => React.ReactNode; - rowItems: string[]; + render?: (item: RowItemTypes) => React.ReactNode; + rowItems: RowItemTypes[]; moreMaxHeight: string; overflowIndexStart: number; } diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx index ceef7e353b521..d2d1d6569854d 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx @@ -4,24 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mount } from 'enzyme'; +import { mount, shallow, ShallowWrapper } from 'enzyme'; import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { encodeIpv6 } from '../../lib/helpers'; +import { useUiSetting$ } from '../../lib/kibana'; import { GoogleLink, HostDetailsLink, IPDetailsLink, ReputationLink, - VirusTotalLink, WhoIsLink, CertificateFingerprintLink, Ja3FingerprintLink, PortOrServiceNameLink, + DEFAULT_NUMBER_OF_LINK, + ExternalLink, } from '.'; +jest.mock('../../lib/kibana', () => { + return { + useUiSetting$: jest.fn(), + }; +}); + describe('Custom Links', () => { const hostName = 'Host Name'; const ipv4 = '192.0.2.255'; @@ -101,53 +109,332 @@ describe('Custom Links', () => { }); }); - describe('ReputationLink', () => { - test('it renders link text', () => { - const wrapper = mountWithIntl( - {'Example Link'} - ); - expect(wrapper.text()).toEqual('Example Link'); - }); + describe('External Link', () => { + const mockLink = 'https://www.virustotal.com/gui/search/'; + const mockLinkName = 'Link'; + let wrapper: ShallowWrapper; - test('it renders correct href', () => { - const wrapper = mountWithIntl( - {'Example Link'} - ); - expect(wrapper.find('a').prop('href')).toEqual( - 'https://www.talosintelligence.com/reputation_center/lookup?search=192.0.2.0' - ); + describe('render', () => { + beforeAll(() => { + wrapper = shallow( + + {mockLinkName} + + ); + }); + + test('it renders tooltip', () => { + expect(wrapper.find('[data-test-subj="externalLinkTooltip"]').exists()).toBeTruthy(); + }); + + test('it renders ExternalLinkIcon', () => { + expect(wrapper.find('[data-test-subj="externalLinkIcon"]').exists()).toBeTruthy(); + }); + + test('it renders correct url', () => { + expect(wrapper.find('[data-test-subj="externalLink"]').prop('href')).toEqual(mockLink); + }); + + test('it renders comma if id is given', () => { + expect(wrapper.find('[data-test-subj="externalLinkComma"]').exists()).toBeTruthy(); + }); }); - test("it encodes ", () => { - const wrapper = mountWithIntl( - alert('XSS')"}>{'Example Link'} - ); - expect(wrapper.find('a').prop('href')).toEqual( - "https://www.talosintelligence.com/reputation_center/lookup?search=%3Cscript%3Ealert('XSS')%3C%2Fscript%3E" - ); + describe('not render', () => { + test('it should not render if childen prop is not given', () => { + wrapper = shallow( + + ); + expect(wrapper.find('[data-test-subj="externalLinkTooltip"]').exists()).toBeFalsy(); + }); + + test('it should not render if url prop is not given', () => { + wrapper = shallow( + + ); + expect(wrapper.find('[data-test-subj="externalLinkTooltip"]').exists()).toBeFalsy(); + }); + + test('it should not render if url prop is invalid', () => { + wrapper = shallow( + + ); + expect(wrapper.find('[data-test-subj="externalLinkTooltip"]').exists()).toBeFalsy(); + }); + + test('it should not render comma if id is not given', () => { + wrapper = shallow( + + {mockLinkName} + + ); + expect(wrapper.find('[data-test-subj="externalLinkComma"]').exists()).toBeFalsy(); + }); + + test('it should not render comma for the last item', () => { + wrapper = shallow( + + {mockLinkName} + + ); + expect(wrapper.find('[data-test-subj="externalLinkComma"]').exists()).toBeFalsy(); + }); }); + + describe.each<[number, number, number, boolean]>([ + [0, 2, 5, true], + [1, 2, 5, false], + [2, 2, 5, false], + [3, 2, 5, false], + [4, 2, 5, false], + [5, 2, 5, false], + ])( + 'renders Comma when overflowIndex is smaller than allItems limit', + (idx, overflowIndexStart, allItemsLimit, showComma) => { + beforeAll(() => { + wrapper = shallow( + + {mockLinkName} + + ); + }); + + test(`should render Comma if current id (${idx}) is smaller than the index of last visible item`, () => { + expect(wrapper.find('[data-test-subj="externalLinkComma"]').exists()).toEqual(showComma); + }); + } + ); + + describe.each<[number, number, number, boolean]>([ + [0, 5, 4, true], + [1, 5, 4, true], + [2, 5, 4, true], + [3, 5, 4, false], + [4, 5, 4, false], + [5, 5, 4, false], + ])( + 'When overflowIndex is grater than allItems limit', + (idx, overflowIndexStart, allItemsLimit, showComma) => { + beforeAll(() => { + wrapper = shallow( + + {mockLinkName} + + ); + }); + + test(`Current item (${idx}) should render Comma execpt the last item`, () => { + expect(wrapper.find('[data-test-subj="externalLinkComma"]').exists()).toEqual(showComma); + }); + } + ); + + describe.each<[number, number, number, boolean]>([ + [0, 5, 5, true], + [1, 5, 5, true], + [2, 5, 5, true], + [3, 5, 5, true], + [4, 5, 5, false], + [5, 5, 5, false], + ])( + 'when overflowIndex equals to allItems limit', + (idx, overflowIndexStart, allItemsLimit, showComma) => { + beforeAll(() => { + wrapper = shallow( + + {mockLinkName} + + ); + }); + + test(`Current item (${idx}) should render Comma correctly`, () => { + expect(wrapper.find('[data-test-subj="externalLinkComma"]').exists()).toEqual(showComma); + }); + } + ); }); - describe('VirusTotalLink', () => { - test('it renders sha passed in as value', () => { - const wrapper = mountWithIntl({'Example Link'}); - expect(wrapper.text()).toEqual('Example Link'); + describe('ReputationLink', () => { + const mockCustomizedReputationLinks = [ + { name: 'Link 1', url_template: 'https://www.virustotal.com/gui/search/{{ip}}' }, + { + name: 'Link 2', + url_template: 'https://talosintelligence.com/reputation_center/lookup?search={{ip}}', + }, + { name: 'Link 3', url_template: 'https://www.virustotal.com/gui/search/{{ip}}' }, + { + name: 'Link 4', + url_template: 'https://talosintelligence.com/reputation_center/lookup?search={{ip}}', + }, + { name: 'Link 5', url_template: 'https://www.virustotal.com/gui/search/{{ip}}' }, + { + name: 'Link 6', + url_template: 'https://talosintelligence.com/reputation_center/lookup?search={{ip}}', + }, + ]; + const mockDefaultReputationLinks = mockCustomizedReputationLinks.slice(0, 2); + + describe('links property', () => { + beforeEach(() => { + (useUiSetting$ as jest.Mock).mockReset(); + (useUiSetting$ as jest.Mock).mockReturnValue([mockDefaultReputationLinks]); + }); + + test('it renders default link text', () => { + const wrapper = shallow(); + wrapper.find('[data-test-subj="externalLink"]').forEach((node, idx) => { + expect(node.at(idx).text()).toEqual(mockDefaultReputationLinks[idx].name); + }); + }); + + test('it renders customized link text', () => { + (useUiSetting$ as jest.Mock).mockReset(); + (useUiSetting$ as jest.Mock).mockReturnValue([mockCustomizedReputationLinks]); + const wrapper = shallow(); + wrapper.find('[data-test-subj="externalLink"]').forEach((node, idx) => { + expect(node.at(idx).text()).toEqual(mockCustomizedReputationLinks[idx].name); + }); + }); + + test('it renders correct href', () => { + const wrapper = shallow(); + wrapper.find('[data-test-subj="externalLink"]').forEach((node, idx) => { + expect(node.prop('href')).toEqual( + mockDefaultReputationLinks[idx].url_template.replace('{{ip}}', '192.0.2.0') + ); + }); + }); }); - test('it renders sha passed in as link', () => { - const wrapper = mountWithIntl( - {'Example Link'} - ); - expect(wrapper.find('a').prop('href')).toEqual('https://www.virustotal.com/#/search/abc'); + describe('number of links', () => { + beforeAll(() => { + (useUiSetting$ as jest.Mock).mockReset(); + (useUiSetting$ as jest.Mock).mockReturnValue([mockCustomizedReputationLinks]); + }); + + afterEach(() => { + (useUiSetting$ as jest.Mock).mockClear(); + }); + + test('it renders correct number of links by default', () => { + const wrapper = mountWithIntl(); + expect(wrapper.find('[data-test-subj="externalLinkComponent"]')).toHaveLength( + DEFAULT_NUMBER_OF_LINK + ); + }); + + test('it renders correct number of tooltips by default', () => { + const wrapper = mountWithIntl(); + expect(wrapper.find('[data-test-subj="externalLinkTooltip"]')).toHaveLength( + DEFAULT_NUMBER_OF_LINK + ); + }); + + test('it renders correct number of visible link', () => { + (useUiSetting$ as jest.Mock).mockReset(); + (useUiSetting$ as jest.Mock).mockReturnValue([mockCustomizedReputationLinks]); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLinkComponent"]')).toHaveLength(1); + }); + + test('it renders correct number of tooltips for visible links', () => { + (useUiSetting$ as jest.Mock).mockReset(); + (useUiSetting$ as jest.Mock).mockReturnValue([mockCustomizedReputationLinks]); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLinkTooltip"]')).toHaveLength(1); + }); }); - test("it encodes ", () => { - const wrapper = mountWithIntl( - alert('XSS')"}>{'Example Link'} - ); - expect(wrapper.find('a').prop('href')).toEqual( - "https://www.virustotal.com/#/search/%3Cscript%3Ealert('XSS')%3C%2Fscript%3E" - ); + describe('invalid customized links', () => { + const mockInvalidLinksEmptyObj = [{}]; + const mockInvalidLinksNoName = [ + { url_template: 'https://talosintelligence.com/reputation_center/lookup?search={{ip}}' }, + ]; + const mockInvalidLinksNoUrl = [{ name: 'Link 1' }]; + const mockInvalidUrl = [{ name: 'Link 1', url_template: "" }]; + afterEach(() => { + (useUiSetting$ as jest.Mock).mockReset(); + }); + + test('it filters empty object', () => { + (useUiSetting$ as jest.Mock).mockReturnValue([mockInvalidLinksEmptyObj]); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLink"]')).toHaveLength(0); + }); + + test('it filters object without name property', () => { + (useUiSetting$ as jest.Mock).mockReturnValue([mockInvalidLinksNoName]); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLink"]')).toHaveLength(0); + }); + + test('it filters object without url_template property', () => { + (useUiSetting$ as jest.Mock).mockReturnValue([mockInvalidLinksNoUrl]); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLink"]')).toHaveLength(0); + }); + + test('it filters object with invalid url', () => { + (useUiSetting$ as jest.Mock).mockReturnValue([mockInvalidUrl]); + + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLink"]')).toHaveLength(0); + }); + }); + + describe('external icon', () => { + beforeAll(() => { + (useUiSetting$ as jest.Mock).mockReset(); + (useUiSetting$ as jest.Mock).mockReturnValue([mockCustomizedReputationLinks]); + }); + + afterEach(() => { + (useUiSetting$ as jest.Mock).mockClear(); + }); + + test('it renders correct number of external icons by default', () => { + const wrapper = mountWithIntl(); + expect(wrapper.find('[data-test-subj="externalLinkIcon"]')).toHaveLength(5); + }); + + test('it renders correct number of external icons', () => { + const wrapper = mountWithIntl( + + ); + expect(wrapper.find('[data-test-subj="externalLinkIcon"]')).toHaveLength(1); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.tsx index b6548e3e950ba..04de0b1d5d3bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.tsx @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink } from '@elastic/eui'; -import React from 'react'; +import { EuiLink, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { isNil } from 'lodash/fp'; +import styled from 'styled-components'; +import { + DefaultFieldRendererOverflow, + DEFAULT_MORE_MAX_HEIGHT, +} from '../field_renderers/field_renderers'; import { encodeIpv6 } from '../../lib/helpers'; import { getCaseDetailsUrl, @@ -15,6 +21,13 @@ import { getCreateCaseUrl, } from '../link_to'; import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types'; +import { useUiSetting$ } from '../../lib/kibana'; +import { IP_REPUTATION_LINKS_SETTING } from '../../../common/constants'; +import * as i18n from '../page/network/ip_overview/translations'; +import { isUrlInvalid } from '../../pages/detection_engine/rules/components/step_about_rule/helpers'; +import { ExternalLinkIcon } from '../external_link_icon'; + +export const DEFAULT_NUMBER_OF_LINK = 5; // Internal Links const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; hostName: string }> = ({ @@ -26,6 +39,39 @@ const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; hostName: ); +const whitelistUrlSchemes = ['http://', 'https://']; +export const ExternalLink = React.memo<{ + url: string; + children?: React.ReactNode; + idx?: number; + overflowIndexStart?: number; + allItemsLimit?: number; +}>( + ({ + url, + children, + idx, + overflowIndexStart = DEFAULT_NUMBER_OF_LINK, + allItemsLimit = DEFAULT_NUMBER_OF_LINK, + }) => { + const lastVisibleItemIndex = overflowIndexStart - 1; + const lastItemIndex = allItemsLimit - 1; + const lastIndexToShow = Math.max(0, Math.min(lastVisibleItemIndex, lastItemIndex)); + const inWhitelist = whitelistUrlSchemes.some(scheme => url.indexOf(scheme) === 0); + return url && inWhitelist && !isUrlInvalid(url) && children ? ( + + + {children} + + {!isNil(idx) && idx < lastIndexToShow && } + + + ) : null; + } +); + +ExternalLink.displayName = 'ExternalLink'; + export const HostDetailsLink = React.memo(HostDetailsLinkComponent); const IPDetailsLinkComponent: React.FC<{ @@ -63,9 +109,9 @@ CreateCaseLink.displayName = 'CreateCaseLink'; // External Links export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>( ({ children, link }) => ( - + {children ? children : link} - + ) ); @@ -120,39 +166,136 @@ export const CertificateFingerprintLink = React.memo<{ CertificateFingerprintLink.displayName = 'CertificateFingerprintLink'; -export const ReputationLink = React.memo<{ children?: React.ReactNode; domain: string }>( - ({ children, domain }) => ( - - {children ? children : domain} - - ) -); +enum DefaultReputationLink { + 'virustotal.com' = 'virustotal.com', + 'talosIntelligence.com' = 'talosIntelligence.com', +} -ReputationLink.displayName = 'ReputationLink'; +export interface ReputationLinkSetting { + name: string; + url_template: string; +} -export const VirusTotalLink = React.memo<{ children?: React.ReactNode; link: string }>( - ({ children, link }) => ( - - {children ? children : link} - - ) -); +function isDefaultReputationLink(name: string): name is DefaultReputationLink { + return ( + name === DefaultReputationLink['virustotal.com'] || + name === DefaultReputationLink['talosIntelligence.com'] + ); +} +const isReputationLink = ( + rowItem: string | ReputationLinkSetting +): rowItem is ReputationLinkSetting => + (rowItem as ReputationLinkSetting).url_template !== undefined && + (rowItem as ReputationLinkSetting).name !== undefined; + +export const Comma = styled('span')` + margin-right: 5px; + margin-left: 5px; + &::after { + content: ' ,'; + } +`; + +Comma.displayName = 'Comma'; + +const defaultNameMapping: Record = { + [DefaultReputationLink['virustotal.com']]: i18n.VIEW_VIRUS_TOTAL, + [DefaultReputationLink['talosIntelligence.com']]: i18n.VIEW_TALOS_INTELLIGENCE, +}; + +const ReputationLinkComponent: React.FC<{ + overflowIndexStart?: number; + allItemsLimit?: number; + showDomain?: boolean; + domain: string; + direction?: 'row' | 'column'; +}> = ({ + overflowIndexStart = DEFAULT_NUMBER_OF_LINK, + allItemsLimit = DEFAULT_NUMBER_OF_LINK, + showDomain = false, + domain, + direction = 'row', +}) => { + const [ipReputationLinksSetting] = useUiSetting$( + IP_REPUTATION_LINKS_SETTING + ); + + const ipReputationLinks: ReputationLinkSetting[] = useMemo( + () => + ipReputationLinksSetting + ?.slice(0, allItemsLimit) + .filter( + ({ url_template, name }) => + !isNil(url_template) && !isNil(name) && !isUrlInvalid(url_template) + ) + .map(({ name, url_template }: { name: string; url_template: string }) => ({ + name: isDefaultReputationLink(name) ? defaultNameMapping[name] : name, + url_template: url_template.replace(`{{ip}}`, encodeURIComponent(domain)), + })), + [ipReputationLinksSetting, domain, defaultNameMapping, allItemsLimit] + ); + + return ipReputationLinks?.length > 0 ? ( +
+ + + {ipReputationLinks + ?.slice(0, overflowIndexStart) + .map(({ name, url_template: urlTemplate }: ReputationLinkSetting, id) => ( + + <>{showDomain ? domain : name ?? domain} + + ))} + + + + { + return ( + isReputationLink(rowItem) && ( + + <>{rowItem.name ?? domain} + + ) + ); + }} + moreMaxHeight={DEFAULT_MORE_MAX_HEIGHT} + overflowIndexStart={overflowIndexStart} + /> + + +
+ ) : null; +}; + +ReputationLinkComponent.displayName = 'ReputationLinkComponent'; -VirusTotalLink.displayName = 'VirusTotalLink'; +export const ReputationLink = React.memo(ReputationLinkComponent); export const WhoIsLink = React.memo<{ children?: React.ReactNode; domain: string }>( ({ children, domain }) => ( - + {children ? children : domain} - +
) ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap index 0a60c8facff9c..6b866aeecc831 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/__snapshots__/zeek_details.test.tsx.snap @@ -498,3 +498,3264 @@ exports[`ZeekDetails rendering it renders the default ZeekDetails 1`] = ` timelineId="test" /> `; + +exports[`ZeekDetails rendering it returns zeek.files if the data does contain zeek.files data 1`] = ` +.c3, +.c3::before, +.c3::after { + -webkit-transition: background 150ms ease, color 150ms ease; + transition: background 150ms ease, color 150ms ease; +} + +.c3 { + border-radius: 2px; + padding: 0 4px 0 8px; + position: relative; + z-index: 0 !important; +} + +.c3::before { + background-image: linear-gradient( 135deg, #535966 25%, transparent 25% ), linear-gradient( -135deg, #535966 25%, transparent 25% ), linear-gradient( 135deg, transparent 75%, #535966 75% ), linear-gradient( -135deg, transparent 75%, #535966 75% ); + background-position: 0 0,1px 0,1px -1px,0px 1px; + background-size: 2px 2px; + bottom: 2px; + content: ''; + display: block; + left: 2px; + position: absolute; + top: 2px; + width: 4px; +} + +.c3:hover, +.c3:hover .euiBadge, +.c3:hover .euiBadge__text { + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; +} + +.event-column-view:hover .c3, +tr:hover .c3 { + background-color: #343741; +} + +.event-column-view:hover .c3::before, +tr:hover .c3::before { + background-image: linear-gradient( 135deg, #98a2b3 25%, transparent 25% ), linear-gradient( -135deg, #98a2b3 25%, transparent 25% ), linear-gradient( 135deg, transparent 75%, #98a2b3 75% ), linear-gradient( -135deg, transparent 75%, #98a2b3 75% ); +} + +.c3:hover, +.c3:focus, +.event-column-view:hover .c3:hover, +.event-column-view:focus .c3:focus, +tr:hover .c3:hover, +tr:hover .c3:focus { + background-color: #1ba9f5; +} + +.c3:hover, +.c3:focus, +.event-column-view:hover .c3:hover, +.event-column-view:focus .c3:focus, +tr:hover .c3:hover, +tr:hover .c3:focus, +.c3:hover a, +.c3:focus a, +.event-column-view:hover .c3:hover a, +.event-column-view:focus .c3:focus a, +tr:hover .c3:hover a, +tr:hover .c3:focus a, +.c3:hover a:hover, +.c3:focus a:hover, +.event-column-view:hover .c3:hover a:hover, +.event-column-view:focus .c3:focus a:hover, +tr:hover .c3:hover a:hover, +tr:hover .c3:focus a:hover { + color: #1d1e24; +} + +.c3:hover::before, +.c3:focus::before, +.event-column-view:hover .c3:hover::before, +.event-column-view:focus .c3:focus::before, +tr:hover .c3:hover::before, +tr:hover .c3:focus::before { + background-image: linear-gradient( 135deg, #1d1e24 25%, transparent 25% ), linear-gradient( -135deg, #1d1e24 25%, transparent 25% ), linear-gradient( 135deg, transparent 75%, #1d1e24 75% ), linear-gradient( -135deg, transparent 75%, #1d1e24 75% ); +} + +.c2 { + display: inline-block; + max-width: 100%; +} + +.c2 [data-rbd-placeholder-context-id] { + display: none !important; +} + +.c4 > span.euiToolTipAnchor { + display: block; +} + +.c8 { + margin: 0 2px; +} + +.c7 { + margin-top: 3px; +} + +.c6 { + margin-right: 10px; +} + +.c1 { + margin-left: 3px; +} + +.c5 { + margin-left: 6px; +} + +.c0 { + margin: 5px 0; +} + + + + + + + + + + + + + + + +
+
+ + +
+ + + +
+ + + +
+ + +
+ + + + + + +
+ + + + + + + + + + + Cu0n232QMyvNtzb75j + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + +
+ + + +
+ + +
+ + + + + + +
+ + + + + + + + + + + files + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + +
+ + + +
+ + +
+ + + + + + +
+ + + + + + + + + + + sha1: fa5195a... + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + +
+ + + +
+ + +
+ + + + + + +
+ + + + + + + + + + + md5: f7653f1... + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + +
+
+ +
+ + + + + + + + +
+
+ +
+
+
+
+
+
+
+
+ +
+ + + + +
+ +
+ + +
+ + +
+ + +
+ + +
+ + + + +
+ + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ + +
+ + + +
+ + +
+ +
+ +
+
+ + + +
+ + + + +
+ +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ +
+
+ +
+ + +
+ + +
+ +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx index 7617a01acf1d9..db51ade6df4c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx @@ -113,9 +113,8 @@ describe('ZeekDetails', () => { /> ); - expect(wrapper.text()).toEqual( - 'Cu0n232QMyvNtzb75jfilessha1: fa5195a...md5: f7653f1...fa5195a5dfacc9d1c68d43600f0e0262cad14dde' - ); + expect(wrapper).toMatchSnapshot(); + expect(wrapper.text()).toEqual('Cu0n232QMyvNtzb75jfilessha1: fa5195a...md5: f7653f1...'); }); test('it returns null for text if the data contains no zeek data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx index c09bd6b7a356d..f199b537f1be0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx @@ -79,14 +79,9 @@ describe('ZeekSignature', () => { ).toBeFalsy(); }); - test('should render value', () => { - const wrapper = mount(); - expect(wrapper.text()).toEqual('abc'); - }); - - test('should render link with sha', () => { - const wrapper = mount(); - expect(wrapper.find('a').prop('href')).toEqual('https://www.virustotal.com/#/search/abcdefg'); + test('should render', () => { + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="reputationLinkSha"]').exists()).toBeTruthy(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx index 72f58df5677e4..57e5ff19eb815 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -13,7 +13,7 @@ import { Ecs } from '../../../../../graphql/types'; import { DragEffects, DraggableWrapper } from '../../../../drag_and_drop/draggable_wrapper'; import { escapeDataProviderId } from '../../../../drag_and_drop/helpers'; import { ExternalLinkIcon } from '../../../../external_link_icon'; -import { GoogleLink, VirusTotalLink } from '../../../../links'; +import { GoogleLink, ReputationLink } from '../../../../links'; import { Provider } from '../../../../timeline/data_providers/provider'; import { IS_OPERATOR } from '../../../data_providers/data_provider'; @@ -148,8 +148,12 @@ export const TotalVirusLinkSha = React.memo(({ value }) value != null ? (
- {value} - +
) : null diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.test.ts new file mode 100644 index 0000000000000..5eb503f8ba074 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.test.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; + * you may not use this file except in compliance with the Elastic License. + */ +import { isUrlInvalid } from './helpers'; + +describe('helpers', () => { + describe('isUrlInvalid', () => { + test('verifies invalid url', () => { + expect(isUrlInvalid('this is not a url')).toBeTruthy(); + }); + + test('verifies valid url', () => { + expect(isUrlInvalid('https://www.elastic.co/')).toBeFalsy(); + }); + }); +}); From 0764380ffd50b9f07d8e48bbc2ce90d4c0fabb05 Mon Sep 17 00:00:00 2001 From: Chris Cowan Date: Wed, 26 Feb 2020 09:51:47 -0700 Subject: [PATCH 13/22] [Metrics UI / Logs UI] Remove field filtering in Source API call (#58553) * [Metrics UI / Logs UI] Remove field filtering in Source API call. * Fixing type_check issues --- .../lib/adapters/fields/adapter_types.ts | 3 +- .../fields/framework_fields_adapter.ts | 99 +------------------ .../infra/server/lib/domains/fields_domain.ts | 3 +- 3 files changed, 4 insertions(+), 101 deletions(-) diff --git a/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts b/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts index 3aaa23b378096..a1630281c2f75 100644 --- a/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts +++ b/x-pack/plugins/infra/server/lib/adapters/fields/adapter_types.ts @@ -9,8 +9,7 @@ import { RequestHandlerContext } from 'src/core/server'; export interface FieldsAdapter { getIndexFields( requestContext: RequestHandlerContext, - indices: string, - timefield: string + indices: string ): Promise; } diff --git a/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts index 834c991d5c6a4..8119c06dedaef 100644 --- a/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/fields/framework_fields_adapter.ts @@ -4,28 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { startsWith, uniq, first } from 'lodash'; import { RequestHandlerContext } from 'src/core/server'; -import { InfraDatabaseSearchResponse } from '../framework'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { FieldsAdapter, IndexFieldDescriptor } from './adapter_types'; -import { getAllowedListForPrefix } from '../../../../common/ecs_allowed_list'; -import { getAllCompositeData } from '../../../utils/get_all_composite_data'; -import { createAfterKeyHandler } from '../../../utils/create_afterkey_handler'; - -interface Bucket { - key: { dataset: string }; - doc_count: number; -} - -interface DataSetResponse { - datasets: { - buckets: Bucket[]; - after_key: { - dataset: string; - }; - }; -} export class FrameworkFieldsAdapter implements FieldsAdapter { private framework: KibanaFramework; @@ -36,91 +17,15 @@ export class FrameworkFieldsAdapter implements FieldsAdapter { public async getIndexFields( requestContext: RequestHandlerContext, - indices: string, - timefield: string + indices: string ): Promise { const indexPatternsService = this.framework.getIndexPatternsService(requestContext); const response = await indexPatternsService.getFieldsForWildcard({ pattern: indices, }); - const { dataSets, modules } = await this.getDataSetsAndModules( - requestContext, - indices, - timefield - ); - const allowedList = modules.reduce( - (acc, name) => uniq([...acc, ...getAllowedListForPrefix(name)]), - [] as string[] - ); - const dataSetsWithAllowedList = [...allowedList, ...dataSets]; return response.map(field => ({ ...field, - displayable: dataSetsWithAllowedList.some(name => startsWith(field.name, name)), + displayable: true, })); } - - private async getDataSetsAndModules( - requestContext: RequestHandlerContext, - indices: string, - timefield: string - ): Promise<{ dataSets: string[]; modules: string[] }> { - const params = { - index: indices, - allowNoIndices: true, - ignoreUnavailable: true, - body: { - size: 0, - query: { - bool: { - filter: [ - { - range: { - [timefield]: { - gte: 'now-24h', - lte: 'now', - }, - }, - }, - ], - }, - }, - aggs: { - datasets: { - composite: { - sources: [ - { - dataset: { - terms: { - field: 'event.dataset', - }, - }, - }, - ], - }, - }, - }, - }, - }; - - const bucketSelector = (response: InfraDatabaseSearchResponse<{}, DataSetResponse>) => - (response.aggregations && response.aggregations.datasets.buckets) || []; - const handleAfterKey = createAfterKeyHandler( - 'body.aggs.datasets.composite.after', - input => input?.aggregations?.datasets?.after_key - ); - - const buckets = await getAllCompositeData( - this.framework, - requestContext, - params, - bucketSelector, - handleAfterKey - ); - const dataSets = buckets.map(bucket => bucket.key.dataset); - const modules = dataSets.reduce((acc, dataset) => { - const module = first(dataset.split(/\./)); - return module ? uniq([...acc, module]) : acc; - }, [] as string[]); - return { modules, dataSets }; - } } diff --git a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts index a00c76216da4c..d2e151ca2c3f5 100644 --- a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts @@ -31,8 +31,7 @@ export class InfraFieldsDomain { requestContext, `${includeMetricIndices ? configuration.metricAlias : ''},${ includeLogIndices ? configuration.logAlias : '' - }`, - configuration.fields.timestamp + }` ); return fields; From 55fb05c09f34f371478fd9a951ddb48f07c7a458 Mon Sep 17 00:00:00 2001 From: Maryia Lapata Date: Wed, 26 Feb 2020 20:12:23 +0300 Subject: [PATCH 14/22] [NP] Move ui/kbn_top_nav/kbn_top_nav to kibana_legacy (#58221) * Migrate kbn_top_nav.js to kibana_legacy * Wrap TopNavMenu into i18nContext * Move the kbnTopNav directive definition to kibana_legacy and remove ui/kbn_top_nav Co-authored-by: Elastic Machine --- src/core/MIGRATION.md | 2 +- .../kibana/public/dashboard/legacy_imports.ts | 2 -- .../public/dashboard/np_ready/application.ts | 8 ++++--- .../public/discover/get_inner_angular.ts | 6 ++--- .../core_plugins/kibana/public/kibana.js | 1 - .../kibana/public/visualize/legacy_imports.ts | 2 -- .../public/visualize/np_ready/application.ts | 6 +++-- .../core_plugins/timelion/public/app.js | 4 +++- src/legacy/ui/public/kbn_top_nav/index.js | 20 ----------------- .../kibana_legacy/public/angular/index.ts | 2 ++ .../public/angular}/kbn_top_nav.js | 22 +++++++++++-------- src/plugins/navigation/public/plugin.ts | 4 ++-- .../top_nav_menu/create_top_nav_menu.tsx | 13 +++++++++-- .../public/top_nav_menu/top_nav_menu.tsx | 3 +-- .../dashboard_mode/public/dashboard_viewer.js | 1 - .../plugins/graph/public/application.ts | 10 ++++----- .../plugins/graph/public/legacy_imports.ts | 2 -- .../maps/public/angular/map_controller.js | 2 ++ x-pack/legacy/plugins/maps/public/index.ts | 1 - .../public/np_imports/angular/modules.ts | 6 +++-- .../public/np_imports/legacy_imports.ts | 2 -- 21 files changed, 56 insertions(+), 63 deletions(-) delete mode 100644 src/legacy/ui/public/kbn_top_nav/index.js rename src/{legacy/ui/public/kbn_top_nav => plugins/kibana_legacy/public/angular}/kbn_top_nav.js (90%) diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 6ee432635a947..4dd6bedfa4f0c 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1169,7 +1169,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | Directive is deprecated. | | `import 'ui/query_bar'` | `import { QueryStringInput } from '../data/public'` | Directives are deprecated. | | `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | -| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. | +| `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive was moved to `src/plugins/kibana_legacy`. | | `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../saved_objects/public'` | | | `core_plugins/interpreter` | `data.expressions` | still in progress | | `ui/courier` | `data.search` | still in progress | diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index c1f679e9eb7ac..beadcda595288 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -28,8 +28,6 @@ export { npSetup, npStart } from 'ui/new_platform'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { KbnUrl } from 'ui/url/kbn_url'; // @ts-ignore -export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url/index'; export { IInjector } from 'ui/chrome'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index 7239d8f2258a7..257ba8a4711b0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -31,8 +31,6 @@ import { import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { configureAppAngularModule, - createTopNavDirective, - createTopNavHelper, IPrivate, KbnUrlProvider, PrivateProvider, @@ -45,7 +43,11 @@ import { IEmbeddableStart } from '../../../../../../plugins/embeddable/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; import { DataPublicPluginStart } from '../../../../../../plugins/data/public'; import { SharePluginStart } from '../../../../../../plugins/share/public'; -import { KibanaLegacyStart } from '../../../../../../plugins/kibana_legacy/public'; +import { + KibanaLegacyStart, + createTopNavDirective, + createTopNavHelper, +} from '../../../../../../plugins/kibana_legacy/public'; import { SavedObjectLoader } from '../../../../../../plugins/saved_objects/public'; export interface RenderDeps { diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index 373395c86636c..bae938d1fb61e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -37,8 +37,6 @@ import { GlobalStateProvider } from 'ui/state_management/global_state'; import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; -// @ts-ignore -import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; @@ -63,7 +61,6 @@ import { createFieldChooserDirective } from './np_ready/components/field_chooser import { createDiscoverFieldDirective } from './np_ready/components/field_chooser/discover_field'; import { CollapsibleSidebarProvider } from './np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar'; import { DiscoverStartPlugins } from './plugin'; -import { initAngularBootstrap } from '../../../../../plugins/kibana_legacy/public'; import { createCssTruncateDirective } from './np_ready/angular/directives/css_truncate'; // @ts-ignore import { FixedScrollProvider } from './np_ready/angular/directives/fixed_scroll'; @@ -71,6 +68,7 @@ import { FixedScrollProvider } from './np_ready/angular/directives/fixed_scroll' import { DebounceProviderTimeout } from './np_ready/angular/directives/debounce/debounce'; import { createRenderCompleteDirective } from './np_ready/angular/directives/render_complete'; import { + initAngularBootstrap, configureAppAngularModule, IPrivate, KbnAccessibleClickProvider, @@ -78,6 +76,8 @@ import { PromiseServiceCreator, registerListenEventListener, watchMultiDecorator, + createTopNavDirective, + createTopNavHelper, } from '../../../../../plugins/kibana_legacy/public'; /** diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 384c6bd80ec33..a83d1176a7197 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -44,7 +44,6 @@ import 'uiExports/shareContextMenuExtensions'; import 'uiExports/interpreter'; import 'ui/autoload/all'; -import 'ui/kbn_top_nav'; import './home'; import './discover/legacy'; import './visualize/legacy'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index d9565938c838d..d52d31c2dd79e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -34,8 +34,6 @@ export { PersistedState } from 'ui/persisted_state'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; // @ts-ignore export { EventsProvider } from 'ui/events'; -// @ts-ignore -export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; // @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts index 6a8d9ce106f9d..0e1abff4b46f2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts @@ -23,8 +23,6 @@ import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { AppMountContext } from 'kibana/public'; import { configureAppAngularModule, - createTopNavDirective, - createTopNavHelper, EventsProvider, GlobalStateProvider, KbnUrlProvider, @@ -36,6 +34,10 @@ import { StateManagementConfigProvider, } from '../legacy_imports'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; +import { + createTopNavDirective, + createTopNavHelper, +} from '../../../../../../plugins/kibana_legacy/public'; // @ts-ignore import { initVisualizeApp } from './legacy_app'; diff --git a/src/legacy/core_plugins/timelion/public/app.js b/src/legacy/core_plugins/timelion/public/app.js index ff8f75c23435e..e4a48c09db832 100644 --- a/src/legacy/core_plugins/timelion/public/app.js +++ b/src/legacy/core_plugins/timelion/public/app.js @@ -37,7 +37,6 @@ require('ui/autoload/all'); import 'ui/directives/input_focus'; import './directives/saved_object_finder'; import 'ui/directives/listen'; -import 'ui/kbn_top_nav'; import './directives/saved_object_save_as_checkbox'; import '../../data/public/legacy'; import './services/saved_sheet_register'; @@ -45,6 +44,9 @@ import './services/saved_sheet_register'; import rootTemplate from 'plugins/timelion/index.html'; import { createSavedVisLoader, TypesService } from '../../visualizations/public'; +import { loadKbnTopNavDirectives } from '../../../../plugins/kibana_legacy/public'; +loadKbnTopNavDirectives(npStart.plugins.navigation.ui); + require('plugins/timelion/directives/cells/cells'); require('plugins/timelion/directives/fixed_element'); require('plugins/timelion/directives/fullscreen/fullscreen'); diff --git a/src/legacy/ui/public/kbn_top_nav/index.js b/src/legacy/ui/public/kbn_top_nav/index.js deleted file mode 100644 index 8a93972c4b226..0000000000000 --- a/src/legacy/ui/public/kbn_top_nav/index.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './kbn_top_nav'; diff --git a/src/plugins/kibana_legacy/public/angular/index.ts b/src/plugins/kibana_legacy/public/angular/index.ts index 0e18869f1c08b..0b234b7042850 100644 --- a/src/plugins/kibana_legacy/public/angular/index.ts +++ b/src/plugins/kibana_legacy/public/angular/index.ts @@ -22,3 +22,5 @@ export { PromiseServiceCreator } from './promises'; export { watchMultiDecorator } from './watch_multi'; export * from './angular_config'; export { ensureDefaultIndexPattern } from './ensure_default_index_pattern'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper, loadKbnTopNavDirectives } from './kbn_top_nav'; diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js similarity index 90% rename from src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js rename to src/plugins/kibana_legacy/public/angular/kbn_top_nav.js index 12c3ca2acc3cd..b0ccb4dbc2375 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/plugins/kibana_legacy/public/angular/kbn_top_nav.js @@ -17,12 +17,8 @@ * under the License. */ +import angular from 'angular'; import 'ngreact'; -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -import { npStart } from 'ui/new_platform'; - -const module = uiModules.get('kibana'); export function createTopNavDirective() { return { @@ -75,10 +71,8 @@ export function createTopNavDirective() { }; } -module.directive('kbnTopNav', createTopNavDirective); - export const createTopNavHelper = ({ TopNavMenu }) => reactDirective => { - return reactDirective(wrapInI18nContext(TopNavMenu), [ + return reactDirective(TopNavMenu, [ ['config', { watchDepth: 'value' }], ['disabledButtons', { watchDepth: 'reference' }], @@ -121,4 +115,14 @@ export const createTopNavHelper = ({ TopNavMenu }) => reactDirective => { ]); }; -module.directive('kbnTopNavHelper', createTopNavHelper(npStart.plugins.navigation.ui)); +let isLoaded = false; + +export function loadKbnTopNavDirectives(navUi) { + if (!isLoaded) { + isLoaded = true; + angular + .module('kibana') + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navUi)); + } +} diff --git a/src/plugins/navigation/public/plugin.ts b/src/plugins/navigation/public/plugin.ts index e8df5bcd616d9..5b30ff4913a40 100644 --- a/src/plugins/navigation/public/plugin.ts +++ b/src/plugins/navigation/public/plugin.ts @@ -40,14 +40,14 @@ export class NavigationPublicPlugin } public start( - core: CoreStart, + { i18n }: CoreStart, { data }: NavigationPluginStartDependencies ): NavigationPublicPluginStart { const extensions = this.topNavMenuExtensionsRegistry.getAll(); return { ui: { - TopNavMenu: createTopNav(data, extensions), + TopNavMenu: createTopNav(data, extensions, i18n), }, }; } diff --git a/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx index a78c48b675911..79201a9da88c5 100644 --- a/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/create_top_nav_menu.tsx @@ -18,17 +18,26 @@ */ import React from 'react'; +import { I18nStart } from 'kibana/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { TopNavMenuProps, TopNavMenu } from './top_nav_menu'; import { RegisteredTopNavMenuData } from './top_nav_menu_data'; -export function createTopNav(data: DataPublicPluginStart, extraConfig: RegisteredTopNavMenuData[]) { +export function createTopNav( + data: DataPublicPluginStart, + extraConfig: RegisteredTopNavMenuData[], + i18n: I18nStart +) { return (props: TopNavMenuProps) => { const relevantConfig = extraConfig.filter( dataItem => dataItem.appName === undefined || dataItem.appName === props.appName ); const config = (props.config || []).concat(relevantConfig); - return ; + return ( + + + + ); }; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx index cf39c82eff3ce..80d1a53cd417f 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu.tsx @@ -20,7 +20,6 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { I18nProvider } from '@kbn/i18n/react'; import { TopNavMenuData } from './top_nav_menu_data'; import { TopNavMenuItem } from './top_nav_menu_item'; @@ -78,7 +77,7 @@ export function TopNavMenu(props: TopNavMenuProps) { ); } - return {renderLayout()}; + return renderLayout(); } TopNavMenu.defaultProps = { diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index e0e49fe59daf4..e76a204a6f27d 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -29,7 +29,6 @@ import 'uiExports/search'; import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; -import 'ui/kbn_top_nav'; import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; diff --git a/x-pack/legacy/plugins/graph/public/application.ts b/x-pack/legacy/plugins/graph/public/application.ts index 7bd18f841b478..536382e62d473 100644 --- a/x-pack/legacy/plugins/graph/public/application.ts +++ b/x-pack/legacy/plugins/graph/public/application.ts @@ -20,11 +20,7 @@ import { IUiSettingsClient, OverlayStart, } from 'kibana/public'; -import { - configureAppAngularModule, - createTopNavDirective, - createTopNavHelper, -} from './legacy_imports'; +import { configureAppAngularModule } from './legacy_imports'; // @ts-ignore import { initGraphApp } from './app'; import { @@ -36,6 +32,10 @@ import { checkLicense } from '../../../../plugins/graph/common/check_license'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../src/plugins/navigation/public'; import { createSavedWorkspacesLoader } from './services/persistence/saved_workspace_loader'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; +import { + createTopNavDirective, + createTopNavHelper, +} from '../../../../../src/plugins/kibana_legacy/public'; import { addAppRedirectMessageToUrl } from '../../../../../src/plugins/kibana_legacy/public'; /** diff --git a/x-pack/legacy/plugins/graph/public/legacy_imports.ts b/x-pack/legacy/plugins/graph/public/legacy_imports.ts index 84fafdb580abe..274cdc65232e2 100644 --- a/x-pack/legacy/plugins/graph/public/legacy_imports.ts +++ b/x-pack/legacy/plugins/graph/public/legacy_imports.ts @@ -6,6 +6,4 @@ import 'ace'; -// @ts-ignore -export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; export { configureAppAngularModule } from '../../../../../src/plugins/kibana_legacy/public'; diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index c90560a4fcfdf..95c8ff975b1d6 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -57,6 +57,8 @@ import { SavedObjectSaveModal, showSaveModal, } from '../../../../../../src/plugins/saved_objects/public'; +import { loadKbnTopNavDirectives } from '../../../../../../src/plugins/kibana_legacy/public'; +loadKbnTopNavDirectives(npStart.plugins.navigation.ui); const savedQueryService = npStart.plugins.data.query.savedQueries; diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts index 404909c5c51b8..f3213a36bb66d 100644 --- a/x-pack/legacy/plugins/maps/public/index.ts +++ b/x-pack/legacy/plugins/maps/public/index.ts @@ -13,7 +13,6 @@ import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'ui/agg_types'; -import 'ui/kbn_top_nav'; import 'ui/autoload/all'; import 'react-vis/dist/style.css'; diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts index 2acb6031c6773..09ac0f95a1dd9 100644 --- a/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/angular/modules.ts @@ -9,6 +9,10 @@ import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { AppMountContext } from 'kibana/public'; import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; +import { + createTopNavDirective, + createTopNavHelper, +} from '../../../../../../../src/plugins/kibana_legacy/public'; import { GlobalStateProvider, @@ -16,8 +20,6 @@ import { AppStateProvider, EventsProvider, PersistedState, - createTopNavDirective, - createTopNavHelper, KbnUrlProvider, RedirectWhenMissingProvider, npStart, diff --git a/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts b/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts index 012cbc77ce9c8..ea29ac95eb03f 100644 --- a/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts +++ b/x-pack/legacy/plugins/monitoring/public/np_imports/legacy_imports.ts @@ -19,7 +19,5 @@ export { AppStateProvider } from 'ui/state_management/app_state'; export { EventsProvider } from 'ui/events'; export { PersistedState } from 'ui/persisted_state'; // @ts-ignore -export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; From 8511fe378018156b86b651435c9f8eb36b25ac2c Mon Sep 17 00:00:00 2001 From: Michail Yasonik Date: Wed, 26 Feb 2020 12:17:00 -0500 Subject: [PATCH 15/22] Fixing Newsfeed Cloud test bug (#58566) --- test/functional/apps/home/_newsfeed.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/test/functional/apps/home/_newsfeed.ts b/test/functional/apps/home/_newsfeed.ts index 35d7ac8adefa5..096e237850c72 100644 --- a/test/functional/apps/home/_newsfeed.ts +++ b/test/functional/apps/home/_newsfeed.ts @@ -47,10 +47,17 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { it('shows all news from newsfeed', async () => { const objects = await PageObjects.newsfeed.getNewsfeedList(); - expect(objects).to.eql([ - '21 June 2019\nYou are functionally testing the newsfeed widget with fixtures!\nSee test/common/fixtures/plugins/newsfeed/newsfeed_simulation\nGeneric feed-viewer could go here', - '21 June 2019\nStaging too!\nHello world\nGeneric feed-viewer could go here', - ]); + + if (await PageObjects.common.isOss()) { + expect(objects).to.eql([ + '21 June 2019\nYou are functionally testing the newsfeed widget with fixtures!\nSee test/common/fixtures/plugins/newsfeed/newsfeed_simulation\nGeneric feed-viewer could go here', + '21 June 2019\nStaging too!\nHello world\nGeneric feed-viewer could go here', + ]); + } else { + // can't shim the API in cloud so going to check that at least something is rendered + // to test that the API was called and returned something that could be rendered + expect(objects.length).to.be.above(0); + } }); it('clicking on newsfeed icon should close opened newsfeed', async () => { From fa5400f606d77e0f7c2252961242898b96e9ad7f Mon Sep 17 00:00:00 2001 From: Elizabet Oliveira Date: Wed, 26 Feb 2020 18:20:03 +0000 Subject: [PATCH 16/22] [Maps] Improve Layer Style UI (#58406) * Improving Layer Style UI * Removing unnecessary method * Updating snapshot * Changing width value to make use of sass variable Co-authored-by: Elastic Machine --- .../layer_panel/_index.scss | 3 +- .../style_settings/_style_settings.scss | 3 + .../style_settings/style_settings.js | 2 +- .../vector/components/color/_color_stops.scss | 21 ------ .../components/color/color_map_select.js | 1 + .../vector/components/color/color_stops.js | 64 ++++++++++--------- .../components/color/dynamic_color_form.js | 6 +- .../components/color/static_color_form.js | 6 +- .../components/label/dynamic_label_form.js | 6 +- .../components/label/static_label_form.js | 6 +- .../orientation/dynamic_orientation_form.js | 6 +- .../orientation/static_orientation_form.js | 6 +- .../components/size/dynamic_size_form.js | 6 +- .../components/size/static_size_form.js | 6 +- .../vector/components/style_prop_editor.js | 6 +- .../__snapshots__/icon_select.test.js.snap | 2 +- .../components/symbol/dynamic_icon_form.js | 6 +- .../vector/components/symbol/icon_select.js | 1 + .../components/symbol/static_icon_form.js | 6 +- 19 files changed, 88 insertions(+), 75 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_index.scss b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_index.scss index b219f59476ce9..fd074edf032fa 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_index.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/_index.scss @@ -1,3 +1,4 @@ @import './layer_panel'; @import './filter_editor/filter_editor'; -@import './join_editor/resources/join'; \ No newline at end of file +@import './join_editor/resources/join'; +@import './style_settings/style_settings'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss new file mode 100644 index 0000000000000..249b6dfca5c76 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/_style_settings.scss @@ -0,0 +1,3 @@ +.mapStyleSettings__fixedBox { + width: $euiSize * 7.5; +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js index 2857065f04c70..69cf51fb29c0d 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/style_settings/style_settings.js @@ -21,7 +21,7 @@ export function StyleSettings({ layer, updateStyleDescriptor }) { return ( - + diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss index 001ca0685d0e9..519e97f4b30cd 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss @@ -1,6 +1,5 @@ .mapColorStop { position: relative; - padding-right: $euiSizeXL + $euiSizeS; & + & { margin-top: $euiSizeS; @@ -17,23 +16,3 @@ } } -.mapColorStop__icons { - flex-shrink: 0; - display: none; - position: absolute; - right: 0; - top: 50%; - margin-right: -$euiSizeS; - margin-top: -$euiSizeM; -} - -@keyframes mapColorStopBecomeVisible { - - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js index e8d5754ef4206..436a92b619909 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js @@ -105,6 +105,7 @@ export class ColorMapSelect extends Component { onChange={this._onColorMapSelect} valueOfSelected={valueOfSelected} hasDividers={true} + compressed /> {this._renderColorStopsInput()} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js index 47c2d037e0c79..3e9b9e2aafc47 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js @@ -10,7 +10,19 @@ import { removeRow, isColorInvalid } from './color_stops_utils'; import { i18n } from '@kbn/i18n'; import { EuiButtonIcon, EuiColorPicker, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; -function getColorStopRow({ index, errors, stopInput, colorInput, deleteButton, onAdd }) { +function getColorStopRow({ index, errors, stopInput, onColorChange, color, deleteButton, onAdd }) { + const colorPickerButtons = ( +
+ {deleteButton} + +
+ ); return ( -
- - {stopInput} - {colorInput} - -
- {deleteButton} - + + {stopInput} + + + -
-
+ +
); } @@ -80,17 +89,6 @@ export const ColorStops = ({ }; } - function getColorInput(onColorChange, color) { - return { - colorError: isColorInvalid(color) - ? i18n.translate('xpack.maps.styles.colorStops.hexWarningLabel', { - defaultMessage: 'Color must provide a valid hex value', - }) - : undefined, - colorInput: , - }; - } - const rows = colorStops.map((colorStop, index) => { const onColorChange = color => { const newColorStops = _.cloneDeep(colorStops); @@ -102,7 +100,15 @@ export const ColorStops = ({ }; const { stopError, stopInput } = getStopInput(colorStop.stop, index); - const { colorError, colorInput } = getColorInput(onColorChange, colorStop.color); + + const color = colorStop.color; + + const colorError = isColorInvalid(color) + ? i18n.translate('xpack.maps.styles.colorStops.hexWarningLabel', { + defaultMessage: 'Color must provide a valid hex value', + }) + : undefined; + const errors = []; if (stopError) { errors.push(stopError); @@ -131,7 +137,7 @@ export const ColorStops = ({ deleteButton = getDeleteButton(onRemove); } - return getColorStopRow({ index, errors, stopInput, colorInput, deleteButton, onAdd }); + return getColorStopRow({ index, errors, stopInput, onColorChange, color, deleteButton, onAdd }); }); return
{rows}
; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js index af5e5b37f5467..3dc356c31cf30 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -90,8 +90,10 @@ export function DynamicColorForm({ return ( - - {staticDynamicSelect} + + + {staticDynamicSelect} + - {staticDynamicSelect} + + + {staticDynamicSelect} + - {staticDynamicSelect} + + + {staticDynamicSelect} + - {staticDynamicSelect} + + + {staticDynamicSelect} + - {staticDynamicSelect} + + + {staticDynamicSelect} + - {staticDynamicSelect} + + + {staticDynamicSelect} + - - {staticDynamicSelect} + + + {staticDynamicSelect} + - {staticDynamicSelect} + + + {staticDynamicSelect} + - - {staticDynamicSelect} + + + {staticDynamicSelect} + diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap index 706dc0763b7ca..8fa69e1a5b467 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap @@ -34,7 +34,7 @@ exports[`Should render icon select 1`] = ` } closePopover={[Function]} - display="inlineBlock" + display="block" hasArrow={true} isOpen={false} ownFocus={true} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js index afa11daf45217..9065102dc8bd7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js @@ -53,8 +53,10 @@ export function DynamicIconForm({ return ( - - {staticDynamicSelect} + + + {staticDynamicSelect} + {this._renderIconSelectable()} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js index b20d8f2eba162..9b00b2fe38d7b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js @@ -20,8 +20,10 @@ export function StaticIconForm({ }; return ( - - {staticDynamicSelect} + + + {staticDynamicSelect} + Date: Wed, 26 Feb 2020 19:00:54 +0000 Subject: [PATCH 17/22] Migrate existing Cypress tests to Cypress + Cucumber (#57299) * chore: move gitignore to the cypress directory * chore: ignore more test files * fix: do not check the Loading Message It seems not relevant to the main purpose of these tests * chore: use cypres + webpack + cucumber scaffolding See https://github.com/TheBrainFamily/cypress-cucumber-webpack-typescript-example * chore: add eslint and prettier for code linting * feat: convert existing Cypress test into BDD style * feat: add support for using proper Node version in MacOSX * chore: use tslint * chore: use old layout We are keeping cypress as rootDir to follow project's structure. On the other hand, having a second cypress directory at the 2nd level is the default structure, as shown in the examples: - https://github.com/TheBrainFamily/cypress-cucumber-webpack-typescript-example - https://github.com/cypress-io/cypress-example-recipes/tree/a240054d7f5626ffcd7bd668dded96d219c4a7eb/examples/preprocessors__typescript-webpack * chore: remove prelint script meanwhile we fix TS lint * chore: move test results to a specific directory * chore: rename variable following old code * chore: remove non-needed lints, as we are going to use kibana build * chore: import snapshot function from cypress * chore: add readFile utils back from a bad removal * chore: change format of JSON spec file It was automatically changed by tests * chore: move CI directory to the proper layout in order for Jenkins to work * chore: store test-results from proper dir on Jenkins * chore: store artifacts properly on Jenkins * Fix type issues * chore: rename test application to e2e (end-to-end) We are keeping the build system within the test application, isolating dependencies * docs: reorganise docs for APM UI e2e tests * fix: Use proper cypress support file * chore: use existing NPM script for running cypress on CI * chore: update paths in CI scripts * docs: document how the CI runs the tests * chore: use Node 10 for tests * chore: Use kibana's Node version for tests * chore: run yarn install * docs: update docs * fix: path was wrong * docs: fix paths and flags used to load data * docs: elasticsearch fix flag * docs: Bootstrap kibana before running it * docs: remove outdated info * chore: move background steps to the scenario This would avoid not reading the background when the number of scenarios grows Co-authored-by: Dario Gieselaar Co-authored-by: Elastic Machine --- .ci/end2end.groovy | 8 +- .eslintignore | 2 +- src/cli/cluster/cluster_manager.ts | 2 +- src/dev/ci_setup/setup_env.sh | 2 +- src/dev/run_check_lockfile_symlinks.js | 2 +- src/dev/typescript/projects.ts | 2 +- x-pack/legacy/plugins/apm/cypress/README.md | 63 - .../legacy/plugins/apm/cypress/cypress.json | 18 - .../apm/cypress/integration/apm.spec.ts | 53 - .../apm/cypress/screenshots/.gitignore | 1 - .../legacy/plugins/apm/cypress/snapshots.js | 3 - .../plugins/apm/cypress/support/index.ts | 10 - x-pack/legacy/plugins/apm/e2e/.gitignore | 4 + x-pack/legacy/plugins/apm/e2e/README.md | 68 + .../apm/{cypress => e2e}/ci/Dockerfile | 3 +- .../apm/{cypress => e2e}/ci/entrypoint.sh | 6 +- .../apm/{cypress => e2e}/ci/kibana.dev.yml | 0 .../apm/{cypress => e2e}/ci/prepare-kibana.sh | 2 +- x-pack/legacy/plugins/apm/e2e/cypress.json | 19 + .../{ => e2e}/cypress/fixtures/example.json | 0 .../{ => e2e}/cypress/ingest-data/replay.js | 0 .../apm/e2e/cypress/integration/apm.feature | 7 + .../{ => e2e}/cypress/integration/helpers.ts | 0 .../cypress/integration/snapshots.js | 9 +- .../apm/{ => e2e}/cypress/plugins/index.js | 16 +- .../apm/e2e/cypress/support/commands.js | 31 + .../plugins/apm/e2e/cypress/support/index.ts | 27 + .../cypress/support/step_definitions/apm.ts | 45 + .../cypress/typings/index.d.ts} | 0 .../plugins/apm/e2e/cypress/webpack.config.js | 41 + .../plugins/apm/{cypress => e2e}/package.json | 7 +- .../apm/{cypress => e2e}/tsconfig.json | 9 +- .../plugins/apm/{cypress => e2e}/yarn.lock | 2759 ++++++++++++----- x-pack/tsconfig.json | 2 +- 34 files changed, 2302 insertions(+), 919 deletions(-) delete mode 100644 x-pack/legacy/plugins/apm/cypress/README.md delete mode 100644 x-pack/legacy/plugins/apm/cypress/cypress.json delete mode 100644 x-pack/legacy/plugins/apm/cypress/integration/apm.spec.ts delete mode 100644 x-pack/legacy/plugins/apm/cypress/screenshots/.gitignore delete mode 100644 x-pack/legacy/plugins/apm/cypress/snapshots.js delete mode 100644 x-pack/legacy/plugins/apm/cypress/support/index.ts create mode 100644 x-pack/legacy/plugins/apm/e2e/.gitignore create mode 100644 x-pack/legacy/plugins/apm/e2e/README.md rename x-pack/legacy/plugins/apm/{cypress => e2e}/ci/Dockerfile (92%) rename x-pack/legacy/plugins/apm/{cypress => e2e}/ci/entrypoint.sh (87%) rename x-pack/legacy/plugins/apm/{cypress => e2e}/ci/kibana.dev.yml (100%) rename x-pack/legacy/plugins/apm/{cypress => e2e}/ci/prepare-kibana.sh (95%) create mode 100644 x-pack/legacy/plugins/apm/e2e/cypress.json rename x-pack/legacy/plugins/apm/{ => e2e}/cypress/fixtures/example.json (100%) rename x-pack/legacy/plugins/apm/{ => e2e}/cypress/ingest-data/replay.js (100%) create mode 100644 x-pack/legacy/plugins/apm/e2e/cypress/integration/apm.feature rename x-pack/legacy/plugins/apm/{ => e2e}/cypress/integration/helpers.ts (100%) rename x-pack/legacy/plugins/apm/{ => e2e}/cypress/integration/snapshots.js (61%) rename x-pack/legacy/plugins/apm/{ => e2e}/cypress/plugins/index.js (79%) create mode 100644 x-pack/legacy/plugins/apm/e2e/cypress/support/commands.js create mode 100644 x-pack/legacy/plugins/apm/e2e/cypress/support/index.ts create mode 100644 x-pack/legacy/plugins/apm/e2e/cypress/support/step_definitions/apm.ts rename x-pack/legacy/plugins/apm/{cypress/cypress.d.ts => e2e/cypress/typings/index.d.ts} (100%) create mode 100644 x-pack/legacy/plugins/apm/e2e/cypress/webpack.config.js rename x-pack/legacy/plugins/apm/{cypress => e2e}/package.json (66%) rename x-pack/legacy/plugins/apm/{cypress => e2e}/tsconfig.json (51%) rename x-pack/legacy/plugins/apm/{cypress => e2e}/yarn.lock (61%) diff --git a/.ci/end2end.groovy b/.ci/end2end.groovy index 38fed4aca19dc..8ad810717d86e 100644 --- a/.ci/end2end.groovy +++ b/.ci/end2end.groovy @@ -13,7 +13,7 @@ pipeline { BASE_DIR = 'src/github.com/elastic/kibana' HOME = "${env.WORKSPACE}" APM_ITS = 'apm-integration-testing' - CYPRESS_DIR = 'x-pack/legacy/plugins/apm/cypress' + CYPRESS_DIR = 'x-pack/legacy/plugins/apm/e2e' PIPELINE_LOG_LEVEL = 'DEBUG' } options { @@ -107,7 +107,7 @@ pipeline { dir("${BASE_DIR}"){ sh ''' jobs -l - docker build --tag cypress ${CYPRESS_DIR}/ci + docker build --tag cypress --build-arg NODE_VERSION=$(cat .node-version) ${CYPRESS_DIR}/ci docker run --rm -t --user "$(id -u):$(id -g)" \ -v `pwd`:/app --network="host" \ --name cypress cypress''' @@ -116,8 +116,8 @@ pipeline { post { always { dir("${BASE_DIR}"){ - archiveArtifacts(allowEmptyArchive: false, artifacts: "${CYPRESS_DIR}/screenshots/**,${CYPRESS_DIR}/videos/**,${CYPRESS_DIR}/*e2e-tests.xml") - junit(allowEmptyResults: true, testResults: "${CYPRESS_DIR}/*e2e-tests.xml") + archiveArtifacts(allowEmptyArchive: false, artifacts: "${CYPRESS_DIR}/**/screenshots/**,${CYPRESS_DIR}/**/videos/**,${CYPRESS_DIR}/**/test-results/*e2e-tests.xml") + junit(allowEmptyResults: true, testResults: "${CYPRESS_DIR}/**/test-results/*e2e-tests.xml") } dir("${APM_ITS}"){ sh 'docker-compose logs > apm-its.log || true' diff --git a/.eslintignore b/.eslintignore index c3921bd22e1ab..357d735e8044b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -39,7 +39,7 @@ src/legacy/core_plugins/vis_type_vislib/public/vislib/__tests__/lib/fixtures/moc /x-pack/legacy/plugins/infra/common/graphql/types.ts /x-pack/legacy/plugins/infra/public/graphql/types.ts /x-pack/legacy/plugins/infra/server/graphql/types.ts -/x-pack/legacy/plugins/apm/cypress/**/snapshots.js +/x-pack/legacy/plugins/apm/e2e/cypress/**/snapshots.js /src/legacy/plugin_discovery/plugin_pack/__tests__/fixtures/plugins/broken **/graphql/types.ts **/*.js.snap diff --git a/src/cli/cluster/cluster_manager.ts b/src/cli/cluster/cluster_manager.ts index 2f308915fb332..8862b96e74401 100644 --- a/src/cli/cluster/cluster_manager.ts +++ b/src/cli/cluster/cluster_manager.ts @@ -264,7 +264,7 @@ export class ClusterManager { fromRoot('src/legacy/server/sass/__tmp__'), fromRoot('x-pack/legacy/plugins/reporting/.chromium'), fromRoot('x-pack/legacy/plugins/siem/cypress'), - fromRoot('x-pack/legacy/plugins/apm/cypress'), + fromRoot('x-pack/legacy/plugins/apm/e2e/cypress'), fromRoot('x-pack/legacy/plugins/apm/scripts'), fromRoot('x-pack/legacy/plugins/canvas/canvas_plugin_src'), // prevents server from restarting twice for Canvas plugin changes, 'plugins/java_languageserver', diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index 823c70e80fe7c..dc3fa38f3129c 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -168,4 +168,4 @@ if [[ -d "$ES_DIR" && -f "$ES_JAVA_PROP_PATH" ]]; then export JAVA_HOME=$HOME/.java/$ES_BUILD_JAVA fi -export CI_ENV_SETUP=true +export CI_ENV_SETUP=true \ No newline at end of file diff --git a/src/dev/run_check_lockfile_symlinks.js b/src/dev/run_check_lockfile_symlinks.js index e7fd7e8831405..54a8cdf638a78 100644 --- a/src/dev/run_check_lockfile_symlinks.js +++ b/src/dev/run_check_lockfile_symlinks.js @@ -35,7 +35,7 @@ const IGNORE_FILE_GLOBS = [ // fixtures aren't used in production, ignore them '**/*fixtures*/**/*', // cypress isn't used in production, ignore it - 'x-pack/legacy/plugins/apm/cypress/*', + 'x-pack/legacy/plugins/apm/e2e/*', ]; run(async ({ log }) => { diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index fb35e5ce526ed..34756912fc247 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -30,7 +30,7 @@ export const PROJECTS = [ new Project(resolve(REPO_ROOT, 'x-pack/legacy/plugins/siem/cypress/tsconfig.json'), { name: 'siem/cypress', }), - new Project(resolve(REPO_ROOT, 'x-pack/legacy/plugins/apm/cypress/tsconfig.json'), { + new Project(resolve(REPO_ROOT, 'x-pack/legacy/plugins/apm/e2e/tsconfig.json'), { name: 'apm/cypress', disableTypeCheck: true, }), diff --git a/x-pack/legacy/plugins/apm/cypress/README.md b/x-pack/legacy/plugins/apm/cypress/README.md deleted file mode 100644 index 7b44e0d9fce28..0000000000000 --- a/x-pack/legacy/plugins/apm/cypress/README.md +++ /dev/null @@ -1,63 +0,0 @@ -### How to run - -_Note: Run the following commands from `kibana/x-pack/legacy/plugins/apm/cypress`._ - -#### Interactive mode - -``` -yarn cypress open -``` - -#### Headless mode - -``` -yarn cypress run -``` - -### Connect to Elasticsearch on Cloud (internal devs only) - -Find the credentials for the cluster [here](https://github.com/elastic/apm-dev/blob/master/docs/credentials/apm-ui-clusters.md#e2e-cluster). The cloud instance contains the static data set - -### Kibana - -#### `--no-base-path` - -Kibana must be started with `yarn start --no-base-path` - -#### Content Security Policy (CSP) Settings - -Your local or cloud Kibana server must have the `csp.strict: false` setting -configured in `kibana.dev.yml`, or `kibana.yml`, as shown in the example below: - -```yaml -csp.strict: false -``` - -The above setting is required to prevent the _Please upgrade -your browser_ / _This Kibana installation has strict security requirements -enabled that your current browser does not meet._ warning that's displayed for -unsupported user agents, like the one reported by Cypress when running tests. - -### Ingest static data into Elasticsearch via APM Server - -1. Download [static data file](https://storage.googleapis.com/apm-ui-e2e-static-data/events.json) - -2. Post to APM Server - -``` -node ingest-data/replay.js --server-url http://localhost:8200 --secret-token abcd --events ./events.json -``` - -### Generate static data - -Capture data from all agents with [apm-integration-testing](https://github.com/elastic/apm-integration-testing): - -``` -./scripts/compose.py start master --all --apm-server-record -``` - -To copy the captured data from the container to the host: - -``` -docker cp localtesting_8.0.0_apm-server-2:/app/events.json . -``` diff --git a/x-pack/legacy/plugins/apm/cypress/cypress.json b/x-pack/legacy/plugins/apm/cypress/cypress.json deleted file mode 100644 index 69852346359fa..0000000000000 --- a/x-pack/legacy/plugins/apm/cypress/cypress.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "baseUrl": "http://localhost:5601", - "video": false, - "trashAssetsBeforeRuns": false, - "fileServerFolder": "../", - "fixturesFolder": "./fixtures", - "integrationFolder": "./integration", - "pluginsFile": "./plugins/index.js", - "screenshotsFolder": "./screenshots", - "supportFile": "./support/index.ts", - "videosFolder": "./videos", - "useRelativeSnapshots": true, - "reporter": "junit", - "reporterOptions": { - "mochaFile": "[hash]-e2e-tests.xml", - "toConsole": false - } -} diff --git a/x-pack/legacy/plugins/apm/cypress/integration/apm.spec.ts b/x-pack/legacy/plugins/apm/cypress/integration/apm.spec.ts deleted file mode 100644 index 3d0ff276fcdd8..0000000000000 --- a/x-pack/legacy/plugins/apm/cypress/integration/apm.spec.ts +++ /dev/null @@ -1,53 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { loginAndWaitForPage } from './helpers'; - -describe('When clicking opbeans-go service', () => { - before(() => { - // open service overview page - loginAndWaitForPage(`/app/apm#/services`); - - // show loading text for services - cy.contains('Loading...'); - - // click opbeans-go service - cy.get(':contains(opbeans-go)') - .last() - .click({ force: true }); - }); - - it('should redirect to correct path with correct params', () => { - cy.url().should('contain', `/app/apm#/services/opbeans-go/transactions`); - cy.url().should('contain', `transactionType=request`); - }); - - describe('transaction duration charts', () => { - it('should have correct y-axis ticks', () => { - const yAxisTick = - '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text'; - - cy.get(yAxisTick) - .eq(2) - .invoke('text') - .snapshot(); - - cy.get(yAxisTick) - .eq(1) - .invoke('text') - .snapshot(); - - cy.get(yAxisTick) - .eq(0) - .invoke('text') - .snapshot(); - }); - }); - - describe('TPM charts', () => {}); - - describe('Transaction group list', () => {}); -}); diff --git a/x-pack/legacy/plugins/apm/cypress/screenshots/.gitignore b/x-pack/legacy/plugins/apm/cypress/screenshots/.gitignore deleted file mode 100644 index 72e8ffc0db8aa..0000000000000 --- a/x-pack/legacy/plugins/apm/cypress/screenshots/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/x-pack/legacy/plugins/apm/cypress/snapshots.js b/x-pack/legacy/plugins/apm/cypress/snapshots.js deleted file mode 100644 index 58e58fdaf8684..0000000000000 --- a/x-pack/legacy/plugins/apm/cypress/snapshots.js +++ /dev/null @@ -1,3 +0,0 @@ -// auto-generated by @cypress/snapshot -{ -} diff --git a/x-pack/legacy/plugins/apm/cypress/support/index.ts b/x-pack/legacy/plugins/apm/cypress/support/index.ts deleted file mode 100644 index 08acd31affeda..0000000000000 --- a/x-pack/legacy/plugins/apm/cypress/support/index.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; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -import { register } from '@cypress/snapshot'; - -register(); diff --git a/x-pack/legacy/plugins/apm/e2e/.gitignore b/x-pack/legacy/plugins/apm/e2e/.gitignore new file mode 100644 index 0000000000000..10c769065fc28 --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/.gitignore @@ -0,0 +1,4 @@ +cypress/ingest-data/events.json +cypress/screenshots/* + +cypress/test-results diff --git a/x-pack/legacy/plugins/apm/e2e/README.md b/x-pack/legacy/plugins/apm/e2e/README.md new file mode 100644 index 0000000000000..73a1e860f5564 --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/README.md @@ -0,0 +1,68 @@ +# End-To-End (e2e) Test for APM UI + +## Ingest static data into Elasticsearch via APM Server + +1. Start Elasticsearch and APM Server, using [apm-integration-testing](https://github.com/elastic/apm-integration-testing): + +```shell +$ git clone https://github.com/elastic/apm-integration-testing.git +$ cd apm-integration-testing +./scripts/compose.py start master --no-kibana --no-xpack-secure +``` + +2. Download [static data file](https://storage.googleapis.com/apm-ui-e2e-static-data/events.json) + +```shell +$ cd x-pack/legacy/plugins/apm/e2e/cypress/ingest-data +$ curl https://storage.googleapis.com/apm-ui-e2e-static-data/events.json --output events.json +``` + +3. Post to APM Server + +```shell +$ cd x-pack/legacy/plugins/apm/e2e/cypress/ingest-data +$ node replay.js --server-url http://localhost:8200 --secret-token abcd --events ./events.json +``` +>This process will take a few minutes to ingest all data + +4. Start Kibana + +```shell +$ yarn kbn bootstrap +$ yarn start --no-base-path --csp.strict=false +``` + +> Content Security Policy (CSP) Settings: Your Kibana instance must have the `csp.strict: false`. + +## How to run the tests + +_Note: Run the following commands from `kibana/x-pack/legacy/plugins/apm/e2e/cypress`._ + +### Interactive mode + +``` +yarn cypress open +``` + +### Headless mode + +``` +yarn cypress run +``` + +## Reproducing CI builds + +>This process is very slow compared to the local development described above. Consider that the CI must install and configure the build tools and create a Docker image for the project to run tests in a consistent manner. + +The Jenkins CI uses a shell script to prepare Kibana: + +```shell +# Prepare and run Kibana locally +$ x-pack/legacy/plugins/apm/e2e/ci/prepare-kibana.sh +# Build Docker image for Kibana +$ docker build --tag cypress --build-arg NODE_VERSION=$(cat .node-version) x-pack/legacy/plugins/apm/e2e/ci +# Run Docker image +$ docker run --rm -t --user "$(id -u):$(id -g)" \ + -v `pwd`:/app --network="host" \ + --name cypress cypress +``` diff --git a/x-pack/legacy/plugins/apm/cypress/ci/Dockerfile b/x-pack/legacy/plugins/apm/e2e/ci/Dockerfile similarity index 92% rename from x-pack/legacy/plugins/apm/cypress/ci/Dockerfile rename to x-pack/legacy/plugins/apm/e2e/ci/Dockerfile index e59e1f47da0b9..2bcc5a5fd843a 100644 --- a/x-pack/legacy/plugins/apm/cypress/ci/Dockerfile +++ b/x-pack/legacy/plugins/apm/e2e/ci/Dockerfile @@ -1,4 +1,5 @@ -FROM node:12 +ARG NODE_VERSION +FROM node:$NODE_VERSION RUN apt-get -qq update \ && apt-get -y -qq install xvfb \ diff --git a/x-pack/legacy/plugins/apm/cypress/ci/entrypoint.sh b/x-pack/legacy/plugins/apm/e2e/ci/entrypoint.sh similarity index 87% rename from x-pack/legacy/plugins/apm/cypress/ci/entrypoint.sh rename to x-pack/legacy/plugins/apm/e2e/ci/entrypoint.sh index 1bcddac3b8020..f7226dca1d276 100755 --- a/x-pack/legacy/plugins/apm/cypress/ci/entrypoint.sh +++ b/x-pack/legacy/plugins/apm/e2e/ci/entrypoint.sh @@ -21,9 +21,9 @@ npm config set cache ${HOME} # --exclude=packages/ \ # --exclude=built_assets --exclude=target \ # --exclude=data /app ${HOME}/ -#cd ${HOME}/app/x-pack/legacy/plugins/apm/cypress +#cd ${HOME}/app/x-pack/legacy/plugins/apm/e2e/cypress -cd /app/x-pack/legacy/plugins/apm/cypress +cd /app/x-pack/legacy/plugins/apm/e2e ## Install dependencies for cypress CI=true npm install yarn install @@ -33,4 +33,4 @@ npm install wait-on ./node_modules/.bin/wait-on ${CYPRESS_BASE_URL}/status && echo 'Kibana is up and running' # Run cypress -./node_modules/.bin/cypress run +npm run cypress:run diff --git a/x-pack/legacy/plugins/apm/cypress/ci/kibana.dev.yml b/x-pack/legacy/plugins/apm/e2e/ci/kibana.dev.yml similarity index 100% rename from x-pack/legacy/plugins/apm/cypress/ci/kibana.dev.yml rename to x-pack/legacy/plugins/apm/e2e/ci/kibana.dev.yml diff --git a/x-pack/legacy/plugins/apm/cypress/ci/prepare-kibana.sh b/x-pack/legacy/plugins/apm/e2e/ci/prepare-kibana.sh similarity index 95% rename from x-pack/legacy/plugins/apm/cypress/ci/prepare-kibana.sh rename to x-pack/legacy/plugins/apm/e2e/ci/prepare-kibana.sh index d6fd620195b94..4f176fd0070f5 100755 --- a/x-pack/legacy/plugins/apm/cypress/ci/prepare-kibana.sh +++ b/x-pack/legacy/plugins/apm/e2e/ci/prepare-kibana.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -CYPRESS_DIR="x-pack/legacy/plugins/apm/cypress" +CYPRESS_DIR="x-pack/legacy/plugins/apm/e2e" echo "1/3 Install dependencies ..." # shellcheck disable=SC1091 diff --git a/x-pack/legacy/plugins/apm/e2e/cypress.json b/x-pack/legacy/plugins/apm/e2e/cypress.json new file mode 100644 index 0000000000000..310964656f107 --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/cypress.json @@ -0,0 +1,19 @@ +{ + "baseUrl": "http://localhost:5601", + "video": false, + "trashAssetsBeforeRuns": false, + "fileServerFolder": "../", + "fixturesFolder": "./cypress/fixtures", + "integrationFolder": "./cypress/integration", + "pluginsFile": "./cypress/plugins/index.js", + "screenshotsFolder": "./cypress/screenshots", + "supportFile": "./cypress/support/index.ts", + "videosFolder": "./cypress/videos", + "useRelativeSnapshots": true, + "reporter": "junit", + "reporterOptions": { + "mochaFile": "./cypress/test-results/[hash]-e2e-tests.xml", + "toConsole": false + }, + "testFiles": "**/*.{feature,features}" +} diff --git a/x-pack/legacy/plugins/apm/cypress/fixtures/example.json b/x-pack/legacy/plugins/apm/e2e/cypress/fixtures/example.json similarity index 100% rename from x-pack/legacy/plugins/apm/cypress/fixtures/example.json rename to x-pack/legacy/plugins/apm/e2e/cypress/fixtures/example.json diff --git a/x-pack/legacy/plugins/apm/cypress/ingest-data/replay.js b/x-pack/legacy/plugins/apm/e2e/cypress/ingest-data/replay.js similarity index 100% rename from x-pack/legacy/plugins/apm/cypress/ingest-data/replay.js rename to x-pack/legacy/plugins/apm/e2e/cypress/ingest-data/replay.js diff --git a/x-pack/legacy/plugins/apm/e2e/cypress/integration/apm.feature b/x-pack/legacy/plugins/apm/e2e/cypress/integration/apm.feature new file mode 100644 index 0000000000000..01fee2bf68b09 --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/cypress/integration/apm.feature @@ -0,0 +1,7 @@ +Feature: APM + + Scenario: Transaction duration charts + Given a user browses the APM UI application + When the user inspects the opbeans-go service + Then should redirect to correct path with correct params + And should have correct y-axis ticks \ No newline at end of file diff --git a/x-pack/legacy/plugins/apm/cypress/integration/helpers.ts b/x-pack/legacy/plugins/apm/e2e/cypress/integration/helpers.ts similarity index 100% rename from x-pack/legacy/plugins/apm/cypress/integration/helpers.ts rename to x-pack/legacy/plugins/apm/e2e/cypress/integration/helpers.ts diff --git a/x-pack/legacy/plugins/apm/cypress/integration/snapshots.js b/x-pack/legacy/plugins/apm/e2e/cypress/integration/snapshots.js similarity index 61% rename from x-pack/legacy/plugins/apm/cypress/integration/snapshots.js rename to x-pack/legacy/plugins/apm/e2e/cypress/integration/snapshots.js index a2ff071645732..0e4b91ab45a40 100644 --- a/x-pack/legacy/plugins/apm/cypress/integration/snapshots.js +++ b/x-pack/legacy/plugins/apm/e2e/cypress/integration/snapshots.js @@ -8,5 +8,12 @@ module.exports = { } } }, - "__version": "3.4.1" + "__version": "3.8.3", + "APM": { + "Transaction duration charts": { + "1": "3.7 min", + "2": "1.8 min", + "3": "0.0 min" + } + } } diff --git a/x-pack/legacy/plugins/apm/cypress/plugins/index.js b/x-pack/legacy/plugins/apm/e2e/cypress/plugins/index.js similarity index 79% rename from x-pack/legacy/plugins/apm/cypress/plugins/index.js rename to x-pack/legacy/plugins/apm/e2e/cypress/plugins/index.js index 4dd55c16d4a0e..15e469f187651 100644 --- a/x-pack/legacy/plugins/apm/cypress/plugins/index.js +++ b/x-pack/legacy/plugins/apm/e2e/cypress/plugins/index.js @@ -22,22 +22,8 @@ const wp = require('@cypress/webpack-preprocessor'); const fs = require('fs'); module.exports = on => { - // add typescript support const options = { - webpackOptions: { - resolve: { - extensions: ['.ts', '.tsx', '.js'] - }, - module: { - rules: [ - { - test: /\.tsx?$/, - loader: 'ts-loader', - options: { transpileOnly: true } - } - ] - } - } + webpackOptions: require('../webpack.config.js') }; on('file:preprocessor', wp(options)); diff --git a/x-pack/legacy/plugins/apm/e2e/cypress/support/commands.js b/x-pack/legacy/plugins/apm/e2e/cypress/support/commands.js new file mode 100644 index 0000000000000..9a2e54b102c5e --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/cypress/support/commands.js @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This is will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) diff --git a/x-pack/legacy/plugins/apm/e2e/cypress/support/index.ts b/x-pack/legacy/plugins/apm/e2e/cypress/support/index.ts new file mode 100644 index 0000000000000..8a7a9f64cc461 --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/cypress/support/index.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +import './commands'; + +// @ts-ignore +import { register } from '@cypress/snapshot'; + +register(); diff --git a/x-pack/legacy/plugins/apm/e2e/cypress/support/step_definitions/apm.ts b/x-pack/legacy/plugins/apm/e2e/cypress/support/step_definitions/apm.ts new file mode 100644 index 0000000000000..f2f1e515f967a --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/cypress/support/step_definitions/apm.ts @@ -0,0 +1,45 @@ +/* + * 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. + */ + +import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps'; +import { loginAndWaitForPage } from '../../integration/helpers'; + +Given(`a user browses the APM UI application`, () => { + // open service overview page + loginAndWaitForPage(`/app/apm#/services`); +}); + +When(`the user inspects the opbeans-go service`, () => { + // click opbeans-go service + cy.get(':contains(opbeans-go)') + .last() + .click({ force: true }); +}); + +Then(`should redirect to correct path with correct params`, () => { + cy.url().should('contain', `/app/apm#/services/opbeans-go/transactions`); + cy.url().should('contain', `transactionType=request`); +}); + +Then(`should have correct y-axis ticks`, () => { + const yAxisTick = + '[data-cy=transaction-duration-charts] .rv-xy-plot__axis--vertical .rv-xy-plot__axis__tick__text'; + + cy.get(yAxisTick) + .eq(2) + .invoke('text') + .snapshot(); + + cy.get(yAxisTick) + .eq(1) + .invoke('text') + .snapshot(); + + cy.get(yAxisTick) + .eq(0) + .invoke('text') + .snapshot(); +}); diff --git a/x-pack/legacy/plugins/apm/cypress/cypress.d.ts b/x-pack/legacy/plugins/apm/e2e/cypress/typings/index.d.ts similarity index 100% rename from x-pack/legacy/plugins/apm/cypress/cypress.d.ts rename to x-pack/legacy/plugins/apm/e2e/cypress/typings/index.d.ts diff --git a/x-pack/legacy/plugins/apm/e2e/cypress/webpack.config.js b/x-pack/legacy/plugins/apm/e2e/cypress/webpack.config.js new file mode 100644 index 0000000000000..823b23cfdffec --- /dev/null +++ b/x-pack/legacy/plugins/apm/e2e/cypress/webpack.config.js @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + resolve: { + extensions: ['.ts', '.js'] + }, + node: { fs: 'empty', child_process: 'empty', readline: 'empty' }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: [/node_modules/], + use: [ + { + loader: 'ts-loader' + } + ] + }, + { + test: /\.feature$/, + use: [ + { + loader: 'cypress-cucumber-preprocessor/loader' + } + ] + }, + { + test: /\.features$/, + use: [ + { + loader: 'cypress-cucumber-preprocessor/lib/featuresLoader' + } + ] + } + ] + } +}; diff --git a/x-pack/legacy/plugins/apm/cypress/package.json b/x-pack/legacy/plugins/apm/e2e/package.json similarity index 66% rename from x-pack/legacy/plugins/apm/cypress/package.json rename to x-pack/legacy/plugins/apm/e2e/package.json index 59f76ba250ad7..c9026636e64fb 100644 --- a/x-pack/legacy/plugins/apm/cypress/package.json +++ b/x-pack/legacy/plugins/apm/e2e/package.json @@ -5,17 +5,20 @@ "license": "MIT", "scripts": { "cypress:open": "cypress open", - "cypress:run": "cypress run" + "cypress:run": "cypress run --spec **/*.feature" }, "dependencies": { "@cypress/snapshot": "^2.1.3", "@cypress/webpack-preprocessor": "^4.1.0", + "@types/cypress-cucumber-preprocessor": "^1.14.0", "@types/js-yaml": "^3.12.1", + "@types/node": "^10.12.11", "cypress": "^3.5.0", + "cypress-cucumber-preprocessor": "^2.0.1", "js-yaml": "^3.13.1", "p-limit": "^2.2.1", "ts-loader": "^6.1.0", - "typescript": "3.7.2", + "typescript": "3.7.5", "webpack": "^4.41.5" } } diff --git a/x-pack/legacy/plugins/apm/cypress/tsconfig.json b/x-pack/legacy/plugins/apm/e2e/tsconfig.json similarity index 51% rename from x-pack/legacy/plugins/apm/cypress/tsconfig.json rename to x-pack/legacy/plugins/apm/e2e/tsconfig.json index e57b9c86a8f03..de498816e30a4 100644 --- a/x-pack/legacy/plugins/apm/cypress/tsconfig.json +++ b/x-pack/legacy/plugins/apm/e2e/tsconfig.json @@ -1,8 +1,13 @@ { "extends": "../../../../tsconfig.json", "exclude": [], - "include": ["./**/*"], + "include": [ + "./**/*" + ], "compilerOptions": { - "types": ["cypress", "node"] + "types": [ + "cypress", + "node" + ] } } diff --git a/x-pack/legacy/plugins/apm/cypress/yarn.lock b/x-pack/legacy/plugins/apm/e2e/yarn.lock similarity index 61% rename from x-pack/legacy/plugins/apm/cypress/yarn.lock rename to x-pack/legacy/plugins/apm/e2e/yarn.lock index 4940217fa7ce3..48e6013fb6986 100644 --- a/x-pack/legacy/plugins/apm/cypress/yarn.lock +++ b/x-pack/legacy/plugins/apm/e2e/yarn.lock @@ -2,592 +2,758 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" - integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" + integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== dependencies: - "@babel/highlight" "^7.0.0" + "@babel/highlight" "^7.8.3" -"@babel/core@^7.0.1": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.0.tgz#9b00f73554edd67bebc86df8303ef678be3d7b48" - integrity sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helpers" "^7.6.0" - "@babel/parser" "^7.6.0" - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" +"@babel/compat-data@^7.8.4": + version "7.8.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.8.5.tgz#d28ce872778c23551cbb9432fc68d28495b613b9" + integrity sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg== + dependencies: + browserslist "^4.8.5" + invariant "^2.2.4" + semver "^5.5.0" + +"@babel/core@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a" + integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.4.4" + "@babel/helpers" "^7.4.4" + "@babel/parser" "^7.4.5" + "@babel/template" "^7.4.4" + "@babel/traverse" "^7.4.5" + "@babel/types" "^7.4.4" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@^7.0.1": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.8.4.tgz#d496799e5c12195b3602d0fddd77294e3e38e80e" + integrity sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.4" + "@babel/helpers" "^7.8.4" + "@babel/parser" "^7.8.4" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.4" + "@babel/types" "^7.8.3" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.0" lodash "^4.17.13" resolve "^1.3.2" semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.0.tgz#e2c21efbfd3293ad819a2359b448f002bfdfda56" - integrity sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA== +"@babel/generator@^7.4.4", "@babel/generator@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e" + integrity sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA== dependencies: - "@babel/types" "^7.6.0" + "@babel/types" "^7.8.3" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" - trim-right "^1.0.1" -"@babel/helper-annotate-as-pure@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" - integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== +"@babel/helper-annotate-as-pure@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee" + integrity sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" - integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== +"@babel/helper-builder-binary-assignment-operator-visitor@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz#c84097a427a061ac56a1c30ebf54b7b22d241503" + integrity sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw== dependencies: - "@babel/helper-explode-assignable-expression" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-explode-assignable-expression" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-call-delegate@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43" - integrity sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ== +"@babel/helper-builder-react-jsx@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.8.3.tgz#dee98d7d79cc1f003d80b76fe01c7f8945665ff6" + integrity sha512-JT8mfnpTkKNCboTqZsQTdGo3l3Ik3l7QIt9hh0O9DYiwVel37VoJpILKM4YFbP2euF32nkQSb+F9cUk9b7DDXQ== dependencies: - "@babel/helper-hoist-variables" "^7.4.4" - "@babel/traverse" "^7.4.4" - "@babel/types" "^7.4.4" + "@babel/types" "^7.8.3" + esutils "^2.0.0" -"@babel/helper-define-map@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369" - integrity sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg== +"@babel/helper-call-delegate@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz#de82619898aa605d409c42be6ffb8d7204579692" + integrity sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/types" "^7.5.5" + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-compilation-targets@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz#03d7ecd454b7ebe19a254f76617e61770aed2c88" + integrity sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg== + dependencies: + "@babel/compat-data" "^7.8.4" + browserslist "^4.8.5" + invariant "^2.2.4" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/helper-create-class-features-plugin@^7.3.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.8.3.tgz#5b94be88c255f140fd2c10dd151e7f98f4bff397" + integrity sha512-qmp4pD7zeTxsv0JNecSBsEmG1ei2MqwJq4YQcK3ZWm/0t07QstWfvuV/vm3Qt5xNMFETn2SZqpMx2MQzbtq+KA== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + +"@babel/helper-create-regexp-features-plugin@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz#c774268c95ec07ee92476a3862b75cc2839beb79" + integrity sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q== + dependencies: + "@babel/helper-regex" "^7.8.3" + regexpu-core "^4.6.0" + +"@babel/helper-define-map@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz#a0655cad5451c3760b726eba875f1cd8faa02c15" + integrity sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/types" "^7.8.3" lodash "^4.17.13" -"@babel/helper-explode-assignable-expression@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" - integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== +"@babel/helper-explode-assignable-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz#a728dc5b4e89e30fc2dfc7d04fa28a930653f982" + integrity sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw== dependencies: - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-function-name@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" - integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== +"@babel/helper-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca" + integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA== dependencies: - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" -"@babel/helper-get-function-arity@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" - integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== +"@babel/helper-get-function-arity@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5" + integrity sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-hoist-variables@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz#0298b5f25c8c09c53102d52ac4a98f773eb2850a" - integrity sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w== +"@babel/helper-hoist-variables@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz#1dbe9b6b55d78c9b4183fc8cdc6e30ceb83b7134" + integrity sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg== dependencies: - "@babel/types" "^7.4.4" + "@babel/types" "^7.8.3" -"@babel/helper-member-expression-to-functions@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590" - integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA== +"@babel/helper-member-expression-to-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c" + integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA== dependencies: - "@babel/types" "^7.5.5" + "@babel/types" "^7.8.3" -"@babel/helper-module-imports@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" - integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498" + integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz#f84ff8a09038dcbca1fd4355661a500937165b4a" - integrity sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw== +"@babel/helper-module-transforms@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz#d305e35d02bee720fbc2c3c3623aa0c316c01590" + integrity sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/template" "^7.4.4" - "@babel/types" "^7.5.5" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" lodash "^4.17.13" -"@babel/helper-optimise-call-expression@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" - integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== +"@babel/helper-optimise-call-expression@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9" + integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ== dependencies: - "@babel/types" "^7.0.0" + "@babel/types" "^7.8.3" -"@babel/helper-plugin-utils@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" - integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670" + integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ== -"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351" - integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw== +"@babel/helper-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.8.3.tgz#139772607d51b93f23effe72105b319d2a4c6965" + integrity sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ== dependencies: lodash "^4.17.13" -"@babel/helper-remap-async-to-generator@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" - integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== - dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-wrap-function" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.0.0" - -"@babel/helper-replace-supers@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2" - integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.5.5" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/traverse" "^7.5.5" - "@babel/types" "^7.5.5" - -"@babel/helper-simple-access@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" - integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== +"@babel/helper-remap-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz#273c600d8b9bf5006142c1e35887d555c12edd86" + integrity sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-wrap-function" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-replace-supers@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz#91192d25f6abbcd41da8a989d4492574fb1530bc" + integrity sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-simple-access@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae" + integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw== + dependencies: + "@babel/template" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helper-split-export-declaration@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9" + integrity sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA== + dependencies: + "@babel/types" "^7.8.3" + +"@babel/helper-wrap-function@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz#9dbdb2bb55ef14aaa01fe8c99b629bd5352d8610" + integrity sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ== + dependencies: + "@babel/helper-function-name" "^7.8.3" + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/helpers@^7.4.4", "@babel/helpers@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.8.4.tgz#754eb3ee727c165e0a240d6c207de7c455f36f73" + integrity sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w== + dependencies: + "@babel/template" "^7.8.3" + "@babel/traverse" "^7.8.4" + "@babel/types" "^7.8.3" + +"@babel/highlight@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797" + integrity sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg== dependencies: - "@babel/template" "^7.1.0" - "@babel/types" "^7.0.0" + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" -"@babel/helper-split-export-declaration@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz#ff94894a340be78f53f06af038b205c49d993677" - integrity sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q== +"@babel/parser@^7.4.5", "@babel/parser@^7.8.3", "@babel/parser@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8" + integrity sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw== + +"@babel/plugin-proposal-async-generator-functions@^7.2.0", "@babel/plugin-proposal-async-generator-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz#bad329c670b382589721b27540c7d288601c6e6f" + integrity sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw== dependencies: - "@babel/types" "^7.4.4" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" -"@babel/helper-wrap-function@^7.1.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" - integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ== +"@babel/plugin-proposal-class-properties@7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz#272636bc0fa19a0bc46e601ec78136a173ea36cd" + integrity sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/template" "^7.1.0" - "@babel/traverse" "^7.1.0" - "@babel/types" "^7.2.0" + "@babel/helper-create-class-features-plugin" "^7.3.0" + "@babel/helper-plugin-utils" "^7.0.0" -"@babel/helpers@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.0.tgz#21961d16c6a3c3ab597325c34c465c0887d31c6e" - integrity sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ== +"@babel/plugin-proposal-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz#38c4fe555744826e97e2ae930b0fb4cc07e66054" + integrity sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w== dependencies: - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" -"@babel/highlight@^7.0.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540" - integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ== +"@babel/plugin-proposal-json-strings@^7.2.0", "@babel/plugin-proposal-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz#da5216b238a98b58a1e05d6852104b10f9a70d6b" + integrity sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q== dependencies: - chalk "^2.0.0" - esutils "^2.0.2" - js-tokens "^4.0.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.0" -"@babel/parser@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.0.tgz#3e05d0647432a8326cb28d0de03895ae5a57f39b" - integrity sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ== +"@babel/plugin-proposal-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz#e4572253fdeed65cddeecfdab3f928afeb2fd5d2" + integrity sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" -"@babel/plugin-proposal-async-generator-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" - integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ== +"@babel/plugin-proposal-object-rest-spread@7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.2.tgz#6d1859882d4d778578e41f82cc5d7bf3d5daf6c1" + integrity sha512-DjeMS+J2+lpANkYLLO+m6GjoTMygYglKmRe6cDTbFv3L9i6mmiE8fe6B8MtCSLZpVXscD5kn7s6SgtHrDoBWoA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" - "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" -"@babel/plugin-proposal-dynamic-import@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506" - integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw== +"@babel/plugin-proposal-object-rest-spread@^7.4.4", "@babel/plugin-proposal-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz#eb5ae366118ddca67bed583b53d7554cad9951bb" + integrity sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" -"@babel/plugin-proposal-json-strings@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" - integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg== +"@babel/plugin-proposal-optional-catch-binding@^7.2.0", "@babel/plugin-proposal-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz#9dee96ab1650eed88646ae9734ca167ac4a9c5c9" + integrity sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" -"@babel/plugin-proposal-object-rest-spread@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58" - integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw== +"@babel/plugin-proposal-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz#ae10b3214cb25f7adb1f3bc87ba42ca10b7e2543" + integrity sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" -"@babel/plugin-proposal-optional-catch-binding@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" - integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== +"@babel/plugin-proposal-unicode-property-regex@^7.4.4", "@babel/plugin-proposal-unicode-property-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz#b646c3adea5f98800c9ab45105ac34d06cd4a47f" + integrity sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78" - integrity sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA== +"@babel/plugin-syntax-async-generators@^7.2.0", "@babel/plugin-syntax-async-generators@^7.8.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-async-generators@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" - integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== +"@babel/plugin-syntax-dynamic-import@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-dynamic-import@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612" - integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w== +"@babel/plugin-syntax-json-strings@^7.2.0", "@babel/plugin-syntax-json-strings@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-json-strings@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" - integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg== +"@babel/plugin-syntax-jsx@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz#521b06c83c40480f1e58b4fd33b92eceb1d6ea94" + integrity sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-syntax-object-rest-spread@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" - integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-syntax-optional-catch-binding@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" - integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== +"@babel/plugin-syntax-object-rest-spread@^7.2.0", "@babel/plugin-syntax-object-rest-spread@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-arrow-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" - integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg== +"@babel/plugin-syntax-optional-catch-binding@^7.2.0", "@babel/plugin-syntax-optional-catch-binding@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-async-to-generator@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e" - integrity sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg== +"@babel/plugin-syntax-optional-chaining@^7.8.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: - "@babel/helper-module-imports" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/helper-plugin-utils" "^7.8.0" -"@babel/plugin-transform-block-scoped-functions@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" - integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w== +"@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz#3acdece695e6b13aaf57fc291d1a800950c71391" + integrity sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-block-scoping@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.0.tgz#c49e21228c4bbd4068a35667e6d951c75439b1dc" - integrity sha512-tIt4E23+kw6TgL/edACZwP1OUKrjOTyMrFMLoT5IOFrfMRabCgekjqFd5o6PaAMildBu46oFkekIdMuGkkPEpA== +"@babel/plugin-transform-arrow-functions@^7.2.0", "@babel/plugin-transform-arrow-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz#82776c2ed0cd9e1a49956daeb896024c9473b8b6" + integrity sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - lodash "^4.17.13" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-classes@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9" - integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg== +"@babel/plugin-transform-async-to-generator@^7.4.4", "@babel/plugin-transform-async-to-generator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz#4308fad0d9409d71eafb9b1a6ee35f9d64b64086" + integrity sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-define-map" "^7.5.5" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-optimise-call-expression" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" - "@babel/helper-split-export-declaration" "^7.4.4" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-remap-async-to-generator" "^7.8.3" + +"@babel/plugin-transform-block-scoped-functions@^7.2.0", "@babel/plugin-transform-block-scoped-functions@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz#437eec5b799b5852072084b3ae5ef66e8349e8a3" + integrity sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-block-scoping@^7.4.4", "@babel/plugin-transform-block-scoping@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz#97d35dab66857a437c166358b91d09050c868f3a" + integrity sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + lodash "^4.17.13" + +"@babel/plugin-transform-classes@^7.4.4", "@babel/plugin-transform-classes@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz#46fd7a9d2bb9ea89ce88720477979fe0d71b21b8" + integrity sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-define-map" "^7.8.3" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-optimise-call-expression" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" - integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA== +"@babel/plugin-transform-computed-properties@^7.2.0", "@babel/plugin-transform-computed-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz#96d0d28b7f7ce4eb5b120bb2e0e943343c86f81b" + integrity sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-destructuring@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz#44bbe08b57f4480094d57d9ffbcd96d309075ba6" - integrity sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ== +"@babel/plugin-transform-destructuring@^7.4.4", "@babel/plugin-transform-destructuring@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz#20ddfbd9e4676906b1056ee60af88590cc7aaa0b" + integrity sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz#361a148bc951444312c69446d76ed1ea8e4450c3" - integrity sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg== +"@babel/plugin-transform-dotall-regex@^7.4.4", "@babel/plugin-transform-dotall-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz#c3c6ec5ee6125c6993c5cbca20dc8621a9ea7a6e" + integrity sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-duplicate-keys@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853" - integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ== +"@babel/plugin-transform-duplicate-keys@^7.2.0", "@babel/plugin-transform-duplicate-keys@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz#8d12df309aa537f272899c565ea1768e286e21f1" + integrity sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-exponentiation-operator@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" - integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A== +"@babel/plugin-transform-exponentiation-operator@^7.2.0", "@babel/plugin-transform-exponentiation-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz#581a6d7f56970e06bf51560cd64f5e947b70d7b7" + integrity sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ== dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-for-of@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556" - integrity sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ== +"@babel/plugin-transform-for-of@^7.4.4", "@babel/plugin-transform-for-of@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz#6fe8eae5d6875086ee185dd0b098a8513783b47d" + integrity sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-function-name@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz#e1436116abb0610c2259094848754ac5230922ad" - integrity sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA== +"@babel/plugin-transform-function-name@^7.4.4", "@babel/plugin-transform-function-name@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz#279373cb27322aaad67c2683e776dfc47196ed8b" + integrity sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ== dependencies: - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" - integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg== +"@babel/plugin-transform-literals@^7.2.0", "@babel/plugin-transform-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz#aef239823d91994ec7b68e55193525d76dbd5dc1" + integrity sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-member-expression-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz#fa10aa5c58a2cb6afcf2c9ffa8cb4d8b3d489a2d" - integrity sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA== +"@babel/plugin-transform-member-expression-literals@^7.2.0", "@babel/plugin-transform-member-expression-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz#963fed4b620ac7cbf6029c755424029fa3a40410" + integrity sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-modules-amd@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91" - integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg== +"@babel/plugin-transform-modules-amd@^7.2.0", "@babel/plugin-transform-modules-amd@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz#65606d44616b50225e76f5578f33c568a0b876a5" + integrity sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-commonjs@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz#39dfe957de4420445f1fcf88b68a2e4aa4515486" - integrity sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g== +"@babel/plugin-transform-modules-commonjs@7.8.3", "@babel/plugin-transform-modules-commonjs@^7.4.4", "@babel/plugin-transform-modules-commonjs@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz#df251706ec331bd058a34bdd72613915f82928a5" + integrity sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg== dependencies: - "@babel/helper-module-transforms" "^7.4.4" - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-simple-access" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-systemjs@^7.5.0": - version "7.5.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249" - integrity sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg== +"@babel/plugin-transform-modules-systemjs@^7.4.4", "@babel/plugin-transform-modules-systemjs@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz#d8bbf222c1dbe3661f440f2f00c16e9bb7d0d420" + integrity sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg== dependencies: - "@babel/helper-hoist-variables" "^7.4.4" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-hoist-variables" "^7.8.3" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" babel-plugin-dynamic-import-node "^2.3.0" -"@babel/plugin-transform-modules-umd@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" - integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw== +"@babel/plugin-transform-modules-umd@^7.2.0", "@babel/plugin-transform-modules-umd@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz#592d578ce06c52f5b98b02f913d653ffe972661a" + integrity sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw== dependencies: - "@babel/helper-module-transforms" "^7.1.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-module-transforms" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-named-capturing-groups-regex@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.0.tgz#1e6e663097813bb4f53d42df0750cf28ad3bb3f1" - integrity sha512-jem7uytlmrRl3iCAuQyw8BpB4c4LWvSpvIeXKpMb+7j84lkx4m4mYr5ErAcmN5KM7B6BqrAvRGjBIbbzqCczew== +"@babel/plugin-transform-named-capturing-groups-regex@^7.4.5", "@babel/plugin-transform-named-capturing-groups-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz#a2a72bffa202ac0e2d0506afd0939c5ecbc48c6c" + integrity sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw== dependencies: - regexp-tree "^0.1.13" + "@babel/helper-create-regexp-features-plugin" "^7.8.3" -"@babel/plugin-transform-new-target@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz#18d120438b0cc9ee95a47f2c72bc9768fbed60a5" - integrity sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA== +"@babel/plugin-transform-new-target@^7.4.4", "@babel/plugin-transform-new-target@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz#60cc2ae66d85c95ab540eb34babb6434d4c70c43" + integrity sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-object-super@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9" - integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ== +"@babel/plugin-transform-object-super@^7.2.0", "@babel/plugin-transform-object-super@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz#ebb6a1e7a86ffa96858bd6ac0102d65944261725" + integrity sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-replace-supers" "^7.5.5" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-replace-supers" "^7.8.3" -"@babel/plugin-transform-parameters@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz#7556cf03f318bd2719fe4c922d2d808be5571e16" - integrity sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw== +"@babel/plugin-transform-parameters@^7.4.4", "@babel/plugin-transform-parameters@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz#1d5155de0b65db0ccf9971165745d3bb990d77d3" + integrity sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA== dependencies: - "@babel/helper-call-delegate" "^7.4.4" - "@babel/helper-get-function-arity" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-call-delegate" "^7.8.3" + "@babel/helper-get-function-arity" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-property-literals@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz#03e33f653f5b25c4eb572c98b9485055b389e905" - integrity sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ== +"@babel/plugin-transform-property-literals@^7.2.0", "@babel/plugin-transform-property-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz#33194300d8539c1ed28c62ad5087ba3807b98263" + integrity sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-regenerator@^7.4.5": - version "7.4.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz#629dc82512c55cee01341fb27bdfcb210354680f" - integrity sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA== +"@babel/plugin-transform-react-display-name@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz#70ded987c91609f78353dd76d2fb2a0bb991e8e5" + integrity sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-transform-react-jsx-self@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.8.3.tgz#c4f178b2aa588ecfa8d077ea80d4194ee77ed702" + integrity sha512-01OT7s5oa0XTLf2I8XGsL8+KqV9lx3EZV+jxn/L2LQ97CGKila2YMroTkCEIE0HV/FF7CMSRsIAybopdN9NTdg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-react-jsx-source@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.8.3.tgz#951e75a8af47f9f120db731be095d2b2c34920e0" + integrity sha512-PLMgdMGuVDtRS/SzjNEQYUT8f4z1xb2BAT54vM1X5efkVuYBf5WyGUMbpmARcfq3NaglIwz08UVQK4HHHbC6ag== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-react-jsx@^7.0.0": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.8.3.tgz#4220349c0390fdefa505365f68c103562ab2fc4a" + integrity sha512-r0h+mUiyL595ikykci+fbwm9YzmuOrUBi0b+FDIKmi3fPQyFokWVEMJnRWHJPPQEjyFJyna9WZC6Viv6UHSv1g== + dependencies: + "@babel/helper-builder-react-jsx" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.8.3" + +"@babel/plugin-transform-regenerator@^7.4.5", "@babel/plugin-transform-regenerator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz#b31031e8059c07495bf23614c97f3d9698bc6ec8" + integrity sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA== dependencies: regenerator-transform "^0.14.0" -"@babel/plugin-transform-reserved-words@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz#4792af87c998a49367597d07fedf02636d2e1634" - integrity sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw== +"@babel/plugin-transform-reserved-words@^7.2.0", "@babel/plugin-transform-reserved-words@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz#9a0635ac4e665d29b162837dd3cc50745dfdf1f5" + integrity sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-shorthand-properties@^7.2.0": +"@babel/plugin-transform-runtime@7.2.0": version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" - integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg== + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.2.0.tgz#566bc43f7d0aedc880eaddbd29168d0f248966ea" + integrity sha512-jIgkljDdq4RYDnJyQsiWbdvGeei/0MOTtSHKO/rfbd/mXBxNpdlulMx49L0HQ4pug1fXannxoqCI+fYSle9eSw== dependencies: + "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" + resolve "^1.8.1" + semver "^5.5.1" -"@babel/plugin-transform-spread@^7.2.0": - version "7.2.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" - integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w== +"@babel/plugin-transform-shorthand-properties@^7.2.0", "@babel/plugin-transform-shorthand-properties@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz#28545216e023a832d4d3a1185ed492bcfeac08c8" + integrity sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-sticky-regex@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" - integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw== +"@babel/plugin-transform-spread@^7.2.0", "@babel/plugin-transform-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz#9c8ffe8170fdfb88b114ecb920b82fb6e95fe5e8" + integrity sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-template-literals@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz#9d28fea7bbce637fb7612a0750989d8321d4bcb0" - integrity sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g== +"@babel/plugin-transform-sticky-regex@^7.2.0", "@babel/plugin-transform-sticky-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz#be7a1290f81dae767475452199e1f76d6175b100" + integrity sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw== dependencies: - "@babel/helper-annotate-as-pure" "^7.0.0" - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/helper-regex" "^7.8.3" -"@babel/plugin-transform-typeof-symbol@^7.2.0": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" - integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw== +"@babel/plugin-transform-template-literals@^7.4.4", "@babel/plugin-transform-template-literals@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz#7bfa4732b455ea6a43130adc0ba767ec0e402a80" + integrity sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-annotate-as-pure" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/plugin-transform-unicode-regex@^7.4.4": - version "7.4.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz#ab4634bb4f14d36728bf5978322b35587787970f" - integrity sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA== +"@babel/plugin-transform-typeof-symbol@^7.2.0", "@babel/plugin-transform-typeof-symbol@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz#ede4062315ce0aaf8a657a920858f1a2f35fc412" + integrity sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg== dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/helper-regex" "^7.4.4" - regexpu-core "^4.5.4" + "@babel/helper-plugin-utils" "^7.8.3" -"@babel/preset-env@^7.0.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.6.0.tgz#aae4141c506100bb2bfaa4ac2a5c12b395619e50" - integrity sha512-1efzxFv/TcPsNXlRhMzRnkBFMeIqBBgzwmZwlFDw5Ubj0AGLeufxugirwZmkkX/ayi3owsSqoQ4fw8LkfK9SYg== +"@babel/plugin-transform-unicode-regex@^7.4.4", "@babel/plugin-transform-unicode-regex@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz#0cef36e3ba73e5c57273effb182f46b91a1ecaad" + integrity sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/preset-env@7.4.5": + version "7.4.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.5.tgz#2fad7f62983d5af563b5f3139242755884998a58" + integrity sha512-f2yNVXM+FsR5V8UwcFeIHzHWgnhXg3NpRmy0ADvALpnhB0SLbCvrCRr4BLOUYbQNLS+Z0Yer46x9dJXpXewI7w== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-proposal-async-generator-functions" "^7.2.0" - "@babel/plugin-proposal-dynamic-import" "^7.5.0" "@babel/plugin-proposal-json-strings" "^7.2.0" - "@babel/plugin-proposal-object-rest-spread" "^7.5.5" + "@babel/plugin-proposal-object-rest-spread" "^7.4.4" "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" "@babel/plugin-syntax-async-generators" "^7.2.0" - "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-syntax-json-strings" "^7.2.0" "@babel/plugin-syntax-object-rest-spread" "^7.2.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" "@babel/plugin-transform-arrow-functions" "^7.2.0" - "@babel/plugin-transform-async-to-generator" "^7.5.0" + "@babel/plugin-transform-async-to-generator" "^7.4.4" "@babel/plugin-transform-block-scoped-functions" "^7.2.0" - "@babel/plugin-transform-block-scoping" "^7.6.0" - "@babel/plugin-transform-classes" "^7.5.5" + "@babel/plugin-transform-block-scoping" "^7.4.4" + "@babel/plugin-transform-classes" "^7.4.4" "@babel/plugin-transform-computed-properties" "^7.2.0" - "@babel/plugin-transform-destructuring" "^7.6.0" + "@babel/plugin-transform-destructuring" "^7.4.4" "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/plugin-transform-duplicate-keys" "^7.5.0" + "@babel/plugin-transform-duplicate-keys" "^7.2.0" "@babel/plugin-transform-exponentiation-operator" "^7.2.0" "@babel/plugin-transform-for-of" "^7.4.4" "@babel/plugin-transform-function-name" "^7.4.4" "@babel/plugin-transform-literals" "^7.2.0" "@babel/plugin-transform-member-expression-literals" "^7.2.0" - "@babel/plugin-transform-modules-amd" "^7.5.0" - "@babel/plugin-transform-modules-commonjs" "^7.6.0" - "@babel/plugin-transform-modules-systemjs" "^7.5.0" + "@babel/plugin-transform-modules-amd" "^7.2.0" + "@babel/plugin-transform-modules-commonjs" "^7.4.4" + "@babel/plugin-transform-modules-systemjs" "^7.4.4" "@babel/plugin-transform-modules-umd" "^7.2.0" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.6.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5" "@babel/plugin-transform-new-target" "^7.4.4" - "@babel/plugin-transform-object-super" "^7.5.5" + "@babel/plugin-transform-object-super" "^7.2.0" "@babel/plugin-transform-parameters" "^7.4.4" "@babel/plugin-transform-property-literals" "^7.2.0" "@babel/plugin-transform-regenerator" "^7.4.5" @@ -598,46 +764,150 @@ "@babel/plugin-transform-template-literals" "^7.4.4" "@babel/plugin-transform-typeof-symbol" "^7.2.0" "@babel/plugin-transform-unicode-regex" "^7.4.4" - "@babel/types" "^7.6.0" + "@babel/types" "^7.4.4" browserslist "^4.6.0" core-js-compat "^3.1.1" invariant "^2.2.2" js-levenshtein "^1.1.3" semver "^5.5.0" -"@babel/template@^7.1.0", "@babel/template@^7.4.4", "@babel/template@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.6.0.tgz#7f0159c7f5012230dad64cca42ec9bdb5c9536e6" - integrity sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ== +"@babel/preset-env@^7.0.0": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.8.4.tgz#9dac6df5f423015d3d49b6e9e5fa3413e4a72c4e" + integrity sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w== + dependencies: + "@babel/compat-data" "^7.8.4" + "@babel/helper-compilation-targets" "^7.8.4" + "@babel/helper-module-imports" "^7.8.3" + "@babel/helper-plugin-utils" "^7.8.3" + "@babel/plugin-proposal-async-generator-functions" "^7.8.3" + "@babel/plugin-proposal-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-json-strings" "^7.8.3" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-object-rest-spread" "^7.8.3" + "@babel/plugin-proposal-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining" "^7.8.3" + "@babel/plugin-proposal-unicode-property-regex" "^7.8.3" + "@babel/plugin-syntax-async-generators" "^7.8.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.0" + "@babel/plugin-syntax-json-strings" "^7.8.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.0" + "@babel/plugin-syntax-optional-chaining" "^7.8.0" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.8.3" + "@babel/plugin-transform-async-to-generator" "^7.8.3" + "@babel/plugin-transform-block-scoped-functions" "^7.8.3" + "@babel/plugin-transform-block-scoping" "^7.8.3" + "@babel/plugin-transform-classes" "^7.8.3" + "@babel/plugin-transform-computed-properties" "^7.8.3" + "@babel/plugin-transform-destructuring" "^7.8.3" + "@babel/plugin-transform-dotall-regex" "^7.8.3" + "@babel/plugin-transform-duplicate-keys" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator" "^7.8.3" + "@babel/plugin-transform-for-of" "^7.8.4" + "@babel/plugin-transform-function-name" "^7.8.3" + "@babel/plugin-transform-literals" "^7.8.3" + "@babel/plugin-transform-member-expression-literals" "^7.8.3" + "@babel/plugin-transform-modules-amd" "^7.8.3" + "@babel/plugin-transform-modules-commonjs" "^7.8.3" + "@babel/plugin-transform-modules-systemjs" "^7.8.3" + "@babel/plugin-transform-modules-umd" "^7.8.3" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.8.3" + "@babel/plugin-transform-new-target" "^7.8.3" + "@babel/plugin-transform-object-super" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.8.4" + "@babel/plugin-transform-property-literals" "^7.8.3" + "@babel/plugin-transform-regenerator" "^7.8.3" + "@babel/plugin-transform-reserved-words" "^7.8.3" + "@babel/plugin-transform-shorthand-properties" "^7.8.3" + "@babel/plugin-transform-spread" "^7.8.3" + "@babel/plugin-transform-sticky-regex" "^7.8.3" + "@babel/plugin-transform-template-literals" "^7.8.3" + "@babel/plugin-transform-typeof-symbol" "^7.8.4" + "@babel/plugin-transform-unicode-regex" "^7.8.3" + "@babel/types" "^7.8.3" + browserslist "^4.8.5" + core-js-compat "^3.6.2" + invariant "^2.2.2" + levenary "^1.1.1" + semver "^5.5.0" + +"@babel/preset-react@7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0" + integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w== dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.0": - version "7.6.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.0.tgz#389391d510f79be7ce2ddd6717be66d3fed4b516" - integrity sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-self" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + +"@babel/runtime@7.3.1": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.1.tgz#574b03e8e8a9898eaf4a872a92ea20b7846f6f2a" + integrity sha512-7jGW8ppV0ant637pIqAcFfQDDH1orEPGJb8aXfUozuCU3QqX7rX4DA8iwrbPrR1hcH0FTTHz47yQnk+bl5xHQA== + dependencies: + regenerator-runtime "^0.12.0" + +"@babel/template@^7.4.4", "@babel/template@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" + integrity sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/parser" "^7.8.3" + "@babel/types" "^7.8.3" + +"@babel/traverse@^7.4.5", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.4.tgz#f0845822365f9d5b0e312ed3959d3f827f869e3c" + integrity sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg== + dependencies: + "@babel/code-frame" "^7.8.3" + "@babel/generator" "^7.8.4" + "@babel/helper-function-name" "^7.8.3" + "@babel/helper-split-export-declaration" "^7.8.3" + "@babel/parser" "^7.8.4" + "@babel/types" "^7.8.3" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0": - version "7.6.1" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648" - integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g== +"@babel/types@^7.4.4", "@babel/types@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c" + integrity sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg== dependencies: esutils "^2.0.2" lodash "^4.17.13" to-fast-properties "^2.0.0" +"@cypress/browserify-preprocessor@^2.1.1": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@cypress/browserify-preprocessor/-/browserify-preprocessor-2.1.3.tgz#abbb8ba52ff33d70745c056e8fc675db2276a539" + integrity sha512-vZskc/EKejnmdm4fMGB1Fm39WelsF4HJHeI5q8I0LvGnrdvxSiCbn27TbhCM5Enq6Fkinf3f7oiHS/m2OUgzdA== + dependencies: + "@babel/core" "7.4.5" + "@babel/plugin-proposal-class-properties" "7.3.0" + "@babel/plugin-proposal-object-rest-spread" "7.3.2" + "@babel/plugin-transform-modules-commonjs" "7.8.3" + "@babel/plugin-transform-runtime" "7.2.0" + "@babel/preset-env" "7.4.5" + "@babel/preset-react" "7.0.0" + "@babel/runtime" "7.3.1" + babelify "10.0.0" + bluebird "3.5.3" + browserify "16.2.3" + coffeeify "3.0.1" + coffeescript "1.12.7" + debug "4.1.1" + fs-extra "7.0.1" + lodash.clonedeep "4.5.0" + watchify "3.11.1" + "@cypress/listr-verbose-renderer@0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" @@ -649,26 +919,26 @@ figures "^1.7.0" "@cypress/snapshot@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@cypress/snapshot/-/snapshot-2.1.3.tgz#85f5c40c4c27df9185773062a5d29e3f1a8d0b1f" - integrity sha512-NAkyxFxeFSJt6K/McsF3u8RoSqLi3trYlO5ZkfoeiKcNm8J0eX4o6Uz12xMPcfTi5Ql9RMyZg6/j6XJU6tBjiQ== + version "2.1.7" + resolved "https://registry.yarnpkg.com/@cypress/snapshot/-/snapshot-2.1.7.tgz#e7360eb628b062f28f03036382619ec72cfb1831" + integrity sha512-f8AcfIg7wOOHSdBODlIwCJE/rG5Yb+kUY+WVTKynB2pLLoDy9nc8CtcazqX19q2Lh++nTJLNRihpbbWvk33mbg== dependencies: - "@wildpeaks/snapshot-dom" "1.2.1" + "@wildpeaks/snapshot-dom" "1.6.0" am-i-a-dependency "1.1.2" check-more-types "2.24.0" its-name "1.0.0" - js-beautify "1.10.0" + js-beautify "1.10.3" lazy-ass "1.6.0" snap-shot-compare "2.8.3" snap-shot-store "1.2.3" "@cypress/webpack-preprocessor@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.1.0.tgz#8c4debc0b1abf045b62524d1996dd9aeaf7e86a8" - integrity sha512-LbxsdYVpHGoC2fMOdW0aQvuvVRD7aZx8p8DrP53HISpl7bD1PqLGWKzhHn7cGG24UHycBJrbaEeKEosW29W1dg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.1.1.tgz#3c0b5b8de6eaac605dac3b1f1c3f5916c1c6eaea" + integrity sha512-SfzDqOvWBSlfGRm8ak/XHUXAnndwHU2qJIRr1LIC7j2UqWcZoJ+286CuNloJbkwfyEAO6tQggLd4E/WHUAcKZQ== dependencies: - bluebird "3.5.0" - debug "3.1.0" + bluebird "3.7.1" + debug "4.1.1" optionalDependencies: "@babel/core" "^7.0.1" "@babel/preset-env" "^7.0.0" @@ -682,10 +952,25 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@types/cypress-cucumber-preprocessor@^1.14.0": + version "1.14.0" + resolved "https://registry.yarnpkg.com/@types/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-1.14.0.tgz#41d8ffb2b608d3ed4ab998a0c4394056f75af1e0" + integrity sha512-bOl4u6seZtxNIGa6J6xydroPntTxxWy8uqIrZ3OY10C96fUes4mZvJKY6NvOoe61/OVafG/UEFa+X2ZWKE6Ltw== + "@types/js-yaml@^3.12.1": - version "3.12.1" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656" - integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA== + version "3.12.2" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.2.tgz#a35a1809c33a68200fb6403d1ad708363c56470a" + integrity sha512-0CFu/g4mDSNkodVwWijdlr8jH7RoplRWNgovjFLEZeT+QEbbZXjBmCe3HwaWheAlCbHwomTwzZoSedeOycABug== + +"@types/node@^10.12.11": + version "10.17.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.14.tgz#b6c60ebf2fb5e4229fdd751ff9ddfae0f5f31541" + integrity sha512-G0UmX5uKEmW+ZAhmZ6PLTQ5eu/VPaT+d/tdLd5IFsKRPcbe6lPxocBtcYBFSaLaCW8O60AX90e91Nsp8lVHCNw== + +"@types/sizzle@2.3.2": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" + integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== "@webassemblyjs/ast@1.8.5": version "1.8.5" @@ -833,10 +1118,10 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" -"@wildpeaks/snapshot-dom@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@wildpeaks/snapshot-dom/-/snapshot-dom-1.2.1.tgz#c4af08cdb175d61dca2878c7a2d91253158f6086" - integrity sha1-xK8IzbF11h3KKHjHotkSUxWPYIY= +"@wildpeaks/snapshot-dom@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@wildpeaks/snapshot-dom/-/snapshot-dom-1.6.0.tgz#83297612bf93b97983beafbe6ae71672642ac884" + integrity sha512-fCM5tYK6VZ1nhbk3Q11lkf6UOJlOCRU0oScQ8NV8OYBPC58wQmQaOF9g+rk+yhNYf3beybOBr+ZuiNen3B0Bxw== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -848,15 +1133,42 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +JSONStream@^1.0.3: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +acorn-node@^1.2.0, acorn-node@^1.3.0, acorn-node@^1.5.2, acorn-node@^1.6.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.1.0.tgz#48387aa9a83bba67a9909164acab4bbc5796cf87" + integrity sha512-4ufNLdC8gOf1dlOjC1nrn2NfzevyDtrDPp/DOtmoOHAFA/1pQc6bWf7oZ71qDURTODPLQ03+oFOvwxq5BvjXug== + acorn@^6.2.1: - version "6.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" - integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== + version "6.4.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.0.tgz#b659d2ffbafa24baf5db1cdbb2c94a983ecd2784" + integrity sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw== + +acorn@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c" + integrity sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ== ajv-errors@^1.0.0: version "1.0.1" @@ -869,11 +1181,11 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.5: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" - integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== dependencies: - fast-deep-equal "^2.0.1" + fast-deep-equal "^3.1.1" fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.4.1" uri-js "^4.2.2" @@ -915,6 +1227,11 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -989,7 +1306,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert@^1.1.1: +assert@^1.1.1, assert@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== @@ -997,6 +1314,20 @@ assert@^1.1.1: object-assign "^4.1.1" util "0.10.3" +assertion-error-formatter@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error-formatter/-/assertion-error-formatter-2.0.1.tgz#6bbdffaec8e2fa9e2b0eb158bfe353132d7c0a9b" + integrity sha512-cjC3jUCh9spkroKue5PDSKH5RFQ/KNuZJhk3GwHYmB/8qqETxLOmMdLH+ohi/VukNzxDlMvIe7zScvLoOdhb6Q== + dependencies: + diff "^3.0.0" + pad-right "^0.2.2" + repeat-string "^1.6.1" + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -1019,7 +1350,7 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -atob@^2.1.1: +atob@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== @@ -1030,9 +1361,9 @@ aws-sign2@~0.7.0: integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== + version "1.9.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e" + integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug== babel-loader@^8.0.2: version "8.0.6" @@ -1051,6 +1382,19 @@ babel-plugin-dynamic-import-node@^2.3.0: dependencies: object.assign "^4.1.0" +babel-runtime@^6.11.6: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babelify@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5" + integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg== + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -1081,6 +1425,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +becke-ch--regex--s0-0-v1--base--pl--lib@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/becke-ch--regex--s0-0-v1--base--pl--lib/-/becke-ch--regex--s0-0-v1--base--pl--lib-1.4.0.tgz#429ceebbfa5f7e936e78d73fbdc7da7162b20e20" + integrity sha1-Qpzuu/pffpNueNc/vcfacWKyDiA= + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1091,15 +1440,32 @@ binary-extensions@^1.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + bluebird@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw= -bluebird@^3.5.5: - version "3.5.5" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" - integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== +bluebird@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + +bluebird@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de" + integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg== + +bluebird@^3.4.1, bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" @@ -1142,6 +1508,25 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-pack@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" + integrity sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA== + dependencies: + JSONStream "^1.0.3" + combine-source-map "~0.8.0" + defined "^1.0.0" + safe-buffer "^5.1.1" + through2 "^2.0.0" + umd "^3.0.0" + +browser-resolve@^1.11.0, browser-resolve@^1.7.0: + version "1.11.3" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6" + integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ== + dependencies: + resolve "1.1.7" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" @@ -1194,21 +1579,129 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.2.0: +browserify-zlib@^0.2.0, browserify-zlib@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" -browserslist@^4.6.0, browserslist@^4.6.6: - version "4.7.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.7.0.tgz#9ee89225ffc07db03409f2fee524dc8227458a17" - integrity sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA== +browserify@16.2.3: + version "16.2.3" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.3.tgz#7ee6e654ba4f92bce6ab3599c3485b1cc7a0ad0b" + integrity sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ== + dependencies: + JSONStream "^1.0.3" + assert "^1.4.0" + browser-pack "^6.0.1" + browser-resolve "^1.11.0" + browserify-zlib "~0.2.0" + buffer "^5.0.2" + cached-path-relative "^1.0.0" + concat-stream "^1.6.0" + console-browserify "^1.1.0" + constants-browserify "~1.0.0" + crypto-browserify "^3.0.0" + defined "^1.0.0" + deps-sort "^2.0.0" + domain-browser "^1.2.0" + duplexer2 "~0.1.2" + events "^2.0.0" + glob "^7.1.0" + has "^1.0.0" + htmlescape "^1.1.0" + https-browserify "^1.0.0" + inherits "~2.0.1" + insert-module-globals "^7.0.0" + labeled-stream-splicer "^2.0.0" + mkdirp "^0.5.0" + module-deps "^6.0.0" + os-browserify "~0.3.0" + parents "^1.0.1" + path-browserify "~0.0.0" + process "~0.11.0" + punycode "^1.3.2" + querystring-es3 "~0.2.0" + read-only-stream "^2.0.0" + readable-stream "^2.0.2" + resolve "^1.1.4" + shasum "^1.0.0" + shell-quote "^1.6.1" + stream-browserify "^2.0.0" + stream-http "^2.0.0" + string_decoder "^1.1.1" + subarg "^1.0.0" + syntax-error "^1.1.1" + through2 "^2.0.0" + timers-browserify "^1.0.1" + tty-browserify "0.0.1" + url "~0.11.0" + util "~0.10.1" + vm-browserify "^1.0.0" + xtend "^4.0.0" + +browserify@^16.1.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.0.tgz#a1c2bc0431bec11fd29151941582e3f645ede881" + integrity sha512-6bfI3cl76YLAnCZ75AGu/XPOsqUhRyc0F/olGIJeCxtfxF2HvPKEcmjU9M8oAPxl4uBY1U7Nry33Q6koV3f2iw== + dependencies: + JSONStream "^1.0.3" + assert "^1.4.0" + browser-pack "^6.0.1" + browser-resolve "^1.11.0" + browserify-zlib "~0.2.0" + buffer "^5.0.2" + cached-path-relative "^1.0.0" + concat-stream "^1.6.0" + console-browserify "^1.1.0" + constants-browserify "~1.0.0" + crypto-browserify "^3.0.0" + defined "^1.0.0" + deps-sort "^2.0.0" + domain-browser "^1.2.0" + duplexer2 "~0.1.2" + events "^2.0.0" + glob "^7.1.0" + has "^1.0.0" + htmlescape "^1.1.0" + https-browserify "^1.0.0" + inherits "~2.0.1" + insert-module-globals "^7.0.0" + labeled-stream-splicer "^2.0.0" + mkdirp "^0.5.0" + module-deps "^6.0.0" + os-browserify "~0.3.0" + parents "^1.0.1" + path-browserify "~0.0.0" + process "~0.11.0" + punycode "^1.3.2" + querystring-es3 "~0.2.0" + read-only-stream "^2.0.0" + readable-stream "^2.0.2" + resolve "^1.1.4" + shasum "^1.0.0" + shell-quote "^1.6.1" + stream-browserify "^2.0.0" + stream-http "^3.0.0" + string_decoder "^1.1.1" + subarg "^1.0.0" + syntax-error "^1.1.1" + through2 "^2.0.0" + timers-browserify "^1.0.1" + tty-browserify "0.0.1" + url "~0.11.0" + util "~0.10.1" + vm-browserify "^1.0.0" + xtend "^4.0.0" + +browserslist@^4.6.0, browserslist@^4.8.3, browserslist@^4.8.5: + version "4.8.6" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.8.6.tgz#96406f3f5f0755d272e27a66f4163ca821590a7e" + integrity sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg== dependencies: - caniuse-lite "^1.0.30000989" - electron-to-chromium "^1.3.247" - node-releases "^1.1.29" + caniuse-lite "^1.0.30001023" + electron-to-chromium "^1.3.341" + node-releases "^1.1.47" buffer-crc32@~0.2.3: version "0.2.13" @@ -1226,14 +1719,22 @@ buffer-xor@^1.0.3: integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= buffer@^4.3.0: - version "4.9.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" - integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" +buffer@^5.0.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.3.tgz#3fbc9c69eb713d323e3fc1a895eee0710c072115" + integrity sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -1275,6 +1776,11 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" + integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== + cachedir@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4" @@ -1282,16 +1788,28 @@ cachedir@1.3.0: dependencies: os-homedir "^1.0.1" -caniuse-lite@^1.0.30000989: - version "1.0.30000989" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" - integrity sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw== +caniuse-lite@^1.0.30001023: + version "1.0.30001027" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz#283e2ef17d94889cc216a22c6f85303d78ca852d" + integrity sha512-7xvKeErvXZFtUItTHgNtLgS9RJpVnwBlWX8jSo/BO8VsF6deszemZSkJJJA1KOKrXuzZH4WALpAJdq5EyfgMLg== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= +chai@^4.1.2: + version "4.2.0" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" + integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + pathval "^1.1.0" + type-detect "^4.0.5" + chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1312,12 +1830,17 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= + check-more-types@2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -chokidar@^2.0.2: +chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.1: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== @@ -1337,9 +1860,9 @@ chokidar@^2.0.2: fsevents "^1.2.7" chownr@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" - integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== + version "1.1.3" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" + integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== chrome-trace-event@^1.0.2: version "1.0.2" @@ -1383,6 +1906,13 @@ cli-spinners@^0.1.2: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw= +cli-table@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" + integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= + dependencies: + colors "1.0.3" + cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -1396,6 +1926,19 @@ code-point-at@^1.0.0: resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= +coffeeify@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/coffeeify/-/coffeeify-3.0.1.tgz#5e2753000c50bd24c693115f33864248dd11136c" + integrity sha512-Qjnr7UX6ldK1PHV7wCnv7AuCd4q19KTUtwJnu/6JRJB4rfm12zvcXtKdacUoePOKr1I4ka/ydKiwWpNAdsQb0g== + dependencies: + convert-source-map "^1.3.0" + through2 "^2.0.0" + +coffeescript@1.12.7: + version "1.12.7" + resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" + integrity sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA== + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1416,6 +1959,26 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +colors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" + integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= + +colors@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" + integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== + +combine-source-map@^0.8.0, combine-source-map@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" + integrity sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos= + dependencies: + convert-source-map "~1.1.0" + inline-source-map "~0.6.0" + lodash.memoize "~3.0.3" + source-map "~0.5.3" + combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -1428,10 +1991,10 @@ commander@2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@^2.19.0, commander@^2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== +commander@^2.19.0, commander@^2.20.0, commander@^2.9.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== common-tags@1.8.0: version "1.8.0" @@ -1453,7 +2016,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@1.6.2, concat-stream@^1.5.0: +concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -1472,29 +2035,32 @@ config-chain@^1.1.12: proto-list "~1.2.1" console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= - dependencies: - date-now "^0.1.4" + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -constants-browserify@^1.0.0: +constants-browserify@^1.0.0, constants-browserify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -convert-source-map@^1.1.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" - integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== +convert-source-map@^1.1.0, convert-source-map@^1.3.0, convert-source-map@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" + integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" +convert-source-map@~1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" + integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA= + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -1512,19 +2078,34 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js-compat@^3.1.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.2.1.tgz#0cbdbc2e386e8e00d3b85dc81c848effec5b8150" - integrity sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A== +core-js-compat@^3.1.1, core-js-compat@^3.6.2: + version "3.6.4" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.6.4.tgz#938476569ebb6cda80d339bcf199fae4f16fff17" + integrity sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA== dependencies: - browserslist "^4.6.6" - semver "^6.3.0" + browserslist "^4.8.3" + semver "7.0.0" + +core-js@^2.4.0: + version "2.6.11" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" + integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cosmiconfig@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-4.0.0.tgz#760391549580bbd2df1e562bc177b13c290972dc" + integrity sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ== + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^4.0.0" + require-from-string "^2.0.1" + create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -1567,7 +2148,7 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -crypto-browserify@^3.11.0: +crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -1584,18 +2165,91 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +cucumber-expressions@^5.0.13: + version "5.0.18" + resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-5.0.18.tgz#6c70779efd3aebc5e9e7853938b1110322429596" + integrity sha1-bHB3nv0668Xp54U5OLERAyJClZY= + dependencies: + becke-ch--regex--s0-0-v1--base--pl--lib "^1.2.0" + +cucumber-expressions@^6.0.1: + version "6.6.2" + resolved "https://registry.yarnpkg.com/cucumber-expressions/-/cucumber-expressions-6.6.2.tgz#d89640eccc72a78380b6c210eae36a64e7462b81" + integrity sha512-WcFSVBiWNLJbIcAAC3t/ACU46vaOKfe1UIF5H3qveoq+Y4XQm9j3YwHurQNufRKBBg8nCnpU7Ttsx7egjS3hwA== + dependencies: + becke-ch--regex--s0-0-v1--base--pl--lib "^1.2.0" + +cucumber-tag-expressions@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cucumber-tag-expressions/-/cucumber-tag-expressions-1.1.1.tgz#7f5c7b70009bc2b666591bfe64854578bedee85a" + integrity sha1-f1x7cACbwrZmWRv+ZIVFeL7e6Fo= + +cucumber@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/cucumber/-/cucumber-4.2.1.tgz#64cfff6150bbe6b5e94b173470057353d6206719" + integrity sha512-3gQ0Vv4kSHsvXEFC6b1c+TfLRDzWD1/kU7e5vm8Kh8j35b95k6favan9/4ixcBNqd7UsU1T6FYcawC87+DlNKw== + dependencies: + assertion-error-formatter "^2.0.1" + babel-runtime "^6.11.6" + bluebird "^3.4.1" + cli-table "^0.3.1" + colors "^1.1.2" + commander "^2.9.0" + cucumber-expressions "^5.0.13" + cucumber-tag-expressions "^1.1.1" + duration "^0.2.0" + escape-string-regexp "^1.0.5" + figures "2.0.0" + gherkin "^5.0.0" + glob "^7.0.0" + indent-string "^3.1.0" + is-generator "^1.0.2" + is-stream "^1.1.0" + knuth-shuffle-seeded "^1.0.6" + lodash "^4.17.4" + mz "^2.4.0" + progress "^2.0.0" + resolve "^1.3.3" + serialize-error "^2.1.0" + stack-chain "^2.0.0" + stacktrace-js "^2.0.0" + string-argv "0.0.2" + title-case "^2.1.1" + util-arity "^1.0.2" + verror "^1.9.0" + cyclist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.4.1.tgz#ca2e4e9864679da686c6a6189603efd409664c30" - integrity sha512-1HBS7t9XXzkt6QHbwfirWYty8vzxNMawGj1yI+Fu6C3/VZJ8UtUngMW6layqwYZzLTZV8tiDpdCNBypn78V4Dg== +cypress-cucumber-preprocessor@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/cypress-cucumber-preprocessor/-/cypress-cucumber-preprocessor-2.0.1.tgz#b6e64041efb4620ca559487152784bc044b35af5" + integrity sha512-i7WjLtv18O6/RoHeVLtfwNZmDL9DgPv4E8+6z5bQ4lqtBSjAKzRNrTnxAKAgUvaLK0TMeUm8GJu4eQ7B4LBqew== + dependencies: + "@cypress/browserify-preprocessor" "^2.1.1" + chai "^4.1.2" + chokidar "^2.0.4" + cosmiconfig "^4.0.0" + cucumber "^4.2.1" + cucumber-expressions "^6.0.1" + cucumber-tag-expressions "^1.1.1" + debug "^3.0.1" + gherkin "^5.1.0" + glob "^7.1.2" + js-string-escape "^1.0.1" + minimist "^1.2.0" + through "^2.3.8" + +cypress@^3.5.0: + version "3.8.3" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.8.3.tgz#e921f5482f1cbe5814891c878f26e704bbffd8f4" + integrity sha512-I9L/d+ilTPPA4vq3NC1OPKmw7jJIpMKNdyfR8t1EXYzYCjyqbc59migOm1YSse/VRbISLJ+QGb5k4Y3bz2lkYw== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/xvfb" "1.2.4" + "@types/sizzle" "2.3.2" arch "2.1.1" bluebird "3.5.0" cachedir "1.3.0" @@ -1604,6 +2258,7 @@ cypress@^3.4.1: commander "2.15.1" common-tags "1.8.0" debug "3.2.6" + eventemitter2 "4.1.2" execa "0.10.0" executable "4.1.1" extract-zip "1.6.7" @@ -1622,9 +2277,23 @@ cypress@^3.4.1: request-progress "3.0.0" supports-color "5.5.0" tmp "0.1.0" + untildify "3.0.3" url "0.11.0" yauzl "2.10.0" +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +dash-ast@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dash-ast/-/dash-ast-1.0.0.tgz#12029ba5fb2f8aa6f0a861795b23c1b4b6c27d37" + integrity sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1637,11 +2306,6 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= - debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -1656,7 +2320,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@3.2.6, debug@^3.1.0, debug@^3.2.6: +debug@3.2.6, debug@^3.0.1, debug@^3.1.0, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -1675,6 +2339,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -1709,6 +2380,11 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1719,10 +2395,20 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +deps-sort@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" + integrity sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw== + dependencies: + JSONStream "^1.0.3" + shasum-object "^1.0.0" + subarg "^1.0.0" + through2 "^2.0.0" + des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" @@ -1732,11 +2418,25 @@ detect-libc@^1.0.2: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +detective@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" + integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg== + dependencies: + acorn-node "^1.6.1" + defined "^1.0.0" + minimist "^1.1.1" + diff@^1.3.2: version "1.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= +diff@^3.0.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -1754,11 +2454,18 @@ disparity@2.0.0: ansi-styles "^2.0.1" diff "^1.3.2" -domain-browser@^1.1.1: +domain-browser@^1.1.1, domain-browser@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== +duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= + dependencies: + readable-stream "^2.0.2" + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -1769,6 +2476,14 @@ duplexify@^3.4.2, duplexify@^3.6.0: readable-stream "^2.0.0" stream-shift "^1.0.0" +duration@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/duration/-/duration-0.2.2.tgz#ddf149bc3bc6901150fe9017111d016b3357f529" + integrity sha512-06kgtea+bGreF5eKYgI/36A6pLXggY7oR4p1pq4SmdFBn1ReOL5D8RhG64VrqfTTKNucqqtBAwEj8aB88mcqrg== + dependencies: + d "1" + es5-ext "~0.10.46" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -1787,10 +2502,10 @@ editorconfig@^0.15.3: semver "^5.6.0" sigmund "^1.0.1" -electron-to-chromium@^1.3.247: - version "1.3.258" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.258.tgz#829b03be37424099b91aefb6815e8801bf30b509" - integrity sha512-rkPYrgFU7k/8ngjHYvzOZ44OQQ1GeIRIQnhGv00RkSlQXEnJKsGonQppbEEWHuuxZegpMao+WZmYraWQJQJMMg== +electron-to-chromium@^1.3.341: + version "1.3.348" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.348.tgz#61fa7d43f6c4fd8b046b0b69213cb55b7a4c76a5" + integrity sha512-6O0IInybavGdYtcbI4ryF/9e3Qi8/soi6C68ELRseJuTwQPKq39uGgVVeQHG28t69Sgsky09nXBRhUiFXsZyFQ== elegant-spinner@^1.0.1: version "1.0.1" @@ -1798,9 +2513,9 @@ elegant-spinner@^1.0.1: integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= elliptic@^6.0.0: - version "6.5.1" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.1.tgz#c380f5f909bf1b9b4428d028cd18d3b0efd6b52b" - integrity sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg== + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -1816,19 +2531,19 @@ emojis-list@^2.0.0: integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== dependencies: once "^1.4.0" enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== + version "4.1.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66" + integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA== dependencies: graceful-fs "^4.1.2" - memory-fs "^0.4.0" + memory-fs "^0.5.0" tapable "^1.0.0" errno@^0.1.3, errno@~0.1.7: @@ -1838,6 +2553,46 @@ errno@^0.1.3, errno@~0.1.7: dependencies: prr "~1.0.1" +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" + integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== + dependencies: + stackframe "^1.1.1" + +es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.46: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1868,15 +2623,25 @@ estraverse@^4.1.0, estraverse@^4.1.1: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -esutils@^2.0.2: +esutils@^2.0.0, esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +eventemitter2@4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-4.1.2.tgz#0e1a8477af821a6ef3995b311bf74c23a5247f15" + integrity sha1-DhqEd6+CGm7zmVsxG/dMI6UkfxU= + +events@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" + integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== + events@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" - integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" + integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" @@ -1924,6 +2689,13 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -1978,15 +2750,20 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" + integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-safe-stringify@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== fd-slicer@~1.0.1: version "1.0.1" @@ -2007,6 +2784,13 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== +figures@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= + dependencies: + escape-string-regexp "^1.0.5" + figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -2015,6 +2799,11 @@ figures@^1.7.0: escape-string-regexp "^1.0.5" object-assign "^4.1.0" +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" @@ -2109,12 +2898,21 @@ fs-extra@5.0.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.6.tgz#2c5cc30ded81282bfe8a0d7c7c1853ddeb102c07" - integrity sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ== + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== dependencies: - minipass "^2.2.1" + minipass "^2.6.0" fs-write-stream-atomic@^1.0.8: version "1.0.10" @@ -2132,12 +2930,12 @@ fs.realpath@^1.0.0: integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= fsevents@^1.2.7: - version "1.2.9" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.9.tgz#3f5ed66583ccd6f400b5a00db6f7e861363e388f" - integrity sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw== + version "1.2.11" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.11.tgz#67bf57f4758f02ede88fb2a1712fef4d15358be3" + integrity sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw== dependencies: + bindings "^1.5.0" nan "^2.12.1" - node-pre-gyp "^0.12.0" function-bind@^1.1.1: version "1.1.1" @@ -2158,6 +2956,21 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" +gensync@^1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" + integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== + +get-assigned-identifiers@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz#6dbf411de648cbaf8d9169ebb0d2d576191e2ff1" + integrity sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ== + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -2182,6 +2995,11 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gherkin@^5.0.0, gherkin@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/gherkin/-/gherkin-5.1.0.tgz#684bbb03add24eaf7bdf544f58033eb28fb3c6d5" + integrity sha1-aEu7A63STq9731RPWAM+so+zxtU= + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -2190,10 +3008,10 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.1.3, glob@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== +glob@^7.0.0, glob@^7.1.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2215,9 +3033,9 @@ globals@^11.1.0: integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.2.2" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" - integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== har-schema@^2.0.0: version "2.0.0" @@ -2245,9 +3063,9 @@ has-flag@^3.0.0: integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== has-unicode@^2.0.0: version "2.0.1" @@ -2285,6 +3103,13 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" +has@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" @@ -2310,6 +3135,11 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +htmlescape@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" + integrity sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E= + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -2342,9 +3172,9 @@ iferr@^0.1.5: integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-walk@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.2.tgz#99d83a246c196ea5c93ef9315ad7b0819c35069b" - integrity sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw== + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== dependencies: minimatch "^3.0.4" @@ -2360,7 +3190,7 @@ indent-string@^2.1.0: dependencies: repeating "^2.0.0" -indent-string@^3.0.0: +indent-string@^3.0.0, indent-string@^3.1.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= @@ -2398,7 +3228,30 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -invariant@^2.2.2: +inline-source-map@~0.6.0: + version "0.6.2" + resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" + integrity sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU= + dependencies: + source-map "~0.5.3" + +insert-module-globals@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.0.tgz#ec87e5b42728479e327bd5c5c71611ddfb4752ba" + integrity sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw== + dependencies: + JSONStream "^1.0.3" + acorn-node "^1.5.2" + combine-source-map "^0.8.0" + concat-stream "^1.6.1" + is-buffer "^1.1.0" + path-is-absolute "^1.0.1" + process "~0.11.0" + through2 "^2.0.0" + undeclared-identifiers "^1.1.2" + xtend "^4.0.0" + +invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -2419,6 +3272,11 @@ is-accessor-descriptor@^1.0.0: dependencies: kind-of "^6.0.0" +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" @@ -2426,7 +3284,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5: +is-buffer@^1.1.0, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== @@ -2470,6 +3328,11 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" @@ -2488,11 +3351,9 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= - dependencies: - number-is-nan "^1.0.0" + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== is-fullwidth-code-point@^1.0.0: version "1.0.0" @@ -2506,6 +3367,11 @@ is-fullwidth-code-point@^2.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= +is-generator@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-generator/-/is-generator-1.0.3.tgz#c14c21057ed36e328db80347966c693f886389f3" + integrity sha1-wUwhBX7TbjKNuANHlmxpP4hjifM= + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -2611,10 +3477,10 @@ its-name@1.0.0: resolved "https://registry.yarnpkg.com/its-name/-/its-name-1.0.0.tgz#2065f1883ecb568c65f7112ddbf123401fae4af0" integrity sha1-IGXxiD7LVoxl9xEt2/EjQB+uSvA= -js-beautify@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.0.tgz#9753a13c858d96828658cd18ae3ca0e5783ea672" - integrity sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q== +js-beautify@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.3.tgz#c73fa10cf69d3dfa52d8ed624f23c64c0a6a94c1" + integrity sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ== dependencies: config-chain "^1.1.12" editorconfig "^0.15.3" @@ -2627,12 +3493,17 @@ js-levenshtein@^1.1.3: resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8= + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1: +js-yaml@^3.13.1, js-yaml@^3.9.0: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -2655,7 +3526,7 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -2670,6 +3541,13 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-stable-stringify@~0.0.0: + version "0.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" + integrity sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U= + dependencies: + jsonify "~0.0.0" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -2683,9 +3561,9 @@ json5@^1.0.1: minimist "^1.2.0" json5@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" - integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + version "2.1.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.1.tgz#81b6cb04e9ba496f1c7005d07b4368a2638f90b6" + integrity sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ== dependencies: minimist "^1.2.0" @@ -2696,6 +3574,16 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -2726,15 +3614,42 @@ kind-of@^5.0.0: integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +knuth-shuffle-seeded@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/knuth-shuffle-seeded/-/knuth-shuffle-seeded-1.0.6.tgz#01f1b65733aa7540ee08d8b0174164d22081e4e1" + integrity sha1-AfG2VzOqdUDuCNiwF0Fk0iCB5OE= + dependencies: + seed-random "~2.2.0" + +labeled-stream-splicer@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz#42a41a16abcd46fd046306cf4f2c3576fffb1c21" + integrity sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw== + dependencies: + inherits "^2.0.1" + stream-splicer "^2.0.0" lazy-ass@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levenary@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/levenary/-/levenary-1.1.1.tgz#842a9ee98d2075aa7faeedbe32679e9205f46f77" + integrity sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ== + dependencies: + leven "^3.1.0" + listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" @@ -2808,12 +3723,22 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" +lodash.clonedeep@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= + +lodash.memoize@~3.0.3: + version "3.0.4" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" + integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8= + lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@4.17.15, lodash@^4.17.10, lodash@^4.17.13: +lodash@4.17.15, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.4: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -2847,6 +3772,11 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -2896,7 +3826,7 @@ md5.js@^1.3.4: inherits "^2.0.1" safe-buffer "^5.1.2" -memory-fs@^0.4.0, memory-fs@^0.4.1: +memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -2904,6 +3834,14 @@ memory-fs@^0.4.0, memory-fs@^0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -2939,17 +3877,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.40.0: - version "1.40.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" - integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== +mime-db@1.43.0: + version "1.43.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58" + integrity sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.24" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" - integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== + version "2.1.26" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.26.tgz#9c921fc09b7e149a65dfdc0da4d20997200b0a06" + integrity sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ== dependencies: - mime-db "1.40.0" + mime-db "1.43.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -2973,25 +3911,25 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@1.2.0, minimist@^1.2.0: +minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minipass@^2.2.1, minipass@^2.3.5: - version "2.5.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.5.1.tgz#cf435a9bf9408796ca3a3525a8b851464279c9b8" - integrity sha512-dmpSnLJtNQioZFI5HfQ55Ad0DzzsMAb+HfokwRTNXwEQjepbTkl5mtIlSVxGIkOkxlpX7wIn5ET/oAd9fZ/Y/Q== +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" minizlib@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.2.tgz#6f0ccc82fa53e1bf2ff145f220d2da9fa6e3a166" - integrity sha512-hR3At21uSrsjjDTWrbu0IMLTpnkpv8IIMFDFaoz43Tmu4LkmAXfH44vNNzpTnf+OAQQCHrb91y/wc2J4x5XgSQ== + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== dependencies: - minipass "^2.2.1" + minipass "^2.9.0" mississippi@^3.0.0: version "3.0.0" @@ -3024,6 +3962,27 @@ mkdirp@0.5.1, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +module-deps@^6.0.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.2.2.tgz#d8a15c2265dfc119153c29bb47386987d0ee423b" + integrity sha512-a9y6yDv5u5I4A+IPHTnqFxcaKr4p50/zxTjcQJaX2ws9tN/W6J6YXnEKhqRyPhl494dkcxx951onSKVezmI+3w== + dependencies: + JSONStream "^1.0.3" + browser-resolve "^1.7.0" + cached-path-relative "^1.0.2" + concat-stream "~1.6.0" + defined "^1.0.0" + detective "^5.2.0" + duplexer2 "^0.1.2" + inherits "^2.0.1" + parents "^1.0.0" + readable-stream "^2.0.2" + resolve "^1.4.0" + stream-combiner2 "^1.1.1" + subarg "^1.0.0" + through2 "^2.0.0" + xtend "^4.0.0" + moment@2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" @@ -3051,6 +4010,15 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -3074,9 +4042,9 @@ nanomatch@^1.2.9: to-regex "^3.0.1" needle@^2.2.1: - version "2.4.0" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" - integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== + version "2.3.2" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.2.tgz#3342dea100b7160960a450dc8c22160ac712a528" + integrity sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w== dependencies: debug "^3.2.6" iconv-lite "^0.4.4" @@ -3087,11 +4055,23 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" @@ -3121,10 +4101,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-pre-gyp@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz#39ba4bb1439da030295f899e3b520b7785766149" - integrity sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A== +node-pre-gyp@*: + version "0.14.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83" + integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -3135,14 +4115,14 @@ node-pre-gyp@^0.12.0: rc "^1.2.7" rimraf "^2.6.1" semver "^5.3.0" - tar "^4" + tar "^4.4.2" -node-releases@^1.1.29: - version "1.1.30" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.30.tgz#35eebf129c63baeb6d8ddeda3c35b05abfd37f7f" - integrity sha512-BHcr1g6NeUH12IL+X3Flvs4IOnl1TL0JczUhEZjDE+FXXPQcVCNr8NEPb01zqGxzhTpdyJL5GXemaCW7aw6Khw== +node-releases@^1.1.47: + version "1.1.48" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.48.tgz#7f647f0c453a0495bcd64cbd4778c26035c2f03a" + integrity sha512-Hr8BbmUl1ujAST0K0snItzEA5zkJTQup8VNTKNfT6Zw8vTJkIiagUPNfxHmgDOyfFYNfKAul40sD0UEYTvwebw== dependencies: - semver "^5.3.0" + semver "^6.3.0" nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" @@ -3165,17 +4145,25 @@ normalize-path@^3.0.0: integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== npm-packlist@^1.1.6: - version "1.4.4" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.4.tgz#866224233850ac534b63d1a6e76050092b5d2f44" - integrity sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw== + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" npm-run-path@^2.0.0: version "2.0.2" @@ -3269,7 +4257,7 @@ ora@^0.2.3: cli-spinners "^0.1.2" object-assign "^4.0.1" -os-browserify@^0.3.0: +os-browserify@^0.3.0, os-browserify@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= @@ -3292,15 +4280,22 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +outpipe@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2" + integrity sha1-UM+GFjZeh+Ax4ppeyTOaPaRyX6I= + dependencies: + shell-quote "^1.4.2" + p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= p-limit@^2.0.0, p-limit@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537" - integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg== + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== dependencies: p-try "^2.0.0" @@ -3321,10 +4316,17 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pad-right@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/pad-right/-/pad-right-0.2.2.tgz#6fbc924045d244f2a2a244503060d3bfc6009774" + integrity sha1-b7ySQEXSRPKiokRQMGDTv8YAl3Q= + dependencies: + repeat-string "^1.5.2" + pako@~1.0.5: - version "1.0.10" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" - integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== parallel-transform@^1.1.0: version "1.2.0" @@ -3335,10 +4337,17 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +parents@^1.0.0, parents@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" + integrity sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E= + dependencies: + path-platform "~0.11.15" + parse-asn1@^5.0.0: - version "5.1.4" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" - integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== + version "5.1.5" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.5.tgz#003271343da58dc94cace494faef3d2147ecea0e" + integrity sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" @@ -3347,12 +4356,20 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.1: +path-browserify@0.0.1, path-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== @@ -3367,7 +4384,7 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= -path-is-absolute@^1.0.0: +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -3387,6 +4404,16 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-platform@~0.11.15: + version "0.11.15" + resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" + integrity sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I= + +pathval@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" + integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -3409,9 +4436,9 @@ performance-now@^2.1.0: integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= picomatch@^2.0.5: - version "2.0.7" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" - integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== + version "2.2.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a" + integrity sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA== pify@^2.2.0: version "2.3.0" @@ -3445,11 +4472,16 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: +process@^0.11.10, process@~0.11.0: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -3471,9 +4503,9 @@ pseudomap@^1.0.2: integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= psl@^1.1.24: - version "1.4.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" - integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw== + version "1.7.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" + integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== public-encrypt@^4.0.0: version "4.0.3" @@ -3517,7 +4549,7 @@ punycode@1.3.2: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -3532,7 +4564,7 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -querystring-es3@^0.2.0: +querystring-es3@^0.2.0, querystring-es3@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= @@ -3577,10 +4609,17 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +read-only-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" + integrity sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A= + dependencies: + readable-stream "^2.0.2" + "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -3590,6 +4629,15 @@ rc@^1.2.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.0.6: + version "3.5.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.5.0.tgz#465d70e6d1087f6162d079cd0b5db7fbebfd1606" + integrity sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -3611,6 +4659,16 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-transform@^0.14.0: version "0.14.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb" @@ -3626,12 +4684,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexp-tree@^0.1.13: - version "0.1.13" - resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.13.tgz#5b19ab9377edc68bc3679256840bb29afc158d7f" - integrity sha512-hwdV/GQY5F8ReLZWO+W1SRoN5YfpOKY6852+tBFcma72DKBIcHjPRIlIvQN35bCOljuAfP2G2iB0FC/w236mUw== - -regexpu-core@^4.5.4: +regexpu-core@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6" integrity sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg== @@ -3644,14 +4697,14 @@ regexpu-core@^4.5.4: unicode-match-property-value-ecmascript "^1.1.0" regjsgen@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd" - integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA== + version "0.5.1" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.1.tgz#48f0bf1a5ea205196929c0d9798b42d1ed98443c" + integrity sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg== regjsparser@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" - integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== + version "0.6.2" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.2.tgz#fd62c753991467d9d1ffe0a9f67f27a529024b96" + integrity sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q== dependencies: jsesc "~0.5.0" @@ -3665,7 +4718,7 @@ repeat-element@^1.1.2: resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== -repeat-string@^1.6.1: +repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -3710,15 +4763,25 @@ request@2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" +require-from-string@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.3.2: - version "1.12.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" - integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= + +resolve@^1.1.4, resolve@^1.3.2, resolve@^1.3.3, resolve@^1.4.0, resolve@^1.8.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== dependencies: path-parse "^1.0.6" @@ -3800,7 +4863,17 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: +seed-random@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" + integrity sha1-KpsZ4lCoFwmSMaW5mk2vgLf77VQ= + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -3810,10 +4883,15 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -serialize-javascript@^1.7.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" - integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A== +serialize-error@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" + integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= + +serialize-javascript@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" + integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== set-blocking@~2.0.0: version "2.0.0" @@ -3835,7 +4913,7 @@ setimmediate@^1.0.4: resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -sha.js@^2.4.0, sha.js@^2.4.8: +sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== @@ -3843,6 +4921,21 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" +shasum-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e" + integrity sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg== + dependencies: + fast-safe-stringify "^2.0.7" + +shasum@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" + integrity sha1-5wEjENj0F/TetXEhUOVni4euVl8= + dependencies: + json-stable-stringify "~0.0.0" + sha.js "~2.4.4" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -3855,6 +4948,11 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +shell-quote@^1.4.2, shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + sigmund@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" @@ -3865,6 +4963,11 @@ signal-exit@^3.0.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= +simple-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" + integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" @@ -3930,20 +5033,20 @@ source-list-map@^2.0.0: integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-resolve@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" - integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== dependencies: - atob "^2.1.1" + atob "^2.1.2" decode-uri-component "^0.2.0" resolve-url "^0.2.1" source-map-url "^0.4.0" urix "^0.1.0" source-map-support@~0.5.12: - version "0.5.13" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" - integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== dependencies: buffer-from "^1.0.0" source-map "^0.6.0" @@ -3953,7 +5056,12 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@^0.5.0, source-map@^0.5.6: +source-map@0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= + +source-map@^0.5.0, source-map@^0.5.6, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -3997,6 +5105,40 @@ ssri@^6.0.1: dependencies: figgy-pudding "^3.5.1" +stack-chain@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-2.0.0.tgz#d73d1172af89565f07438b5bcc086831b6689b2d" + integrity sha512-GGrHXePi305aW7XQweYZZwiRwR7Js3MWoK/EHzzB9ROdc75nCnjSJVi21rdAGxFl+yCx2L2qdfl5y7NO4lTyqg== + +stack-generator@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36" + integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q== + dependencies: + stackframe "^1.1.1" + +stackframe@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.1.1.tgz#ffef0a3318b1b60c3b58564989aca5660729ec71" + integrity sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ== + +stacktrace-gps@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a" + integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg== + dependencies: + source-map "0.5.6" + stackframe "^1.1.1" + +stacktrace-js@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== + dependencies: + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -4005,7 +5147,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -stream-browserify@^2.0.1: +stream-browserify@^2.0.0, stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== @@ -4013,6 +5155,14 @@ stream-browserify@^2.0.1: inherits "~2.0.1" readable-stream "^2.0.2" +stream-combiner2@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -4021,7 +5171,7 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-http@^2.7.2: +stream-http@^2.0.0, stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== @@ -4032,16 +5182,39 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" +stream-http@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.0.tgz#22fb33fe9b4056b4eccf58bd8f400c4b993ffe57" + integrity sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^3.0.6" + xtend "^4.0.0" + stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +stream-splicer@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.1.tgz#0b13b7ee2b5ac7e0609a7463d83899589a363fcd" + integrity sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.2" stream-to-observable@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4= +string-argv@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.0.2.tgz#dac30408690c21f3c3630a3ff3a05877bdcbd736" + integrity sha1-2sMECGkMIfPDYwo/86BYd73L1zY= + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4059,7 +5232,7 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0: +string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -4104,6 +5277,13 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= +subarg@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" + integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI= + dependencies: + minimist "^1.1.0" + supports-color@5.5.0, supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -4121,48 +5301,69 @@ symbol-observable@1.0.1: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= +syntax-error@^1.1.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" + integrity sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w== + dependencies: + acorn-node "^1.2.0" + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tar@^4: - version "4.4.10" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.10.tgz#946b2810b9a5e0b26140cf78bea6b0b0d689eba1" - integrity sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA== +tar@^4.4.2: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== dependencies: chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.5" + minipass "^2.8.6" minizlib "^1.2.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" yallist "^3.0.3" -terser-webpack-plugin@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" - integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== +terser-webpack-plugin@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c" + integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA== dependencies: cacache "^12.0.2" find-cache-dir "^2.1.0" is-wsl "^1.1.0" schema-utils "^1.0.0" - serialize-javascript "^1.7.0" + serialize-javascript "^2.1.2" source-map "^0.6.1" terser "^4.1.2" webpack-sources "^1.4.0" worker-farm "^1.7.0" terser@^4.1.2: - version "4.3.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.1.tgz#09820bcb3398299c4b48d9a86aefc65127d0ed65" - integrity sha512-pnzH6dnFEsR2aa2SJaKb1uSCl3QmIsJ8dEkj0Fky+2AwMMcC9doMqLOQIH6wVTEKaVfKVvLSk5qxPBEZT9mywg== + version "4.6.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" + integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== dependencies: commander "^2.20.0" source-map "~0.6.1" source-map-support "~0.5.12" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + dependencies: + any-promise "^1.0.0" + throttleit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" @@ -4176,6 +5377,18 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" +"through@>=2.2.7 <3", through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +timers-browserify@^1.0.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" + integrity sha1-ycWLV1voQHN1y14kYtrO50NZ9B0= + dependencies: + process "~0.11.0" + timers-browserify@^2.0.4: version "2.0.11" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f" @@ -4183,6 +5396,14 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +title-case@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa" + integrity sha1-PhJyFtpY0rxb7PE3q5Ha46fNj6o= + dependencies: + no-case "^2.2.0" + upper-case "^1.0.3" + tmp@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" @@ -4240,15 +5461,10 @@ tough-cookie@~2.4.3: psl "^1.1.24" punycode "^1.4.1" -trim-right@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= - ts-loader@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.1.0.tgz#999cb0a7644f9c7c6c0901802dce50ceb0a76e5b" - integrity sha512-7JedeOu2rsYHQDEr2fwmMozABwbQTZXEaEMZPSIWG7gpzRefOLJCqwdazcegHtyaxp04PeEgs/b0m08WMpnIzQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" + integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -4266,6 +5482,11 @@ tty-browserify@0.0.0: resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= +tty-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" + integrity sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -4278,15 +5499,46 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" + integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.6.3: - version "3.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" - integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== +typescript@3.7.5: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + +umd@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" + integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow== + +undeclared-identifiers@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz#9254c1d37bdac0ac2b52de4b6722792d2a91e30f" + integrity sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw== + dependencies: + acorn-node "^1.3.0" + dash-ast "^1.0.0" + get-assigned-identifiers "^1.2.0" + simple-concat "^1.0.0" + xtend "^4.0.1" unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" @@ -4348,11 +5600,21 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" +untildify@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.3.tgz#1e7b42b140bcfd922b22e70ca1265bfe3634c7c9" + integrity sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA== + upath@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== +upper-case@^1.0.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -4365,7 +5627,7 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url@0.11.0, url@^0.11.0: +url@0.11.0, url@^0.11.0, url@~0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= @@ -4378,7 +5640,12 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -util-deprecate@~1.0.1: +util-arity@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/util-arity/-/util-arity-1.1.0.tgz#59d01af1fdb3fede0ac4e632b0ab5f6ce97c9330" + integrity sha1-WdAa8f2z/t4KxOYysKtfbOl8kzA= + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -4397,10 +5664,17 @@ util@^0.11.0: dependencies: inherits "2.0.3" +util@~0.10.1: + version "0.10.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" + integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== + dependencies: + inherits "2.0.3" + uuid@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" - integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== variable-diff@1.1.0: version "1.1.0" @@ -4410,7 +5684,7 @@ variable-diff@1.1.0: chalk "^1.1.1" object-assign "^4.0.1" -verror@1.10.0: +verror@1.10.0, verror@^1.9.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= @@ -4419,10 +5693,23 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vm-browserify@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.0.tgz#bd76d6a23323e2ca8ffa12028dc04559c75f9019" - integrity sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw== +vm-browserify@^1.0.0, vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +watchify@3.11.1: + version "3.11.1" + resolved "https://registry.yarnpkg.com/watchify/-/watchify-3.11.1.tgz#8e4665871fff1ef64c0430d1a2c9d084d9721881" + integrity sha512-WwnUClyFNRMB2NIiHgJU9RQPQNqVeFk7OmZaWf5dC5EnNa0Mgr7imBydbaJ7tGTuPM2hz1Cb4uiBvK9NVxMfog== + dependencies: + anymatch "^2.0.0" + browserify "^16.1.0" + chokidar "^2.1.1" + defined "^1.0.0" + outpipe "^1.1.0" + through2 "^2.0.0" + xtend "^4.0.0" watchpack@^1.6.0: version "1.6.0" @@ -4442,9 +5729,9 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-map "~0.6.1" webpack@^4.40.2: - version "4.40.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.40.2.tgz#d21433d250f900bf0facbabe8f50d585b2dc30a7" - integrity sha512-5nIvteTDCUws2DVvP9Qe+JPla7kWPPIDFZv55To7IycHWZ+Z5qBdaBYPyuXWdhggTufZkQwfIK+5rKQTVovm2A== + version "4.41.5" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c" + integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw== dependencies: "@webassemblyjs/ast" "1.8.5" "@webassemblyjs/helper-module-context" "1.8.5" @@ -4466,7 +5753,7 @@ webpack@^4.40.2: node-libs-browser "^2.2.1" schema-utils "^1.0.0" tapable "^1.1.3" - terser-webpack-plugin "^1.4.1" + terser-webpack-plugin "^1.4.3" watchpack "^1.6.0" webpack-sources "^1.4.1" @@ -4496,7 +5783,7 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xtend@^4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -4512,9 +5799,9 @@ yallist@^2.1.2: integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yauzl@2.10.0: version "2.10.0" diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 723da7cef6a77..31ef0bef18a85 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -12,7 +12,7 @@ "exclude": [ "test/**/*", "legacy/plugins/siem/cypress/**/*", - "legacy/plugins/apm/cypress/**/*", + "legacy/plugins/apm/e2e/cypress/**/*", "**/typespec_tests.ts" ], "compilerOptions": { From 07fec2f7251e6b2fe0ed30ce9d08f1133416806e Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Wed, 26 Feb 2020 14:17:59 -0500 Subject: [PATCH 18/22] Documentation for numeral pattern formatting (#57616) * Documentation for Elastic Numeral formatting * Tweaks from feedback * Updates from feedback * Fix and update examples * Add TODOs * Fix typo Co-authored-by: Elastic Machine --- .../canvas/canvas-function-reference.asciidoc | 6 +- docs/management/advanced-options.asciidoc | 10 +- docs/management/managing-fields.asciidoc | 6 +- docs/management/numeral.asciidoc | 184 ++++++++++++++++++ docs/user/management.asciidoc | 2 + package.json | 2 +- .../legacy/plugins/canvas/i18n/constants.ts | 2 +- .../i18n/functions/dict/formatnumber.ts | 8 +- .../canvas/i18n/functions/dict/metric.ts | 5 +- .../canvas/public/lib/documentation_links.ts | 3 + x-pack/package.json | 2 +- .../translations/translations/ja-JP.json | 3 - .../translations/translations/zh-CN.json | 3 - yarn.lock | 13 +- 14 files changed, 212 insertions(+), 37 deletions(-) create mode 100644 docs/management/numeral.asciidoc diff --git a/docs/canvas/canvas-function-reference.asciidoc b/docs/canvas/canvas-function-reference.asciidoc index 330cc63d10548..85e9d22490497 100644 --- a/docs/canvas/canvas-function-reference.asciidoc +++ b/docs/canvas/canvas-function-reference.asciidoc @@ -1244,7 +1244,7 @@ Alias: `format` [[formatnumber_fn]] === `formatnumber` -Formats a number into a formatted number string using NumeralJS. For more information, see http://numeraljs.com/#format. +Formats a number into a formatted number string using the <>. *Expression syntax* [source,js] @@ -1276,7 +1276,7 @@ The `formatnumber` subexpression receives the same `context` as the `progress` f Alias: `format` |`string` -|A NumeralJS format string. For example, `"0.0a"` or `"0%"`. See http://numeraljs.com/#format. +|A <> string. For example, `"0.0a"` or `"0%"`. |=== *Returns:* `string` @@ -1675,7 +1675,7 @@ Default: `${font size=48 family="'Open Sans', Helvetica, Arial, sans-serif" colo Alias: `format` |`string` -|A NumeralJS format string. For example, `"0.0a"` or `"0%"`. See http://numeraljs.com/#format. +|A <> string. For example, `"0.0a"` or `"0%"`. |=== *Returns:* `render` diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 9d4052bbd0156..c698e2db86ddb 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -51,13 +51,13 @@ adapt to the interval between measurements. Keys are http://en.wikipedia.org/wik `fields:popularLimit`:: The top N most popular fields to show. `filterEditor:suggestValues`:: Set this property to `false` to prevent the filter editor from suggesting values for fields. `filters:pinnedByDefault`:: Set this property to `true` to make filters have a global state (be pinned) by default. -`format:bytes:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "bytes" format. -`format:currency:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "currency" format. +`format:bytes:defaultPattern`:: The default <> format for the "bytes" format. +`format:currency:defaultPattern`:: The default <> format for the "currency" format. `format:defaultTypeMap`:: A map of the default format name for each field type. Field types that are not explicitly mentioned use "\_default_". -`format:number:defaultLocale`:: The http://numeraljs.com/[numeral language] locale. -`format:number:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "number" format. -`format:percent:defaultPattern`:: The default http://numeraljs.com/[numeral format] for the "percent" format. +`format:number:defaultLocale`:: The <> locale. +`format:number:defaultPattern`:: The <> for the "number" format. +`format:percent:defaultPattern`:: The <> for the "percent" format. `histogram:barTarget`:: When date histograms use the `auto` interval, Kibana attempts to generate this number of bars. `histogram:maxBars`:: Date histograms are not generated with more bars than the value of this property, scaling values when necessary. diff --git a/docs/management/managing-fields.asciidoc b/docs/management/managing-fields.asciidoc index f66976b3715d1..b54f4fe5194ad 100644 --- a/docs/management/managing-fields.asciidoc +++ b/docs/management/managing-fields.asciidoc @@ -92,6 +92,9 @@ include::field-formatters/string-formatter.asciidoc[] Numeric fields support the `Url`, `Bytes`, `Duration`, `Number`, `Percentage`, `String`, and `Color` formatters. +The `Bytes`, `Number`, and `Percentage` formatters enable you to choose the display formats of numbers in this field using +the <> syntax that Kibana maintains. + include::field-formatters/url-formatter.asciidoc[] include::field-formatters/string-formatter.asciidoc[] @@ -100,9 +103,6 @@ include::field-formatters/duration-formatter.asciidoc[] include::field-formatters/color-formatter.asciidoc[] -The `Bytes`, `Number`, and `Percentage` formatters enable you to choose the display formats of numbers in this field using -the https://adamwdraper.github.io/Numeral-js/[numeral.js] standard format definitions. - [[scripted-fields]] === Scripted Fields diff --git a/docs/management/numeral.asciidoc b/docs/management/numeral.asciidoc new file mode 100644 index 0000000000000..861277fd18478 --- /dev/null +++ b/docs/management/numeral.asciidoc @@ -0,0 +1,184 @@ +[[numeral]] +== Numeral Formatting + +Numeral formatting in {kib} is done through a pattern-based syntax. +These patterns express common number formats in a concise way, similar +to date formatting. While these patterns are originally based on Numeral.js, +they are now maintained by {kib}. + +Numeral formatting patterns are used in multiple places in {kib}, including: + +* <> +* <> +* <> +* <> + +The simplest pattern format is `0`, and the default {kib} pattern is `0,0.[000]`. +The numeral pattern syntax expresses: + +Number of decimal places:: The `.` character turns on the option to show decimal +places using a locale-specific decimal separator, most often `.` or `,`. +To add trailing zeroes such as `5.00`, use a pattern like `0.00`. +To have optional zeroes, use the `[]` characters. Examples below. +Thousands separator:: The thousands separator `,` turns on the option to group +thousands using a locale-specific separator. The separator is most often `,` or `.`, +and sometimes ` `. +Accounting notation:: Putting parentheses around your format like `(0.00)` will use accounting notation to show negative numbers. + +The display of these patterns is affected by the <> `format:number:defaultLocale`. +The default locale is `en`, but some examples will specify that they are using an alternate locale. + +Most basic examples: + +|=== +| **Input** | **Pattern** | **Locale** | **Output** +| 10000.23 | 0,0 | en (English) | 10,000 +| 10000.23 | 0.0 | en (English) | 10000.2 +| 10000.23 | 0,0.0 | fr (French) | 10 000,2 +| 10000.23 | 0,0.000 | fr (French) | 10 000,230 +| 10000.23 | 0,0[.]0 | en (English) | 10,000.2 +| 10000.23 | 0.00[0] | en (English) | 10,000.23 +| -10000.23 | (0) | en (English) | (10000) +|=== + +[float] +=== Percentages + +By adding the `%` symbol to any of the previous patterns, the value +is multiplied by 100 and the `%` symbol is added in the place indicated. + +The default percentage formatter in {kib} is `0,0.[000]%`, which shows +up to three decimal places. + +|=== +| **Input** | **Pattern** | **Locale** | **Output** +| 0.43 | 0,0.[000]% | en (English) | 43.00% +| 0.43 | 0,0.[000]% | fr (French) | 43,00% +| 1 | 0% | en (English) | 100% +| -0.43 | 0 % | en (English) | -43 % +|=== + +[float] +=== Bytes and bits + +The bytes and bits formatters will shorten the input by adding a suffix like `GB` or `TB`. Bytes and bits formatters include the following suffixes: + +`b`:: Bytes with binary values and suffixes. 1024 = `1KB` +`bb`:: Bytes with binary values and binary suffixes. 1024 = `1KiB` +`bd`:: Bytes with decimal values and suffixes. 1000 = `1kB` +`bitb`:: Bits with binary values and suffixes. 1024 = `1Kibit` +`bitd`:: Bits with decimal values and suffixes. 1000 = `1kbit` + +Suffixes are not localized with this formatter. + +|=== +| **Input** | **Pattern** | **Locale** | **Output** +| 2000 | 0.00b | en (English) | 1.95KB +| 2000 | 0.00bb | en (English) | 1.95KiB +| 2000 | 0.00bd | en (English) | 2.00kB +| 3153654400000 | 0.00bd | en (English) | 3.15GB +| 2000 | 0.00bitb | en (English) | 1.95Kibit +| 2000 | 0.00bitd | en (English) | 2.00kbit +|=== + +[float] +=== Currency + +Currency formatting is limited in {kib} due to the limitations of the pattern +syntax. To enable currency formatting, use the symbol `$` in the pattern syntax. +The number formatting locale will affect the result. + +|=== +| **Input** | **Pattern** | **Locale** | **Output** +| 1000.234 | $0,0.00 | en (English) | $1,000.23 +| 1000.234 | $0,0.00 | fr (French) | €1 000,23 +| 1000.234 | $0,0.00 | chs (Simplified Chinese) | ¥1,000.23 +|=== + +[float] +=== Duration formatting + +Converts a value in seconds to display hours, minutes, and seconds. + +|=== +| **Input** | **Pattern** | **Output** +| 25 | 00:00:00 | 0:00:25 +| 25 | 00:00 | 0:00:25 +| 238 | 00:00:00 | 0:03:58 +| 63846 | 00:00:00 | 17:44:06 +| -1 | 00:00:00 | -0:00:01 +|=== + +[float] +=== Displaying abbreviated numbers + +The `a` pattern will look for the shortest abbreviation for your +number, and use a locale-specific display for it. The abbreviations +`aK`, `aM`, `aB`, and `aT` can indicate that the number should be +abbreviated to a specific order of magnitude. + +|=== +| **Input** | **Pattern** | **Locale** | **Output** +| 2000000000 | 0.00a | en (English) | 2.00b +| 2000000000 | 0.00a | ja (Japanese) | 2.00十億 +| -5444333222111 | 0,0 aK | en (English) | -5,444,333,222 k +| -5444333222111 | 0,0 aM | en (English) | -5,444,333 m +| -5444333222111 | 0,0 aB | en (English) | -5,444 b +| -5444333222111 | 0,0 aT | en (English) | -5 t +|=== + +[float] +=== Ordinal numbers + +The `o` pattern will display a locale-specific positional value like `1st` or `2nd`. +This pattern has limited support for localization, especially in languages +with multiple forms, such as German. + +|=== +| **Input** | **Pattern** | **Locale** | **Output** +| 3 | 0o | en (English) | 3rd +| 34 | 0o | en (English) | 34th +| 3 | 0o | es (Spanish) | 2er +| 3 | 0o | ru (Russian) | 3. +|=== + +[float] +=== Complete number pattern reference + +These number formats, combined with the patterns described above, +produce the complete set of options for numeral formatting. +The output here is all for the `en` locale. + +|=== +| **Input** | **Pattern** | **Output** +| 10000 | 0,0.0000 | 10,000.0000 +| 10000.23 | 0,0 | 10,000 +| -10000 | 0,0.0 | -10,000.0 +| 10000.1234 | 0.000 | 10000.123 +| 10000 | 0[.]00 | 10000 +| 10000.1 | 0[.]00 | 10000.10 +| 10000.123 | 0[.]00 | 10000.12 +| 10000.456 | 0[.]00 | 10000.46 +| 10000.001 | 0[.]00 | 10000 +| 10000.45 | 0[.]00[0] | 10000.45 +| 10000.456 | 0[.]00[0] | 10000.456 +| -10000 | (0,0.0000) | (10,000.0000) +| -12300 | +0,0.0000 | -12,300.0000 +| 1230 | +0,0 | +1,230 +| 100.78 | 0 | 101 +| 100.28 | 0 | 100 +| 1.932 | 0.0 | 1.9 +| 1.9687 | 0 | 2 +| 1.9687 | 0.0 | 2.0 +| -0.23 | .00 | -.23 +| -0.23 | (.00) | (.23) +| 0.23 | 0.00000 | 0.23000 +| 0.67 | 0.0[0000] | 0.67 +| 1.005 | 0.00 | 1.01 +| 1e35 | 000 | 1e+35 +| -1e35 | 000 | -1e+35 +| 1e-27 | 000 | 1e-27 +| -1e-27 | 000 | -1e-27 +|=== + + diff --git a/docs/user/management.asciidoc b/docs/user/management.asciidoc index 1c55ffc73ca72..34a3790529ca3 100644 --- a/docs/user/management.asciidoc +++ b/docs/user/management.asciidoc @@ -129,6 +129,8 @@ include::{kib-repo-dir}/management/managing-fields.asciidoc[] include::{kib-repo-dir}/management/managing-licenses.asciidoc[] +include::{kib-repo-dir}/management/numeral.asciidoc[] + include::{kib-repo-dir}/management/managing-remote-clusters.asciidoc[] include::{kib-repo-dir}/management/rollups/create_and_manage_rollups.asciidoc[] diff --git a/package.json b/package.json index c3fe290e7934f..0f04a2fba3b65 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "@elastic/eui": "19.0.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", - "@elastic/numeral": "2.3.5", + "@elastic/numeral": "2.4.0", "@elastic/request-crypto": "^1.0.2", "@elastic/ui-ace": "0.2.3", "@hapi/wreck": "^15.0.1", diff --git a/x-pack/legacy/plugins/canvas/i18n/constants.ts b/x-pack/legacy/plugins/canvas/i18n/constants.ts index 8aee6ca148681..4cb05b0426fa1 100644 --- a/x-pack/legacy/plugins/canvas/i18n/constants.ts +++ b/x-pack/legacy/plugins/canvas/i18n/constants.ts @@ -28,7 +28,7 @@ export const LUCENE = 'Lucene'; export const MARKDOWN = 'Markdown'; export const MOMENTJS = 'MomentJS'; export const MOMENTJS_TIMEZONE_URL = 'https://momentjs.com/timezone/'; -export const NUMERALJS = 'NumeralJS'; +export const NUMERALJS = 'Numeral pattern'; export const PDF = 'PDF'; export const POST = 'POST'; export const RGB = 'RGB'; diff --git a/x-pack/legacy/plugins/canvas/i18n/functions/dict/formatnumber.ts b/x-pack/legacy/plugins/canvas/i18n/functions/dict/formatnumber.ts index df306eb1c04ed..f3e8a8858fc36 100644 --- a/x-pack/legacy/plugins/canvas/i18n/functions/dict/formatnumber.ts +++ b/x-pack/legacy/plugins/canvas/i18n/functions/dict/formatnumber.ts @@ -12,21 +12,19 @@ import { NUMERALJS } from '../../constants'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.formatnumberHelpText', { - defaultMessage: 'Formats a number into a formatted number string using {NUMERALJS}. See {url}.', + defaultMessage: 'Formats a number into a formatted number string using {NUMERALJS}.', values: { NUMERALJS, - url: 'http://numeraljs.com/#format', }, }), args: { + // TODO: Find a way to generate the docs URL here format: i18n.translate('xpack.canvas.functions.formatnumber.args.formatHelpText', { - defaultMessage: - 'A {NUMERALJS} format string. For example, {example1} or {example2}. See {url}.', + defaultMessage: 'A {NUMERALJS} format string. For example, {example1} or {example2}.', values: { example1: `"0.0a"`, example2: `"0%"`, NUMERALJS, - url: 'http://numeraljs.com/#format', }, }), }, diff --git a/x-pack/legacy/plugins/canvas/i18n/functions/dict/metric.ts b/x-pack/legacy/plugins/canvas/i18n/functions/dict/metric.ts index 8fa98667212a5..8258226e5dfc3 100644 --- a/x-pack/legacy/plugins/canvas/i18n/functions/dict/metric.ts +++ b/x-pack/legacy/plugins/canvas/i18n/functions/dict/metric.ts @@ -36,14 +36,13 @@ export const help: FunctionHelp> = { FONT_WEIGHT, }, }), + // TODO: Find a way to generate the docs URL here metricFormat: i18n.translate('xpack.canvas.functions.metric.args.metricFormatHelpText', { - defaultMessage: - 'A {NUMERALJS} format string. For example, {example1} or {example2}. See {url}.', + defaultMessage: 'A {NUMERALJS} format string. For example, {example1} or {example2}.', values: { example1: `"0.0a"`, example2: `"0%"`, NUMERALJS, - url: 'http://numeraljs.com/#format', }, }), }, diff --git a/x-pack/legacy/plugins/canvas/public/lib/documentation_links.ts b/x-pack/legacy/plugins/canvas/public/lib/documentation_links.ts index a43653f420ce0..40e09ee39d714 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/documentation_links.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/documentation_links.ts @@ -10,4 +10,7 @@ export const getDocumentationLinks = () => ({ canvas: `${getCoreStart().docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${ getCoreStart().docLinks.DOC_LINK_VERSION }/canvas.html`, + numeral: `${getCoreStart().docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${ + getCoreStart().docLinks.DOC_LINK_VERSION + }/guide/numeral.html`, }); diff --git a/x-pack/package.json b/x-pack/package.json index b8fe0326903b6..96e06dd71b3fe 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -183,7 +183,7 @@ "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", - "@elastic/numeral": "2.3.3", + "@elastic/numeral": "2.4.0", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3c7d0ce47acb7..b99a54160bb65 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4586,8 +4586,6 @@ "xpack.canvas.functions.filtersHelpText": "ワークパッドのエレメントフィルターを他 (通常データソース) で使用できるように集約します。", "xpack.canvas.functions.formatdate.args.formatHelpText": "{MOMENTJS} フォーマットです。例: {example}。{url} を参照。", "xpack.canvas.functions.formatdateHelpText": "{MOMENTJS} を使って {ISO8601} 日付文字列、または新世紀からのミリ秒での日付をフォーマットします。{url} を参照。", - "xpack.canvas.functions.formatnumber.args.formatHelpText": "{NUMERALJS} 文字列のフォーマットです。例: {example1} または {example2}。{url} を参照。", - "xpack.canvas.functions.formatnumberHelpText": "{NUMERALJS} を使って数字をフォーマットされた数字文字列にフォーマットします。{url} を参照。", "xpack.canvas.functions.getCell.args.columnHelpText": "値を取得する元の列の名前です。この値は入力されていないと、初めの列から取得されます。", "xpack.canvas.functions.getCell.args.rowHelpText": "行番号で、0 から開始します。", "xpack.canvas.functions.getCell.columnNotFoundErrorMessage": "列が見つかりません: 「{column}」", @@ -4633,7 +4631,6 @@ "xpack.canvas.functions.metric.args.labelFontHelpText": "ラベルの {CSS} フォントプロパティです。例: {FONT_FAMILY} または {FONT_WEIGHT}。", "xpack.canvas.functions.metric.args.labelHelpText": "メトリックを説明するテキストです。", "xpack.canvas.functions.metric.args.metricFontHelpText": "メトリックの {CSS} フォントプロパティです。例: {FONT_FAMILY} または {FONT_WEIGHT}。", - "xpack.canvas.functions.metric.args.metricFormatHelpText": "{NUMERALJS} 文字列のフォーマットです。例: {example1} または {example2}。{url} を参照。", "xpack.canvas.functions.metricHelpText": "ラベルの上に数字を表示します。", "xpack.canvas.functions.neq.args.valueHelpText": "{CONTEXT} と比較される値です。", "xpack.canvas.functions.neqHelpText": "{CONTEXT} が引数と等しくないかを戻します。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b262be626aa53..32f648826da84 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4587,8 +4587,6 @@ "xpack.canvas.functions.filtersHelpText": "聚合 Workpad 的元素筛选以用于他处,通常用于数据源。", "xpack.canvas.functions.formatdate.args.formatHelpText": "{MOMENTJS} 格式。例如:{example}。请参见 {url}。", "xpack.canvas.functions.formatdateHelpText": "使用 {MOMENTJS} 格式化 {ISO8601} 日期字符串或自 Epoch 起以毫秒表示的日期。请参见 {url}。", - "xpack.canvas.functions.formatnumber.args.formatHelpText": "{NUMERALJS} 格式字符串。例如 {example1} 或 {example2}。请参见 {url}。", - "xpack.canvas.functions.formatnumberHelpText": "使用 {NUMERALJS} 将数字格式化为带格式数字字符串。请参见 {url}。", "xpack.canvas.functions.getCell.args.columnHelpText": "从其中提取值的列的名称。如果未提供,将从第一列检索值。", "xpack.canvas.functions.getCell.args.rowHelpText": "行编号,从 0 开始。", "xpack.canvas.functions.getCell.columnNotFoundErrorMessage": "找不到列:“{column}”", @@ -4634,7 +4632,6 @@ "xpack.canvas.functions.metric.args.labelFontHelpText": "标签的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", "xpack.canvas.functions.metric.args.labelHelpText": "描述指标的文本。", "xpack.canvas.functions.metric.args.metricFontHelpText": "指标的 {CSS} 字体属性。例如 {FONT_FAMILY} 或 {FONT_WEIGHT}。", - "xpack.canvas.functions.metric.args.metricFormatHelpText": "{NUMERALJS} 格式字符串。例如 {example1} 或 {example2}。请参见 {url}。", "xpack.canvas.functions.metricHelpText": "在标签上显示数字。", "xpack.canvas.functions.neq.args.valueHelpText": "与 {CONTEXT} 比较的值。", "xpack.canvas.functions.neqHelpText": "返回 {CONTEXT} 是否不等于参数。", diff --git a/yarn.lock b/yarn.lock index 7906f363813b8..7f38495c20f4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2037,15 +2037,10 @@ resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.0.0.tgz#4d325df333fe1319556bb4d54214098ada1171d4" integrity sha512-bbjbEyILPRTRt0xnda18OttLtlkJBPuXx3CjISUSn9jhWqHoFMzfOaZ73D5jxZE2SaFZUrJYfPpqXP6qqPufAQ== -"@elastic/numeral@2.3.3": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.3.tgz#94d38a35bd315efa7a6918b22695128fc40a885e" - integrity sha512-0OyB9oztlYIq8F1LHjcNf+T089PKfYw78tgUY+q2dtox/jmb4xzFKtI9kv1hwAt5tcgBUTtUMK9kszpSh1UZaQ== - -"@elastic/numeral@2.3.5": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.5.tgz#fcaeac57ddc55cd4b7f0b9c7e070c242dd5d0600" - integrity sha512-lJVZHPuI2csrfwDIEdKedFqNIF+5YsyqvX2soAqhu49iKOd9n7tifLRn30vP6D7oKd+6HsiGfPzT0nzdJWsexQ== +"@elastic/numeral@2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.4.0.tgz#883197b7f4bf3c2dd994f53b274769ddfa2bf79a" + integrity sha512-uGBKGCNghTgUZPHClji/00v+AKt5nidPTGOIbcT+lbTPVxNB6QPpPLGWtXyrg3QZAxobPM/LAZB1mAqtJeq44Q== "@elastic/request-crypto@^1.0.2": version "1.0.2" From 68624da3d57abf191ab1a52b004fbd8bef6fc38f Mon Sep 17 00:00:00 2001 From: Thomas Neirynck Date: Wed, 26 Feb 2020 14:34:44 -0500 Subject: [PATCH 19/22] [Maps] Add XYZTMSSource and TileLayer unit test suites (#58567) --- .../legacy/plugins/maps/common/constants.ts | 2 +- .../plugins/maps/common/descriptor_types.d.ts | 7 ++- .../plugins/maps/public/layers/layer.d.ts | 21 +++++++ .../maps/public/layers/sources/source.d.ts | 19 +++++++ .../public/layers/sources/tms_source.d.ts | 15 +++++ .../public/layers/sources/xyz_tms_source.d.ts | 11 ++++ .../public/layers/sources/xyz_tms_source.js | 3 +- .../layers/sources/xyz_tms_source.test.ts | 30 ++++++++++ .../maps/public/layers/tile_layer.d.ts | 18 ++++++ .../maps/public/layers/tile_layer.test.ts | 55 +++++++++++++++++++ 10 files changed, 177 insertions(+), 4 deletions(-) create mode 100644 x-pack/legacy/plugins/maps/public/layers/layer.d.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts create mode 100644 x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts diff --git a/x-pack/legacy/plugins/maps/common/constants.ts b/x-pack/legacy/plugins/maps/common/constants.ts index 542abfb004d0d..0e93fd6593710 100644 --- a/x-pack/legacy/plugins/maps/common/constants.ts +++ b/x-pack/legacy/plugins/maps/common/constants.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; - export const EMS_CATALOGUE_PATH = 'ems/catalogue'; export const EMS_FILES_CATALOGUE_PATH = 'ems/files'; @@ -54,6 +53,7 @@ export const EMS_FILE = 'EMS_FILE'; export const ES_GEO_GRID = 'ES_GEO_GRID'; export const ES_SEARCH = 'ES_SEARCH'; export const ES_PEW_PEW = 'ES_PEW_PEW'; +export const EMS_XYZ = 'EMS_XYZ'; // identifies a custom TMS source. Name is a little unfortunate. export const FIELD_ORIGIN = { SOURCE: 'source', diff --git a/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts b/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts index 05123c9c86b63..c024721dfb870 100644 --- a/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts +++ b/x-pack/legacy/plugins/maps/common/descriptor_types.d.ts @@ -4,14 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IFieldType } from '../../../../../src/plugins/data/common/index_patterns/fields'; - export interface ISourceDescriptor { id: string; type: string; } +export interface IXYZTMSSourceDescriptor extends ISourceDescriptor { + urlTemplate: string; +} + export interface ILayerDescriptor { sourceDescriptor: ISourceDescriptor; id: string; + label?: string; } diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts b/x-pack/legacy/plugins/maps/public/layers/layer.d.ts new file mode 100644 index 0000000000000..ed7dcb062d8c4 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/layer.d.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ILayerDescriptor } from '../../common/descriptor_types'; +import { ISource } from './sources/source'; + +export interface ILayer { + getDisplayName(): Promise; +} + +export interface ILayerArguments { + layerDescriptor: ILayerDescriptor; + source: ISource; +} + +export class AbstractLayer implements ILayer { + constructor(layerArguments: ILayerArguments); + getDisplayName(): Promise; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts new file mode 100644 index 0000000000000..372981de42597 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ISourceDescriptor } from '../../../common/descriptor_types'; +import { ILayer } from '../layer'; + +export interface ISource { + createDefaultLayer(): ILayer; + getDisplayName(): Promise; +} + +export class AbstractSource implements ISource { + constructor(sourceDescriptor: ISourceDescriptor, inspectorAdapters: object); + createDefaultLayer(): ILayer; + getDisplayName(): Promise; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts new file mode 100644 index 0000000000000..90b6f28e050fd --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AbstractSource, ISource } from './source'; + +export interface ITMSSource extends ISource { + getUrlTemplate(): Promise; +} + +export class AbstractTMSSource extends AbstractSource implements ITMSSource { + getUrlTemplate(): Promise; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts new file mode 100644 index 0000000000000..1150c39b79db5 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { AbstractTMSSource } from './tms_source'; +import { IXYZTMSSourceDescriptor } from '../../../common/descriptor_types'; + +export class XYZTMSSource extends AbstractTMSSource { + constructor(sourceDescriptor: IXYZTMSSourceDescriptor, inspectorAdapters: unknown); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js index a4d331f48beae..354883372e244 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js @@ -12,9 +12,10 @@ import { TileLayer } from '../tile_layer'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel, getUrlLabel } from '../../../common/i18n_getters'; import _ from 'lodash'; +import { EMS_XYZ } from '../../../common/constants'; export class XYZTMSSource extends AbstractTMSSource { - static type = 'EMS_XYZ'; + static type = EMS_XYZ; static title = i18n.translate('xpack.maps.source.ems_xyzTitle', { defaultMessage: 'Tile Map Service', }); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts new file mode 100644 index 0000000000000..5de85adde158f --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { XYZTMSSource } from './xyz_tms_source'; +import { ILayer } from '../layer'; +import { TileLayer } from '../tile_layer'; +import { EMS_XYZ } from '../../../common/constants'; +import { IXYZTMSSourceDescriptor } from '../../../common/descriptor_types'; + +const descriptor: IXYZTMSSourceDescriptor = { + type: EMS_XYZ, + urlTemplate: 'https://example.com/{x}/{y}/{z}.png', + id: 'foobar', +}; +describe('xyz Tilemap Source', () => { + it('should create a tile-layer', () => { + const source = new XYZTMSSource(descriptor, null); + const layer: ILayer = source.createDefaultLayer(); + expect(layer instanceof TileLayer).toEqual(true); + }); + + it('should echo url template for url template', async () => { + const source = new XYZTMSSource(descriptor, null); + const template = await source.getUrlTemplate(); + expect(template).toEqual(descriptor.urlTemplate); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts b/x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts new file mode 100644 index 0000000000000..fdb37a8af805f --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/tile_layer.d.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AbstractLayer, ILayerArguments } from './layer'; +import { ITMSSource } from './sources/tms_source'; +import { ILayerDescriptor } from '../../common/descriptor_types'; + +interface ITileLayerArguments extends ILayerArguments { + source: ITMSSource; + layerDescriptor: ILayerDescriptor; +} + +export class TileLayer extends AbstractLayer { + constructor(args: ITileLayerArguments); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts new file mode 100644 index 0000000000000..b2d2b08c637cf --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TileLayer } from './tile_layer'; +import { EMS_XYZ } from '../../common/constants'; +import { IXYZTMSSourceDescriptor } from '../../common/descriptor_types'; +import { ITMSSource } from './sources/tms_source'; +import { ILayer } from './layer'; + +const sourceDescriptor: IXYZTMSSourceDescriptor = { + type: EMS_XYZ, + urlTemplate: 'https://example.com/{x}/{y}/{z}.png', + id: 'foobar', +}; + +class MockTileSource implements ITMSSource { + private _descriptor: IXYZTMSSourceDescriptor; + constructor(descriptor: IXYZTMSSourceDescriptor) { + this._descriptor = descriptor; + } + createDefaultLayer(): ILayer { + throw new Error('not implemented'); + } + + async getDisplayName(): Promise { + return this._descriptor.urlTemplate; + } + + async getUrlTemplate(): Promise { + return 'template/{x}/{y}/{z}.png'; + } +} + +describe('TileLayer', () => { + it('should use display-label from source', async () => { + const source = new MockTileSource(sourceDescriptor); + const layer: ILayer = new TileLayer({ + source, + layerDescriptor: { id: 'layerid', sourceDescriptor }, + }); + expect(await source.getDisplayName()).toEqual(await layer.getDisplayName()); + }); + + it('should override with custom display-label if present', async () => { + const source = new MockTileSource(sourceDescriptor); + const layer: ILayer = new TileLayer({ + source, + layerDescriptor: { id: 'layerid', sourceDescriptor, label: 'custom' }, + }); + expect('custom').toEqual(await layer.getDisplayName()); + }); +}); From 4cb9bc44721e26ac2898eb11621b85f4418d913e Mon Sep 17 00:00:00 2001 From: Josh Dover Date: Wed, 26 Feb 2020 12:43:12 -0700 Subject: [PATCH 20/22] Add ScopedHistory to AppMountParams (#56705) --- .../core/public/kibana-plugin-public.app.md | 4 +- .../public/kibana-plugin-public.app.mount.md | 2 +- ...plugin-public.applicationsetup.register.md | 4 +- .../public/kibana-plugin-public.appmount.md | 2 +- ...kibana-plugin-public.appmountdeprecated.md | 2 +- ...n-public.appmountparameters.appbasepath.md | 9 +- ...lugin-public.appmountparameters.history.md | 58 +++ ...kibana-plugin-public.appmountparameters.md | 3 +- .../core/public/kibana-plugin-public.md | 1 + ...ugin-public.scopedhistory._constructor_.md | 21 ++ ...bana-plugin-public.scopedhistory.action.md | 13 + ...ibana-plugin-public.scopedhistory.block.md | 18 + ...-plugin-public.scopedhistory.createhref.md | 13 + ...n-public.scopedhistory.createsubhistory.md | 13 + .../kibana-plugin-public.scopedhistory.go.md | 13 + ...bana-plugin-public.scopedhistory.goback.md | 13 + ...a-plugin-public.scopedhistory.goforward.md | 13 + ...bana-plugin-public.scopedhistory.length.md | 13 + ...bana-plugin-public.scopedhistory.listen.md | 13 + ...na-plugin-public.scopedhistory.location.md | 13 + .../kibana-plugin-public.scopedhistory.md | 41 +++ ...kibana-plugin-public.scopedhistory.push.md | 13 + ...ana-plugin-public.scopedhistory.replace.md | 13 + .../application/application_service.test.ts | 30 ++ .../application/application_service.tsx | 17 +- src/core/public/application/index.ts | 1 + .../integration_tests/router.test.tsx | 136 ++++++-- .../application/integration_tests/utils.tsx | 22 +- .../public/application/scoped_history.mock.ts | 57 +++ .../public/application/scoped_history.test.ts | 329 ++++++++++++++++++ src/core/public/application/scoped_history.ts | 318 +++++++++++++++++ src/core/public/application/types.ts | 76 +++- .../application/ui/app_container.test.tsx | 7 + .../public/application/ui/app_container.tsx | 9 +- src/core/public/application/ui/app_router.tsx | 22 +- src/core/public/index.ts | 1 + src/core/public/mocks.ts | 1 + src/core/public/public.api.md | 36 +- .../local_application_service.ts | 8 +- .../new_platform/new_platform.test.mocks.ts | 7 + .../public/new_platform/new_platform.test.ts | 6 +- .../ui/public/new_platform/new_platform.ts | 19 +- src/plugins/dev_tools/public/application.tsx | 8 +- .../core_plugin_a/public/application.tsx | 14 +- .../core_plugin_b/public/application.tsx | 14 +- .../plugins/core_plugin_legacy/index.ts | 1 + .../plugins/legacy_plugin/index.ts | 34 ++ .../plugins/legacy_plugin/package.json | 17 + .../plugins/legacy_plugin/public/index.tsx | 35 ++ .../plugins/legacy_plugin/tsconfig.json | 15 + .../core_plugins/application_leave_confirm.ts | 2 +- .../test_suites/core_plugins/applications.ts | 28 +- .../core_plugins/legacy_plugins.ts | 2 +- .../test_suites/core_plugins/rendering.ts | 8 +- .../public/legacy.ts | 4 +- .../public/register_route.ts | 4 +- x-pack/legacy/plugins/ml/public/plugin.ts | 1 + .../plugins/uptime/public/apps/plugin.ts | 1 - 58 files changed, 1479 insertions(+), 119 deletions(-) create mode 100644 docs/development/core/public/kibana-plugin-public.appmountparameters.history.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory._constructor_.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.action.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.block.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.createhref.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.createsubhistory.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.go.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.goback.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.goforward.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.length.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.listen.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.location.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.push.md create mode 100644 docs/development/core/public/kibana-plugin-public.scopedhistory.replace.md create mode 100644 src/core/public/application/scoped_history.mock.ts create mode 100644 src/core/public/application/scoped_history.test.ts create mode 100644 src/core/public/application/scoped_history.ts create mode 100644 test/plugin_functional/plugins/legacy_plugin/index.ts create mode 100644 test/plugin_functional/plugins/legacy_plugin/package.json create mode 100644 test/plugin_functional/plugins/legacy_plugin/public/index.tsx create mode 100644 test/plugin_functional/plugins/legacy_plugin/tsconfig.json diff --git a/docs/development/core/public/kibana-plugin-public.app.md b/docs/development/core/public/kibana-plugin-public.app.md index faea94c467726..f31db3674f5ba 100644 --- a/docs/development/core/public/kibana-plugin-public.app.md +++ b/docs/development/core/public/kibana-plugin-public.app.md @@ -9,7 +9,7 @@ Extension of [common app properties](./kibana-plugin-public.appbase.md) with the Signature: ```typescript -export interface App extends AppBase +export interface App extends AppBase ``` ## Properties @@ -18,5 +18,5 @@ export interface App extends AppBase | --- | --- | --- | | [appRoute](./kibana-plugin-public.app.approute.md) | string | Override the application's routing path from /app/${id}. Must be unique across registered applications. Should not include the base path from HTTP. | | [chromeless](./kibana-plugin-public.app.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | -| [mount](./kibana-plugin-public.app.mount.md) | AppMount | AppMountDeprecated | A mount function called when the user navigates to this app's route. May have signature of [AppMount](./kibana-plugin-public.appmount.md) or [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md). | +| [mount](./kibana-plugin-public.app.mount.md) | AppMount<HistoryLocationState> | AppMountDeprecated<HistoryLocationState> | A mount function called when the user navigates to this app's route. May have signature of [AppMount](./kibana-plugin-public.appmount.md) or [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md). | diff --git a/docs/development/core/public/kibana-plugin-public.app.mount.md b/docs/development/core/public/kibana-plugin-public.app.mount.md index 2af5f0277759a..4829307ff267c 100644 --- a/docs/development/core/public/kibana-plugin-public.app.mount.md +++ b/docs/development/core/public/kibana-plugin-public.app.mount.md @@ -9,7 +9,7 @@ A mount function called when the user navigates to this app's route. May have si Signature: ```typescript -mount: AppMount | AppMountDeprecated; +mount: AppMount | AppMountDeprecated; ``` ## Remarks diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md index 5c6c7cd252b0a..27c3e28c05a0d 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md +++ b/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md @@ -9,14 +9,14 @@ Register an mountable application to the system. Signature: ```typescript -register(app: App): void; +register(app: App): void; ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| app | App | an [App](./kibana-plugin-public.app.md) | +| app | App<HistoryLocationState> | an [App](./kibana-plugin-public.app.md) | Returns: diff --git a/docs/development/core/public/kibana-plugin-public.appmount.md b/docs/development/core/public/kibana-plugin-public.appmount.md index 84a52b09a119c..a001b1f75c99e 100644 --- a/docs/development/core/public/kibana-plugin-public.appmount.md +++ b/docs/development/core/public/kibana-plugin-public.appmount.md @@ -9,5 +9,5 @@ A mount function called when the user navigates to this app's route. Signature: ```typescript -export declare type AppMount = (params: AppMountParameters) => AppUnmount | Promise; +export declare type AppMount = (params: AppMountParameters) => AppUnmount | Promise; ``` diff --git a/docs/development/core/public/kibana-plugin-public.appmountdeprecated.md b/docs/development/core/public/kibana-plugin-public.appmountdeprecated.md index 8c8114182b60f..2bd2e956124c0 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountdeprecated.md +++ b/docs/development/core/public/kibana-plugin-public.appmountdeprecated.md @@ -13,7 +13,7 @@ A mount function called when the user navigates to this app's route. Signature: ```typescript -export declare type AppMountDeprecated = (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; +export declare type AppMountDeprecated = (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; ``` ## Remarks diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md index 041d976aa42a2..beedda98d8f4d 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md +++ b/docs/development/core/public/kibana-plugin-public.appmountparameters.appbasepath.md @@ -4,6 +4,11 @@ ## AppMountParameters.appBasePath property +> Warning: This API is now obsolete. +> +> Use [AppMountParameters.history](./kibana-plugin-public.appmountparameters.history.md) instead. +> + The route path for configuring navigation to the application. This string should not include the base path from HTTP. Signature: @@ -39,10 +44,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter, Route } from 'react-router-dom'; -import { CoreStart, AppMountParams } from 'src/core/public'; +import { CoreStart, AppMountParameters } from 'src/core/public'; import { MyPluginDepsStart } from './plugin'; -export renderApp = ({ appBasePath, element }: AppMountParams) => { +export renderApp = ({ appBasePath, element }: AppMountParameters) => { ReactDOM.render( // pass `appBasePath` to `basename` diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.history.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.history.md new file mode 100644 index 0000000000000..9a3fa1a1bb48a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appmountparameters.history.md @@ -0,0 +1,58 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountParameters](./kibana-plugin-public.appmountparameters.md) > [history](./kibana-plugin-public.appmountparameters.history.md) + +## AppMountParameters.history property + +A scoped history instance for your application. Should be used to wire up your applications Router. + +Signature: + +```typescript +history: ScopedHistory; +``` + +## Example + +How to configure react-router with a base path: + +```ts +// inside your plugin's setup function +export class MyPlugin implements Plugin { + setup({ application }) { + application.register({ + id: 'my-app', + appRoute: '/my-app', + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } +} + +``` + +```ts +// application.tsx +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Router, Route } from 'react-router-dom'; + +import { CoreStart, AppMountParameters } from 'src/core/public'; +import { MyPluginDepsStart } from './plugin'; + +export renderApp = ({ element, history }: AppMountParameters) => { + ReactDOM.render( + // pass `appBasePath` to `basename` + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +} + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.md index c21889c28bda4..e652379fc3034 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.md +++ b/docs/development/core/public/kibana-plugin-public.appmountparameters.md @@ -8,7 +8,7 @@ Signature: ```typescript -export interface AppMountParameters +export interface AppMountParameters ``` ## Properties @@ -17,5 +17,6 @@ export interface AppMountParameters | --- | --- | --- | | [appBasePath](./kibana-plugin-public.appmountparameters.appbasepath.md) | string | The route path for configuring navigation to the application. This string should not include the base path from HTTP. | | [element](./kibana-plugin-public.appmountparameters.element.md) | HTMLElement | The container element to render the application into. | +| [history](./kibana-plugin-public.appmountparameters.history.md) | ScopedHistory<HistoryLocationState> | A scoped history instance for your application. Should be used to wire up your applications Router. | | [onAppLeave](./kibana-plugin-public.appmountparameters.onappleave.md) | (handler: AppLeaveHandler) => void | A function that can be used to register a handler that will be called when the user is leaving the current application, allowing to prompt a confirmation message before actually changing the page.This will be called either when the user goes to another application, or when trying to close the tab or manually changing the url. | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 95a4327728139..79bdb11b80fa4 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -15,6 +15,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Class | Description | | --- | --- | | [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. | +| [ScopedHistory](./kibana-plugin-public.scopedhistory.md) | A wrapper around a History instance that is scoped to a particular base path of the history stack. Behaves similarly to the basename option except that this wrapper hides any history stack entries from outside the scope of this base path.This wrapper also allows Core and Plugins to share a single underlying global History instance without exposing the history of other applications.The [createSubHistory](./kibana-plugin-public.scopedhistory.createsubhistory.md) method is particularly useful for applications that contain any number of "sub-apps" which should not have access to the main application's history or basePath. | | [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md).It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. | | [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. | diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory._constructor_.md b/docs/development/core/public/kibana-plugin-public.scopedhistory._constructor_.md new file mode 100644 index 0000000000000..d21c908890b6b --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory._constructor_.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [(constructor)](./kibana-plugin-public.scopedhistory._constructor_.md) + +## ScopedHistory.(constructor) + +Constructs a new instance of the `ScopedHistory` class + +Signature: + +```typescript +constructor(parentHistory: History, basePath: string); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| parentHistory | History | | +| basePath | string | | + diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.action.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.action.md new file mode 100644 index 0000000000000..c3b52b9041871 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.action.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [action](./kibana-plugin-public.scopedhistory.action.md) + +## ScopedHistory.action property + +The last action dispatched on the history stack. + +Signature: + +```typescript +get action(): Action; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.block.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.block.md new file mode 100644 index 0000000000000..b6c5da58d4684 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.block.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [block](./kibana-plugin-public.scopedhistory.block.md) + +## ScopedHistory.block property + +Not supported. Use [AppMountParameters.onAppLeave](./kibana-plugin-public.appmountparameters.onappleave.md). + +Signature: + +```typescript +block: (prompt?: string | boolean | History.TransitionPromptHook | undefined) => UnregisterCallback; +``` + +## Remarks + +We prefer that applications use the `onAppLeave` API because it supports a more graceful experience that prefers a modal when possible, falling back to a confirm dialog box in the beforeunload case. + diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.createhref.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.createhref.md new file mode 100644 index 0000000000000..6bbd78dc9b3c9 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.createhref.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [createHref](./kibana-plugin-public.scopedhistory.createhref.md) + +## ScopedHistory.createHref property + +Creates an href (string) to the location. + +Signature: + +```typescript +createHref: (location: LocationDescriptorObject) => string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.createsubhistory.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.createsubhistory.md new file mode 100644 index 0000000000000..045289c98e4ee --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.createsubhistory.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [createSubHistory](./kibana-plugin-public.scopedhistory.createsubhistory.md) + +## ScopedHistory.createSubHistory property + +Creates a `ScopedHistory` for a subpath of this `ScopedHistory`. Useful for applications that may have sub-apps that do not need access to the containing application's history. + +Signature: + +```typescript +createSubHistory: (basePath: string) => ScopedHistory; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.go.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.go.md new file mode 100644 index 0000000000000..b9d124d06a109 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.go.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [go](./kibana-plugin-public.scopedhistory.go.md) + +## ScopedHistory.go property + +Send the user forward or backwards in the history stack. + +Signature: + +```typescript +go: (n: number) => void; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.goback.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.goback.md new file mode 100644 index 0000000000000..8f1d4b25ebcbf --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.goback.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [goBack](./kibana-plugin-public.scopedhistory.goback.md) + +## ScopedHistory.goBack property + +Send the user one location back in the history stack. Equivalent to calling [ScopedHistory.go(-1)](./kibana-plugin-public.scopedhistory.go.md). If no more entries are available backwards, this is a no-op. + +Signature: + +```typescript +goBack: () => void; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.goforward.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.goforward.md new file mode 100644 index 0000000000000..587d5035bb5b5 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.goforward.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [goForward](./kibana-plugin-public.scopedhistory.goforward.md) + +## ScopedHistory.goForward property + +Send the user one location forward in the history stack. Equivalent to calling [ScopedHistory.go(1)](./kibana-plugin-public.scopedhistory.go.md). If no more entries are available forwards, this is a no-op. + +Signature: + +```typescript +goForward: () => void; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.length.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.length.md new file mode 100644 index 0000000000000..8a8d6accc1fbc --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.length.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [length](./kibana-plugin-public.scopedhistory.length.md) + +## ScopedHistory.length property + +The number of entries in the history stack, including all entries forwards and backwards from the current location. + +Signature: + +```typescript +get length(): number; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.listen.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.listen.md new file mode 100644 index 0000000000000..a0693e5a0428d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.listen.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [listen](./kibana-plugin-public.scopedhistory.listen.md) + +## ScopedHistory.listen property + +Adds a listener for location updates. + +Signature: + +```typescript +listen: (listener: (location: Location, action: Action) => void) => UnregisterCallback; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.location.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.location.md new file mode 100644 index 0000000000000..c40f73333ec7d --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.location.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [location](./kibana-plugin-public.scopedhistory.location.md) + +## ScopedHistory.location property + +The current location of the history stack. + +Signature: + +```typescript +get location(): Location; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.md new file mode 100644 index 0000000000000..5b429c1e6ec9e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.md @@ -0,0 +1,41 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) + +## ScopedHistory class + +A wrapper around a `History` instance that is scoped to a particular base path of the history stack. Behaves similarly to the `basename` option except that this wrapper hides any history stack entries from outside the scope of this base path. + +This wrapper also allows Core and Plugins to share a single underlying global `History` instance without exposing the history of other applications. + +The [createSubHistory](./kibana-plugin-public.scopedhistory.createsubhistory.md) method is particularly useful for applications that contain any number of "sub-apps" which should not have access to the main application's history or basePath. + +Signature: + +```typescript +export declare class ScopedHistory implements History +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(parentHistory, basePath)](./kibana-plugin-public.scopedhistory._constructor_.md) | | Constructs a new instance of the ScopedHistory class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [action](./kibana-plugin-public.scopedhistory.action.md) | | Action | The last action dispatched on the history stack. | +| [block](./kibana-plugin-public.scopedhistory.block.md) | | (prompt?: string | boolean | History.TransitionPromptHook<HistoryLocationState> | undefined) => UnregisterCallback | Not supported. Use [AppMountParameters.onAppLeave](./kibana-plugin-public.appmountparameters.onappleave.md). | +| [createHref](./kibana-plugin-public.scopedhistory.createhref.md) | | (location: LocationDescriptorObject<HistoryLocationState>) => string | Creates an href (string) to the location. | +| [createSubHistory](./kibana-plugin-public.scopedhistory.createsubhistory.md) | | <SubHistoryLocationState = unknown>(basePath: string) => ScopedHistory<SubHistoryLocationState> | Creates a ScopedHistory for a subpath of this ScopedHistory. Useful for applications that may have sub-apps that do not need access to the containing application's history. | +| [go](./kibana-plugin-public.scopedhistory.go.md) | | (n: number) => void | Send the user forward or backwards in the history stack. | +| [goBack](./kibana-plugin-public.scopedhistory.goback.md) | | () => void | Send the user one location back in the history stack. Equivalent to calling [ScopedHistory.go(-1)](./kibana-plugin-public.scopedhistory.go.md). If no more entries are available backwards, this is a no-op. | +| [goForward](./kibana-plugin-public.scopedhistory.goforward.md) | | () => void | Send the user one location forward in the history stack. Equivalent to calling [ScopedHistory.go(1)](./kibana-plugin-public.scopedhistory.go.md). If no more entries are available forwards, this is a no-op. | +| [length](./kibana-plugin-public.scopedhistory.length.md) | | number | The number of entries in the history stack, including all entries forwards and backwards from the current location. | +| [listen](./kibana-plugin-public.scopedhistory.listen.md) | | (listener: (location: Location<HistoryLocationState>, action: Action) => void) => UnregisterCallback | Adds a listener for location updates. | +| [location](./kibana-plugin-public.scopedhistory.location.md) | | Location<HistoryLocationState> | The current location of the history stack. | +| [push](./kibana-plugin-public.scopedhistory.push.md) | | (pathOrLocation: string | LocationDescriptorObject<HistoryLocationState>, state?: HistoryLocationState | undefined) => void | Pushes a new location onto the history stack. If there are forward entries in the stack, they will be removed. | +| [replace](./kibana-plugin-public.scopedhistory.replace.md) | | (pathOrLocation: string | LocationDescriptorObject<HistoryLocationState>, state?: HistoryLocationState | undefined) => void | Replaces the current location in the history stack. Does not remove forward or backward entries. | + diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.push.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.push.md new file mode 100644 index 0000000000000..0d8d635d0f189 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.push.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [push](./kibana-plugin-public.scopedhistory.push.md) + +## ScopedHistory.push property + +Pushes a new location onto the history stack. If there are forward entries in the stack, they will be removed. + +Signature: + +```typescript +push: (pathOrLocation: string | LocationDescriptorObject, state?: HistoryLocationState | undefined) => void; +``` diff --git a/docs/development/core/public/kibana-plugin-public.scopedhistory.replace.md b/docs/development/core/public/kibana-plugin-public.scopedhistory.replace.md new file mode 100644 index 0000000000000..f9c1171d4217e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.scopedhistory.replace.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ScopedHistory](./kibana-plugin-public.scopedhistory.md) > [replace](./kibana-plugin-public.scopedhistory.replace.md) + +## ScopedHistory.replace property + +Replaces the current location in the history stack. Does not remove forward or backward entries. + +Signature: + +```typescript +replace: (pathOrLocation: string | LocationDescriptorObject, state?: HistoryLocationState | undefined) => void; +``` diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index 5487ca53170dd..c25918c6b7328 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -588,6 +588,16 @@ describe('#start()', () => { expect(getUrlForApp('app1', { path: 'deep/link///' })).toBe('/base-path/app/app1/deep/link'); }); + it('does not append trailing slash if hash is provided in path parameter', async () => { + service.setup(setupDeps); + const { getUrlForApp } = await service.start(startDeps); + + expect(getUrlForApp('app1', { path: '#basic-hash' })).toBe('/base-path/app/app1#basic-hash'); + expect(getUrlForApp('app1', { path: '#/hash/router/path' })).toBe( + '/base-path/app/app1#/hash/router/path' + ); + }); + it('creates absolute URLs when `absolute` parameter is true', async () => { service.setup(setupDeps); const { getUrlForApp } = await service.start(startDeps); @@ -646,6 +656,26 @@ describe('#start()', () => { ); }); + it('appends a path if specified with hash', async () => { + const { register } = service.setup(setupDeps); + + register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/path' })); + + const { navigateToApp } = await service.start(startDeps); + + await navigateToApp('myTestApp', { path: '#basic-hash' }); + expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp#basic-hash', undefined); + + await navigateToApp('myTestApp', { path: '#/hash/router/path' }); + expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp#/hash/router/path', undefined); + + await navigateToApp('app2', { path: '#basic-hash' }); + expect(MockHistory.push).toHaveBeenCalledWith('/custom/path#basic-hash', undefined); + + await navigateToApp('app2', { path: '#/hash/router/path' }); + expect(MockHistory.push).toHaveBeenCalledWith('/custom/path#/hash/router/path', undefined); + }); + it('includes state if specified', async () => { const { register } = service.setup(setupDeps); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 77f06e316c0aa..1c9492d81c7f6 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -76,10 +76,19 @@ function filterAvailable(m: Map, capabilities: Capabilities) { } const findMounter = (mounters: Map, appRoute?: string) => [...mounters].find(([, mounter]) => mounter.appRoute === appRoute); -const getAppUrl = (mounters: Map, appId: string, path: string = '') => - `/${mounters.get(appId)?.appRoute ?? `/app/${appId}`}/${path}` + +const getAppUrl = (mounters: Map, appId: string, path: string = '') => { + const appBasePath = mounters.get(appId)?.appRoute + ? `/${mounters.get(appId)!.appRoute}` + : `/app/${appId}`; + + // Only preppend slash if not a hash or query path + path = path.startsWith('#') || path.startsWith('?') ? path : `/${path}`; + + return `${appBasePath}${path}` .replace(/\/{2,}/g, '/') // Remove duplicate slashes .replace(/\/$/, ''); // Remove trailing slash +}; const allApplicationsFilter = '__ALL__'; @@ -93,7 +102,7 @@ interface AppUpdaterWrapper { * @internal */ export class ApplicationService { - private readonly apps = new Map(); + private readonly apps = new Map | LegacyApp>(); private readonly mounters = new Map(); private readonly capabilities = new CapabilitiesService(); private readonly appLeaveHandlers = new Map(); @@ -143,7 +152,7 @@ export class ApplicationService { return { registerMountContext: this.mountContext!.registerContext, - register: (plugin, app) => { + register: (plugin, app: App) => { app = { appRoute: `/app/${app.id}`, ...app }; if (this.registrationClosed) { diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index e7ea330657648..ec10d2bc22871 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -19,6 +19,7 @@ export { ApplicationService } from './application_service'; export { Capabilities } from './capabilities'; +export { ScopedHistory } from './scoped_history'; export { App, AppBase, diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index 0c5f5a138d58f..2f26bc1409104 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -25,15 +25,17 @@ import { AppRouter, AppNotFound } from '../ui'; import { EitherApp, MockedMounterMap, MockedMounterTuple } from '../test_types'; import { createRenderer, createAppMounter, createLegacyAppMounter, getUnmounter } from './utils'; import { AppStatus } from '../types'; +import { ScopedHistory } from '../scoped_history'; describe('AppContainer', () => { let mounters: MockedMounterMap; - let history: History; + let globalHistory: History; let appStatuses$: BehaviorSubject>; let update: ReturnType; + let scopedAppHistory: History; const navigate = (path: string) => { - history.push(path); + globalHistory.push(path); return update(); }; const mockMountersToMounters = () => @@ -53,20 +55,35 @@ describe('AppContainer', () => { beforeEach(() => { mounters = new Map([ - createAppMounter('app1', 'App 1'), + createAppMounter({ appId: 'app1', html: 'App 1' }), createLegacyAppMounter('legacyApp1', jest.fn()), - createAppMounter('app2', '
App 2
'), + createAppMounter({ appId: 'app2', html: '
App 2
' }), createLegacyAppMounter('baseApp:legacyApp2', jest.fn()), - createAppMounter('app3', '
Chromeless A
', '/chromeless-a/path'), - createAppMounter('app4', '
Chromeless B
', '/chromeless-b/path'), - createAppMounter('disabledApp', '
Disabled app
'), + createAppMounter({ + appId: 'app3', + html: '
Chromeless A
', + appRoute: '/chromeless-a/path', + }), + createAppMounter({ + appId: 'app4', + html: '
Chromeless B
', + appRoute: '/chromeless-b/path', + }), + createAppMounter({ appId: 'disabledApp', html: '
Disabled app
' }), createLegacyAppMounter('disabledLegacyApp', jest.fn()), + createAppMounter({ + appId: 'scopedApp', + extraMountHook: ({ history }) => { + scopedAppHistory = history; + history.push('/subpath'); + }, + }), ] as Array>); - history = createMemoryHistory(); + globalHistory = createMemoryHistory(); appStatuses$ = mountersToAppStatus$(); update = createRenderer( { }); it('should not mount when partial route path matches', async () => { - mounters.set(...createAppMounter('spaces', '
Custom Space
', '/spaces/fake-login')); - mounters.set(...createAppMounter('login', '
Login Page
', '/fake-login')); - history = createMemoryHistory(); + mounters.set( + ...createAppMounter({ + appId: 'spaces', + html: '
Custom Space
', + appRoute: '/spaces/fake-login', + }) + ); + mounters.set( + ...createAppMounter({ + appId: 'login', + html: '
Login Page
', + appRoute: '/fake-login', + }) + ); + globalHistory = createMemoryHistory(); update = createRenderer( { }); it('should not mount when partial route path has higher specificity', async () => { - mounters.set(...createAppMounter('login', '
Login Page
', '/fake-login')); - mounters.set(...createAppMounter('spaces', '
Custom Space
', '/spaces/fake-login')); - history = createMemoryHistory(); + mounters.set( + ...createAppMounter({ + appId: 'login', + html: '
Login Page
', + appRoute: '/fake-login', + }) + ); + mounters.set( + ...createAppMounter({ + appId: 'spaces', + html: '
Custom Space
', + appRoute: '/spaces/fake-login', + }) + ); + globalHistory = createMemoryHistory(); update = createRenderer( { // Hitting back button within app does not trigger re-render await navigate('/app/app1/page2'); - history.goBack(); + globalHistory.goBack(); await update(); expect(mounter.mount).toHaveBeenCalledTimes(1); expect(unmount).not.toHaveBeenCalled(); }); it('should not remount when when changing pages within app using hash history', async () => { - history = createHashHistory(); + globalHistory = createHashHistory(); update = createRenderer( { expect(unmount).toHaveBeenCalledTimes(1); }); + it('pushes global history changes to inner scoped history', async () => { + const scopedApp = mounters.get('scopedApp'); + await navigate('/app/scopedApp'); + + // Verify that internal app's redirect propagated + expect(scopedApp?.mounter.mount).toHaveBeenCalledTimes(1); + expect(scopedAppHistory.location.pathname).toEqual('/subpath'); + expect(globalHistory.location.pathname).toEqual('/app/scopedApp/subpath'); + + // Simulate user clicking on navlink again to return to app root + globalHistory.push('/app/scopedApp'); + // Should not call mount again + expect(scopedApp?.mounter.mount).toHaveBeenCalledTimes(1); + expect(scopedApp?.unmount).not.toHaveBeenCalled(); + // Inner scoped history should be synced + expect(scopedAppHistory.location.pathname).toEqual(''); + + // Make sure going back to subpath works + globalHistory.goBack(); + expect(scopedApp?.mounter.mount).toHaveBeenCalledTimes(1); + expect(scopedApp?.unmount).not.toHaveBeenCalled(); + expect(scopedAppHistory.location.pathname).toEqual('/subpath'); + expect(globalHistory.location.pathname).toEqual('/app/scopedApp/subpath'); + }); + it('calls legacy mount handler', async () => { await navigate('/app/legacyApp1'); - expect(mounters.get('legacyApp1')!.mounter.mount.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "appBasePath": "/app/legacyApp1", - "element":
, - "onAppLeave": [Function], - }, - ] - `); + expect(mounters.get('legacyApp1')!.mounter.mount.mock.calls[0][0]).toMatchObject({ + appBasePath: '/app/legacyApp1', + element: expect.any(HTMLDivElement), + onAppLeave: expect.any(Function), + history: expect.any(ScopedHistory), + }); }); it('handles legacy apps with subapps', async () => { await navigate('/app/baseApp'); - expect(mounters.get('baseApp:legacyApp2')!.mounter.mount.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "appBasePath": "/app/baseApp", - "element":
, - "onAppLeave": [Function], - }, - ] - `); + expect(mounters.get('baseApp:legacyApp2')!.mounter.mount.mock.calls[0][0]).toMatchObject({ + appBasePath: '/app/baseApp', + element: expect.any(HTMLDivElement), + onAppLeave: expect.any(Function), + history: expect.any(ScopedHistory), + }); }); it('displays error page if no app is found', async () => { diff --git a/src/core/public/application/integration_tests/utils.tsx b/src/core/public/application/integration_tests/utils.tsx index 4f34438fc822a..9092177da5ad4 100644 --- a/src/core/public/application/integration_tests/utils.tsx +++ b/src/core/public/application/integration_tests/utils.tsx @@ -40,11 +40,17 @@ export const createRenderer = (element: ReactElement | null): Renderer => { }); }; -export const createAppMounter = ( - appId: string, - html: string, - appRoute = `/app/${appId}` -): MockedMounterTuple => { +export const createAppMounter = ({ + appId, + html = `
App ${appId}
`, + appRoute = `/app/${appId}`, + extraMountHook, +}: { + appId: string; + html?: string; + appRoute?: string; + extraMountHook?: (params: AppMountParameters) => void; +}): MockedMounterTuple => { const unmount = jest.fn(); return [ appId, @@ -52,11 +58,15 @@ export const createAppMounter = ( mounter: { appRoute, appBasePath: appRoute, - mount: jest.fn(async ({ appBasePath: basename, element }: AppMountParameters) => { + mount: jest.fn(async (params: AppMountParameters) => { + const { appBasePath: basename, element } = params; Object.assign(element, { innerHTML: `
\nbasename: ${basename}\nhtml: ${html}\n
`, }); unmount.mockImplementation(() => Object.assign(element, { innerHTML: '' })); + if (extraMountHook) { + extraMountHook(params); + } return unmount; }), }, diff --git a/src/core/public/application/scoped_history.mock.ts b/src/core/public/application/scoped_history.mock.ts new file mode 100644 index 0000000000000..56de97e630bf0 --- /dev/null +++ b/src/core/public/application/scoped_history.mock.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Location } from 'history'; +import { ScopedHistory } from './scoped_history'; + +type ScopedHistoryMock = jest.Mocked>; +const createMock = ({ + pathname = '/', + search = '', + hash = '', + key, + state, +}: Partial = {}) => { + const mock: ScopedHistoryMock = { + block: jest.fn(), + createHref: jest.fn(), + createSubHistory: jest.fn(), + go: jest.fn(), + goBack: jest.fn(), + goForward: jest.fn(), + listen: jest.fn(), + push: jest.fn(), + replace: jest.fn(), + action: 'PUSH', + length: 1, + location: { + pathname, + search, + state, + hash, + key, + }, + }; + + return mock; +}; + +export const scopedHistoryMock = { + create: createMock, +}; diff --git a/src/core/public/application/scoped_history.test.ts b/src/core/public/application/scoped_history.test.ts new file mode 100644 index 0000000000000..c01eb50830516 --- /dev/null +++ b/src/core/public/application/scoped_history.test.ts @@ -0,0 +1,329 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ScopedHistory } from './scoped_history'; +import { createMemoryHistory } from 'history'; + +describe('ScopedHistory', () => { + describe('construction', () => { + it('succeeds if current location matches basePath', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + expect(() => new ScopedHistory(gh, '/app/wow')).not.toThrow(); + gh.push('/app/wow/'); + expect(() => new ScopedHistory(gh, '/app/wow')).not.toThrow(); + gh.push('/app/wow/sub-page'); + expect(() => new ScopedHistory(gh, '/app/wow')).not.toThrow(); + }); + + it('fails if current location does not match basePath', () => { + const gh = createMemoryHistory(); + gh.push('/app/other'); + expect(() => new ScopedHistory(gh, '/app/wow')).toThrowErrorMatchingInlineSnapshot( + `"Browser location [/app/other] is not currently in expected basePath [/app/wow]"` + ); + }); + }); + + describe('navigation', () => { + it('supports push', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const pushSpy = jest.spyOn(gh, 'push'); + const h = new ScopedHistory(gh, '/app/wow'); + h.push('/new-page', { some: 'state' }); + expect(pushSpy).toHaveBeenCalledWith('/app/wow/new-page', { some: 'state' }); + expect(gh.length).toBe(3); // ['', '/app/wow', '/app/wow/new-page'] + expect(h.length).toBe(2); + }); + + it('supports unbound push', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const pushSpy = jest.spyOn(gh, 'push'); + const h = new ScopedHistory(gh, '/app/wow'); + const { push } = h; + push('/new-page', { some: 'state' }); + expect(pushSpy).toHaveBeenCalledWith('/app/wow/new-page', { some: 'state' }); + expect(gh.length).toBe(3); // ['', '/app/wow', '/app/wow/new-page'] + expect(h.length).toBe(2); + }); + + it('supports replace', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const replaceSpy = jest.spyOn(gh, 'replace'); + const h = new ScopedHistory(gh, '/app/wow'); // [''] + h.push('/first-page'); // ['', '/first-page'] + h.push('/second-page'); // ['', '/first-page', '/second-page'] + h.goBack(); // ['', '/first-page', '/second-page'] + h.replace('/first-page-replacement', { some: 'state' }); // ['', '/first-page-replacement', '/second-page'] + expect(replaceSpy).toHaveBeenCalledWith('/app/wow/first-page-replacement', { some: 'state' }); + expect(h.length).toBe(3); + expect(gh.length).toBe(4); // ['', '/app/wow', '/app/wow/first-page-replacement', '/app/wow/second-page'] + }); + + it('hides previous stack', () => { + const gh = createMemoryHistory(); + gh.push('/app/alpha'); + gh.push('/app/beta'); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + expect(h.length).toBe(1); + expect(h.location.pathname).toEqual(''); + }); + + it('cannot go back further than local stack', () => { + const gh = createMemoryHistory(); + gh.push('/app/alpha'); + gh.push('/app/beta'); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + h.push('/new-page'); + expect(h.length).toBe(2); + expect(h.location.pathname).toEqual('/new-page'); + + // Test first back + h.goBack(); + expect(h.length).toBe(2); + expect(h.location.pathname).toEqual(''); + expect(gh.location.pathname).toEqual('/app/wow'); + + // Second back should be no-op + h.goBack(); + expect(h.length).toBe(2); + expect(h.location.pathname).toEqual(''); + expect(gh.location.pathname).toEqual('/app/wow'); + }); + + it('cannot go forward further than local stack', () => { + const gh = createMemoryHistory(); + gh.push('/app/alpha'); + gh.push('/app/beta'); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + h.push('/new-page'); + expect(h.length).toBe(2); + + // Go back so we can go forward + h.goBack(); + expect(h.length).toBe(2); + expect(h.location.pathname).toEqual(''); + expect(gh.location.pathname).toEqual('/app/wow'); + + // Going forward should increase length and return to /new-page + h.goForward(); + expect(h.length).toBe(2); + expect(h.location.pathname).toEqual('/new-page'); + expect(gh.location.pathname).toEqual('/app/wow/new-page'); + + // Second forward should be no-op + h.goForward(); + expect(h.length).toBe(2); + expect(h.location.pathname).toEqual('/new-page'); + expect(gh.location.pathname).toEqual('/app/wow/new-page'); + }); + + it('reacts to navigations from parent history', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + h.push('/page-1'); + h.push('/page-2'); + + gh.goBack(); + expect(h.location.pathname).toEqual('/page-1'); + expect(h.length).toBe(3); + + gh.goForward(); + expect(h.location.pathname).toEqual('/page-2'); + expect(h.length).toBe(3); + + // Go back to /app/wow and push a new location + gh.goBack(); + gh.goBack(); + gh.push('/app/wow/page-3'); + expect(h.location.pathname).toEqual('/page-3'); + expect(h.length).toBe(2); // ['', '/page-3'] + }); + + it('increments length on push and removes length when going back and then pushing', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + expect(gh.length).toBe(2); + const h = new ScopedHistory(gh, '/app/wow'); + expect(h.length).toBe(1); + h.push('/page1'); + expect(h.length).toBe(2); + h.push('/page2'); + expect(h.length).toBe(3); + h.push('/page3'); + expect(h.length).toBe(4); + h.push('/page4'); + expect(h.length).toBe(5); + h.push('/page5'); + expect(h.length).toBe(6); + h.goBack(); // back/forward should not reduce the length + expect(h.length).toBe(6); + h.goBack(); + expect(h.length).toBe(6); + h.push('/page6'); // length should only change if a new location is pushed from a point further back in the history + expect(h.length).toBe(5); + h.goBack(); + expect(h.length).toBe(5); + h.goBack(); + expect(h.length).toBe(5); + h.push('/page7'); + expect(h.length).toBe(4); + }); + }); + + describe('teardown behavior', () => { + it('throws exceptions after falling out of scope', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + expect(gh.length).toBe(2); + const h = new ScopedHistory(gh, '/app/wow'); + gh.push('/app/other'); + expect(() => h.location).toThrowErrorMatchingInlineSnapshot( + `"ScopedHistory instance has fell out of navigation scope for basePath: /app/wow"` + ); + expect(() => h.push('/new-page')).toThrow(); + expect(() => h.replace('/new-page')).toThrow(); + expect(() => h.goBack()).toThrow(); + expect(() => h.goForward()).toThrow(); + }); + }); + + describe('listen', () => { + it('calls callback with scoped location', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + const listenPaths: string[] = []; + h.listen(l => listenPaths.push(l.pathname)); + h.push('/first-page'); + h.push('/second-page'); + h.push('/third-page'); + h.go(-2); + h.goForward(); + expect(listenPaths).toEqual([ + '/first-page', + '/second-page', + '/third-page', + '/first-page', + '/second-page', + ]); + }); + + it('stops calling callback after unlisten is called', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + const listenPaths: string[] = []; + const unlisten = h.listen(l => listenPaths.push(l.pathname)); + h.push('/first-page'); + unlisten(); + h.push('/second-page'); + h.push('/third-page'); + h.go(-2); + h.goForward(); + expect(listenPaths).toEqual(['/first-page']); + }); + + it('stops calling callback after browser leaves scope', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + const listenPaths: string[] = []; + h.listen(l => listenPaths.push(l.pathname)); + h.push('/first-page'); + gh.push('/app/other'); + gh.push('/second-page'); + gh.push('/third-page'); + gh.go(-2); + gh.goForward(); + expect(listenPaths).toEqual(['/first-page']); + }); + }); + + describe('createHref', () => { + it('creates scoped hrefs', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + expect(h.createHref({ pathname: '' })).toEqual(`/`); + expect(h.createHref({ pathname: '/new-page', search: '?alpha=true' })).toEqual( + `/new-page?alpha=true` + ); + }); + }); + + describe('action', () => { + it('provides last history action', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + gh.push('/alpha'); + gh.goBack(); + const h = new ScopedHistory(gh, '/app/wow'); + expect(h.action).toBe('POP'); + gh.push('/app/wow/page-1'); + expect(h.action).toBe('PUSH'); + h.replace('/page-2'); + expect(h.action).toBe('REPLACE'); + }); + }); + + describe('createSubHistory', () => { + it('supports push', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const ghPushSpy = jest.spyOn(gh, 'push'); + const h1 = new ScopedHistory(gh, '/app/wow'); + h1.push('/new-page'); + const h1PushSpy = jest.spyOn(h1, 'push'); + const h2 = h1.createSubHistory('/new-page'); + h2.push('/sub-page', { some: 'state' }); + expect(h1PushSpy).toHaveBeenCalledWith('/new-page/sub-page', { some: 'state' }); + expect(ghPushSpy).toHaveBeenCalledWith('/app/wow/new-page/sub-page', { some: 'state' }); + expect(h2.length).toBe(2); + expect(h1.length).toBe(3); + expect(gh.length).toBe(4); + }); + + it('supports replace', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const ghReplaceSpy = jest.spyOn(gh, 'replace'); + const h1 = new ScopedHistory(gh, '/app/wow'); + h1.push('/new-page'); + const h1ReplaceSpy = jest.spyOn(h1, 'replace'); + const h2 = h1.createSubHistory('/new-page'); + h2.push('/sub-page'); + h2.replace('/other-sub-page', { some: 'state' }); + expect(h1ReplaceSpy).toHaveBeenCalledWith('/new-page/other-sub-page', { some: 'state' }); + expect(ghReplaceSpy).toHaveBeenCalledWith('/app/wow/new-page/other-sub-page', { + some: 'state', + }); + expect(h2.length).toBe(2); + expect(h1.length).toBe(3); + expect(gh.length).toBe(4); + }); + }); +}); diff --git a/src/core/public/application/scoped_history.ts b/src/core/public/application/scoped_history.ts new file mode 100644 index 0000000000000..c5febc7604feb --- /dev/null +++ b/src/core/public/application/scoped_history.ts @@ -0,0 +1,318 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + History, + Path, + LocationDescriptorObject, + TransitionPromptHook, + UnregisterCallback, + LocationListener, + Location, + Href, + Action, +} from 'history'; + +/** + * A wrapper around a `History` instance that is scoped to a particular base path of the history stack. Behaves + * similarly to the `basename` option except that this wrapper hides any history stack entries from outside the scope + * of this base path. + * + * This wrapper also allows Core and Plugins to share a single underlying global `History` instance without exposing + * the history of other applications. + * + * The {@link ScopedHistory.createSubHistory | createSubHistory} method is particularly useful for applications that + * contain any number of "sub-apps" which should not have access to the main application's history or basePath. + * + * @public + */ +export class ScopedHistory + implements History { + /** + * Tracks whether or not the user has left this history's scope. All methods throw errors if called after scope has + * been left. + */ + private isActive = true; + /** + * All active listeners on this history instance. + */ + private listeners = new Set>(); + /** + * Array of the local history stack. Only stores {@link Location.key} to use tracking an index of the current + * position of the window in the history stack. + */ + private locationKeys: Array = []; + /** + * The key of the current position of the window in the history stack. + */ + private currentLocationKeyIndex: number = 0; + + constructor(private readonly parentHistory: History, private readonly basePath: string) { + const parentPath = this.parentHistory.location.pathname; + if (!parentPath.startsWith(basePath)) { + throw new Error( + `Browser location [${parentPath}] is not currently in expected basePath [${basePath}]` + ); + } + + this.locationKeys.push(this.parentHistory.location.key); + this.setupHistoryListener(); + } + + /** + * Creates a `ScopedHistory` for a subpath of this `ScopedHistory`. Useful for applications that may have sub-apps + * that do not need access to the containing application's history. + * + * @param basePath the URL path scope for the sub history + */ + public createSubHistory = ( + basePath: string + ): ScopedHistory => { + return new ScopedHistory(this, basePath); + }; + + /** + * The number of entries in the history stack, including all entries forwards and backwards from the current location. + */ + public get length() { + this.verifyActive(); + return this.locationKeys.length; + } + + /** + * The current location of the history stack. + */ + public get location() { + this.verifyActive(); + return this.stripBasePath(this.parentHistory.location); + } + + /** + * The last action dispatched on the history stack. + */ + public get action() { + this.verifyActive(); + return this.parentHistory.action; + } + + /** + * Pushes a new location onto the history stack. If there are forward entries in the stack, they will be removed. + * + * @param pathOrLocation a string or location descriptor + * @param state + */ + public push = ( + pathOrLocation: Path | LocationDescriptorObject, + state?: HistoryLocationState + ): void => { + this.verifyActive(); + if (typeof pathOrLocation === 'string') { + this.parentHistory.push(this.prependBasePath(pathOrLocation), state); + } else { + this.parentHistory.push(this.prependBasePath(pathOrLocation)); + } + }; + + /** + * Replaces the current location in the history stack. Does not remove forward or backward entries. + * + * @param pathOrLocation a string or location descriptor + * @param state + */ + public replace = ( + pathOrLocation: Path | LocationDescriptorObject, + state?: HistoryLocationState + ): void => { + this.verifyActive(); + if (typeof pathOrLocation === 'string') { + this.parentHistory.replace(this.prependBasePath(pathOrLocation), state); + } else { + this.parentHistory.replace(this.prependBasePath(pathOrLocation)); + } + }; + + /** + * Send the user forward or backwards in the history stack. + * + * @param n number of positions in the stack to go. Negative numbers indicate number of entries backward, positive + * numbers for forwards. If passed 0, the current location will be reloaded. If `n` exceeds the number of + * entries available, this is a no-op. + */ + public go = (n: number): void => { + this.verifyActive(); + if (n === 0) { + this.parentHistory.go(n); + } else if (n < 0) { + if (this.currentLocationKeyIndex + 1 + n >= 1) { + this.parentHistory.go(n); + } + } else if (n <= this.currentLocationKeyIndex + this.locationKeys.length - 1) { + this.parentHistory.go(n); + } + // no-op if no conditions above are met + }; + + /** + * Send the user one location back in the history stack. Equivalent to calling + * {@link ScopedHistory.go | ScopedHistory.go(-1)}. If no more entries are available backwards, this is a no-op. + */ + public goBack = () => { + this.verifyActive(); + this.go(-1); + }; + + /** + * Send the user one location forward in the history stack. Equivalent to calling + * {@link ScopedHistory.go | ScopedHistory.go(1)}. If no more entries are available forwards, this is a no-op. + */ + public goForward = () => { + this.verifyActive(); + this.go(1); + }; + + /** + * Not supported. Use {@link AppMountParameters.onAppLeave}. + * + * @remarks + * We prefer that applications use the `onAppLeave` API because it supports a more graceful experience that prefers + * a modal when possible, falling back to a confirm dialog box in the beforeunload case. + */ + public block = ( + prompt?: boolean | string | TransitionPromptHook + ): UnregisterCallback => { + throw new Error( + `history.block is not supported. Please use the AppMountParams.onAppLeave API.` + ); + }; + + /** + * Adds a listener for location updates. + * + * @param listener a function that receives location updates. + * @returns an function to unsubscribe the listener. + */ + public listen = ( + listener: (location: Location, action: Action) => void + ): UnregisterCallback => { + this.verifyActive(); + this.listeners.add(listener); + return () => { + this.listeners.delete(listener); + }; + }; + + /** + * Creates an href (string) to the location. + * + * @param location + */ + public createHref = (location: LocationDescriptorObject): Href => { + this.verifyActive(); + return this.parentHistory.createHref(location); + }; + + private prependBasePath(path: Path): Path; + private prependBasePath( + location: LocationDescriptorObject + ): LocationDescriptorObject; + /** + * Prepends the scoped base path to the Path or Location + */ + private prependBasePath( + pathOrLocation: Path | LocationDescriptorObject + ): Path | LocationDescriptorObject { + if (typeof pathOrLocation === 'string') { + return this.prependBasePathToString(pathOrLocation); + } else { + return { + ...pathOrLocation, + pathname: + pathOrLocation.pathname !== undefined + ? this.prependBasePathToString(pathOrLocation.pathname) + : undefined, + }; + } + } + + /** + * Prepends the base path to string. + */ + private prependBasePathToString(path: string): string { + path = path.startsWith('/') ? path.slice(1) : path; + return path.length ? `${this.basePath}/${path}` : this.basePath; + } + + /** + * Removes the base path from a location. + */ + private stripBasePath(location: Location): Location { + return { + ...location, + pathname: location.pathname.replace(new RegExp(`^${this.basePath}`), ''), + }; + } + + /** Called on each public method to ensure that we have not fallen out of scope yet. */ + private verifyActive() { + if (!this.isActive) { + throw new Error( + `ScopedHistory instance has fell out of navigation scope for basePath: ${this.basePath}` + ); + } + } + + /** + * Sets up the listener on the parent history instance used to follow navigation updates and track our internal + * state. Also forwards events to child listeners with the base path stripped from the location. + */ + private setupHistoryListener() { + const unlisten = this.parentHistory.listen((location, action) => { + // If the user navigates outside the scope of this basePath, tear it down. + if (!location.pathname.startsWith(this.basePath)) { + unlisten(); + this.isActive = false; + return; + } + + /** + * Track location keys using the same algorithm the browser uses internally. + * - On PUSH, remove all items that came after the current location and append the new location. + * - On POP, set the current location, but do not change the entries. + * - On REPLACE, override the location for the current index with the new location. + */ + if (action === 'PUSH') { + this.locationKeys = [ + ...this.locationKeys.slice(0, this.currentLocationKeyIndex + 1), + location.key, + ]; + this.currentLocationKeyIndex = this.locationKeys.indexOf(location.key); // should always be the last index + } else if (action === 'POP') { + this.currentLocationKeyIndex = this.locationKeys.indexOf(location.key); + } else if (action === 'REPLACE') { + this.locationKeys[this.currentLocationKeyIndex] = location.key; + } else { + throw new Error(`Unrecognized history action: ${action}`); + } + + [...this.listeners].forEach(listener => { + listener(this.stripBasePath(location), action); + }); + }); + } +} diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 977bb7a52da22..facb818c60ff9 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -32,6 +32,7 @@ import { IUiSettingsClient } from '../ui_settings'; import { RecursiveReadonly } from '../../utils'; import { SavedObjectsStart } from '../saved_objects'; import { AppCategory } from '../../types'; +import { ScopedHistory } from './scoped_history'; /** @public */ export interface AppBase { @@ -199,7 +200,7 @@ export type AppUpdater = (app: AppBase) => Partial | undefin * Extension of {@link AppBase | common app properties} with the mount function. * @public */ -export interface App extends AppBase { +export interface App extends AppBase { /** * A mount function called when the user navigates to this app's route. May have signature of {@link AppMount} or * {@link AppMountDeprecated}. @@ -208,7 +209,7 @@ export interface App extends AppBase { * When function has two arguments, it will be called with a {@link AppMountContext | context} as the first argument. * This behavior is **deprecated**, and consumers should instead use {@link CoreSetup.getStartServices}. */ - mount: AppMount | AppMountDeprecated; + mount: AppMount | AppMountDeprecated; /** * Hide the UI chrome when the application is mounted. Defaults to `false`. @@ -240,7 +241,9 @@ export interface LegacyApp extends AppBase { * * @public */ -export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; +export type AppMount = ( + params: AppMountParameters +) => AppUnmount | Promise; /** * A mount function called when the user navigates to this app's route. @@ -256,9 +259,9 @@ export type AppMount = (params: AppMountParameters) => AppUnmount | Promise = ( context: AppMountContext, - params: AppMountParameters + params: AppMountParameters ) => AppUnmount | Promise; /** @@ -304,16 +307,65 @@ export interface AppMountContext { } /** @public */ -export interface AppMountParameters { +export interface AppMountParameters { /** * The container element to render the application into. */ element: HTMLElement; + /** + * A scoped history instance for your application. Should be used to wire up + * your applications Router. + * + * @example + * How to configure react-router with a base path: + * + * ```ts + * // inside your plugin's setup function + * export class MyPlugin implements Plugin { + * setup({ application }) { + * application.register({ + * id: 'my-app', + * appRoute: '/my-app', + * async mount(params) { + * const { renderApp } = await import('./application'); + * return renderApp(params); + * }, + * }); + * } + * } + * ``` + * + * ```ts + * // application.tsx + * import React from 'react'; + * import ReactDOM from 'react-dom'; + * import { Router, Route } from 'react-router-dom'; + * + * import { CoreStart, AppMountParameters } from 'src/core/public'; + * import { MyPluginDepsStart } from './plugin'; + * + * export renderApp = ({ element, history }: AppMountParameters) => { + * ReactDOM.render( + * // pass `appBasePath` to `basename` + * + * + * , + * element + * ); + * + * return () => ReactDOM.unmountComponentAtNode(element); + * } + * ``` + */ + history: ScopedHistory; + /** * The route path for configuring navigation to the application. * This string should not include the base path from HTTP. * + * @deprecated Use {@link AppMountParameters.history} instead. + * * @example * * How to configure react-router with a base path: @@ -340,10 +392,10 @@ export interface AppMountParameters { * import ReactDOM from 'react-dom'; * import { BrowserRouter, Route } from 'react-router-dom'; * - * import { CoreStart, AppMountParams } from 'src/core/public'; + * import { CoreStart, AppMountParameters } from 'src/core/public'; * import { MyPluginDepsStart } from './plugin'; * - * export renderApp = ({ appBasePath, element }: AppMountParams) => { + * export renderApp = ({ appBasePath, element }: AppMountParameters) => { * ReactDOM.render( * // pass `appBasePath` to `basename` * @@ -498,8 +550,9 @@ export interface ApplicationSetup { /** * Register an mountable application to the system. * @param app - an {@link App} + * @typeParam HistoryLocationState - shape of the `History` state on {@link AppMountParameters.history}, defaults to `unknown`. */ - register(app: App): void; + register(app: App): void; /** * Register an application updater that can be used to change the {@link AppUpdatableFields} fields @@ -551,7 +604,10 @@ export interface InternalApplicationSetup extends Pick( + plugin: PluginOpaqueId, + app: App + ): void; /** * Register metadata about legacy applications. Legacy apps will not be mounted when navigated to. diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index a46243a2da493..c538227e8f098 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -22,6 +22,8 @@ import { mount } from 'enzyme'; import { AppContainer } from './app_container'; import { Mounter, AppMountParameters, AppStatus } from '../types'; +import { createMemoryHistory } from 'history'; +import { ScopedHistory } from '../scoped_history'; describe('AppContainer', () => { const appId = 'someApp'; @@ -60,10 +62,15 @@ describe('AppContainer', () => { const wrapper = mount( + // Create a history using the appPath as the current location + new ScopedHistory(createMemoryHistory({ initialEntries: [appPath] }), appPath) + } /> ); diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index 885157843e7df..e12a0f2cf2fcd 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -28,18 +28,24 @@ import React, { import { AppLeaveHandler, AppStatus, AppUnmount, Mounter } from '../types'; import { AppNotFound } from './app_not_found_screen'; +import { ScopedHistory } from '../scoped_history'; interface Props { + /** Path application is mounted on without the global basePath */ + appPath: string; appId: string; mounter?: Mounter; appStatus: AppStatus; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; + createScopedHistory: (appUrl: string) => ScopedHistory; } export const AppContainer: FunctionComponent = ({ mounter, appId, + appPath, setAppLeaveHandler, + createScopedHistory, appStatus, }: Props) => { const [appNotFound, setAppNotFound] = useState(false); @@ -67,6 +73,7 @@ export const AppContainer: FunctionComponent = ({ unmountRef.current = (await mounter.mount({ appBasePath: mounter.appBasePath, + history: createScopedHistory(appPath), element: elementRef.current!, onAppLeave: handler => setAppLeaveHandler(appId, handler), })) || null; @@ -75,7 +82,7 @@ export const AppContainer: FunctionComponent = ({ mount(); return unmount; - }, [appId, appStatus, mounter, setAppLeaveHandler]); + }, [appId, appStatus, mounter, createScopedHistory, setAppLeaveHandler, appPath]); return ( diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index 50e5f5ee1bd62..61c8bc3cadae5 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useMemo } from 'react'; import { Route, RouteComponentProps, Router, Switch } from 'react-router-dom'; import { History } from 'history'; import { Observable } from 'rxjs'; @@ -25,6 +25,7 @@ import { useObservable } from 'react-use'; import { AppLeaveHandler, AppStatus, Mounter } from '../types'; import { AppContainer } from './app_container'; +import { ScopedHistory } from '../scoped_history'; interface Props { mounters: Map; @@ -44,6 +45,11 @@ export const AppRouter: FunctionComponent = ({ appStatuses$, }) => { const appStatuses = useObservable(appStatuses$, new Map()); + const createScopedHistory = useMemo( + () => (appPath: string) => new ScopedHistory(history, appPath), + [history] + ); + return ( @@ -56,12 +62,12 @@ export const AppRouter: FunctionComponent = ({ ( + render={({ match: { url } }) => ( )} />, @@ -72,6 +78,7 @@ export const AppRouter: FunctionComponent = ({ render={({ match: { params: { appId }, + url, }, }: RouteComponentProps) => { // Find the mounter including legacy mounters with subapps: @@ -81,10 +88,11 @@ export const AppRouter: FunctionComponent = ({ return ( ); }} diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 6d756e36d7379..483d4dbfdf7c5 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -108,6 +108,7 @@ export { AppNavLinkStatus, AppUpdatableFields, AppUpdater, + ScopedHistory, } from './application'; export { diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 3301d71e2cdaf..8ea672890ca29 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -41,6 +41,7 @@ export { notificationServiceMock } from './notifications/notifications_service.m export { overlayServiceMock } from './overlays/overlay_service.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; +export { scopedHistoryMock } from './application/scoped_history.mock'; function createCoreSetupMock({ basePath = '' } = {}) { const mock = { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index ba1988b857385..cd956eb17531a 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -4,25 +4,30 @@ ```ts +import { Action } from 'history'; import { Breadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; +import { History } from 'history'; import { IconType } from '@elastic/eui'; +import { Location } from 'history'; +import { LocationDescriptorObject } from 'history'; import { MaybePromise } from '@kbn/utility-types'; import { Observable } from 'rxjs'; import React from 'react'; import * as Rx from 'rxjs'; import { ShallowPromise } from '@kbn/utility-types'; import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types'; +import { UnregisterCallback } from 'history'; import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; // @public -export interface App extends AppBase { +export interface App extends AppBase { appRoute?: string; chromeless?: boolean; - mount: AppMount | AppMountDeprecated; + mount: AppMount | AppMountDeprecated; } // @public (undocumented) @@ -89,7 +94,7 @@ export type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction // @public (undocumented) export interface ApplicationSetup { - register(app: App): void; + register(app: App): void; registerAppUpdater(appUpdater$: Observable): void; // @deprecated registerMountContext(contextName: T, provider: IContextProvider): void; @@ -112,7 +117,7 @@ export interface ApplicationStart { } // @public -export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; +export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; // @public @deprecated export interface AppMountContext { @@ -133,12 +138,14 @@ export interface AppMountContext { } // @public @deprecated -export type AppMountDeprecated = (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; +export type AppMountDeprecated = (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; // @public (undocumented) -export interface AppMountParameters { +export interface AppMountParameters { + // @deprecated appBasePath: string; element: HTMLElement; + history: ScopedHistory; onAppLeave: (handler: AppLeaveHandler) => void; } @@ -1175,6 +1182,23 @@ export interface SavedObjectsUpdateOptions { version?: string; } +// @public +export class ScopedHistory implements History { + constructor(parentHistory: History, basePath: string); + get action(): Action; + block: (prompt?: string | boolean | History.TransitionPromptHook | undefined) => UnregisterCallback; + createHref: (location: LocationDescriptorObject) => string; + createSubHistory: (basePath: string) => ScopedHistory; + go: (n: number) => void; + goBack: () => void; + goForward: () => void; + get length(): number; + listen: (listener: (location: Location, action: Action) => void) => UnregisterCallback; + get location(): Location; + push: (pathOrLocation: string | LocationDescriptorObject, state?: HistoryLocationState | undefined) => void; + replace: (pathOrLocation: string | LocationDescriptorObject, state?: HistoryLocationState | undefined) => void; + } + // @public export class SimpleSavedObject { constructor(client: SavedObjectsClientContract, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index 90328003c8292..14564cfd9ee78 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -68,7 +68,13 @@ export class LocalApplicationService { isUnmounted = true; }); (async () => { - const params = { element, appBasePath: '', onAppLeave: () => undefined }; + const params = { + element, + appBasePath: '', + onAppLeave: () => undefined, + // TODO: adapt to use Core's ScopedHistory + history: {} as any, + }; unmountHandler = isAppMountDeprecated(app.mount) ? await app.mount({ core: npStart.core }, params) : await app.mount(params); diff --git a/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts b/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts index e660ad1f55840..f44efe17ef8ee 100644 --- a/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts +++ b/src/legacy/ui/public/new_platform/new_platform.test.mocks.ts @@ -17,8 +17,15 @@ * under the License. */ +import { scopedHistoryMock } from '../../../../core/public/mocks'; + export const setRootControllerMock = jest.fn(); jest.doMock('ui/chrome', () => ({ setRootController: setRootControllerMock, })); + +export const historyMock = scopedHistoryMock.create(); +jest.doMock('../../../../core/public', () => ({ + ScopedHistory: jest.fn(() => historyMock), +})); diff --git a/src/legacy/ui/public/new_platform/new_platform.test.ts b/src/legacy/ui/public/new_platform/new_platform.test.ts index e050ffd5b530c..498f05457bba9 100644 --- a/src/legacy/ui/public/new_platform/new_platform.test.ts +++ b/src/legacy/ui/public/new_platform/new_platform.test.ts @@ -17,7 +17,9 @@ * under the License. */ -import { setRootControllerMock } from './new_platform.test.mocks'; +jest.mock('history'); + +import { setRootControllerMock, historyMock } from './new_platform.test.mocks'; import { legacyAppRegister, __reset__, __setup__ } from './new_platform'; import { coreMock } from '../../../../core/public/mocks'; @@ -63,6 +65,7 @@ describe('ui/new_platform', () => { element: elementMock[0], appBasePath: '/test/base/path/app/test', onAppLeave: expect.any(Function), + history: historyMock, }); }); @@ -84,6 +87,7 @@ describe('ui/new_platform', () => { element: elementMock[0], appBasePath: '/test/base/path/app/test', onAppLeave: expect.any(Function), + history: historyMock, }); }); diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index b7994c7f68afb..00d76bc341322 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -20,7 +20,14 @@ import { IScope } from 'angular'; import { UiActionsStart, UiActionsSetup } from 'src/plugins/ui_actions/public'; import { IEmbeddableStart, IEmbeddableSetup } from 'src/plugins/embeddable/public'; -import { LegacyCoreSetup, LegacyCoreStart, App, AppMountDeprecated } from '../../../../core/public'; +import { createBrowserHistory } from 'history'; +import { + LegacyCoreSetup, + LegacyCoreStart, + App, + AppMountDeprecated, + ScopedHistory, +} from '../../../../core/public'; import { Plugin as DataPlugin } from '../../../../plugins/data/public'; import { Plugin as ExpressionsPlugin } from '../../../../plugins/expressions/public'; import { @@ -126,7 +133,7 @@ let legacyAppRegistered = false; * Exported for testing only. Use `npSetup.core.application.register` in legacy apps. * @internal */ -export const legacyAppRegister = (app: App) => { +export const legacyAppRegister = (app: App) => { if (legacyAppRegistered) { throw new Error(`core.application.register may only be called once for legacy plugins.`); } @@ -137,9 +144,15 @@ export const legacyAppRegister = (app: App) => { // Root controller cannot return a Promise so use an internal async function and call it immediately (async () => { + const appRoute = app.appRoute || `/app/${app.id}`; + const appBasePath = npSetup.core.http.basePath.prepend(appRoute); const params = { element, - appBasePath: npSetup.core.http.basePath.prepend(`/app/${app.id}`), + appBasePath, + history: new ScopedHistory( + createBrowserHistory({ basename: npSetup.core.http.basePath.get() }), + appRoute + ), onAppLeave: () => undefined, }; const unmount = isAppMountDeprecated(app.mount) diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index a179be6946c76..2c21b451cb9f7 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -91,7 +91,13 @@ function DevToolsWrapper({ if (mountedTool.current) { mountedTool.current.unmountHandler(); } - const params = { element, appBasePath: '', onAppLeave: () => undefined }; + const params = { + element, + appBasePath: '', + onAppLeave: () => undefined, + // TODO: adapt to use Core's ScopedHistory + history: {} as any, + }; const unmountHandler = isAppMountDeprecated(activeDevTool.mount) ? await activeDevTool.mount(appMountContext, params) : await activeDevTool.mount(params); diff --git a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx index 7c1406f5b20c3..abea970749cbc 100644 --- a/test/plugin_functional/plugins/core_plugin_a/public/application.tsx +++ b/test/plugin_functional/plugins/core_plugin_a/public/application.tsx @@ -17,9 +17,10 @@ * under the License. */ +import { History } from 'history'; import React from 'react'; import ReactDOM from 'react-dom'; -import { BrowserRouter as Router, Route, withRouter, RouteComponentProps } from 'react-router-dom'; +import { Router, Route, withRouter, RouteComponentProps } from 'react-router-dom'; import { EuiPage, @@ -115,8 +116,8 @@ const Nav = withRouter(({ history, navigateToApp }: NavProps) => ( /> )); -const FooApp = ({ basename, context }: { basename: string; context: AppMountContext }) => ( - +const FooApp = ({ history, context }: { history: History; context: AppMountContext }) => ( +