+ }
+ >
+ {indicesSwitch}
+
+ ) : (
+ indicesSwitch
+ )}
+ {isAllIndices ? null : (
+
+
+
+
-
-
-
- ) : (
-
-
-
-
-
- {
- setSelectIndicesMode('list');
- updatePolicyConfig({ indices: indicesSelection });
- }}
- >
+
+
+ {
+ setSelectIndicesMode('custom');
+ updatePolicyConfig({ indices: indexPatterns.join(',') });
+ }}
+ >
+
+
+
+
+ ) : (
+
+
-
-
-
- )
- }
- helpText={
- selectIndicesMode === 'list' ? (
- 0 ? (
- {
- // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
- indicesOptions.forEach((option: Option) => {
- option.checked = undefined;
- });
- updatePolicyConfig({ indices: [] });
- setIndicesSelection([]);
- }}
- >
-
-
- ) : (
- {
- // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
- indicesOptions.forEach((option: Option) => {
- option.checked = 'on';
- });
- updatePolicyConfig({ indices: [...indices] });
- setIndicesSelection([...indices]);
- }}
- >
-
-
- ),
+
+
+ {
+ setSelectIndicesMode('list');
+ updatePolicyConfig({ indices: indicesSelection });
+ }}
+ >
+
+
+
+
+ )
+ }
+ helpText={
+ selectIndicesMode === 'list' ? (
+ 0 ? (
+ {
+ // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
+ indicesOptions.forEach((option: Option) => {
+ option.checked = undefined;
+ });
+ updatePolicyConfig({ indices: [] });
+ setIndicesSelection([]);
+ }}
+ >
+
+
+ ) : (
+ {
+ // TODO: Change this to setIndicesOptions() when https://github.com/elastic/eui/issues/2071 is fixed
+ indicesOptions.forEach((option: Option) => {
+ option.checked = 'on';
+ });
+ updatePolicyConfig({ indices: [...indices] });
+ setIndicesSelection([...indices]);
+ }}
+ >
+
+
+ ),
+ }}
+ />
+ ) : null
+ }
+ isInvalid={Boolean(errors.indices)}
+ error={errors.indices}
+ >
+ {selectIndicesMode === 'list' ? (
+ {
+ const newSelectedIndices: string[] = [];
+ options.forEach(({ label, checked }) => {
+ if (checked === 'on') {
+ newSelectedIndices.push(label);
+ }
+ });
+ setIndicesOptions(options);
+ updatePolicyConfig({ indices: newSelectedIndices });
+ setIndicesSelection(newSelectedIndices);
}}
- />
- ) : null
- }
- isInvalid={Boolean(errors.indices)}
- error={errors.indices}
- >
- {selectIndicesMode === 'list' ? (
- {
- const newSelectedIndices: string[] = [];
- options.forEach(({ label, checked }) => {
- if (checked === 'on') {
- newSelectedIndices.push(label);
+ searchable
+ height={300}
+ >
+ {(list, search) => (
+
+ {search}
+ {list}
+
+ )}
+
+ ) : (
+ ({ label: index }))}
+ placeholder={i18n.translate(
+ 'xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder',
+ {
+ defaultMessage: 'Enter index patterns, i.e. logstash-*',
}
- });
- setIndicesOptions(options);
- updatePolicyConfig({ indices: newSelectedIndices });
- setIndicesSelection(newSelectedIndices);
- }}
- searchable
- height={300}
- >
- {(list, search) => (
-
- {search}
- {list}
-
- )}
-
- ) : (
- ({ label: index }))}
- placeholder={i18n.translate(
- 'xpack.snapshotRestore.policyForm.stepSettings.indicesPatternPlaceholder',
- {
- defaultMessage: 'Enter index patterns, i.e. logstash-*',
- }
- )}
- selectedOptions={indexPatterns.map(pattern => ({ label: pattern }))}
- onCreateOption={(pattern: string) => {
- if (!pattern.trim().length) {
- return;
- }
- const newPatterns = [...indexPatterns, pattern];
- setIndexPatterns(newPatterns);
- updatePolicyConfig({
- indices: newPatterns.join(','),
- });
- }}
- onChange={(patterns: Array<{ label: string }>) => {
- const newPatterns = patterns.map(({ label }) => label);
- setIndexPatterns(newPatterns);
- updatePolicyConfig({
- indices: newPatterns.join(','),
- });
- }}
- />
- )}
-
-
- )}
-
-
-
- );
+ )}
+ selectedOptions={indexPatterns.map(pattern => ({ label: pattern }))}
+ onCreateOption={(pattern: string) => {
+ if (!pattern.trim().length) {
+ return;
+ }
+ const newPatterns = [...indexPatterns, pattern];
+ setIndexPatterns(newPatterns);
+ updatePolicyConfig({
+ indices: newPatterns.join(','),
+ });
+ }}
+ onChange={(patterns: Array<{ label: string }>) => {
+ const newPatterns = patterns.map(({ label }) => label);
+ setIndexPatterns(newPatterns);
+ updatePolicyConfig({
+ indices: newPatterns.join(','),
+ });
+ }}
+ />
+ )}
+
+
+ )}
+
+
+
+ );
+ };
const renderIgnoreUnavailableField = () => (
= ({
}
),
icon: 'trash',
+ disabled: policyDetails.policy.isManagedPolicy,
onClick: () =>
deletePolicyPrompt([policyName], onPolicyDeleted),
},
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx
index 31553e110b2159..e719f3cd1451b1 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_details/tabs/tab_summary.tsx
@@ -5,6 +5,7 @@
*/
import React, { useState, useEffect, Fragment } from 'react';
import {
+ EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
@@ -46,6 +47,7 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => {
config,
stats,
retention,
+ isManagedPolicy,
} = policy;
const { includeGlobalState, ignoreUnavailable, indices, partial } = config || {
includeGlobalState: undefined,
@@ -130,6 +132,22 @@ export const TabSummary: React.FunctionComponent = ({ policy }) => {
return (
+ {isManagedPolicy ? (
+ <>
+
+ }
+ />
+
+ >
+ ) : null}
{/** Stats panel */}
{stats && (
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx
index 62038f99638366..e08753c55bf7b4 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/policy_list/policy_table/policy_table.tsx
@@ -16,6 +16,7 @@ import {
EuiLoadingSpinner,
EuiText,
EuiIcon,
+ EuiIconTip,
} from '@elastic/eui';
import { SlmPolicy } from '../../../../../../common/types';
@@ -60,7 +61,7 @@ export const PolicyTable: React.FunctionComponent = ({
}),
truncateText: true,
sortable: true,
- render: (name: SlmPolicy['name'], { inProgress }: SlmPolicy) => {
+ render: (name: SlmPolicy['name'], { inProgress, isManagedPolicy }: SlmPolicy) => {
return (
@@ -71,8 +72,21 @@ export const PolicyTable: React.FunctionComponent = ({
data-test-subj="policyLink"
>
{name}
-
+ {' '}
+ {isManagedPolicy ? (
+
+
+ }
+ position="right"
+ />
+
+ ) : null}
{inProgress ? (
= ({
}),
actions: [
{
- render: ({ name, inProgress }: SlmPolicy) => {
+ render: ({ name, inProgress, isManagedPolicy }: SlmPolicy) => {
return (
@@ -246,13 +260,19 @@ export const PolicyTable: React.FunctionComponent = ({
{deletePolicyPrompt => {
- return (
-
+ )
+ : i18n.translate(
+ 'xpack.snapshotRestore.policyList.table.deleteManagedPolicyTableActionTooltip',
+ {
+ defaultMessage: 'You cannot delete a managed policy.',
+ }
+ );
+ return (
+ = ({
color="danger"
data-test-subj="deletePolicyButton"
onClick={() => deletePolicyPrompt([name], onPolicyDeleted)}
+ isDisabled={isManagedPolicy}
/>
);
@@ -294,6 +315,17 @@ export const PolicyTable: React.FunctionComponent = ({
const selection = {
onSelectionChange: (newSelectedItems: SlmPolicy[]) => setSelectedItems(newSelectedItems),
+ selectable: ({ isManagedPolicy }: SlmPolicy) => !isManagedPolicy,
+ selectableMessage: (selectable: boolean) => {
+ if (!selectable) {
+ return i18n.translate(
+ 'xpack.snapshotRestore.policyList.table.deleteManagedPolicySelectTooltip',
+ {
+ defaultMessage: 'You cannot delete a managed policy.',
+ }
+ );
+ }
+ },
};
const search = {
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx
index 5e3250f082fce3..dbbc0e09111ebb 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx
@@ -40,7 +40,9 @@ export const RepositoryList: React.FunctionComponent = ({
{managedRepository === name ? (
-
-
-
+
+ }
+ position="right"
+ />
) : null}
);
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx
index 955cade353d2ed..dd453a062fb59e 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_details/snapshot_details.tsx
@@ -200,14 +200,18 @@ export const SnapshotDetails: React.FunctionComponent = ({
onSnapshotDeleted
)
}
- isDisabled={snapshotDetails.isManagedRepository}
+ isDisabled={
+ snapshotDetails.managedRepository &&
+ snapshotDetails.isLastSuccessfulSnapshot
+ }
title={
- snapshotDetails.isManagedRepository
+ snapshotDetails.managedRepository &&
+ snapshotDetails.isLastSuccessfulSnapshot
? i18n.translate(
'xpack.snapshotRestore.snapshotDetails.deleteManagedRepositorySnapshotButtonTitle',
{
defaultMessage:
- 'You cannot delete a snapshot stored in a managed repository.',
+ 'You cannot delete the last successful snapshot stored in a managed repository.',
}
)
: null
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
index dbf584acd54707..d85d86ee6425f5 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_table/snapshot_table.tsx
@@ -33,6 +33,21 @@ interface Props {
onSnapshotDeleted: (snapshotsDeleted: Array<{ snapshot: string; repository: string }>) => void;
}
+const getLastSuccessfulManagedSnapshot = (
+ snapshots: SnapshotDetails[]
+): SnapshotDetails | undefined => {
+ const successfulSnapshots = snapshots
+ .filter(
+ ({ state, repository, managedRepository }) =>
+ repository === managedRepository && state === 'SUCCESS'
+ )
+ .sort((a, b) => {
+ return +new Date(b.endTime) - +new Date(a.endTime);
+ });
+
+ return successfulSnapshots[0];
+};
+
export const SnapshotTable: React.FunctionComponent = ({
snapshots,
repositories,
@@ -49,6 +64,8 @@ export const SnapshotTable: React.FunctionComponent = ({
const { trackUiMetric } = uiMetricService;
const [selectedItems, setSelectedItems] = useState([]);
+ const lastSuccessfulManagedSnapshot = getLastSuccessfulManagedSnapshot(snapshots);
+
const columns = [
{
field: 'snapshot',
@@ -193,21 +210,24 @@ export const SnapshotTable: React.FunctionComponent = ({
},
},
{
- render: ({ snapshot, repository, isManagedRepository }: SnapshotDetails) => {
+ render: ({ snapshot, repository }: SnapshotDetails) => {
return (
{deleteSnapshotPrompt => {
- const label = !isManagedRepository
+ const isDeleteDisabled = Boolean(lastSuccessfulManagedSnapshot)
+ ? snapshot === lastSuccessfulManagedSnapshot!.snapshot
+ : false;
+ const label = isDeleteDisabled
? i18n.translate(
- 'xpack.snapshotRestore.snapshotList.table.actionDeleteTooltip',
- { defaultMessage: 'Delete' }
- )
- : i18n.translate(
'xpack.snapshotRestore.snapshotList.table.deleteManagedRepositorySnapshotTooltip',
{
defaultMessage:
- 'You cannot delete a snapshot stored in a managed repository.',
+ 'You must store the last successful snapshot in a managed repository.',
}
+ )
+ : i18n.translate(
+ 'xpack.snapshotRestore.snapshotList.table.actionDeleteTooltip',
+ { defaultMessage: 'Delete' }
);
return (
@@ -215,7 +235,7 @@ export const SnapshotTable: React.FunctionComponent = ({
aria-label={i18n.translate(
'xpack.snapshotRestore.snapshotList.table.actionDeleteAriaLabel',
{
- defaultMessage: 'Delete snapshot `{name}`',
+ defaultMessage: `Delete snapshot '{name}'`,
values: { name: snapshot },
}
)}
@@ -225,7 +245,7 @@ export const SnapshotTable: React.FunctionComponent = ({
onClick={() =>
deleteSnapshotPrompt([{ snapshot, repository }], onSnapshotDeleted)
}
- isDisabled={isManagedRepository}
+ isDisabled={isDeleteDisabled}
/>
);
@@ -265,13 +285,16 @@ export const SnapshotTable: React.FunctionComponent = ({
const selection = {
onSelectionChange: (newSelectedItems: SnapshotDetails[]) => setSelectedItems(newSelectedItems),
- selectable: ({ isManagedRepository }: SnapshotDetails) => !isManagedRepository,
+ selectable: ({ snapshot }: SnapshotDetails) =>
+ Boolean(lastSuccessfulManagedSnapshot)
+ ? snapshot !== lastSuccessfulManagedSnapshot!.snapshot
+ : true,
selectableMessage: (selectable: boolean) => {
if (!selectable) {
return i18n.translate(
- 'xpack.snapshotRestore.snapshotList.table.deleteManagedRepositorySnapshotTooltip',
+ 'xpack.snapshotRestore.snapshotList.table.deleteManagedRepositorySnapshotDescription',
{
- defaultMessage: 'You cannot delete a snapshot stored in a managed repository.',
+ defaultMessage: 'You must retain the last successful snapshot in a managed repository.',
}
);
}
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx
index 191d31cfba6293..da89807a147c39 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_add/policy_add.tsx
@@ -71,6 +71,7 @@ export const PolicyAdd: React.FunctionComponent = ({
maxCount: '',
minCount: '',
},
+ isManagedPolicy: false,
};
const renderSaveError = () => {
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx
index 0bfb84cef93b47..de6bedd9110033 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/policy_edit/policy_edit.tsx
@@ -6,7 +6,7 @@
import React, { useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router-dom';
-import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui';
+import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle, EuiCallOut } from '@elastic/eui';
import { SlmPolicyPayload } from '../../../../common/types';
import { TIME_UNITS } from '../../../../common/constants';
@@ -51,6 +51,7 @@ export const PolicyEdit: React.FunctionComponent
+ <>
+ {policy.isManagedPolicy ? (
+ <>
+
+ }
+ />
+
+ >
+ ) : null}
+
+ >
);
};
diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts
index 3f27da82bf56d1..7d44979e697a7f 100644
--- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts
@@ -15,10 +15,21 @@ const isStringEmpty = (str: string | null): boolean => {
return str ? !Boolean(str.trim()) : true;
};
-export const validatePolicy = (policy: SlmPolicyPayload): PolicyValidation => {
+export const validatePolicy = (
+ policy: SlmPolicyPayload,
+ validationHelperData: {
+ managedRepository?: {
+ name: string;
+ policy: string;
+ };
+ isEditing?: boolean;
+ policyName?: string;
+ }
+): PolicyValidation => {
const i18n = textService.i18n;
const { name, snapshotName, schedule, repository, config, retention } = policy;
+ const { managedRepository, isEditing, policyName } = validationHelperData;
const validation: PolicyValidation = {
isValid: true,
@@ -95,6 +106,22 @@ export const validatePolicy = (policy: SlmPolicyPayload): PolicyValidation => {
);
}
+ if (
+ managedRepository &&
+ managedRepository.name === repository &&
+ managedRepository.policy &&
+ !(isEditing && managedRepository.policy === policyName)
+ ) {
+ validation.errors.repository.push(
+ i18n.translate('xpack.snapshotRestore.policyValidation.invalidRepoErrorMessage', {
+ defaultMessage: 'Policy "{policyName}" is already associated with this repository.',
+ values: {
+ policyName: managedRepository.policy,
+ },
+ })
+ );
+ }
+
if (retention && retention.expireAfterValue && retention.expireAfterValue < 0) {
validation.errors.expireAfterValue.push(
i18n.translate(
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/get_managed_policy_names.ts b/x-pack/legacy/plugins/snapshot_restore/server/lib/get_managed_policy_names.ts
new file mode 100644
index 00000000000000..c86eaffa6fe117
--- /dev/null
+++ b/x-pack/legacy/plugins/snapshot_restore/server/lib/get_managed_policy_names.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.
+ */
+
+// Cloud has its own system for managing SLM policies and we want to make
+// this clear when Snapshot and Restore is used in a Cloud deployment.
+// Retrieve the Cloud-managed policies so that UI can switch
+// logical paths based on this information.
+export const getManagedPolicyNames = async (callWithInternalUser: any): Promise => {
+ try {
+ const { persistent, transient, defaults } = await callWithInternalUser('cluster.getSettings', {
+ filterPath: '*.*managed_policies',
+ flatSettings: true,
+ includeDefaults: true,
+ });
+ const { 'cluster.metadata.managed_policies': managedPolicyNames = [] } = {
+ ...defaults,
+ ...persistent,
+ ...transient,
+ };
+ return managedPolicyNames;
+ } catch (e) {
+ // Silently swallow error and return empty array for managed policy names
+ // so that downstream calls are not blocked. In a healthy environment, we do
+ // not expect to reach here.
+ return [];
+ }
+};
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts b/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts
index 6e54f997209ab8..e79a6b6c97d46c 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/lib/index.ts
@@ -10,4 +10,5 @@ export {
} from './repository_serialization';
export { cleanSettings } from './clean_settings';
export { getManagedRepositoryName } from './get_managed_repository_name';
+export { getManagedPolicyNames } from './get_managed_policy_names';
export { deserializeRestoreShard } from './restore_serialization';
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts
index c0016a4f643cd2..3b251bdd9f9902 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.test.ts
@@ -48,6 +48,7 @@ describe('[Snapshot and Restore API Routes] Policy', () => {
maxCount: 10,
},
nextExecutionMillis: 1562722200000,
+ isManagedPolicy: false,
};
describe('getAllHandler()', () => {
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts
index f9f4daff53def3..38f9a2301af5a3 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts
@@ -11,6 +11,7 @@ import {
import { SlmPolicyEs, SlmPolicy, SlmPolicyPayload } from '../../../common/types';
import { deserializePolicy, serializePolicy } from '../../../common/lib';
import { Plugins } from '../../../shim';
+import { getManagedPolicyNames } from '../../lib';
let callWithInternalUser: any;
@@ -34,6 +35,8 @@ export const getAllHandler: RouterRouteHandler = async (
): Promise<{
policies: SlmPolicy[];
}> => {
+ const managedPolicies = await getManagedPolicyNames(callWithInternalUser);
+
// Get policies
const policiesByName: {
[key: string]: SlmPolicyEs;
@@ -43,9 +46,9 @@ export const getAllHandler: RouterRouteHandler = async (
// Deserialize policies
return {
- policies: Object.entries(policiesByName).map(([name, policy]) =>
- deserializePolicy(name, policy)
- ),
+ policies: Object.entries(policiesByName).map(([name, policy]) => {
+ return deserializePolicy(name, policy, managedPolicies);
+ }),
};
};
@@ -69,9 +72,11 @@ export const getOneHandler: RouterRouteHandler = async (
throw wrapCustomError(new Error('Policy not found'), 404);
}
+ const managedPolicies = await getManagedPolicyNames(callWithInternalUser);
+
// Deserialize policy
return {
- policy: deserializePolicy(name, policiesByName[name]),
+ policy: deserializePolicy(name, policiesByName[name], managedPolicies),
};
};
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.test.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.test.ts
index 183396ef639c15..f6717a4ddf26c9 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.test.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.test.ts
@@ -46,11 +46,24 @@ describe('[Snapshot and Restore API Routes] Repositories', () => {
describe('getAllHandler()', () => {
it('should arrify repositories returned from ES', async () => {
- const mockEsResponse = {
+ const mockRepositoryEsResponse = {
fooRepository: {},
barRepository: {},
};
- const callWithRequest = jest.fn().mockReturnValueOnce(mockEsResponse);
+
+ const mockPolicyEsResponse = {
+ my_policy: {
+ policy: {
+ repository: 'found-snapshots',
+ },
+ },
+ };
+
+ const callWithRequest = jest
+ .fn()
+ .mockReturnValueOnce(mockRepositoryEsResponse)
+ .mockReturnValueOnce(mockPolicyEsResponse);
+
const expectedResponse = {
repositories: [
{
@@ -64,7 +77,10 @@ describe('[Snapshot and Restore API Routes] Repositories', () => {
settings: {},
},
],
- managedRepository: 'found-snapshots',
+ managedRepository: {
+ name: 'found-snapshots',
+ policy: 'my_policy',
+ },
};
await expect(
getAllHandler(mockRequest, callWithRequest, mockResponseToolkit)
@@ -72,11 +88,26 @@ describe('[Snapshot and Restore API Routes] Repositories', () => {
});
it('should return empty array if no repositories returned from ES', async () => {
- const mockEsResponse = {};
- const callWithRequest = jest.fn().mockReturnValueOnce(mockEsResponse);
+ const mockRepositoryEsResponse = {};
+ const mockPolicyEsResponse = {
+ my_policy: {
+ policy: {
+ repository: 'found-snapshots',
+ },
+ },
+ };
+
+ const callWithRequest = jest
+ .fn()
+ .mockReturnValueOnce(mockRepositoryEsResponse)
+ .mockReturnValueOnce(mockPolicyEsResponse);
+
const expectedResponse = {
repositories: [],
- managedRepository: 'found-snapshots',
+ managedRepository: {
+ name: 'found-snapshots',
+ policy: 'my_policy',
+ },
};
await expect(
getAllHandler(mockRequest, callWithRequest, mockResponseToolkit)
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts
index bf4d7538b9d346..815655df283998 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts
@@ -10,7 +10,12 @@ import {
} from '../../../../../server/lib/create_router/error_wrappers';
import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common/constants';
-import { Repository, RepositoryType, RepositoryVerification } from '../../../common/types';
+import {
+ Repository,
+ RepositoryType,
+ RepositoryVerification,
+ SlmPolicyEs,
+} from '../../../common/types';
import { Plugins } from '../../../shim';
import {
@@ -34,14 +39,19 @@ export function registerRepositoriesRoutes(router: Router, plugins: Plugins) {
router.delete('repositories/{names}', deleteHandler);
}
+interface ManagedRepository {
+ name?: string;
+ policy?: string;
+}
+
export const getAllHandler: RouterRouteHandler = async (
req,
callWithRequest
): Promise<{
repositories: Repository[];
- managedRepository?: string;
+ managedRepository: ManagedRepository;
}> => {
- const managedRepository = await getManagedRepositoryName(callWithInternalUser);
+ const managedRepositoryName = await getManagedRepositoryName(callWithInternalUser);
const repositoriesByName = await callWithRequest('snapshot.getRepository', {
repository: '_all',
});
@@ -54,6 +64,35 @@ export const getAllHandler: RouterRouteHandler = async (
settings: deserializeRepositorySettings(settings),
};
});
+
+ const managedRepository = {
+ name: managedRepositoryName,
+ } as ManagedRepository;
+
+ // If a managed repository, we also need to check if a policy is associated to it
+ if (managedRepositoryName) {
+ try {
+ const policiesByName: {
+ [key: string]: SlmPolicyEs;
+ } = await callWithRequest('slm.policies', {
+ human: true,
+ });
+ const managedRepositoryPolicy = Object.entries(policiesByName)
+ .filter(([, data]) => {
+ const { policy } = data;
+ return policy.repository === managedRepositoryName;
+ })
+ .flat();
+
+ const [policyName] = managedRepositoryPolicy;
+
+ managedRepository.policy = policyName as ManagedRepository['name'];
+ } catch (e) {
+ // swallow error for now
+ // we don't want to block repositories from loading if request fails
+ }
+ }
+
return { repositories, managedRepository };
};
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
index 70c66071c1bf6d..fdd50db3091d0a 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.test.ts
@@ -98,13 +98,13 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => {
...defaultSnapshot,
repository: 'fooRepository',
snapshot: 'snapshot1',
- isManagedRepository: false,
+ managedRepository: 'found-snapshots',
},
{
...defaultSnapshot,
repository: 'barRepository',
snapshot: 'snapshot2',
- isManagedRepository: false,
+ managedRepository: 'found-snapshots',
},
],
};
@@ -167,7 +167,7 @@ describe('[Snapshot and Restore API Routes] Snapshots', () => {
...defaultSnapshot,
snapshot,
repository,
- isManagedRepository: false,
+ managedRepository: 'found-snapshots',
};
const response = await getOneHandler(mockOneRequest, callWithRequest, mockResponseToolkit);
diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts
index 1067f3e207b825..eed47b7343ec5b 100644
--- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts
@@ -110,8 +110,9 @@ export const getOneHandler: RouterRouteHandler = async (
): Promise => {
const { repository, snapshot } = req.params;
const managedRepository = await getManagedRepositoryName(callWithInternalUser);
+
const {
- responses: snapshotResponses,
+ responses: snapshotsResponse,
}: {
responses: Array<{
repository: string;
@@ -120,19 +121,32 @@ export const getOneHandler: RouterRouteHandler = async (
}>;
} = await callWithRequest('snapshot.get', {
repository,
- snapshot,
+ snapshot: '_all',
+ ignore_unavailable: true,
});
- if (snapshotResponses && snapshotResponses[0] && snapshotResponses[0].snapshots) {
- return deserializeSnapshotDetails(
- repository,
- snapshotResponses[0].snapshots[0],
- managedRepository
- );
+ const snapshotsList = snapshotsResponse && snapshotsResponse[0] && snapshotsResponse[0].snapshots;
+ const selectedSnapshot = snapshotsList.find(
+ ({ snapshot: snapshotName }) => snapshot === snapshotName
+ ) as SnapshotDetailsEs;
+
+ if (!selectedSnapshot) {
+ // If snapshot doesn't exist, manually throw 404 here
+ throw wrapCustomError(new Error('Snapshot not found'), 404);
}
- // If snapshot doesn't exist, ES will return 200 with an error object, so manually throw 404 here
- throw wrapCustomError(new Error('Snapshot not found'), 404);
+ const successfulSnapshots = snapshotsList
+ .filter(({ state }) => state === 'SUCCESS')
+ .sort((a, b) => {
+ return +new Date(b.end_time) - +new Date(a.end_time);
+ });
+
+ return deserializeSnapshotDetails(
+ repository,
+ selectedSnapshot,
+ managedRepository,
+ successfulSnapshots
+ );
};
export const deleteHandler: RouterRouteHandler = async (req, callWithRequest) => {
diff --git a/x-pack/legacy/plugins/snapshot_restore/test/fixtures/policy.ts b/x-pack/legacy/plugins/snapshot_restore/test/fixtures/policy.ts
index 3dc5f78c42457e..510edb6b919f30 100644
--- a/x-pack/legacy/plugins/snapshot_restore/test/fixtures/policy.ts
+++ b/x-pack/legacy/plugins/snapshot_restore/test/fixtures/policy.ts
@@ -32,6 +32,7 @@ export const getPolicy = ({
snapshotName = `snapshot-${getRandomString()}`,
stats = DEFAULT_STATS,
version = getRandomNumber(),
+ isManagedPolicy = false,
}: Partial = {}): SlmPolicy => ({
name,
config,
@@ -45,4 +46,5 @@ export const getPolicy = ({
snapshotName,
stats,
version,
+ isManagedPolicy,
});
From 085a2af8ec3771c80ea2aeb8e592fd7c4c18c259 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Wed, 4 Dec 2019 14:55:41 +0100
Subject: [PATCH 06/30] [APM] Fix failing ACM integration test (#52149)
---
.../__snapshots__/queries.test.ts.snap | 33 ++++++---
.../agent_configuration/search.mocks.ts | 71 -------------------
.../agent_configuration/search.test.ts | 63 ----------------
.../settings/agent_configuration/search.ts | 53 +++++---------
.../routes/settings/agent_configuration.ts | 3 +
5 files changed, 44 insertions(+), 179 deletions(-)
delete mode 100644 x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.mocks.ts
delete mode 100644 x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap
index d64907691ef9ae..27c013ab4c6d38 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/__snapshots__/queries.test.ts.snap
@@ -55,16 +55,26 @@ Object {
"minimum_should_match": 2,
"should": Array [
Object {
- "term": Object {
- "service.name": Object {
- "value": "foo",
+ "constant_score": Object {
+ "boost": 2,
+ "filter": Object {
+ "term": Object {
+ "service.name": Object {
+ "value": "foo",
+ },
+ },
},
},
},
Object {
- "term": Object {
- "service.environment": Object {
- "value": "bar",
+ "constant_score": Object {
+ "boost": 1,
+ "filter": Object {
+ "term": Object {
+ "service.environment": Object {
+ "value": "bar",
+ },
+ },
},
},
},
@@ -106,9 +116,14 @@ Object {
"minimum_should_match": 2,
"should": Array [
Object {
- "term": Object {
- "service.name": Object {
- "value": "foo",
+ "constant_score": Object {
+ "boost": 2,
+ "filter": Object {
+ "term": Object {
+ "service.name": Object {
+ "value": "foo",
+ },
+ },
},
},
},
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.mocks.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.mocks.ts
deleted file mode 100644
index 1ed7f56e0b10d3..00000000000000
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.mocks.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export const searchMocks = {
- took: 1,
- timed_out: false,
- _shards: {
- total: 1,
- successful: 1,
- skipped: 0,
- failed: 0
- },
- hits: {
- total: {
- value: 3,
- relation: 'eq'
- },
- max_score: 0.9808292,
- hits: [
- {
- _index: '.apm-agent-configuration',
- _id: '-aQHsm0BxZLczArvNQYW',
- _score: 0.9808292,
- _source: {
- service: {
- environment: 'production'
- },
- settings: {
- transaction_sample_rate: 0.3
- },
- '@timestamp': 1570649879829,
- applied_by_agent: false,
- etag: 'c511f4c1df457371c4446c9c4925662e18726f51'
- }
- },
- {
- _index: '.apm-agent-configuration',
- _id: '-KQHsm0BxZLczArvNAb0',
- _score: 0.18232156,
- _source: {
- service: {
- name: 'my_service'
- },
- settings: {
- transaction_sample_rate: 0.2
- },
- '@timestamp': 1570649879795,
- applied_by_agent: false,
- etag: 'a13cd8fee5a2fcc2ae773a60a4deaf7f76b90a65'
- }
- },
- {
- _index: '.apm-agent-configuration',
- _id: '96QHsm0BxZLczArvNAbD',
- _score: 0.0,
- _source: {
- service: {},
- settings: {
- transaction_sample_rate: 0.1
- },
- '@timestamp': 1570649879743,
- applied_by_agent: false,
- etag: 'c7f4ba16f00a9c9bf3c49024c5b6d4632ff05ff5'
- }
- }
- ]
- }
-};
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts
deleted file mode 100644
index dcf7329b229d85..00000000000000
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.test.ts
+++ /dev/null
@@ -1,63 +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 { searchConfigurations } from './search';
-import { searchMocks } from './search.mocks';
-import { Setup } from '../../helpers/setup_request';
-
-describe('search configurations', () => {
- it('should return configuration by matching on service.name', async () => {
- const res = await searchConfigurations({
- serviceName: 'my_service',
- environment: 'production',
- setup: ({
- config: { get: () => '' },
- client: { search: async () => searchMocks },
- internalClient: { search: async () => searchMocks },
- indices: {
- apm_oss: {
- sourcemapIndices: 'myIndex',
- errorIndices: 'myIndex',
- onboardingIndices: 'myIndex',
- spanIndices: 'myIndex',
- transactionIndices: 'myIndex',
- metricsIndices: 'myIndex',
- apmAgentConfigurationIndex: 'myIndex'
- }
- }
- } as unknown) as Setup
- });
-
- expect(res!._source.service).toEqual({ name: 'my_service' });
- expect(res!._source.settings).toEqual({ transaction_sample_rate: 0.2 });
- });
-
- it('should return configuration by matching on "production" env', async () => {
- const res = await searchConfigurations({
- serviceName: 'non_existing_service',
- environment: 'production',
- setup: ({
- config: { get: () => '' },
- client: { search: async () => searchMocks },
- internalClient: { search: async () => searchMocks },
- indices: {
- apm_oss: {
- sourcemapIndices: 'myIndex',
- errorIndices: 'myIndex',
- onboardingIndices: 'myIndex',
- spanIndices: 'myIndex',
- transactionIndices: 'myIndex',
- metricsIndices: 'myIndex',
- apmAgentConfigurationIndex: 'myIndex'
- }
- }
- } as unknown) as Setup
- });
-
- expect(res!._source.service).toEqual({ environment: 'production' });
- expect(res!._source.settings).toEqual({ transaction_sample_rate: 0.3 });
- });
-});
diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts
index a02dd7af755e0f..766baead006b6e 100644
--- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts
+++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/search.ts
@@ -21,17 +21,20 @@ export async function searchConfigurations({
setup: Setup;
}) {
const { internalClient, indices } = setup;
-
- // sorting order
- // 1. exact match: service.name AND service.environment (eg. opbeans-node / production)
- // 2. Partial match: service.name and no service.environment (eg. opbeans-node / All)
- // 3. Partial match: service.environment and no service.name (eg. All / production)
- // 4. Catch all: no service.name and no service.environment (eg. All / All)
-
const environmentFilter = environment
- ? [{ term: { [SERVICE_ENVIRONMENT]: { value: environment } } }]
+ ? [
+ {
+ constant_score: {
+ filter: { term: { [SERVICE_ENVIRONMENT]: { value: environment } } },
+ boost: 1
+ }
+ }
+ ]
: [];
+ // In the following `constant_score` is being used to disable IDF calculation (where frequency of a term influences scoring)
+ // Additionally a boost has been added to service.name to ensure it scores higher
+ // if there is tie between a config with a matching service.name and a config with a matching environment
const params = {
index: indices.apmAgentConfigurationIndex,
body: {
@@ -39,7 +42,12 @@ export async function searchConfigurations({
bool: {
minimum_should_match: 2,
should: [
- { term: { [SERVICE_NAME]: { value: serviceName } } },
+ {
+ constant_score: {
+ filter: { term: { [SERVICE_NAME]: { value: serviceName } } },
+ boost: 2
+ }
+ },
...environmentFilter,
{ bool: { must_not: [{ exists: { field: SERVICE_NAME } }] } },
{ bool: { must_not: [{ exists: { field: SERVICE_ENVIRONMENT } }] } }
@@ -52,33 +60,6 @@ export async function searchConfigurations({
const resp = await internalClient.search(
params
);
- const { hits } = resp.hits;
-
- const exactMatch = hits.find(
- hit =>
- hit._source.service.name === serviceName &&
- hit._source.service.environment === environment
- );
-
- if (exactMatch) {
- return exactMatch;
- }
-
- const matchWithServiceName = hits.find(
- hit => hit._source.service.name === serviceName
- );
-
- if (matchWithServiceName) {
- return matchWithServiceName;
- }
-
- const matchWithEnvironment = hits.find(
- hit => hit._source.service.environment === environment
- );
-
- if (matchWithEnvironment) {
- return matchWithEnvironment;
- }
return resp.hits.hits[0] as ESSearchHit | undefined;
}
diff --git a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
index b897dfb4b91235..a6709110790403 100644
--- a/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
+++ b/x-pack/legacy/plugins/apm/server/routes/settings/agent_configuration.ts
@@ -170,6 +170,9 @@ export const agentConfigurationSearchRoute = createRoute(core => ({
});
if (!config) {
+ context.logger.info(
+ `Config was not found for ${body.service.name}/${body.service.environment}`
+ );
throw new Boom('Not found', { statusCode: 404 });
}
From 73651a1b28437db3596c516cf301cb5cd1e8667f Mon Sep 17 00:00:00 2001
From: Vadim Dalecky
Date: Wed, 4 Dec 2019 05:59:53 -0800
Subject: [PATCH 07/30] Disabled actions (#51975)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: disable actions from SIEM by using `disabledActions` list
* feat: filter out actions specified in `disabledActions` input prop
* test: 💍 remove legacy test
* chore: 🤖 remove unused import
* test: 💍 add disabledActions prop tests
---
.../public/lib/embeddables/i_embeddable.ts | 5 +
.../lib/panel/embeddable_panel.test.tsx | 102 +++++++++++++++++-
.../public/lib/panel/embeddable_panel.tsx | 21 ++--
.../components/embeddables/embedded_map.tsx | 13 +--
.../embeddables/embedded_map_helpers.test.tsx | 10 +-
.../embeddables/embedded_map_helpers.tsx | 26 +----
6 files changed, 125 insertions(+), 52 deletions(-)
diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
index 0b864e1a3573be..33cb146a056cbc 100644
--- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
+++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts
@@ -28,6 +28,11 @@ export interface EmbeddableInput {
id: string;
lastReloadRequestTime?: number;
hidePanelTitles?: boolean;
+
+ /**
+ * List of action IDs that this embeddable should not render.
+ */
+ disabledActions?: string[];
}
export interface EmbeddableOutput {
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
index 9eed400daf9c9b..196d6f934134bf 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx
@@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers';
import { findTestSubject } from '@elastic/eui/lib/test';
import { I18nProvider } from '@kbn/i18n/react';
import { CONTEXT_MENU_TRIGGER } from '../triggers';
-import { IAction, ITrigger } from 'src/plugins/ui_actions/public';
+import { IAction, ITrigger, IUiActionsApi } from 'src/plugins/ui_actions/public';
import { Trigger, GetEmbeddableFactory, ViewMode } from '../types';
import { EmbeddableFactory, isErrorEmbeddable } from '../embeddables';
import { EmbeddablePanel } from './embeddable_panel';
@@ -42,6 +42,7 @@ import {
} from '../test_samples/embeddables/contact_card/contact_card_embeddable';
// eslint-disable-next-line
import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks';
+import { EuiBadge } from '@elastic/eui';
const actionRegistry = new Map();
const triggerRegistry = new Map();
@@ -174,6 +175,105 @@ test('HelloWorldContainer in view mode hides edit mode actions', async () => {
expect(findTestSubject(component, `embeddablePanelAction-${editModeAction.id}`).length).toBe(0);
});
+const renderInEditModeAndOpenContextMenu = async (
+ embeddableInputs: any,
+ getActions: IUiActionsApi['getTriggerCompatibleActions'] = () => Promise.resolve([])
+) => {
+ const inspector = inspectorPluginMock.createStartContract();
+
+ const container = new HelloWorldContainer({ id: '123', panels: {}, viewMode: ViewMode.VIEW }, {
+ getEmbeddableFactory,
+ } as any);
+
+ const embeddable = await container.addNewEmbeddable<
+ ContactCardEmbeddableInput,
+ ContactCardEmbeddableOutput,
+ ContactCardEmbeddable
+ >(CONTACT_CARD_EMBEDDABLE, embeddableInputs);
+
+ const component = mount(
+
+ []) as any}
+ getEmbeddableFactory={(() => undefined) as any}
+ notifications={{} as any}
+ overlays={{} as any}
+ inspector={inspector}
+ SavedObjectFinder={() => null}
+ />
+
+ );
+
+ findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click');
+ await nextTick();
+ component.update();
+
+ return { component };
+};
+
+test('HelloWorldContainer in edit mode hides disabledActions', async () => {
+ const action = {
+ id: 'FOO',
+ type: 'FOO',
+ getIconType: () => undefined,
+ getDisplayName: () => 'foo',
+ isCompatible: async () => true,
+ execute: async () => {},
+ };
+ const getActions = () => Promise.resolve([action]);
+
+ const { component: component1 } = await renderInEditModeAndOpenContextMenu(
+ {
+ firstName: 'Bob',
+ },
+ getActions
+ );
+ const { component: component2 } = await renderInEditModeAndOpenContextMenu(
+ {
+ firstName: 'Bob',
+ disabledActions: ['FOO'],
+ },
+ getActions
+ );
+
+ const fooContextMenuActionItem1 = findTestSubject(component1, 'embeddablePanelAction-FOO');
+ const fooContextMenuActionItem2 = findTestSubject(component2, 'embeddablePanelAction-FOO');
+
+ expect(fooContextMenuActionItem1.length).toBe(1);
+ expect(fooContextMenuActionItem2.length).toBe(0);
+});
+
+test('HelloWorldContainer hides disabled badges', async () => {
+ const action = {
+ id: 'BAR',
+ type: 'BAR',
+ getIconType: () => undefined,
+ getDisplayName: () => 'bar',
+ isCompatible: async () => true,
+ execute: async () => {},
+ };
+ const getActions = () => Promise.resolve([action]);
+
+ const { component: component1 } = await renderInEditModeAndOpenContextMenu(
+ {
+ firstName: 'Bob',
+ },
+ getActions
+ );
+ const { component: component2 } = await renderInEditModeAndOpenContextMenu(
+ {
+ firstName: 'Bob',
+ disabledActions: ['BAR'],
+ },
+ getActions
+ );
+
+ expect(component1.find(EuiBadge).length).toBe(1);
+ expect(component2.find(EuiBadge).length).toBe(0);
+});
+
test('HelloWorldContainer in edit mode shows edit mode actions', async () => {
const inspector = inspectorPluginMock.createStartContract();
diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
index 2b48bf237829c0..234d8508bb97a4 100644
--- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
+++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx
@@ -91,15 +91,19 @@ export class EmbeddablePanel extends React.Component {
}
private async refreshBadges() {
- const badges = await this.props.getActions(PANEL_BADGE_TRIGGER, {
+ let badges: IAction[] = await this.props.getActions(PANEL_BADGE_TRIGGER, {
embeddable: this.props.embeddable,
});
+ if (!this.mounted) return;
- if (this.mounted) {
- this.setState({
- badges,
- });
+ const { disabledActions } = this.props.embeddable.getInput();
+ if (disabledActions) {
+ badges = badges.filter(badge => disabledActions.indexOf(badge.id) === -1);
}
+
+ this.setState({
+ badges,
+ });
}
public UNSAFE_componentWillMount() {
@@ -200,10 +204,15 @@ export class EmbeddablePanel extends React.Component {
};
private getActionContextMenuPanel = async () => {
- const actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, {
+ let actions = await this.props.getActions(CONTEXT_MENU_TRIGGER, {
embeddable: this.props.embeddable,
});
+ const { disabledActions } = this.props.embeddable.getInput();
+ if (disabledActions) {
+ actions = actions.filter(action => disabledActions.indexOf(action.id) === -1);
+ }
+
const createGetUserData = (overlays: OverlayStart) =>
async function getUserData(context: { embeddable: IEmbeddable }) {
return new Promise<{ title: string | undefined }>(resolve => {
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
index 5a3a689e12d833..cb73cf73b8d06c 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx
@@ -23,7 +23,7 @@ import { Loader } from '../loader';
import { useStateToaster } from '../toasters';
import { Embeddable } from './embeddable';
import { EmbeddableHeader } from './embeddable_header';
-import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers';
+import { createEmbeddable, displayErrorToast } from './embedded_map_helpers';
import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapToolTip } from './map_tool_tip/map_tool_tip';
import * as i18n from './translations';
@@ -104,17 +104,6 @@ export const EmbeddedMapComponent = ({
const plugins = useKibanaPlugins();
const core = useKibanaCore();
- // Setup embeddables API (i.e. detach extra actions) useEffect
- useEffect(() => {
- try {
- setupEmbeddablesAPI(plugins);
- } catch (e) {
- displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster);
- setIsLoading(false);
- setIsError(true);
- }
- }, []);
-
// Initial Load useEffect
useEffect(() => {
let isSubscribed = true;
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
index a2c3e2cc288d7c..b4b2b98ddb8d6b 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx
@@ -4,10 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { createEmbeddable, displayErrorToast, setupEmbeddablesAPI } from './embedded_map_helpers';
+import { createEmbeddable, displayErrorToast } from './embedded_map_helpers';
import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers';
import { createPortalNode } from 'react-reverse-portal';
-import { PluginsStart } from 'ui/new_platform/new_platform';
jest.mock('ui/new_platform');
jest.mock('../../lib/settings/use_kibana_ui_setting');
@@ -45,13 +44,6 @@ describe('embedded_map_helpers', () => {
});
});
- describe('setupEmbeddablesAPI', () => {
- test('detaches extra UI actions', () => {
- setupEmbeddablesAPI((npStart.plugins as unknown) as PluginsStart);
- expect(npStart.plugins.uiActions.detachAction).toHaveBeenCalledTimes(2);
- });
- });
-
describe('createEmbeddable', () => {
test('attaches refresh action', async () => {
const setQueryMock = jest.fn();
diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
index 03c4492b77f1be..b9a9df9824eee4 100644
--- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
+++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx
@@ -7,14 +7,8 @@
import uuid from 'uuid';
import React from 'react';
import { OutPortal, PortalNode } from 'react-reverse-portal';
-import { PluginsStart } from 'ui/new_platform/new_platform';
-
import { ActionToaster, AppToast } from '../toasters';
-import {
- CONTEXT_MENU_TRIGGER,
- PANEL_BADGE_TRIGGER,
- ViewMode,
-} from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
+import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public';
import {
IndexPatternMapping,
MapEmbeddable,
@@ -53,23 +47,6 @@ export const displayErrorToast = (
});
};
-/**
- * Temporary Embeddables API configuration override until ability to edit actions is addressed:
- * https://github.com/elastic/kibana/issues/43643
- *
- * @param plugins new platform plugins
- *
- * @throws Error if trigger/action doesn't exist
- */
-export const setupEmbeddablesAPI = (plugins: PluginsStart) => {
- try {
- plugins.uiActions.detachAction(CONTEXT_MENU_TRIGGER, 'CUSTOM_TIME_RANGE');
- plugins.uiActions.detachAction(PANEL_BADGE_TRIGGER, 'CUSTOM_TIME_RANGE_BADGE');
- } catch (e) {
- throw e;
- }
-};
-
/**
* Creates MapEmbeddable with provided initial configuration
*
@@ -115,6 +92,7 @@ export const createEmbeddable = async (
openTOCDetails: [],
hideFilterActions: false,
mapCenter: { lon: -1.05469, lat: 15.96133, zoom: 1 },
+ disabledActions: ['CUSTOM_TIME_RANGE', 'CUSTOM_TIME_RANGE_BADGE'],
};
const renderTooltipContent = ({
From 95e5edd9c4413bdb97fcf189a631df0cf1d13f89 Mon Sep 17 00:00:00 2001
From: Thomas Watson
Date: Wed, 4 Dec 2019 15:21:07 +0100
Subject: [PATCH 08/30] Instrument Kibana with Elastic APM (#43548)
Instruments Kibana with Elastic APM by adding the Node.js agent to the
source code. The agent is not turned on by default but can be enabled by
setting the environment variable `ELASTIC_APM_ACTIVE=true` or by
creating an apm config file called `config/apm.dev.js` and setting
`active: true` inside of it.
This implementation is not meant to be used by end-users of Kibana as it
lacks integration with the regular Kibana config file. For now, this is
meant as a useful internal tool for Elastic employees when developing
Kibana.
By default, it's pre-configured with a `serverUrl` pointing to an APM
Server hosted on Elastic Cloud. The data is stored in an ES cluster
accessible only by Elastic employees. These defaults can easily be
overwritten using environment variables or via the custom config file.
---
.gitignore | 1 +
CONTRIBUTING.md | 16 +
config/apm.js | 81 ++++
package.json | 1 +
scripts/kibana.js | 1 +
src/apm.js | 39 ++
src/cli/index.js | 1 +
.../server/http/base_path_proxy_server.ts | 3 +
src/dev/build/tasks/copy_source_task.js | 1 +
yarn.lock | 371 +++++++++++++++++-
10 files changed, 510 insertions(+), 5 deletions(-)
create mode 100644 config/apm.js
create mode 100644 src/apm.js
diff --git a/.gitignore b/.gitignore
index 02b20da297fc67..e7391a5c292d0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ disabledPlugins
webpackstats.json
/config/*
!/config/kibana.yml
+!/config/apm.js
coverage
selenium
.babel_register_cache.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e2a8459c2b01ab..53e44fbede7247 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -24,6 +24,7 @@ A high level overview of our contributing guidelines.
- [Internationalization](#internationalization)
- [Testing and Building](#testing-and-building)
- [Debugging server code](#debugging-server-code)
+ - [Instrumenting with Elastic APM](#instrumenting-with-elastic-apm)
- [Debugging Unit Tests](#debugging-unit-tests)
- [Unit Testing Plugins](#unit-testing-plugins)
- [Cross-browser compatibility](#cross-browser-compatibility)
@@ -374,6 +375,21 @@ macOS users on a machine with a discrete graphics card may see significant speed
### Debugging Server Code
`yarn debug` will start the server with Node's inspect flag. Kibana's development mode will start three processes on ports `9229`, `9230`, and `9231`. Chrome's developer tools need to be configured to connect to all three connections. Add `localhost:` for each Kibana process in Chrome's developer tools connection tab.
+### Instrumenting with Elastic APM
+Kibana ships with the [Elastic APM Node.js Agent](https://github.com/elastic/apm-agent-nodejs) built-in for debugging purposes.
+
+Its default configuration is meant to be used by core Kibana developers only, but it can easily be re-configured to your needs.
+In its default configuration it's disabled and will, once enabled, send APM data to a centrally managed Elasticsearch cluster accessible only to Elastic employees.
+
+To change the location where data is sent, use the [`serverUrl`](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#server-url) APM config option.
+To activate the APM agent, use the [`active`](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html#active) APM config option.
+
+All config options can be set either via environment variables, or by creating an appropriate config file under `config/apm.dev.js`.
+For more information about configuring the APM agent, please refer to [the documentation](https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuring-the-agent.html).
+
+Once the agent is active, it will trace all incoming HTTP requests to Kibana, monitor for errors, and collect process-level metrics.
+The collected data will be sent to the APM Server and is viewable in the APM UI in Kibana.
+
### Unit testing frameworks
Kibana is migrating unit testing from Mocha to Jest. Legacy unit tests still
exist in Mocha but all new unit tests should be written in Jest. Mocha tests
diff --git a/config/apm.js b/config/apm.js
new file mode 100644
index 00000000000000..8efbbf87487e36
--- /dev/null
+++ b/config/apm.js
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+/**
+ * DO NOT EDIT THIS FILE!
+ *
+ * This file contains the configuration for the Elastic APM instrumentaion of
+ * Kibana itself and is only intented to be used during development of Kibana.
+ *
+ * Instrumentation is turned off by default. Once activated it will send APM
+ * data to an Elasticsearch cluster accessible by Elastic employees.
+ *
+ * To modify the configuration, either use environment variables, or create a
+ * file named `config/apm.dev.js`, which exports a config object as described
+ * in the docs.
+ *
+ * For an overview over the available configuration files, see:
+ * https://www.elastic.co/guide/en/apm/agent/nodejs/current/configuration.html
+ *
+ * For general information about Elastic APM, see:
+ * https://www.elastic.co/guide/en/apm/get-started/current/index.html
+ */
+
+const { readFileSync } = require('fs');
+const { join } = require('path');
+const { execSync } = require('child_process');
+const merge = require('lodash.merge');
+
+module.exports = merge({
+ active: false,
+ serverUrl: 'https://f1542b814f674090afd914960583265f.apm.us-central1.gcp.cloud.es.io:443',
+ // The secretToken below is intended to be hardcoded in this file even though
+ // it makes it public. This is not a security/privacy issue. Normally we'd
+ // instead disable the need for a secretToken in the APM Server config where
+ // the data is transmitted to, but due to how it's being hosted, it's easier,
+ // for now, to simply leave it in.
+ secretToken: 'R0Gjg46pE9K9wGestd',
+ globalLabels: {},
+ centralConfig: false,
+ logUncaughtExceptions: true
+}, devConfig());
+
+const rev = gitRev();
+if (rev !== null) module.exports.globalLabels.git_rev = rev;
+
+try {
+ const filename = join(__dirname, '..', 'data', 'uuid');
+ module.exports.globalLabels.kibana_uuid = readFileSync(filename, 'utf-8');
+} catch (e) {} // eslint-disable-line no-empty
+
+function gitRev() {
+ try {
+ return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
+ } catch (e) {
+ return null;
+ }
+}
+
+function devConfig() {
+ try {
+ return require('./apm.dev'); // eslint-disable-line import/no-unresolved
+ } catch (e) {
+ return {};
+ }
+}
diff --git a/package.json b/package.json
index ea6276496e84da..98ba41772ed60a 100644
--- a/package.json
+++ b/package.json
@@ -159,6 +159,7 @@
"d3-cloud": "1.2.5",
"deepmerge": "^4.2.2",
"del": "^5.1.0",
+ "elastic-apm-node": "^3.2.0",
"elasticsearch": "^16.5.0",
"elasticsearch-browser": "^16.5.0",
"encode-uri-query": "1.0.1",
diff --git a/scripts/kibana.js b/scripts/kibana.js
index b1b470a37535fc..f5a63e6c07dd60 100644
--- a/scripts/kibana.js
+++ b/scripts/kibana.js
@@ -17,5 +17,6 @@
* under the License.
*/
+require('../src/apm')(process.env.ELASTIC_APM_PROXY_SERVICE_NAME || 'kibana-proxy');
require('../src/setup_node_env');
require('../src/cli/cli');
diff --git a/src/apm.js b/src/apm.js
new file mode 100644
index 00000000000000..04a70ee71c53ec
--- /dev/null
+++ b/src/apm.js
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+const { existsSync } = require('fs');
+const { join } = require('path');
+const { name, version } = require('../package.json');
+
+module.exports = function (serviceName = name) {
+ if (process.env.kbnWorkerType === 'optmzr') return;
+
+ const conf = {
+ serviceName: `${serviceName}-${version.replace(/\./g, '_')}`
+ };
+
+ if (configFileExists()) conf.configFile = 'config/apm.js';
+ else conf.active = false;
+
+ require('elastic-apm-node').start(conf);
+};
+
+function configFileExists() {
+ return existsSync(join(__dirname, '..', 'config', 'apm.js'));
+}
diff --git a/src/cli/index.js b/src/cli/index.js
index 4af5e3c68423cd..45f88eaf82a5b8 100644
--- a/src/cli/index.js
+++ b/src/cli/index.js
@@ -17,5 +17,6 @@
* under the License.
*/
+require('../apm')();
require('../setup_node_env');
require('./cli');
diff --git a/src/core/server/http/base_path_proxy_server.ts b/src/core/server/http/base_path_proxy_server.ts
index cde35f3cbe995a..276e3955a4678b 100644
--- a/src/core/server/http/base_path_proxy_server.ts
+++ b/src/core/server/http/base_path_proxy_server.ts
@@ -17,6 +17,8 @@
* under the License.
*/
+import apm from 'elastic-apm-node';
+
import { ByteSizeValue } from '@kbn/config-schema';
import { Server, Request } from 'hapi';
import Url from 'url';
@@ -139,6 +141,7 @@ export class BasePathProxyServer {
// Before we proxy request to a target port we may want to wait until some
// condition is met (e.g. until target listener is ready).
async (request, responseToolkit) => {
+ apm.setTransactionName(`${request.method.toUpperCase()} /{basePath}/{kbnPath*}`);
await blockUntil();
return responseToolkit.continue;
},
diff --git a/src/dev/build/tasks/copy_source_task.js b/src/dev/build/tasks/copy_source_task.js
index e487ac0567f766..a693c6ce8a8a6f 100644
--- a/src/dev/build/tasks/copy_source_task.js
+++ b/src/dev/build/tasks/copy_source_task.js
@@ -46,6 +46,7 @@ export const CopySourceTask = {
'typings/**',
'webpackShims/**',
'config/kibana.yml',
+ 'config/apm.js',
'tsconfig*.json',
'.i18nrc.json',
'kibana.d.ts'
diff --git a/yarn.lock b/yarn.lock
index bda9f056bdf5c7..76bc070e27842d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4674,6 +4674,11 @@ affine-hull@^1.0.0:
dependencies:
robust-orientation "^1.1.3"
+after-all-results@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/after-all-results/-/after-all-results-2.0.0.tgz#6ac2fc202b500f88da8f4f5530cfa100f4c6a2d0"
+ integrity sha1-asL8ICtQD4jaj09VMM+hAPTGotA=
+
after@0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
@@ -5702,6 +5707,13 @@ astral-regex@^1.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9"
integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==
+async-cache@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/async-cache/-/async-cache-1.1.0.tgz#4a9a5a89d065ec5d8e5254bd9ee96ba76c532b5a"
+ integrity sha1-SppaidBl7F2OUlS9nulrp2xTK1o=
+ dependencies:
+ lru-cache "^4.0.0"
+
async-done@^1.2.0, async-done@^1.2.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2"
@@ -5746,6 +5758,18 @@ async-settle@^1.0.0:
dependencies:
async-done "^1.2.2"
+async-value-promise@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/async-value-promise/-/async-value-promise-1.1.1.tgz#68957819e3eace804f3b4b69477e2bd276c15378"
+ integrity sha512-c2RFDKjJle1rHa0YxN9Ysu97/QBu3Wa+NOejJxsX+1qVDJrkD3JL/GN1B3gaILAEXJXbu/4Z1lcoCHFESe/APA==
+ dependencies:
+ async-value "^1.2.2"
+
+async-value@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/async-value/-/async-value-1.2.2.tgz#84517a1e7cb6b1a5b5e181fa31be10437b7fb125"
+ integrity sha1-hFF6Hny2saW14YH6Mb4QQ3t/sSU=
+
async@1.x, async@^1.4.2, async@^1.5.2, async@~1.5.2:
version "1.5.2"
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
@@ -5829,6 +5853,11 @@ autoprefixer@9.6.1, autoprefixer@^9.4.9:
postcss "^7.0.17"
postcss-value-parser "^4.0.0"
+await-event@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/await-event/-/await-event-2.1.0.tgz#78e9f92684bae4022f9fa0b5f314a11550f9aa76"
+ integrity sha1-eOn5JoS65AIvn6C18xShFVD5qnY=
+
aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@@ -6508,6 +6537,13 @@ base@^0.11.1:
mixin-deep "^1.2.0"
pascalcase "^0.1.1"
+basic-auth@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
+ integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
+ dependencies:
+ safe-buffer "5.1.2"
+
batch-processor@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8"
@@ -6584,6 +6620,11 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
+binary-search@^1.3.3:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c"
+ integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==
+
binaryextensions@2:
version "2.1.1"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
@@ -6853,6 +6894,13 @@ braces@^3.0.1, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
+breadth-filter@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/breadth-filter/-/breadth-filter-2.0.0.tgz#7b3f8737f46ba1946aec19355ecf5df2bdb7e47c"
+ integrity sha512-thQShDXnFWSk2oVBixRCyrWsFoV5tfOpWKHmxwafHQDNxCfDBk539utpvytNjmlFrTMqz41poLwJvA1MW3z0MQ==
+ dependencies:
+ object.entries "^1.0.4"
+
brfs@^1.3.0, brfs@^1.4.0:
version "1.4.3"
resolved "https://registry.yarnpkg.com/brfs/-/brfs-1.4.3.tgz#db675d6f5e923e6df087fca5859c9090aaed3216"
@@ -8537,6 +8585,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+console-log-level@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/console-log-level/-/console-log-level-1.4.1.tgz#9c5a6bb9ef1ef65b05aba83028b0ff894cdf630a"
+ integrity sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==
+
const-max-uint32@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/const-max-uint32/-/const-max-uint32-1.0.2.tgz#f009bb6230e678ed874dd2d6a9cd9e3cbfabb676"
@@ -8575,6 +8628,11 @@ constate@^1.2.0:
resolved "https://registry.yarnpkg.com/constate/-/constate-1.3.2.tgz#fa5f0fc292207f1ec21b46a5eb81f59c8b0a8b84"
integrity sha512-aaILV4vXwGTUZaQZHS5F1xBV8wRCR0Ow1505fdkS5/BPg6hbQrhNqdHL4wgxWgaDeEj43mu/Fb+LhqOKTMcrgQ==
+container-info@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/container-info/-/container-info-1.0.1.tgz#6b383cb5e197c8d921e88983388facb04124b56b"
+ integrity sha512-wk/+uJvPHOFG+JSwQS+fw6H6yw3Oyc8Kw9L4O2MN817uA90OqJ59nlZbbLPqDudsjJ7Tetee3pwExdKpd2ahjQ==
+
contains-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
@@ -8650,7 +8708,7 @@ cookie@0.3.1:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
-cookie@0.4.0:
+cookie@0.4.0, cookie@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
@@ -8744,7 +8802,7 @@ core-js@^3.0.1, core-js@^3.0.4, core-js@^3.2.1:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.2.1.tgz#cd41f38534da6cc59f7db050fe67307de9868b09"
integrity sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw==
-core-util-is@1.0.2, core-util-is@~1.0.0:
+core-util-is@1.0.2, 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=
@@ -10625,6 +10683,56 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
+elastic-apm-http-client@^9.2.0:
+ version "9.2.1"
+ resolved "https://registry.yarnpkg.com/elastic-apm-http-client/-/elastic-apm-http-client-9.2.1.tgz#e0e980ceb9975ff770bdbf2f5cdaac39fd70e8e6"
+ integrity sha512-KythghGrgsozTVZdsUdKED1+IcfN1CEIWS4zL8crsV234Dj9QaffG88E7pu11PZ04HiOSVemAKby21aNRV0kLQ==
+ dependencies:
+ breadth-filter "^2.0.0"
+ container-info "^1.0.1"
+ end-of-stream "^1.4.4"
+ fast-safe-stringify "^2.0.7"
+ fast-stream-to-buffer "^1.0.0"
+ pump "^3.0.0"
+ readable-stream "^3.4.0"
+ stream-chopper "^3.0.1"
+ unicode-byte-truncate "^1.0.0"
+
+elastic-apm-node@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-3.2.0.tgz#a1aa5b255f8867788b38e2854aa331c3a0cc0e22"
+ integrity sha512-GnoYcge/Xy8/I/0pHF2V9tZU1aRFMqcP7PQ0WXCqdETMUUq7Gmf0fcdzjA8CoDALR+rfsjn4ByF1p7uXoTJoPQ==
+ dependencies:
+ after-all-results "^2.0.0"
+ async-value-promise "^1.1.1"
+ basic-auth "^2.0.1"
+ console-log-level "^1.4.1"
+ cookie "^0.4.0"
+ core-util-is "^1.0.2"
+ elastic-apm-http-client "^9.2.0"
+ end-of-stream "^1.4.1"
+ fast-safe-stringify "^2.0.7"
+ http-headers "^3.0.2"
+ http-request-to-url "^1.0.0"
+ is-native "^1.0.1"
+ measured-reporting "^1.51.1"
+ monitor-event-loop-delay "^1.0.0"
+ object-filter-sequence "^1.0.0"
+ object-identity-map "^1.0.2"
+ original-url "^1.2.3"
+ read-pkg-up "^7.0.0"
+ redact-secrets "^1.0.0"
+ relative-microtime "^2.0.0"
+ require-ancestors "^1.0.0"
+ require-in-the-middle "^5.0.0"
+ semver "^6.1.1"
+ set-cookie-serde "^1.0.0"
+ shallow-clone-shim "^1.0.0"
+ sql-summary "^1.0.1"
+ stackman "^4.0.0"
+ traceparent "^1.0.0"
+ unicode-byte-truncate "^1.0.0"
+
elasticsearch-browser@^16.5.0:
version "16.5.0"
resolved "https://registry.yarnpkg.com/elasticsearch-browser/-/elasticsearch-browser-16.5.0.tgz#d2efbbf8751bb563e91b74117a14b9211df5cfe9"
@@ -10736,7 +10844,7 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
-end-of-stream@^1.4.1:
+end-of-stream@^1.4.1, end-of-stream@^1.4.4:
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==
@@ -10922,6 +11030,11 @@ errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
dependencies:
prr "~1.0.1"
+error-callsites@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/error-callsites/-/error-callsites-2.0.2.tgz#55c17a9490a85d72158563f13dc078851ca05b1e"
+ integrity sha512-s35ELWAKAY9oPqnnfP1V4AnasWV0r2ihaLlpsCGrykZgcR/YKsMXV3q8Ap4Mmp8U90VxJqxKJE5Io0IkkRhJIg==
+
error-ex@^1.2.0, error-ex@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
@@ -12159,6 +12272,18 @@ fast-safe-stringify@^2.0.4:
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2"
integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==
+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==
+
+fast-stream-to-buffer@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fast-stream-to-buffer/-/fast-stream-to-buffer-1.0.0.tgz#793340cc753e7ec9c7fb6d57a53a0b911cb0f588"
+ integrity sha512-bI/544WUQlD2iXBibQbOMSmG07Hay7YrpXlKaeGTPT7H7pC0eitt3usak5vUwEvCGK/O7rUAM3iyQValGU22TQ==
+ dependencies:
+ end-of-stream "^1.4.1"
+
fastparse@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8"
@@ -12842,6 +12967,11 @@ formsy-react@^1.1.5:
form-data-to-object "^0.2.0"
prop-types "^15.5.10"
+forwarded-parse@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/forwarded-parse/-/forwarded-parse-2.1.0.tgz#1ae9d7a4be3af884f74d936d856f7d8c6abd0439"
+ integrity sha512-as9a7Xelt0CvdUy7/qxrY73dZq2vMx49F556fwjjFrUyzq5uHHfeLgD2cCq/6P4ZvusGZzjD6aL2NdgGdS5Cew==
+
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -13234,6 +13364,11 @@ get-own-enumerable-property-symbols@^3.0.0:
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.0.tgz#b877b49a5c16aefac3655f2ed2ea5b684df8d203"
integrity sha512-CIJYJC4GGF06TakLg8z4GQKvDsx9EMspVxOYih7LerEL/WosUnFIww45CGfxfeKHqlg3twgUrYRT1O3WQqjGCg==
+get-own-property-descriptors-polyfill@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/get-own-property-descriptors-polyfill/-/get-own-property-descriptors-polyfill-1.0.1.tgz#e0814a5c32bd9ef387a1de44147f93056a904002"
+ integrity sha512-S1k3UgpTshd171qaPldcr+BY82277tsNI+ETIZLJ/re6KYQYbV4qRtUw5kmHIZlEy4hZvwdzHFn+8xupNVl4YQ==
+
get-port@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
@@ -14953,6 +15088,13 @@ http-errors@1.7.2, http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
+http-headers@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/http-headers/-/http-headers-3.0.2.tgz#5147771292f0b39d6778d930a3a59a76fc7ef44d"
+ integrity sha512-87E1I+2Wg4dxxz4rcxElo3dxO/w1ZtgL1yA0Sb6vH3qU16vRKq1NjWQv9SCY3ly2OQROcoxHZOUpmelS+k6wOw==
+ dependencies:
+ next-line "^1.1.0"
+
http-https@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b"
@@ -14998,6 +15140,14 @@ http-proxy@^1.17.0:
follow-redirects "^1.0.0"
requires-port "^1.0.0"
+http-request-to-url@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/http-request-to-url/-/http-request-to-url-1.0.0.tgz#e56b9418f79f29d344fed05cfe2c56ccb8cc79ac"
+ integrity sha512-YYx0lKXG9+T1fT2q3ZgXLczMI3jW09g9BvIA6L3BG0tFqGm83Ka/+RUZGANRG7Ut/yueD7LPcZQ/+pA5ndNajw==
+ dependencies:
+ await-event "^2.1.0"
+ socket-location "^1.0.0"
+
http-signature@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
@@ -15973,6 +16123,13 @@ is-installed-globally@0.1.0, is-installed-globally@^0.1.0:
global-dirs "^0.1.0"
is-path-inside "^1.0.0"
+is-integer@^1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/is-integer/-/is-integer-1.0.7.tgz#6bde81aacddf78b659b6629d629cadc51a886d5c"
+ integrity sha1-a96Bqs3feLZZtmKdYpytxRqIbVw=
+ dependencies:
+ is-finite "^1.0.0"
+
is-invalid-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34"
@@ -16003,6 +16160,14 @@ is-my-json-valid@^2.10.0:
jsonpointer "^4.0.0"
xtend "^4.0.0"
+is-native@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-native/-/is-native-1.0.1.tgz#cd18cc162e8450d683b5babe79ac99c145449675"
+ integrity sha1-zRjMFi6EUNaDtbq+eayZwUVElnU=
+ dependencies:
+ is-nil "^1.0.0"
+ to-source-code "^1.0.0"
+
is-natural-number@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
@@ -16013,6 +16178,11 @@ is-negated-glob@^1.0.0:
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=
+is-nil@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-nil/-/is-nil-1.0.1.tgz#2daba29e0b585063875e7b539d071f5b15937969"
+ integrity sha1-LauingtYUGOHXntTnQcfWxWTeWk=
+
is-npm@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
@@ -16209,6 +16379,11 @@ is-scoped@^1.0.0:
dependencies:
scoped-regex "^1.0.0"
+is-secret@^1.0.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-secret/-/is-secret-1.2.1.tgz#04b9ca1880ea763049606cfe6c2a08a93f33abe3"
+ integrity sha512-VtBantcgKL2a64fDeCmD1JlkHToh3v0bVOhyJZ5aGTjxtCgrdNcjaC9GaaRFXi19gA4/pYFpnuyoscIgQCFSMQ==
+
is-ssh@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3"
@@ -18033,6 +18208,15 @@ load-json-file@^4.0.0:
pify "^3.0.0"
strip-bom "^3.0.0"
+load-source-map@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/load-source-map/-/load-source-map-1.0.0.tgz#318f49905ce8a709dfb7cc3f16f3efe3bcf1dd05"
+ integrity sha1-MY9JkFzopwnft8w/FvPv47zx3QU=
+ dependencies:
+ in-publish "^2.0.0"
+ semver "^5.3.0"
+ source-map "^0.5.6"
+
loader-runner@^2.3.0, loader-runner@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
@@ -18742,6 +18926,11 @@ mapbox-gl@1.3.1:
tinyqueue "^2.0.0"
vt-pbf "^3.1.1"
+mapcap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/mapcap/-/mapcap-1.0.0.tgz#e8e29d04a160eaf8c92ec4bcbd2c5d07ed037e5a"
+ integrity sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g==
+
marge@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/marge/-/marge-1.0.1.tgz#52d6026911e62e1dd1cf60a07313dde285a8370c"
@@ -18849,6 +19038,24 @@ mdurl@^1.0.1:
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=
+measured-core@^1.51.1:
+ version "1.51.1"
+ resolved "https://registry.yarnpkg.com/measured-core/-/measured-core-1.51.1.tgz#98989705c00bfb0d8a20e665a9f8d6e246a40518"
+ integrity sha512-DZQP9SEwdqqYRvT2slMK81D/7xwdxXosZZBtLVfPSo6y5P672FBTbzHVdN4IQyUkUpcVOR9pIvtUy5Ryl7NKyg==
+ dependencies:
+ binary-search "^1.3.3"
+ optional-js "^2.0.0"
+
+measured-reporting@^1.51.1:
+ version "1.51.1"
+ resolved "https://registry.yarnpkg.com/measured-reporting/-/measured-reporting-1.51.1.tgz#6aeb209ad55edf3940e8afa75c8f97f541216b31"
+ integrity sha512-JCt+2u6XT1I5lG3SuYqywE0e62DJuAzBcfMzWGUhIYtPQV2Vm4HiYt/durqmzsAbZV181CEs+o/jMKWJKkYIWw==
+ dependencies:
+ console-log-level "^1.4.1"
+ mapcap "^1.0.0"
+ measured-core "^1.51.1"
+ optional-js "^2.0.0"
+
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@@ -19506,6 +19713,11 @@ module-definition@^3.0.0, module-definition@^3.1.0:
ast-module-types "^2.4.0"
node-source-walk "^4.0.0"
+module-details-from-path@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b"
+ integrity sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=
+
module-lookup-amd@^6.1.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-6.2.0.tgz#70600008b3f26630fde9ef9ae6165ac69de6ecbb"
@@ -19545,6 +19757,11 @@ monaco-editor@~0.17.0:
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.17.1.tgz#8fbe96ca54bfa75262706e044f8f780e904aa45c"
integrity sha512-JAc0mtW7NeO+0SwPRcdkfDbWLgkqL9WfP1NbpP9wNASsW6oWqgZqNIWt4teymGjZIXTElx3dnQmUYHmVrJ7HxA==
+monitor-event-loop-delay@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/monitor-event-loop-delay/-/monitor-event-loop-delay-1.0.0.tgz#b5ab78165a3bb93f2b275c50d01430c7f155d1f7"
+ integrity sha512-YRIr1exCIfBDLZle8WHOfSo7Xg3M+phcZfq9Fx1L6Abo+atGp7cge5pM7PjyBn4s1oZI/BRD4EMrzQBbPpVb5Q==
+
monocle-ts@^1.0.0:
version "1.7.1"
resolved "https://registry.yarnpkg.com/monocle-ts/-/monocle-ts-1.7.1.tgz#03a615938aa90983a4fa29749969d30f72d80ba1"
@@ -19810,6 +20027,11 @@ newtype-ts@^0.2.4:
fp-ts "^1.0.0"
monocle-ts "^1.0.0"
+next-line@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/next-line/-/next-line-1.1.0.tgz#fcae57853052b6a9bae8208e40dd7d3c2d304603"
+ integrity sha1-/K5XhTBStqm66CCOQN19PC0wRgM=
+
next-tick@1:
version "1.0.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
@@ -20400,11 +20622,23 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
+object-filter-sequence@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/object-filter-sequence/-/object-filter-sequence-1.0.0.tgz#10bb05402fff100082b80d7e83991b10db411692"
+ integrity sha512-CsubGNxhIEChNY4cXYuA6KXafztzHqzLLZ/y3Kasf3A+sa3lL9thq3z+7o0pZqzEinjXT6lXDPAfVWI59dUyzQ==
+
object-hash@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
+object-identity-map@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/object-identity-map/-/object-identity-map-1.0.2.tgz#2b4213a4285ca3a8cd2e696782c9964f887524e7"
+ integrity sha512-a2XZDGyYTngvGS67kWnqVdpoaJWsY7C1GhPJvejWAFCsUioTAaiTu8oBad7c6cI4McZxr4CmvnZeycK05iav5A==
+ dependencies:
+ object.entries "^1.1.0"
+
object-inspect@^1.6.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
@@ -20675,6 +20909,11 @@ optimist@^0.6.1, optimist@~0.6.1:
minimist "~0.0.1"
wordwrap "~0.0.2"
+optional-js@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/optional-js/-/optional-js-2.1.1.tgz#c2dc519ad119648510b4d241dbb60b1167c36a46"
+ integrity sha512-mUS4bDngcD5kKzzRUd1HVQkr9Lzzby3fSrrPR9wOHhQiyYo+hDS5NVli5YQzGjQRQ15k5Sno4xH9pfykJdeEUA==
+
optionator@^0.8.1, optionator@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
@@ -20726,6 +20965,13 @@ ordered-read-streams@^1.0.0:
dependencies:
readable-stream "^2.0.1"
+original-url@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/original-url/-/original-url-1.2.3.tgz#133aff4b2d27e38a98d736f7629c56262b7153e1"
+ integrity sha512-BYm+pKYLtS4mVe/mgT3YKGtWV5HzN/XKiaIu1aK4rsxyjuHeTW9N+xVBEpJcY1onB3nccfH0RbzUEoimMqFUHQ==
+ dependencies:
+ forwarded-parse "^2.1.0"
+
original@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@@ -22533,6 +22779,11 @@ randexp@0.4.6:
discontinuous-range "1.0.0"
ret "~0.1.10"
+random-poly-fill@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/random-poly-fill/-/random-poly-fill-1.0.1.tgz#13634dc0255a31ecf85d4a182d92c40f9bbcf5ed"
+ integrity sha512-bMOL0hLfrNs52+EHtIPIXxn2PxYwXb0qjnKruTjXiM/sKfYqj506aB2plFwWW1HN+ri724bAVVGparh4AtlJKw==
+
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80"
@@ -23512,6 +23763,15 @@ read-pkg-up@^6.0.0:
read-pkg "^5.1.1"
type-fest "^0.5.0"
+read-pkg-up@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.0.tgz#3f3e53858ec5ae5e6fe14bc479da0a7c98f85ff3"
+ integrity sha512-t2ODkS/vTTcRlKwZiZsaLGb5iwfx9Urp924aGzVyboU6+7Z2i6eGr/G1Z4mjvwLLQV3uFOBKobNRGM3ux2PD/w==
+ dependencies:
+ find-up "^4.1.0"
+ read-pkg "^5.2.0"
+ type-fest "^0.8.1"
+
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
@@ -23699,6 +23959,14 @@ recursive-readdir@2.2.2:
dependencies:
minimatch "3.0.4"
+redact-secrets@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/redact-secrets/-/redact-secrets-1.0.0.tgz#60f1db56924fe90a203ba8ccb39283cdbb0d907c"
+ integrity sha1-YPHbVpJP6QogO6jMs5KDzbsNkHw=
+ dependencies:
+ is-secret "^1.0.0"
+ traverse "^0.6.6"
+
redent@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
@@ -23979,6 +24247,11 @@ relateurl@0.2.x:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
+relative-microtime@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/relative-microtime/-/relative-microtime-2.0.0.tgz#cceed2af095ecd72ea32011279c79e5fcc7de29b"
+ integrity sha512-l18ha6HEZc+No/uK4GyAnNxgKW7nvEe35IaeN54sShMojtqik2a6GbTyuiezkjpPaqP874Z3lW5ysBo5irz4NA==
+
relative@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f"
@@ -24269,6 +24542,11 @@ request@^2.87.0:
tunnel-agent "^0.6.0"
uuid "^3.1.0"
+require-ancestors@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/require-ancestors/-/require-ancestors-1.0.0.tgz#807831f8f8081fb12863da81ddb15c8f2a73a004"
+ integrity sha512-Nqeo9Gfp0KvnxTixnxLGEbThMAi+YYgnwRoigtOs1Oo3eGBYfqCd3dagq1vBCVVuc1EnIt3Eu1eGemwOOEZozw==
+
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@@ -24279,6 +24557,15 @@ require-from-string@^2.0.1:
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==
+require-in-the-middle@^5.0.0:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.0.2.tgz#ce3593007a61583b39ccdcd2c167a2a326c670b2"
+ integrity sha512-l2r6F9i6t5xp4OE9cw/daB/ooQKHZOOW1AYPADhEvk/Tj/THJDS8gePp76Zyuht6Cj57a0KL+eHK5Dyv7wZnKA==
+ dependencies:
+ debug "^4.1.1"
+ module-details-from-path "^1.0.3"
+ resolve "^1.12.0"
+
require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
@@ -25075,7 +25362,7 @@ semver@^6.1.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b"
integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==
-semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
+semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -25190,6 +25477,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+set-cookie-serde@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/set-cookie-serde/-/set-cookie-serde-1.0.0.tgz#bcf9c260ed2212ac4005a53eacbaaa37c07ac452"
+ integrity sha512-Vq8e5GsupfJ7okHIvEPcfs5neCo7MZ1ZuWrO3sllYi3DOWt6bSSCpADzqXjz3k0fXehnoFIrmmhty9IN6U6BXQ==
+
set-getter@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376"
@@ -25240,6 +25532,13 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
+shallow-clone-shim@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/shallow-clone-shim/-/shallow-clone-shim-1.1.0.tgz#c1048ba9167f313f4f4c019ff3f0a40626322960"
+ integrity sha512-ZY+sf7fm8CDFecoL/IntHFhqu8Ll+elOcuXO5WlVgSfnpxuUMni/Y9sB9gMf85nWsVDM+CfMJpLBwiN/lOO5/w==
+ dependencies:
+ get-own-property-descriptors-polyfill "^1.0.1"
+
shallow-clone@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060"
@@ -25516,6 +25815,13 @@ sntp@2.x.x:
dependencies:
hoek "4.x.x"
+socket-location@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/socket-location/-/socket-location-1.0.0.tgz#6f0c6f891c9a61c9a750265c14921d12196d266f"
+ integrity sha512-TwxpRM0pPE/3b24XQGLx8zq2J8kOwTy40FtiNC1KrWvl/Tsf7RYXruE9icecMhQwicXMo/HUJlGap8DNt2cgYw==
+ dependencies:
+ await-event "^2.1.0"
+
socket.io-adapter@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
@@ -25851,6 +26157,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+sql-summary@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/sql-summary/-/sql-summary-1.0.1.tgz#a2dddb5435bae294eb11424a7330dc5bafe09c2b"
+ integrity sha512-IpCr2tpnNkP3Jera4ncexsZUp0enJBLr+pHCyTweMUBrbJsTgQeLWx1FXLhoBj/MvcnUQpkgOn2EY8FKOkUzww==
+
squel@^5.13.0:
version "5.13.0"
resolved "https://registry.yarnpkg.com/squel/-/squel-5.13.0.tgz#09cc73e91f0d0e326482605ee76e3b7ac881ddf6"
@@ -25901,6 +26212,17 @@ stack-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
integrity sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=
+stackman@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/stackman/-/stackman-4.0.0.tgz#3ccdc8682fee36373ed2492dc3dad546eb44647d"
+ integrity sha512-JHhUxla4KkXVzPRJoBdIolVbXWBv2qIUe/XdsH9/fkXCgsIdFhCny91tqy9Zld66ROj+dZ0E54l/I3vL3y3Uiw==
+ dependencies:
+ after-all-results "^2.0.0"
+ async-cache "^1.1.0"
+ debug "^4.1.1"
+ error-callsites "^2.0.2"
+ load-source-map "^1.0.0"
+
state-toggle@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.0.tgz#d20f9a616bb4f0c3b98b91922d25b640aa2bc425"
@@ -26024,6 +26346,13 @@ stream-browserify@^2.0.1:
inherits "~2.0.1"
readable-stream "^2.0.2"
+stream-chopper@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/stream-chopper/-/stream-chopper-3.0.1.tgz#73791ae7bf954c297d6683aec178648efc61dd75"
+ integrity sha512-f7h+ly8baAE26iIjcp3VbnBkbIRGtrvV0X0xxFM/d7fwLTYnLzDPTXRKNxa2HZzohOrc96NTrR+FaV3mzOelNA==
+ dependencies:
+ readable-stream "^3.0.6"
+
stream-each@^1.1.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae"
@@ -27250,6 +27579,13 @@ to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
+to-source-code@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/to-source-code/-/to-source-code-1.0.2.tgz#dd136bdb1e1dbd80bbeacf088992678e9070bfea"
+ integrity sha1-3RNr2x4dvYC76s8IiZJnjpBwv+o=
+ dependencies:
+ is-nil "^1.0.0"
+
to-through@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6"
@@ -27317,12 +27653,19 @@ tr46@^1.0.0, tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+traceparent@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/traceparent/-/traceparent-1.0.0.tgz#9b14445cdfe5c19f023f1c04d249c3d8e003a5ce"
+ integrity sha512-b/hAbgx57pANQ6cg2eBguY3oxD6FGVLI1CC2qoi01RmHR7AYpQHPXTig9FkzbWohEsVuHENZHP09aXuw3/LM+w==
+ dependencies:
+ random-poly-fill "^1.0.1"
+
traverse-chain@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/traverse-chain/-/traverse-chain-0.1.0.tgz#61dbc2d53b69ff6091a12a168fd7d433107e40f1"
integrity sha1-YdvC1Ttp/2CRoSoWj9fUMxB+QPE=
-traverse@0.6.6, traverse@~0.6.6:
+traverse@0.6.6, traverse@^0.6.6, traverse@~0.6.6:
version "0.6.6"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=
@@ -27985,6 +28328,11 @@ type-fest@^0.6.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==
+type-fest@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
+ integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
+
type-is@~1.6.15, type-is@~1.6.16:
version "1.6.16"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194"
@@ -28154,6 +28502,14 @@ unherit@^1.0.4:
inherits "^2.0.1"
xtend "^4.0.1"
+unicode-byte-truncate@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unicode-byte-truncate/-/unicode-byte-truncate-1.0.0.tgz#aa6f0f3475193fe20c320ac9213e36e62e8764a7"
+ integrity sha1-qm8PNHUZP+IMMgrJIT425i6HZKc=
+ dependencies:
+ is-integer "^1.0.6"
+ unicode-substring "^0.1.0"
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -28185,6 +28541,11 @@ unicode-property-aliases-ecmascript@^1.0.4:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==
+unicode-substring@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/unicode-substring/-/unicode-substring-0.1.0.tgz#6120ce3c390385dbcd0f60c32b9065c4181d4b36"
+ integrity sha1-YSDOPDkDhdvND2DDK5BlxBgdSzY=
+
unicode-trie@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-0.3.1.tgz#d671dddd89101a08bac37b6a5161010602052085"
From 57865e43ad590e3088a4bf5b1423c09a4f4aa145 Mon Sep 17 00:00:00 2001
From: Dmitry Lemeshko
Date: Wed, 4 Dec 2019 15:24:42 +0100
Subject: [PATCH 09/30] Functional tests: elastic chart provider (#52085)
* adding elastic-chart service
* update visual test
---
test/functional/page_objects/discover_page.js | 4 ++
test/functional/services/elastic_chart.ts | 55 +++++++++++++++++++
test/functional/services/index.ts | 2 +
.../tests/discover/chart_visualization.js | 18 +++---
4 files changed, 70 insertions(+), 9 deletions(-)
create mode 100644 test/functional/services/elastic_chart.ts
diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js
index 8d12b2117a2149..28d7526388b40f 100644
--- a/test/functional/page_objects/discover_page.js
+++ b/test/functional/page_objects/discover_page.js
@@ -31,6 +31,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
const config = getService('config');
const defaultFindTimeout = config.get('timeouts.find');
const comboBox = getService('comboBox');
+ const elasticChart = getService('elasticChart');
class DiscoverPage {
async getQueryField() {
@@ -292,6 +293,9 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
await testSubjects.missingOrFail('filterSelectionPanel', { allowHidden: true });
}
+ async waitForChartLoadingComplete(renderCount) {
+ await elasticChart.waitForRenderingCount('discoverChart', renderCount);
+ }
}
return new DiscoverPage();
diff --git a/test/functional/services/elastic_chart.ts b/test/functional/services/elastic_chart.ts
new file mode 100644
index 00000000000000..4f4dbcba5f0b87
--- /dev/null
+++ b/test/functional/services/elastic_chart.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { FtrProviderContext } from '../ftr_provider_context';
+
+export function ElasticChartProvider({ getService }: FtrProviderContext) {
+ const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+ const log = getService('log');
+
+ class ElasticChart {
+ public async waitForRenderComplete(dataTestSubj: string) {
+ const chart = await testSubjects.find(dataTestSubj);
+ const rendered = await chart.findAllByCssSelector('.echChart[data-ech-render-complete=true]');
+ expect(rendered).to.equal(
+ 1,
+ `Rendering for elastic-chart with data-test-subj='${dataTestSubj}' was not finished in time`
+ );
+ }
+
+ public async getVisualizationRenderingCount(dataTestSubj: string) {
+ const chart = await testSubjects.find(dataTestSubj);
+ const visContainer = await chart.findByCssSelector('.echChart');
+ const renderingCount = await visContainer.getAttribute('data-ech-render-count');
+ return Number(renderingCount);
+ }
+
+ public async waitForRenderingCount(dataTestSubj: string, previousCount = 1) {
+ await retry.waitFor(`rendering count to be equal to [${previousCount + 1}]`, async () => {
+ const currentRenderingCount = await this.getVisualizationRenderingCount(dataTestSubj);
+ log.debug(`-- currentRenderingCount=${currentRenderingCount}`);
+ return currentRenderingCount === previousCount + 1;
+ });
+ }
+ }
+
+ return new ElasticChart();
+}
diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts
index 6098e9931f299f..ea47ccf1d2704c 100644
--- a/test/functional/services/index.ts
+++ b/test/functional/services/index.ts
@@ -31,6 +31,7 @@ import {
// @ts-ignore not TS yet
} from './dashboard';
import { DocTableProvider } from './doc_table';
+import { ElasticChartProvider } from './elastic_chart';
import { EmbeddingProvider } from './embedding';
import { FailureDebuggingProvider } from './failure_debugging';
import { FilterBarProvider } from './filter_bar';
@@ -81,4 +82,5 @@ export const services = {
globalNav: GlobalNavProvider,
toasts: ToastsProvider,
savedQueryManagementComponent: SavedQueryManagementComponentProvider,
+ elasticChart: ElasticChartProvider,
};
diff --git a/test/visual_regression/tests/discover/chart_visualization.js b/test/visual_regression/tests/discover/chart_visualization.js
index c90f29c66acb89..da6b7ff50e8f49 100644
--- a/test/visual_regression/tests/discover/chart_visualization.js
+++ b/test/visual_regression/tests/discover/chart_visualization.js
@@ -27,7 +27,6 @@ export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']);
const visualTesting = getService('visualTesting');
- const find = getService('find');
const defaultSettings = {
defaultIndex: 'logstash-*',
'discover:sampleSize': 1
@@ -54,7 +53,7 @@ export default function ({ getService, getPageObjects }) {
it('should show bars in the correct time zone', async function () {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -64,7 +63,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.setChartInterval('Hourly');
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -74,7 +73,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.setChartInterval('Daily');
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -84,7 +83,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.setChartInterval('Weekly');
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -98,7 +97,7 @@ export default function ({ getService, getPageObjects }) {
});
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -108,7 +107,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.setChartInterval('Monthly');
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -118,7 +117,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.setChartInterval('Yearly');
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -128,7 +127,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
await PageObjects.discover.setChartInterval('Auto');
- await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`);
+ await PageObjects.discover.waitForChartLoadingComplete(++renderCounter);
await visualTesting.snapshot({
show: ['discoverChart'],
});
@@ -143,6 +142,7 @@ export default function ({ getService, getPageObjects }) {
await PageObjects.timePicker.setDefaultAbsoluteRange();
await PageObjects.header.awaitGlobalLoadingIndicatorHidden();
await PageObjects.discover.waitUntilSearchingHasFinished();
+ await PageObjects.discover.waitForChartLoadingComplete(1);
await visualTesting.snapshot({
show: ['discoverChart'],
});
From 686afd7ce0b3195b64f290c26923792e30f072d1 Mon Sep 17 00:00:00 2001
From: Melissa Alvarez
Date: Wed, 4 Dec 2019 08:04:10 -0700
Subject: [PATCH 10/30] [ML] DF Analytics: create classification jobs via the
UI (#51619)
* wip: classification job config in form
* ability to create classification job in form + validation
* ensure classification types are correct for validation
* update reducer test to include jobType validity check
* update analytics jobs help text
* update newJobCapsService to support boolean fields
---
.../data_frame_analytics/common/analytics.ts | 24 +++++++++++++-
.../components/analytics_list/actions.tsx | 9 +++++-
.../create_analytics_form.tsx | 31 ++++++++++++++-----
.../create_analytics_form/job_type.tsx | 15 +++++++--
.../use_create_analytics_form/reducer.test.ts | 3 +-
.../use_create_analytics_form/reducer.ts | 18 ++++++++---
.../hooks/use_create_analytics_form/state.ts | 9 ++++--
.../job_service/new_job_caps/field_service.ts | 1 +
8 files changed, 90 insertions(+), 20 deletions(-)
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
index 33b84bbe1caefa..1d3f26dfd88eca 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts
@@ -31,6 +31,14 @@ interface RegressionAnalysis {
};
}
+interface ClassificationAnalysis {
+ classification: {
+ dependent_variable: string;
+ training_percent?: number;
+ num_top_classes?: string;
+ };
+}
+
export const SEARCH_SIZE = 1000;
export const defaultSearchQuery = {
@@ -77,11 +85,16 @@ interface LoadEvaluateResult {
error: string | null;
}
-type AnalysisConfig = OutlierAnalysis | RegressionAnalysis | GenericAnalysis;
+type AnalysisConfig =
+ | OutlierAnalysis
+ | RegressionAnalysis
+ | ClassificationAnalysis
+ | GenericAnalysis;
export enum ANALYSIS_CONFIG_TYPE {
OUTLIER_DETECTION = 'outlier_detection',
REGRESSION = 'regression',
+ CLASSIFICATION = 'classification',
UNKNOWN = 'unknown',
}
@@ -100,6 +113,10 @@ export const getDependentVar = (analysis: AnalysisConfig) => {
if (isRegressionAnalysis(analysis)) {
depVar = analysis.regression.dependent_variable;
}
+
+ if (isClassificationAnalysis(analysis)) {
+ depVar = analysis.classification.dependent_variable;
+ }
return depVar;
};
@@ -132,6 +149,11 @@ export const isRegressionAnalysis = (arg: any): arg is RegressionAnalysis => {
return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.REGRESSION;
};
+export const isClassificationAnalysis = (arg: any): arg is ClassificationAnalysis => {
+ const keys = Object.keys(arg);
+ return keys.length === 1 && keys[0] === ANALYSIS_CONFIG_TYPE.CLASSIFICATION;
+};
+
export const isRegressionResultsSearchBoolQuery = (
arg: any
): arg is RegressionResultsSearchBoolQuery => {
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
index 5e5283f9e6c49b..f3da4839d4b977 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
@@ -13,7 +13,11 @@ import {
createPermissionFailureMessage,
} from '../../../../../privilege/check_privilege';
-import { getAnalysisType } from '../../../../common/analytics';
+import {
+ getAnalysisType,
+ isRegressionAnalysis,
+ isOutlierAnalysis,
+} from '../../../../common/analytics';
import { getResultsUrl, isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common';
import { stopAnalytics } from '../../services/analytics_service';
@@ -26,10 +30,13 @@ export const AnalyticsViewAction = {
render: (item: DataFrameAnalyticsListRow) => {
const analysisType = getAnalysisType(item.config.analysis);
const jobStatus = item.stats.state;
+ const isDisabled =
+ !isRegressionAnalysis(item.config.analysis) && !isOutlierAnalysis(item.config.analysis);
const url = getResultsUrl(item.id, analysisType, jobStatus);
return (
(window.location.href = url)}
size="xs"
color="text"
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx
index 9cbdc457e75cae..e478087c2a8cc8 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx
@@ -54,6 +54,8 @@ const NUMERICAL_FIELD_TYPES = new Set([
'scaled_float',
]);
+const SUPPORTED_CLASSIFICATION_FIELD_TYPES = new Set(['boolean', 'text', 'keyword', 'ip']);
+
// List of system fields we want to ignore for the numeric field check.
const OMIT_FIELDS: string[] = ['_source', '_type', '_index', '_id', '_version', '_score'];
@@ -112,6 +114,18 @@ export const CreateAnalyticsForm: FC = ({ actions, sta
}
};
+ // Regression supports numeric fields. Classification supports numeric, boolean, text, keyword and ip.
+ const shouldAddFieldOption = (field: Field) => {
+ if (field.id === EVENT_RATE_FIELD_ID) return false;
+
+ const isNumerical = NUMERICAL_FIELD_TYPES.has(field.type);
+ const isSupportedByClassification =
+ isNumerical || SUPPORTED_CLASSIFICATION_FIELD_TYPES.has(field.type);
+
+ if (jobType === JOB_TYPES.REGRESSION) return isNumerical;
+ if (jobType === JOB_TYPES.CLASSIFICATION) return isNumerical || isSupportedByClassification;
+ };
+
const debouncedMmlEstimateLoad = debounce(async () => {
try {
const jobConfig = getJobConfigFromFormState(form);
@@ -146,12 +160,12 @@ export const CreateAnalyticsForm: FC = ({ actions, sta
if (indexPattern !== undefined) {
await newJobCapsService.initializeFromIndexPattern(indexPattern);
- // Get fields and filter for numeric
+ // Get fields and filter for supported types for job type
const { fields } = newJobCapsService;
const options: Array<{ label: string }> = [];
fields.forEach((field: Field) => {
- if (NUMERICAL_FIELD_TYPES.has(field.type) && field.id !== EVENT_RATE_FIELD_ID) {
+ if (shouldAddFieldOption(field)) {
options.push({ label: field.id });
}
});
@@ -196,7 +210,10 @@ export const CreateAnalyticsForm: FC = ({ actions, sta
};
useEffect(() => {
- if (jobType === JOB_TYPES.REGRESSION && sourceIndexNameEmpty === false) {
+ if (
+ (jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) &&
+ sourceIndexNameEmpty === false
+ ) {
loadDependentFieldOptions();
} else if (jobType === JOB_TYPES.OUTLIER_DETECTION && sourceIndexNameEmpty === false) {
validateSourceIndexFields();
@@ -206,11 +223,11 @@ export const CreateAnalyticsForm: FC = ({ actions, sta
useEffect(() => {
const hasBasicRequiredFields =
jobType !== undefined && sourceIndex !== '' && sourceIndexNameValid === true;
+ const jobTypesWithDepVar =
+ jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION;
const hasRequiredAnalysisFields =
- (jobType === JOB_TYPES.REGRESSION &&
- dependentVariable !== '' &&
- trainingPercent !== undefined) ||
+ (jobTypesWithDepVar && dependentVariable !== '' && trainingPercent !== undefined) ||
jobType === JOB_TYPES.OUTLIER_DETECTION;
if (hasBasicRequiredFields && hasRequiredAnalysisFields) {
@@ -406,7 +423,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta
isInvalid={!destinationIndexNameEmpty && !destinationIndexNameValid}
/>
- {jobType === JOB_TYPES.REGRESSION && (
+ {(jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) && (
>;
}
export const JobType: FC = ({ type, setFormState }) => {
@@ -21,7 +21,7 @@ export const JobType: FC = ({ type, setFormState }) => {
'xpack.ml.dataframe.analytics.create.outlierDetectionHelpText',
{
defaultMessage:
- 'Outlier detection jobs require a source index that is mapped as a table-like data structure and will only analyze numeric and boolean fields. Please use the advanced editor to apply custom options such as the model memory limit and analysis type.',
+ 'Outlier detection jobs require a source index that is mapped as a table-like data structure and will only analyze numeric and boolean fields. Please use the advanced editor to add custom options to the configuration.',
}
);
@@ -29,13 +29,22 @@ export const JobType: FC = ({ type, setFormState }) => {
'xpack.ml.dataframe.analytics.create.outlierRegressionHelpText',
{
defaultMessage:
- 'Regression jobs will only analyze numeric fields. Please use the advanced editor to apply custom options such as the model memory limit and prediction field name.',
+ 'Regression jobs will only analyze numeric fields. Please use the advanced editor to apply custom options such as the prediction field name.',
+ }
+ );
+
+ const classificationHelpText = i18n.translate(
+ 'xpack.ml.dataframe.analytics.create.classificationHelpText',
+ {
+ defaultMessage:
+ 'Classification jobs require a source index that is mapped as a table-like data structure and supports fields that are numeric, boolean, text, keyword or ip. Please use the advanced editor to apply custom options such as the prediction field name.',
}
);
const helpText = {
outlier_detection: outlierHelpText,
regression: regressionHelpText,
+ classification: classificationHelpText,
};
return (
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
index cabf0946ce8713..7db9420396a9a8 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts
@@ -10,7 +10,7 @@ import { DataFrameAnalyticsConfig } from '../../../../common';
import { ACTION } from './actions';
import { reducer, validateAdvancedEditor } from './reducer';
-import { getInitialState } from './state';
+import { getInitialState, JOB_TYPES } from './state';
jest.mock('ui/index_patterns', () => ({
validateIndexPattern: () => true,
@@ -51,6 +51,7 @@ describe('useCreateAnalyticsForm', () => {
destinationIndex: 'the-destination-index',
jobId: 'the-analytics-job-id',
sourceIndex: 'the-source-index',
+ jobType: JOB_TYPES.OUTLIER_DETECTION,
},
});
expect(updatedState.isValid).toBe(true);
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
index 56d09169a3c390..918c42f480e1e5 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts
@@ -22,7 +22,11 @@ import {
JOB_ID_MAX_LENGTH,
ALLOWED_DATA_UNITS,
} from '../../../../../../../common/constants/validation';
-import { getDependentVar, isRegressionAnalysis } from '../../../../common/analytics';
+import {
+ getDependentVar,
+ isRegressionAnalysis,
+ isClassificationAnalysis,
+} from '../../../../common/analytics';
const mmlAllowedUnitsStr = `${ALLOWED_DATA_UNITS.slice(0, ALLOWED_DATA_UNITS.length - 1).join(
', '
@@ -53,7 +57,7 @@ const getSourceIndexString = (state: State) => {
};
export const validateAdvancedEditor = (state: State): State => {
- const { jobIdEmpty, jobIdValid, jobIdExists, jobType, createIndexPattern } = state.form;
+ const { jobIdEmpty, jobIdValid, jobIdExists, createIndexPattern } = state.form;
const { jobConfig } = state;
state.advancedEditorMessages = [];
@@ -89,9 +93,9 @@ export const validateAdvancedEditor = (state: State): State => {
}
let dependentVariableEmpty = false;
- if (isRegressionAnalysis(jobConfig.analysis)) {
+ if (isRegressionAnalysis(jobConfig.analysis) || isClassificationAnalysis(jobConfig.analysis)) {
const dependentVariableName = getDependentVar(jobConfig.analysis) || '';
- dependentVariableEmpty = jobType === JOB_TYPES.REGRESSION && dependentVariableName === '';
+ dependentVariableEmpty = dependentVariableName === '';
}
if (sourceIndexNameEmpty) {
@@ -201,7 +205,10 @@ const validateForm = (state: State): State => {
modelMemoryLimit,
} = state.form;
- const dependentVariableEmpty = jobType === JOB_TYPES.REGRESSION && dependentVariable === '';
+ const jobTypeEmpty = jobType === undefined;
+ const dependentVariableEmpty =
+ (jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) &&
+ dependentVariable === '';
const modelMemoryLimitEmpty = modelMemoryLimit === '';
if (!modelMemoryLimitEmpty && modelMemoryLimit !== undefined) {
@@ -210,6 +217,7 @@ const validateForm = (state: State): State => {
}
state.isValid =
+ !jobTypeEmpty &&
state.form.modelMemoryLimitUnitValid &&
!jobIdEmpty &&
jobIdValid &&
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
index f911b5a45e158c..64bd3681afce92 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts
@@ -14,6 +14,7 @@ export enum DEFAULT_MODEL_MEMORY_LIMIT {
regression = '100mb',
// eslint-disable-next-line @typescript-eslint/camelcase
outlier_detection = '50mb',
+ classification = '100mb',
}
export type EsIndexName = string;
@@ -34,6 +35,7 @@ export interface FormMessage {
export enum JOB_TYPES {
OUTLIER_DETECTION = 'outlier_detection',
REGRESSION = 'regression',
+ CLASSIFICATION = 'classification',
}
export interface State {
@@ -149,9 +151,12 @@ export const getJobConfigFromFormState = (
model_memory_limit: formState.modelMemoryLimit,
};
- if (formState.jobType === JOB_TYPES.REGRESSION) {
+ if (
+ formState.jobType === JOB_TYPES.REGRESSION ||
+ formState.jobType === JOB_TYPES.CLASSIFICATION
+ ) {
jobConfig.analysis = {
- regression: {
+ [formState.jobType]: {
dependent_variable: formState.dependentVariable,
training_percent: formState.trainingPercent,
},
diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
index af4d869274d73a..3cfb5521890628 100644
--- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
+++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job_caps/field_service.ts
@@ -33,6 +33,7 @@ const supportedTypes: string[] = [
ES_FIELD_TYPES.IP,
ES_FIELD_TYPES.GEO_POINT,
ES_FIELD_TYPES.GEO_SHAPE,
+ ES_FIELD_TYPES.BOOLEAN,
];
export function fieldServiceProvider(
From 43b97d8a056db70c997f7b73c4e98da34af8d786 Mon Sep 17 00:00:00 2001
From: Thomas Watson
Date: Wed, 4 Dec 2019 16:17:19 +0100
Subject: [PATCH 11/30] Clean up uses of deprecated API's in node core (#51431)
Ensure no deprecated Node.js core API's are used in Kibana. This is
achieved by throwing an error in either development mode or in CI if one
of the deprecated API's is called, and as such, new PR's should no
longer be able to be merged if they use deprecated API's.
Some of these API's (like the `Buffer` constructor`) is a security risk.
---
package.json | 5 +-
.../kbn-babel-code-parser/src/strategies.js | 17 +-
.../tasks/build/rewrite_package_json.js | 12 +-
.../http/integration_tests/router.test.ts | 2 +-
src/dev/ci_setup/setup_env.sh | 2 +
.../__tests__/proxy_config_collection.js | 2 +-
.../__jest__/step_time_field.test.js | 1 +
.../usage/telemetry_usage_collector.test.ts | 2 +-
.../series_functions/__tests__/yaxis.js | 8 +-
src/legacy/server/sass/build.js | 38 +-
.../ui/public/chrome/__mocks__/index.js | 50 +++
.../utils/deep_clone_with_buffers.test.ts | 4 +-
src/legacy/utils/deep_clone_with_buffers.ts | 2 +-
.../dynamic_dll_plugin/dll_compiler.js | 11 +-
.../actions/replace_panel_action.test.tsx | 23 +-
.../embeddable/grid/dashboard_grid.test.tsx | 21 +-
.../viewport/dashboard_viewport.test.tsx | 8 +-
.../query_string_input.test.tsx.snap | 408 ++++++++++++++++++
.../query_bar_top_row.test.tsx | 4 +-
.../query_string_input.test.tsx | 2 +
.../saved_object_finder.test.tsx | 2 +
utilities/visual_regression.js | 34 +-
.../__test__/createErrorGroupWatch.test.ts | 2 +-
.../index_management/__mocks__/ui/notify.js | 2 +
.../public/components/report_info_button.tsx | 1 -
.../server/lib/esqueue/__tests__/worker.js | 24 +-
.../index_privileges.test.tsx.snap | 2 +-
.../privileges/es/index_privileges.test.tsx | 14 +-
.../privileges/es/index_privileges.tsx | 4 +-
.../__tests__/get_xpack.js | 14 +-
x-pack/package.json | 2 +-
.../authentication/authenticator.test.ts | 32 +-
yarn.lock | 90 +---
33 files changed, 638 insertions(+), 207 deletions(-)
diff --git a/package.json b/package.json
index 98ba41772ed60a..be8ef669e76c0e 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
"typespec": "typings-tester --config x-pack/legacy/plugins/canvas/public/lib/aeroelastic/tsconfig.json x-pack/legacy/plugins/canvas/public/lib/aeroelastic/__fixtures__/typescript/typespec_tests.ts",
"checkLicenses": "node scripts/check_licenses --dev",
"build": "node scripts/build --all-platforms",
- "start": "node --trace-warnings --trace-deprecation scripts/kibana --dev ",
+ "start": "node --trace-warnings --throw-deprecation scripts/kibana --dev",
"debug": "node --nolazy --inspect scripts/kibana --dev",
"debug-break": "node --nolazy --inspect-brk scripts/kibana --dev",
"karma": "karma start",
@@ -406,7 +406,6 @@
"gulp-sourcemaps": "2.6.5",
"has-ansi": "^3.0.0",
"iedriver": "^3.14.1",
- "image-diff": "1.6.3",
"intl-messageformat-parser": "^1.4.0",
"is-path-inside": "^2.1.0",
"istanbul-instrumenter-loader": "3.0.1",
@@ -434,7 +433,7 @@
"node-sass": "^4.9.4",
"normalize-path": "^3.0.0",
"nyc": "^14.1.1",
- "pixelmatch": "4.0.2",
+ "pixelmatch": "^5.1.0",
"pkg-up": "^2.0.0",
"pngjs": "^3.4.0",
"postcss": "^7.0.5",
diff --git a/packages/kbn-babel-code-parser/src/strategies.js b/packages/kbn-babel-code-parser/src/strategies.js
index 89621bc53bd534..f116abde9e0e6d 100644
--- a/packages/kbn-babel-code-parser/src/strategies.js
+++ b/packages/kbn-babel-code-parser/src/strategies.js
@@ -20,6 +20,7 @@
import { canRequire } from './can_require';
import { dependenciesVisitorsGenerator } from './visitors';
import { dirname, isAbsolute, resolve } from 'path';
+import { builtinModules } from 'module';
export function _calculateTopLevelDependency(inputDep, outputDep = '') {
// The path separator will be always the forward slash
@@ -48,14 +49,18 @@ export function _calculateTopLevelDependency(inputDep, outputDep = '') {
return _calculateTopLevelDependency(depSplitPaths.join(pathSeparator), outputDep);
}
-export async function dependenciesParseStrategy(cwd, parseSingleFile, mainEntry, wasParsed, results) {
- // Retrieve native nodeJS modules
- const natives = process.binding('natives');
-
+export async function dependenciesParseStrategy(
+ cwd,
+ parseSingleFile,
+ mainEntry,
+ wasParsed,
+ results
+) {
// Get dependencies from a single file and filter
// out node native modules from the result
- const dependencies = (await parseSingleFile(mainEntry, dependenciesVisitorsGenerator))
- .filter(dep => !natives[dep]);
+ const dependencies = (await parseSingleFile(mainEntry, dependenciesVisitorsGenerator)).filter(
+ dep => !builtinModules.includes(dep)
+ );
// Return the list of all the new entries found into
// the current mainEntry that we could use to look for
diff --git a/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js b/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js
index db33b209951ebd..64656baee6fd2b 100644
--- a/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js
+++ b/packages/kbn-plugin-helpers/tasks/build/rewrite_package_json.js
@@ -41,19 +41,9 @@ module.exports = function rewritePackage(buildSource, buildVersion, kibanaVersio
delete pkg.scripts;
delete pkg.devDependencies;
- file.contents = toBuffer(JSON.stringify(pkg, null, 2));
+ file.contents = Buffer.from(JSON.stringify(pkg, null, 2));
}
return file;
});
};
-
-function toBuffer(string) {
- if (typeof Buffer.from === 'function') {
- return Buffer.from(string, 'utf8');
- } else {
- // this was deprecated in node v5 in favor
- // of Buffer.from(string, encoding)
- return new Buffer(string, 'utf8');
- }
-}
diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts
index 7d95110b98a129..6117190c57ba89 100644
--- a/src/core/server/http/integration_tests/router.test.ts
+++ b/src/core/server/http/integration_tests/router.test.ts
@@ -329,7 +329,7 @@ describe('Response factory', () => {
const router = createRouter('/');
router.get({ path: '/', validate: false }, (context, req, res) => {
- const buffer = new Buffer('abc');
+ const buffer = Buffer.from('abc');
return res.ok({
body: buffer,
diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh
index 6cfcaca5843b3e..e5edf5bd9c2609 100644
--- a/src/dev/ci_setup/setup_env.sh
+++ b/src/dev/ci_setup/setup_env.sh
@@ -14,6 +14,8 @@ cacheDir="$HOME/.kibana"
RED='\033[0;31m'
C_RESET='\033[0m' # Reset color
+export NODE_OPTIONS="$NODE_OPTIONS --throw-deprecation"
+
###
### Since the Jenkins logging output collector doesn't look like a TTY
### Node/Chalk and other color libs disable their color output. But Jenkins
diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js b/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js
index 91797f897d8ba4..e575b0f707aadd 100644
--- a/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js
+++ b/src/legacy/core_plugins/console/server/__tests__/proxy_config_collection.js
@@ -28,7 +28,7 @@ import { ProxyConfigCollection } from '../proxy_config_collection';
describe('ProxyConfigCollection', function () {
beforeEach(function () {
- sinon.stub(fs, 'readFileSync').callsFake(() => new Buffer(0));
+ sinon.stub(fs, 'readFileSync').callsFake(() => Buffer.alloc(0));
});
afterEach(function () {
diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js
index 5677d2293a725c..1f9d80440f6cbc 100644
--- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js
+++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/__jest__/step_time_field.test.js
@@ -43,6 +43,7 @@ const indexPatternsService = {
make: async () => ({
fieldsFetcher: {
fetch: noop,
+ fetchForWildcard: noop,
},
}),
};
diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
index 2b2e946198e0a8..cf6059faf0c056 100644
--- a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
+++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts
@@ -75,7 +75,7 @@ describe('telemetry_usage_collector', () => {
// empty
writeFileSync(tempFiles.empty, '');
// 1 byte too big
- writeFileSync(tempFiles.too_big, new Buffer(MAX_FILE_SIZE + 1));
+ writeFileSync(tempFiles.too_big, Buffer.alloc(MAX_FILE_SIZE + 1));
// write-only file
writeFileSync(tempFiles.unreadable, 'valid: true', { mode: 0o222 });
// valid
diff --git a/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js b/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js
index ebc187100c7e18..2aa4b9a471c482 100644
--- a/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js
+++ b/src/legacy/core_plugins/timelion/server/series_functions/__tests__/yaxis.js
@@ -95,16 +95,16 @@ describe('yaxis.js', () => {
it('throws an error if currency is not three letter code', () => {
invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:abcde']).catch(e => {
- expect(e).to.be.an(Error);
+ expect(e).to.be.an.instanceof(Error);
});
invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:12']).catch(e => {
- expect(e).to.be.an(Error);
+ expect(e).to.be.an.instanceof(Error);
});
invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:$#']).catch(e => {
- expect(e).to.be.an(Error);
+ expect(e).to.be.an.instanceof(Error);
});
invoke(fn, [seriesList, 1, null, null, null, null, null, 'currency:ab']).catch(e => {
- expect(e).to.be.an(Error);
+ expect(e).to.be.an.instanceof(Error);
});
});
diff --git a/src/legacy/server/sass/build.js b/src/legacy/server/sass/build.js
index db1b26e1028348..2872ef5daa2004 100644
--- a/src/legacy/server/sass/build.js
+++ b/src/legacy/server/sass/build.js
@@ -30,7 +30,7 @@ import { PUBLIC_PATH_PLACEHOLDER } from '../../../optimize/public_path_placehold
const renderSass = promisify(sass.render);
const writeFile = promisify(fs.writeFile);
-const exists = promisify(fs.exists);
+const access = promisify(fs.access);
const copyFile = promisify(fs.copyFile);
const mkdirAsync = promisify(fs.mkdir);
@@ -145,23 +145,27 @@ export class Build {
];
// verify that asset sources exist and import is valid before writing anything
- await Promise.all(urlAssets.map(async (asset) => {
- if (!await exists(asset.path)) {
- throw this._makeError(
- 'Invalid url() in css output',
- `url("${asset.requestUrl}") resolves to "${asset.path}", which does not exist.\n` +
- ` Make sure that the request is relative to "${asset.root}"`
- );
- }
+ await Promise.all(
+ urlAssets.map(async asset => {
+ try {
+ await access(asset.path);
+ } catch (e) {
+ throw this._makeError(
+ 'Invalid url() in css output',
+ `url("${asset.requestUrl}") resolves to "${asset.path}", which does not exist.\n` +
+ ` Make sure that the request is relative to "${asset.root}"`
+ );
+ }
- if (!isPathInside(asset.path, asset.boundry)) {
- throw this._makeError(
- 'Invalid url() in css output',
- `url("${asset.requestUrl}") resolves to "${asset.path}"\n` +
- ` which is outside of "${asset.boundry}"`
- );
- }
- }));
+ if (!isPathInside(asset.path, asset.boundry)) {
+ throw this._makeError(
+ 'Invalid url() in css output',
+ `url("${asset.requestUrl}") resolves to "${asset.path}"\n` +
+ ` which is outside of "${asset.boundry}"`
+ );
+ }
+ })
+ );
// write css
await mkdirAsync(this.targetDir, { recursive: true });
diff --git a/src/legacy/ui/public/chrome/__mocks__/index.js b/src/legacy/ui/public/chrome/__mocks__/index.js
index e8149970002a13..0d3e580d4b4f08 100644
--- a/src/legacy/ui/public/chrome/__mocks__/index.js
+++ b/src/legacy/ui/public/chrome/__mocks__/index.js
@@ -40,3 +40,53 @@ const chrome = {
// eslint-disable-next-line import/no-default-export
export default chrome;
+
+// Copied from `src/legacy/ui/public/chrome/chrome.js`
+import _ from 'lodash';
+import angular from 'angular';
+import { metadata } from '../../metadata';
+
+const internals = _.defaults(
+ _.cloneDeep(metadata),
+ {
+ basePath: '',
+ rootController: null,
+ rootTemplate: null,
+ showAppsLink: null,
+ xsrfToken: null,
+ devMode: true,
+ brand: null,
+ nav: [],
+ applicationClasses: []
+ }
+);
+
+const waitForBootstrap = new Promise(resolve => {
+ chrome.bootstrap = function (targetDomElement) {
+ // import chrome nav controls and hacks now so that they are executed after
+ // everything else, can safely import the chrome, and interact with services
+ // and such setup by all other modules
+ require('uiExports/chromeNavControls');
+ require('uiExports/hacks');
+
+ // sets attribute on body for stylesheet sandboxing
+ document.body.setAttribute('id', `${internals.app.id}-app`);
+
+ chrome.setupAngular();
+ targetDomElement.setAttribute('kbn-chrome', 'true');
+ targetDomElement.setAttribute('ng-class', '{ \'hidden-chrome\': !chrome.getVisible() }');
+ targetDomElement.className = 'app-wrapper';
+ angular.bootstrap(targetDomElement, ['kibana']);
+ resolve(targetDomElement);
+ };
+});
+
+chrome.dangerouslyGetActiveInjector = () => {
+ return waitForBootstrap.then((targetDomElement) => {
+ const $injector = angular.element(targetDomElement).injector();
+ if (!$injector) {
+ return Promise.reject('targetDomElement had no angular context after bootstrapping');
+ }
+ return $injector;
+ });
+};
diff --git a/src/legacy/utils/deep_clone_with_buffers.test.ts b/src/legacy/utils/deep_clone_with_buffers.test.ts
index 8fdf1ae7bfd98e..7a0906a715c2e1 100644
--- a/src/legacy/utils/deep_clone_with_buffers.test.ts
+++ b/src/legacy/utils/deep_clone_with_buffers.test.ts
@@ -52,7 +52,7 @@ describe('deepCloneWithBuffers()', () => {
});
it('copies buffers but keeps them buffers', () => {
- const input = new Buffer('i am a teapot', 'utf8');
+ const input = Buffer.from('i am a teapot', 'utf8');
const output = deepCloneWithBuffers(input);
expect(Buffer.isBuffer(input)).toBe(true);
@@ -65,7 +65,7 @@ describe('deepCloneWithBuffers()', () => {
const input = {
a: {
b: {
- c: new Buffer('i am a teapot', 'utf8'),
+ c: Buffer.from('i am a teapot', 'utf8'),
},
},
};
diff --git a/src/legacy/utils/deep_clone_with_buffers.ts b/src/legacy/utils/deep_clone_with_buffers.ts
index 6938b9371435e7..2e9120eb32b7c8 100644
--- a/src/legacy/utils/deep_clone_with_buffers.ts
+++ b/src/legacy/utils/deep_clone_with_buffers.ts
@@ -24,7 +24,7 @@ import { cloneDeep } from 'lodash';
// type of the customizer function doesn't expect that.
function cloneBuffersCustomizer(val: unknown): any {
if (Buffer.isBuffer(val)) {
- return new Buffer(val);
+ return Buffer.from(val);
}
}
diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js
index 3f3bb3e4e196c7..42543655151138 100644
--- a/src/optimize/dynamic_dll_plugin/dll_compiler.js
+++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js
@@ -29,7 +29,7 @@ import del from 'del';
const readFileAsync = promisify(fs.readFile);
const mkdirAsync = promisify(fs.mkdir);
-const existsAsync = promisify(fs.exists);
+const accessAsync = promisify(fs.access);
const writeFileAsync = promisify(fs.writeFile);
export class DllCompiler {
@@ -127,13 +127,14 @@ export class DllCompiler {
}
async ensurePathExists(filePath) {
- const exists = await existsAsync(filePath);
-
- if (!exists) {
+ try {
+ await accessAsync(filePath);
+ } catch (e) {
await mkdirAsync(path.dirname(filePath), { recursive: true });
+ return false;
}
- return exists;
+ return true;
}
async ensureOutputPathExists() {
diff --git a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx
index de29e1dec85a86..4438a6c9971261 100644
--- a/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/actions/replace_panel_action.test.tsx
@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-
import { isErrorEmbeddable, EmbeddableFactory } from '../embeddable_plugin';
import { ReplacePanelAction } from './replace_panel_action';
import { DashboardContainer } from '../embeddable';
@@ -29,6 +28,8 @@ import {
ContactCardEmbeddableOutput,
} from '../embeddable_plugin_test_samples';
import { DashboardOptions } from '../embeddable/dashboard_container_factory';
+import { coreMock } from '../../../../core/public/mocks';
+import { CoreStart } from 'kibana/public';
const embeddableFactories = new Map();
embeddableFactories.set(
@@ -39,8 +40,9 @@ const getEmbeddableFactories = () => embeddableFactories.values();
let container: DashboardContainer;
let embeddable: ContactCardEmbeddable;
-
+let coreStart: CoreStart;
beforeEach(async () => {
+ coreStart = coreMock.createStart();
const options: DashboardOptions = {
ExitFullScreenButton: () => null,
SavedObjectFinder: () => null,
@@ -50,7 +52,7 @@ beforeEach(async () => {
} as any,
inspector: {} as any,
notifications: {} as any,
- overlays: {} as any,
+ overlays: coreStart.overlays,
savedObjectMetaData: {} as any,
uiActions: {} as any,
};
@@ -80,11 +82,10 @@ beforeEach(async () => {
});
test('Executes the replace panel action', async () => {
- let core: any;
let SavedObjectFinder: any;
let notifications: any;
const action = new ReplacePanelAction(
- core,
+ coreStart,
SavedObjectFinder,
notifications,
getEmbeddableFactories
@@ -93,11 +94,10 @@ test('Executes the replace panel action', async () => {
});
test('Is not compatible when embeddable is not in a dashboard container', async () => {
- let core: any;
let SavedObjectFinder: any;
let notifications: any;
const action = new ReplacePanelAction(
- core,
+ coreStart,
SavedObjectFinder,
notifications,
getEmbeddableFactories
@@ -113,11 +113,10 @@ test('Is not compatible when embeddable is not in a dashboard container', async
});
test('Execute throws an error when called with an embeddable not in a parent', async () => {
- let core: any;
let SavedObjectFinder: any;
let notifications: any;
const action = new ReplacePanelAction(
- core,
+ coreStart,
SavedObjectFinder,
notifications,
getEmbeddableFactories
@@ -129,11 +128,10 @@ test('Execute throws an error when called with an embeddable not in a parent', a
});
test('Returns title', async () => {
- let core: any;
let SavedObjectFinder: any;
let notifications: any;
const action = new ReplacePanelAction(
- core,
+ coreStart,
SavedObjectFinder,
notifications,
getEmbeddableFactories
@@ -142,11 +140,10 @@ test('Returns title', async () => {
});
test('Returns an icon', async () => {
- let core: any;
let SavedObjectFinder: any;
let notifications: any;
const action = new ReplacePanelAction(
- core,
+ coreStart,
SavedObjectFinder,
notifications,
getEmbeddableFactories
diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx
index e4338dc89153d3..c1a3d88979f490 100644
--- a/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/embeddable/grid/dashboard_grid.test.tsx
@@ -21,7 +21,7 @@
import sizeMe from 'react-sizeme';
import React from 'react';
-import { nextTick, mountWithIntl } from 'test_utils/enzyme_helpers';
+import { mountWithIntl } from 'test_utils/enzyme_helpers';
import { skip } from 'rxjs/operators';
import { EmbeddableFactory, GetEmbeddableFactory } from '../../embeddable_plugin';
import { DashboardGrid, DashboardGridProps } from './dashboard_grid';
@@ -65,10 +65,14 @@ function prepare(props?: Partial) {
} as any,
notifications: {} as any,
overlays: {} as any,
- inspector: {} as any,
+ inspector: {
+ isAvailable: jest.fn(),
+ } as any,
SavedObjectFinder: () => null,
ExitFullScreenButton: () => null,
- uiActions: {} as any,
+ uiActions: {
+ getTriggerCompatibleActions: (() => []) as any,
+ } as any,
};
dashboardContainer = new DashboardContainer(initialInput, options);
const defaultTestProps: DashboardGridProps = {
@@ -100,12 +104,11 @@ test('renders DashboardGrid', () => {
);
-
const panelElements = component.find('EmbeddableChildPanel');
expect(panelElements.length).toBe(2);
});
-test('renders DashboardGrid with no visualizations', async () => {
+test('renders DashboardGrid with no visualizations', () => {
const { props, options } = prepare();
const component = mountWithIntl(
@@ -114,12 +117,11 @@ test('renders DashboardGrid with no visualizations', async () => {
);
props.container.updateInput({ panels: {} });
- await nextTick();
component.update();
expect(component.find('EmbeddableChildPanel').length).toBe(0);
});
-test('DashboardGrid removes panel when removed from container', async () => {
+test('DashboardGrid removes panel when removed from container', () => {
const { props, options } = prepare();
const component = mountWithIntl(
@@ -131,13 +133,12 @@ test('DashboardGrid removes panel when removed from container', async () => {
const filteredPanels = { ...originalPanels };
delete filteredPanels['1'];
props.container.updateInput({ panels: filteredPanels });
- await nextTick();
component.update();
const panelElements = component.find('EmbeddableChildPanel');
expect(panelElements.length).toBe(1);
});
-test('DashboardGrid renders expanded panel', async () => {
+test('DashboardGrid renders expanded panel', () => {
const { props, options } = prepare();
const component = mountWithIntl(
@@ -146,7 +147,6 @@ test('DashboardGrid renders expanded panel', async () => {
);
props.container.updateInput({ expandedPanelId: '1' });
- await nextTick();
component.update();
// Both panels should still exist in the dom, so nothing needs to be re-fetched once minimized.
expect(component.find('EmbeddableChildPanel').length).toBe(2);
@@ -156,7 +156,6 @@ test('DashboardGrid renders expanded panel', async () => {
).toBe('1');
props.container.updateInput({ expandedPanelId: undefined });
- await nextTick();
component.update();
expect(component.find('EmbeddableChildPanel').length).toBe(2);
diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx
index 7b83407bf8063c..a2f7b8dc28fb05 100644
--- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx
+++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/dashboard_viewport.test.tsx
@@ -56,10 +56,14 @@ function getProps(
} as any,
notifications: {} as any,
overlays: {} as any,
- inspector: {} as any,
+ inspector: {
+ isAvailable: jest.fn(),
+ } as any,
SavedObjectFinder: () => null,
ExitFullScreenButton,
- uiActions: {} as any,
+ uiActions: {
+ getTriggerCompatibleActions: (() => []) as any,
+ } as any,
};
const input = getSampleDashboardInput({
diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap
index 61aac70b4a7ecb..4a5b0bed5e819a 100644
--- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap
+++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap
@@ -148,6 +148,74 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
"setIsCollapsed": [MockFunction],
"setIsVisible": [MockFunction],
},
+ "data": Object {
+ "autocomplete": Object {
+ "addProvider": [MockFunction],
+ "clearProviders": [MockFunction],
+ "getProvider": [MockFunction],
+ },
+ "fieldFormats": Object {
+ "getByFieldType": [MockFunction],
+ "getConfig": [MockFunction],
+ "getDefaultConfig": [MockFunction],
+ "getDefaultInstance": [MockFunction],
+ "getDefaultInstanceCacheResolver": [MockFunction],
+ "getDefaultInstancePlain": [MockFunction],
+ "getDefaultType": [MockFunction],
+ "getDefaultTypeName": [MockFunction],
+ "getInstance": [MockFunction],
+ "getType": [MockFunction],
+ "getTypeNameByEsTypes": [MockFunction],
+ "init": [MockFunction],
+ "parseDefaultTypeMap": [MockFunction],
+ "register": [MockFunction],
+ },
+ "getSuggestions": [MockFunction],
+ "indexPatterns": Object {
+ "FieldList": Object {},
+ "IndexPatternSelect": [MockFunction],
+ "flattenHitWrapper": [MockFunction],
+ "formatHitProvider": [MockFunction],
+ "indexPatterns": [MockFunction],
+ },
+ "query": Object {
+ "filterManager": [MockFunction],
+ "savedQueries": [MockFunction],
+ "timefilter": Object {
+ "history": Object {
+ "add": [MockFunction],
+ "get": [MockFunction],
+ },
+ "timefilter": Object {
+ "calculateBounds": [MockFunction],
+ "createFilter": [MockFunction],
+ "disableAutoRefreshSelector": [MockFunction],
+ "disableTimeRangeSelector": [MockFunction],
+ "enableAutoRefreshSelector": [MockFunction],
+ "enableTimeRangeSelector": [MockFunction],
+ "getActiveBounds": [MockFunction],
+ "getAutoRefreshFetch$": [MockFunction],
+ "getBounds": [MockFunction],
+ "getEnabledUpdated$": [MockFunction],
+ "getFetch$": [MockFunction],
+ "getRefreshInterval": [MockFunction],
+ "getRefreshIntervalUpdate$": [MockFunction],
+ "getTime": [MockFunction],
+ "getTimeUpdate$": [MockFunction],
+ "isAutoRefreshSelectorEnabled": [MockFunction],
+ "isTimeRangeSelectorEnabled": [MockFunction],
+ "setRefreshInterval": [MockFunction],
+ "setTime": [MockFunction],
+ },
+ },
+ },
+ "search": Object {
+ "search": [MockFunction],
+ },
+ "ui": Object {
+ "IndexPatternSelect": [MockFunction],
+ },
+ },
"docLinks": Object {
"DOC_LINK_VERSION": "mocked-test-branch",
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
@@ -710,6 +778,74 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA
"setIsCollapsed": [MockFunction],
"setIsVisible": [MockFunction],
},
+ "data": Object {
+ "autocomplete": Object {
+ "addProvider": [MockFunction],
+ "clearProviders": [MockFunction],
+ "getProvider": [MockFunction],
+ },
+ "fieldFormats": Object {
+ "getByFieldType": [MockFunction],
+ "getConfig": [MockFunction],
+ "getDefaultConfig": [MockFunction],
+ "getDefaultInstance": [MockFunction],
+ "getDefaultInstanceCacheResolver": [MockFunction],
+ "getDefaultInstancePlain": [MockFunction],
+ "getDefaultType": [MockFunction],
+ "getDefaultTypeName": [MockFunction],
+ "getInstance": [MockFunction],
+ "getType": [MockFunction],
+ "getTypeNameByEsTypes": [MockFunction],
+ "init": [MockFunction],
+ "parseDefaultTypeMap": [MockFunction],
+ "register": [MockFunction],
+ },
+ "getSuggestions": [MockFunction],
+ "indexPatterns": Object {
+ "FieldList": Object {},
+ "IndexPatternSelect": [MockFunction],
+ "flattenHitWrapper": [MockFunction],
+ "formatHitProvider": [MockFunction],
+ "indexPatterns": [MockFunction],
+ },
+ "query": Object {
+ "filterManager": [MockFunction],
+ "savedQueries": [MockFunction],
+ "timefilter": Object {
+ "history": Object {
+ "add": [MockFunction],
+ "get": [MockFunction],
+ },
+ "timefilter": Object {
+ "calculateBounds": [MockFunction],
+ "createFilter": [MockFunction],
+ "disableAutoRefreshSelector": [MockFunction],
+ "disableTimeRangeSelector": [MockFunction],
+ "enableAutoRefreshSelector": [MockFunction],
+ "enableTimeRangeSelector": [MockFunction],
+ "getActiveBounds": [MockFunction],
+ "getAutoRefreshFetch$": [MockFunction],
+ "getBounds": [MockFunction],
+ "getEnabledUpdated$": [MockFunction],
+ "getFetch$": [MockFunction],
+ "getRefreshInterval": [MockFunction],
+ "getRefreshIntervalUpdate$": [MockFunction],
+ "getTime": [MockFunction],
+ "getTimeUpdate$": [MockFunction],
+ "isAutoRefreshSelectorEnabled": [MockFunction],
+ "isTimeRangeSelectorEnabled": [MockFunction],
+ "setRefreshInterval": [MockFunction],
+ "setTime": [MockFunction],
+ },
+ },
+ },
+ "search": Object {
+ "search": [MockFunction],
+ },
+ "ui": Object {
+ "IndexPatternSelect": [MockFunction],
+ },
+ },
"docLinks": Object {
"DOC_LINK_VERSION": "mocked-test-branch",
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
@@ -1260,6 +1396,74 @@ exports[`QueryStringInput Should pass the query language to the language switche
"setIsCollapsed": [MockFunction],
"setIsVisible": [MockFunction],
},
+ "data": Object {
+ "autocomplete": Object {
+ "addProvider": [MockFunction],
+ "clearProviders": [MockFunction],
+ "getProvider": [MockFunction],
+ },
+ "fieldFormats": Object {
+ "getByFieldType": [MockFunction],
+ "getConfig": [MockFunction],
+ "getDefaultConfig": [MockFunction],
+ "getDefaultInstance": [MockFunction],
+ "getDefaultInstanceCacheResolver": [MockFunction],
+ "getDefaultInstancePlain": [MockFunction],
+ "getDefaultType": [MockFunction],
+ "getDefaultTypeName": [MockFunction],
+ "getInstance": [MockFunction],
+ "getType": [MockFunction],
+ "getTypeNameByEsTypes": [MockFunction],
+ "init": [MockFunction],
+ "parseDefaultTypeMap": [MockFunction],
+ "register": [MockFunction],
+ },
+ "getSuggestions": [MockFunction],
+ "indexPatterns": Object {
+ "FieldList": Object {},
+ "IndexPatternSelect": [MockFunction],
+ "flattenHitWrapper": [MockFunction],
+ "formatHitProvider": [MockFunction],
+ "indexPatterns": [MockFunction],
+ },
+ "query": Object {
+ "filterManager": [MockFunction],
+ "savedQueries": [MockFunction],
+ "timefilter": Object {
+ "history": Object {
+ "add": [MockFunction],
+ "get": [MockFunction],
+ },
+ "timefilter": Object {
+ "calculateBounds": [MockFunction],
+ "createFilter": [MockFunction],
+ "disableAutoRefreshSelector": [MockFunction],
+ "disableTimeRangeSelector": [MockFunction],
+ "enableAutoRefreshSelector": [MockFunction],
+ "enableTimeRangeSelector": [MockFunction],
+ "getActiveBounds": [MockFunction],
+ "getAutoRefreshFetch$": [MockFunction],
+ "getBounds": [MockFunction],
+ "getEnabledUpdated$": [MockFunction],
+ "getFetch$": [MockFunction],
+ "getRefreshInterval": [MockFunction],
+ "getRefreshIntervalUpdate$": [MockFunction],
+ "getTime": [MockFunction],
+ "getTimeUpdate$": [MockFunction],
+ "isAutoRefreshSelectorEnabled": [MockFunction],
+ "isTimeRangeSelectorEnabled": [MockFunction],
+ "setRefreshInterval": [MockFunction],
+ "setTime": [MockFunction],
+ },
+ },
+ },
+ "search": Object {
+ "search": [MockFunction],
+ },
+ "ui": Object {
+ "IndexPatternSelect": [MockFunction],
+ },
+ },
"docLinks": Object {
"DOC_LINK_VERSION": "mocked-test-branch",
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
@@ -1819,6 +2023,74 @@ exports[`QueryStringInput Should pass the query language to the language switche
"setIsCollapsed": [MockFunction],
"setIsVisible": [MockFunction],
},
+ "data": Object {
+ "autocomplete": Object {
+ "addProvider": [MockFunction],
+ "clearProviders": [MockFunction],
+ "getProvider": [MockFunction],
+ },
+ "fieldFormats": Object {
+ "getByFieldType": [MockFunction],
+ "getConfig": [MockFunction],
+ "getDefaultConfig": [MockFunction],
+ "getDefaultInstance": [MockFunction],
+ "getDefaultInstanceCacheResolver": [MockFunction],
+ "getDefaultInstancePlain": [MockFunction],
+ "getDefaultType": [MockFunction],
+ "getDefaultTypeName": [MockFunction],
+ "getInstance": [MockFunction],
+ "getType": [MockFunction],
+ "getTypeNameByEsTypes": [MockFunction],
+ "init": [MockFunction],
+ "parseDefaultTypeMap": [MockFunction],
+ "register": [MockFunction],
+ },
+ "getSuggestions": [MockFunction],
+ "indexPatterns": Object {
+ "FieldList": Object {},
+ "IndexPatternSelect": [MockFunction],
+ "flattenHitWrapper": [MockFunction],
+ "formatHitProvider": [MockFunction],
+ "indexPatterns": [MockFunction],
+ },
+ "query": Object {
+ "filterManager": [MockFunction],
+ "savedQueries": [MockFunction],
+ "timefilter": Object {
+ "history": Object {
+ "add": [MockFunction],
+ "get": [MockFunction],
+ },
+ "timefilter": Object {
+ "calculateBounds": [MockFunction],
+ "createFilter": [MockFunction],
+ "disableAutoRefreshSelector": [MockFunction],
+ "disableTimeRangeSelector": [MockFunction],
+ "enableAutoRefreshSelector": [MockFunction],
+ "enableTimeRangeSelector": [MockFunction],
+ "getActiveBounds": [MockFunction],
+ "getAutoRefreshFetch$": [MockFunction],
+ "getBounds": [MockFunction],
+ "getEnabledUpdated$": [MockFunction],
+ "getFetch$": [MockFunction],
+ "getRefreshInterval": [MockFunction],
+ "getRefreshIntervalUpdate$": [MockFunction],
+ "getTime": [MockFunction],
+ "getTimeUpdate$": [MockFunction],
+ "isAutoRefreshSelectorEnabled": [MockFunction],
+ "isTimeRangeSelectorEnabled": [MockFunction],
+ "setRefreshInterval": [MockFunction],
+ "setTime": [MockFunction],
+ },
+ },
+ },
+ "search": Object {
+ "search": [MockFunction],
+ },
+ "ui": Object {
+ "IndexPatternSelect": [MockFunction],
+ },
+ },
"docLinks": Object {
"DOC_LINK_VERSION": "mocked-test-branch",
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
@@ -2369,6 +2641,74 @@ exports[`QueryStringInput Should render the given query 1`] = `
"setIsCollapsed": [MockFunction],
"setIsVisible": [MockFunction],
},
+ "data": Object {
+ "autocomplete": Object {
+ "addProvider": [MockFunction],
+ "clearProviders": [MockFunction],
+ "getProvider": [MockFunction],
+ },
+ "fieldFormats": Object {
+ "getByFieldType": [MockFunction],
+ "getConfig": [MockFunction],
+ "getDefaultConfig": [MockFunction],
+ "getDefaultInstance": [MockFunction],
+ "getDefaultInstanceCacheResolver": [MockFunction],
+ "getDefaultInstancePlain": [MockFunction],
+ "getDefaultType": [MockFunction],
+ "getDefaultTypeName": [MockFunction],
+ "getInstance": [MockFunction],
+ "getType": [MockFunction],
+ "getTypeNameByEsTypes": [MockFunction],
+ "init": [MockFunction],
+ "parseDefaultTypeMap": [MockFunction],
+ "register": [MockFunction],
+ },
+ "getSuggestions": [MockFunction],
+ "indexPatterns": Object {
+ "FieldList": Object {},
+ "IndexPatternSelect": [MockFunction],
+ "flattenHitWrapper": [MockFunction],
+ "formatHitProvider": [MockFunction],
+ "indexPatterns": [MockFunction],
+ },
+ "query": Object {
+ "filterManager": [MockFunction],
+ "savedQueries": [MockFunction],
+ "timefilter": Object {
+ "history": Object {
+ "add": [MockFunction],
+ "get": [MockFunction],
+ },
+ "timefilter": Object {
+ "calculateBounds": [MockFunction],
+ "createFilter": [MockFunction],
+ "disableAutoRefreshSelector": [MockFunction],
+ "disableTimeRangeSelector": [MockFunction],
+ "enableAutoRefreshSelector": [MockFunction],
+ "enableTimeRangeSelector": [MockFunction],
+ "getActiveBounds": [MockFunction],
+ "getAutoRefreshFetch$": [MockFunction],
+ "getBounds": [MockFunction],
+ "getEnabledUpdated$": [MockFunction],
+ "getFetch$": [MockFunction],
+ "getRefreshInterval": [MockFunction],
+ "getRefreshIntervalUpdate$": [MockFunction],
+ "getTime": [MockFunction],
+ "getTimeUpdate$": [MockFunction],
+ "isAutoRefreshSelectorEnabled": [MockFunction],
+ "isTimeRangeSelectorEnabled": [MockFunction],
+ "setRefreshInterval": [MockFunction],
+ "setTime": [MockFunction],
+ },
+ },
+ },
+ "search": Object {
+ "search": [MockFunction],
+ },
+ "ui": Object {
+ "IndexPatternSelect": [MockFunction],
+ },
+ },
"docLinks": Object {
"DOC_LINK_VERSION": "mocked-test-branch",
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
@@ -2928,6 +3268,74 @@ exports[`QueryStringInput Should render the given query 1`] = `
"setIsCollapsed": [MockFunction],
"setIsVisible": [MockFunction],
},
+ "data": Object {
+ "autocomplete": Object {
+ "addProvider": [MockFunction],
+ "clearProviders": [MockFunction],
+ "getProvider": [MockFunction],
+ },
+ "fieldFormats": Object {
+ "getByFieldType": [MockFunction],
+ "getConfig": [MockFunction],
+ "getDefaultConfig": [MockFunction],
+ "getDefaultInstance": [MockFunction],
+ "getDefaultInstanceCacheResolver": [MockFunction],
+ "getDefaultInstancePlain": [MockFunction],
+ "getDefaultType": [MockFunction],
+ "getDefaultTypeName": [MockFunction],
+ "getInstance": [MockFunction],
+ "getType": [MockFunction],
+ "getTypeNameByEsTypes": [MockFunction],
+ "init": [MockFunction],
+ "parseDefaultTypeMap": [MockFunction],
+ "register": [MockFunction],
+ },
+ "getSuggestions": [MockFunction],
+ "indexPatterns": Object {
+ "FieldList": Object {},
+ "IndexPatternSelect": [MockFunction],
+ "flattenHitWrapper": [MockFunction],
+ "formatHitProvider": [MockFunction],
+ "indexPatterns": [MockFunction],
+ },
+ "query": Object {
+ "filterManager": [MockFunction],
+ "savedQueries": [MockFunction],
+ "timefilter": Object {
+ "history": Object {
+ "add": [MockFunction],
+ "get": [MockFunction],
+ },
+ "timefilter": Object {
+ "calculateBounds": [MockFunction],
+ "createFilter": [MockFunction],
+ "disableAutoRefreshSelector": [MockFunction],
+ "disableTimeRangeSelector": [MockFunction],
+ "enableAutoRefreshSelector": [MockFunction],
+ "enableTimeRangeSelector": [MockFunction],
+ "getActiveBounds": [MockFunction],
+ "getAutoRefreshFetch$": [MockFunction],
+ "getBounds": [MockFunction],
+ "getEnabledUpdated$": [MockFunction],
+ "getFetch$": [MockFunction],
+ "getRefreshInterval": [MockFunction],
+ "getRefreshIntervalUpdate$": [MockFunction],
+ "getTime": [MockFunction],
+ "getTimeUpdate$": [MockFunction],
+ "isAutoRefreshSelectorEnabled": [MockFunction],
+ "isTimeRangeSelectorEnabled": [MockFunction],
+ "setRefreshInterval": [MockFunction],
+ "setTime": [MockFunction],
+ },
+ },
+ },
+ "search": Object {
+ "search": [MockFunction],
+ },
+ "ui": Object {
+ "IndexPatternSelect": [MockFunction],
+ },
+ },
"docLinks": Object {
"DOC_LINK_VERSION": "mocked-test-branch",
"ELASTIC_WEBSITE_URL": "https://www.elastic.co/",
diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx
index 51f0abbd102ccd..70d0c96b4733f3 100644
--- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx
+++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.test.tsx
@@ -24,6 +24,7 @@ import { mount } from 'enzyme';
import { QueryBarTopRow } from './query_bar_top_row';
import { coreMock } from '../../../../../core/public/mocks';
+import { dataPluginMock } from '../../mocks';
import { KibanaContextProvider } from 'src/plugins/kibana_react/public';
import { I18nProvider } from '@kbn/i18n/react';
import { stubIndexPatternWithFields } from '../../stubs';
@@ -95,6 +96,7 @@ function wrapQueryBarTopRowInContext(testProps: any) {
const services = {
...startMock,
+ data: dataPluginMock.createStartContract(),
appName: 'discover',
storage: createMockStorage(),
};
@@ -117,7 +119,7 @@ describe('QueryBarTopRowTopRow', () => {
jest.clearAllMocks();
});
- it('Should render the given query', () => {
+ it('Should render query and time picker', () => {
const component = mount(
wrapQueryBarTopRowInContext({
query: kqlQuery,
diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx
index 3c98ee948c7d88..4435bd87cd2d71 100644
--- a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx
+++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx
@@ -28,6 +28,7 @@ import React from 'react';
import { QueryLanguageSwitcher } from './language_switcher';
import { QueryStringInput, QueryStringInputUI } from './query_string_input';
import { coreMock } from '../../../../../core/public/mocks';
+import { dataPluginMock } from '../../mocks';
const startMock = coreMock.createStart();
import { stubIndexPatternWithFields } from '../../stubs';
@@ -74,6 +75,7 @@ function wrapQueryStringInputInContext(testProps: any, storage?: any) {
const services = {
...startMock,
+ data: dataPluginMock.createStartContract(),
appName: testProps.appName || 'test',
storage: storage || createMockStorage(),
};
diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx
index 5b3d638f9e9351..b35ba427378abb 100644
--- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx
+++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.test.tsx
@@ -251,6 +251,7 @@ describe('SavedObjectsFinder', () => {
it('should include additional fields in search if listed in meta data', async () => {
const core = coreMock.createStart();
core.uiSettings.get.mockImplementation(() => 10);
+ (core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] });
const wrapper = shallow(
{
describe('loading state', () => {
it('should display a spinner during initial loading', () => {
const core = coreMock.createStart();
+ (core.savedObjects.client.find as jest.Mock).mockResolvedValue({ savedObjects: [] });
const wrapper = shallow(
{
- imageDiff.getFullResult({
- actualImage: sessionImagePath,
- expectedImage: baselineImagePath,
- diffImage: diffImagePath,
- shadow: true,
- }, cb);
- });
+ const sessionImage = PNG.sync.read(await fs.promises.readFile(sessionImagePath));
+ const baselineImage = PNG.sync.read(await fs.promises.readFile(baselineImagePath));
+ const { width, height } = sessionImage;
+ const diff = new PNG({ width, height });
+
+ const numDiffPixels = pixelmatch(
+ sessionImage.data,
+ baselineImage.data,
+ diff.data,
+ width,
+ height,
+ { threshold: 0 }
+ );
+
+ await fs.promises.writeFile(diffImagePath, PNG.sync.write(diff));
- const change = diffResult.percentage;
+ const change = numDiffPixels / (width * height);
const changePercentage = (change * 100).toFixed(2);
console.log(`(${changePercentage}%) ${screenshot}`);
comparison.percentage = changePercentage;
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts
index f05d343ad7ba5b..de423e967b0b3c 100644
--- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts
+++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts
@@ -27,7 +27,7 @@ describe('createErrorGroupWatch', () => {
.mockResolvedValue(undefined);
beforeEach(async () => {
- jest.spyOn(uuid, 'v4').mockReturnValue(new Buffer('mocked-uuid'));
+ jest.spyOn(uuid, 'v4').mockReturnValue(Buffer.from('mocked-uuid'));
createWatchResponse = await createErrorGroupWatch({
http: {} as HttpServiceBase,
diff --git a/x-pack/legacy/plugins/index_management/__mocks__/ui/notify.js b/x-pack/legacy/plugins/index_management/__mocks__/ui/notify.js
index 27d24efb79c9e6..d508c3383d5f91 100644
--- a/x-pack/legacy/plugins/index_management/__mocks__/ui/notify.js
+++ b/x-pack/legacy/plugins/index_management/__mocks__/ui/notify.js
@@ -10,3 +10,5 @@ export const toastNotifications = {
addWarning: () => {},
addError: () => {},
};
+
+export function fatalError() {}
diff --git a/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx b/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx
index 52dd1071deaa44..77869c40d35778 100644
--- a/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx
+++ b/x-pack/legacy/plugins/reporting/public/components/report_info_button.tsx
@@ -266,7 +266,6 @@ export class ReportInfoButton extends Component {
info: null,
error: kfetchError,
});
- throw kfetchError;
}
}
};
diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js
index b2e87482b73a1e..f2a405d51fbf5b 100644
--- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js
+++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js
@@ -246,7 +246,9 @@ describe('Worker class', function () {
it('should use error multiplier when processPendingJobs rejects the Promise', async function () {
worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions);
- const processPendingJobsStub = sinon.stub(worker, '_processPendingJobs').returns(Promise.reject(new Error('test error')));
+ const processPendingJobsStub = sinon
+ .stub(worker, '_processPendingJobs')
+ .rejects(new Error('test error'));
await allowPoll(defaultWorkerOptions.interval);
expect(processPendingJobsStub.callCount).to.be(1);
@@ -517,7 +519,7 @@ describe('Worker class', function () {
it('should emit for errors from claiming job', function (done) {
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.reject({ statusCode: 401 }));
+ .rejects({ statusCode: 401 });
worker.once(constants.EVENT_WORKER_JOB_CLAIM_ERROR, function (err) {
try {
@@ -531,13 +533,13 @@ describe('Worker class', function () {
}
});
- worker._claimPendingJobs(getMockJobs());
+ worker._claimPendingJobs(getMockJobs()).catch(() => {});
});
it('should reject the promise if an error claiming the job', function () {
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.reject({ statusCode: 409 }));
+ .rejects({ statusCode: 409 });
return worker._claimPendingJobs(getMockJobs())
.catch(err => {
expect(err).to.eql({ statusCode: 409 });
@@ -547,7 +549,7 @@ describe('Worker class', function () {
it('should get the pending job', function () {
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.resolve({ test: 'cool' }));
+ .resolves({ test: 'cool' });
sinon.stub(worker, '_performJob').callsFake(identity);
return worker._claimPendingJobs(getMockJobs())
.then(claimedJob => {
@@ -607,7 +609,7 @@ describe('Worker class', function () {
mockQueue.client.callWithInternalUser.restore();
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.reject({ statusCode: 409 }));
+ .rejects({ statusCode: 409 });
return worker._failJob(job)
.then((res) => expect(res).to.equal(true));
});
@@ -616,7 +618,7 @@ describe('Worker class', function () {
mockQueue.client.callWithInternalUser.restore();
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.reject({ statusCode: 401 }));
+ .rejects({ statusCode: 401 });
return worker._failJob(job)
.then((res) => expect(res).to.equal(false));
});
@@ -654,7 +656,7 @@ describe('Worker class', function () {
mockQueue.client.callWithInternalUser.restore();
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.reject({ statusCode: 401 }));
+ .rejects({ statusCode: 401 });
worker.on(constants.EVENT_WORKER_FAIL_UPDATE_ERROR, function (err) {
try {
@@ -842,7 +844,7 @@ describe('Worker class', function () {
describe('job failures', function () {
function getFailStub(workerWithFailure) {
- return sinon.stub(workerWithFailure, '_failJob').returns(Promise.resolve());
+ return sinon.stub(workerWithFailure, '_failJob').resolves();
}
describe('saving output failure', () => {
@@ -857,7 +859,7 @@ describe('Worker class', function () {
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('update')
- .returns(Promise.reject({ statusCode: 413 }));
+ .rejects({ statusCode: 413 });
const workerFn = function (jobPayload) {
return new Promise(function (resolve) {
@@ -878,7 +880,7 @@ describe('Worker class', function () {
it('causes _processPendingJobs to reject the Promise', function () {
sinon.stub(mockQueue.client, 'callWithInternalUser')
.withArgs('search')
- .returns(Promise.reject(new Error('test error')));
+ .rejects(new Error('test error'));
worker = new Worker(mockQueue, 'test', noop, defaultWorkerOptions);
return worker._processPendingJobs()
.then(() => {
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privileges.test.tsx.snap b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privileges.test.tsx.snap
index 943c3e1518cab7..1c4e553daa9692 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privileges.test.tsx.snap
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/__snapshots__/index_privileges.test.tsx.snap
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`it renders without crashing 1`] = `null`;
+exports[`it renders without crashing 1`] = ``;
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.test.tsx
index fbfb9460ecaa7b..783d2f9893b4cb 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.test.tsx
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.test.tsx
@@ -10,31 +10,36 @@ import { RoleValidator } from '../../../lib/validate_role';
import { IndexPrivilegeForm } from './index_privilege_form';
import { IndexPrivileges } from './index_privileges';
-test('it renders without crashing', () => {
+// the IndexPrivileges post-mount hook kicks off some promises;
+// we need to wait for those promises to resolve to ensure any errors are properly caught
+const flushPromises = () => new Promise(setImmediate);
+
+test('it renders without crashing', async () => {
const props = {
role: {
name: '',
+ kibana: [],
elasticsearch: {
cluster: [],
indices: [],
run_as: [],
},
- kibana: [],
},
httpClient: jest.fn(),
onChange: jest.fn(),
indexPatterns: [],
+ editable: true,
allowDocumentLevelSecurity: true,
allowFieldLevelSecurity: true,
- editable: true,
validator: new RoleValidator(),
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
};
const wrapper = shallowWithIntl();
+ await flushPromises();
expect(wrapper).toMatchSnapshot();
});
-test('it renders a IndexPrivilegeForm for each privilege on the role', () => {
+test('it renders a IndexPrivilegeForm for each privilege on the role', async () => {
const props = {
role: {
name: '',
@@ -64,5 +69,6 @@ test('it renders a IndexPrivilegeForm for each privilege on the role', () => {
availableIndexPrivileges: ['all', 'read', 'write', 'index'],
};
const wrapper = mountWithIntl();
+ await flushPromises();
expect(wrapper.find(IndexPrivilegeForm)).toHaveLength(1);
});
diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx
index 1f54a5aacf948d..f09084ad2bb382 100644
--- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx
+++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/es/index_privileges.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
-import React, { Component } from 'react';
+import React, { Component, Fragment } from 'react';
import { Role, RoleIndexPrivilege } from '../../../../../../../common/model';
import { isReadOnlyRole, isRoleEnabled } from '../../../../../../lib/role_utils';
import { getFields } from '../../../../../../objects';
@@ -74,7 +74,7 @@ export class IndexPrivileges extends Component {
/>
));
- return forms;
+ return {forms};
}
public addIndexPrivilege = () => {
diff --git a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js
index 7a11497c10718b..86d7374a8f0d12 100644
--- a/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js
+++ b/x-pack/legacy/plugins/xpack_main/server/telemetry_collection/__tests__/get_xpack.js
@@ -23,7 +23,12 @@ function mockGetXPackLicense(callCluster, license, req) {
local: 'true'
}
})
- .returns(license.then(response => ({ license: response })));
+ .returns(
+ license.then(
+ response => ({ license: response }),
+ () => {} // Catch error so that we don't emit UnhandledPromiseRejectionWarning for tests with invalid license
+ )
+ );
callCluster.withArgs('transport.request', {
method: 'GET',
@@ -33,7 +38,12 @@ function mockGetXPackLicense(callCluster, license, req) {
}
})
// conveniently wraps the passed in license object as { license: response }, like it really is
- .returns(license.then(response => ({ license: response })));
+ .returns(
+ license.then(
+ response => ({ license: response }),
+ () => {} // Catch error so that we don't emit UnhandledPromiseRejectionWarning for tests with invalid license
+ )
+ );
}
function mockGetXPackUsage(callCluster, usage, req) {
diff --git a/x-pack/package.json b/x-pack/package.json
index 9faf2f0983b707..2dc78be0f834fc 100644
--- a/x-pack/package.json
+++ b/x-pack/package.json
@@ -151,7 +151,7 @@
"null-loader": "^3.0.0",
"pdf-image": "2.0.0",
"pdfjs-dist": "^2.0.943",
- "pixelmatch": "4.0.2",
+ "pixelmatch": "^5.1.0",
"proxyquire": "1.8.0",
"react-docgen-typescript-loader": "^3.1.1",
"react-test-renderer": "^16.12.0",
diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts
index 1ba98d58a3a5f7..a81246c8f78b04 100644
--- a/x-pack/plugins/security/server/authentication/authenticator.test.ts
+++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts
@@ -515,16 +515,19 @@ describe('Authenticator', () => {
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
});
- it('only updates the session lifespan expiration if it does not match the current server config.', async () => {
- const user = mockAuthenticatedUser();
- const request = httpServerMock.createKibanaRequest();
+ describe('conditionally updates the session lifespan expiration', () => {
const hr = 1000 * 60 * 60;
+ const currentDate = new Date(Date.UTC(2019, 10, 10)).valueOf();
async function createAndUpdateSession(
lifespan: number | null,
oldExpiration: number | null,
newExpiration: number | null
) {
+ const user = mockAuthenticatedUser();
+ const request = httpServerMock.createKibanaRequest();
+ jest.spyOn(Date, 'now').mockImplementation(() => currentDate);
+
mockOptions = getMockOptions({
session: {
idleTimeout: null,
@@ -536,7 +539,7 @@ describe('Authenticator', () => {
mockSessionStorage = sessionStorageMock.create();
mockSessionStorage.get.mockResolvedValue({
...mockSessVal,
- idleTimeoutExpiration: 1,
+ idleTimeoutExpiration: null,
lifespanExpiration: oldExpiration,
});
mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage);
@@ -554,17 +557,24 @@ describe('Authenticator', () => {
expect(mockSessionStorage.set).toHaveBeenCalledTimes(1);
expect(mockSessionStorage.set).toHaveBeenCalledWith({
...mockSessVal,
- idleTimeoutExpiration: 1,
+ idleTimeoutExpiration: null,
lifespanExpiration: newExpiration,
});
expect(mockSessionStorage.clear).not.toHaveBeenCalled();
}
- // do not change max expiration
- createAndUpdateSession(hr * 8, 1234, 1234);
- createAndUpdateSession(null, null, null);
- // change max expiration
- createAndUpdateSession(null, 1234, null);
- createAndUpdateSession(hr * 8, null, hr * 8);
+
+ it('does not change a non-null lifespan expiration when configured to non-null value.', async () => {
+ await createAndUpdateSession(hr * 8, 1234, 1234);
+ });
+ it('does not change a null lifespan expiration when configured to null value.', async () => {
+ await createAndUpdateSession(null, null, null);
+ });
+ it('does change a non-null lifespan expiration when configured to null value.', async () => {
+ await createAndUpdateSession(null, 1234, null);
+ });
+ it('does change a null lifespan expiration when configured to non-null value', async () => {
+ await createAndUpdateSession(hr * 8, null, currentDate + hr * 8);
+ });
});
it('does not touch session for system API calls if authentication fails with non-401 reason.', async () => {
diff --git a/yarn.lock b/yarn.lock
index 76bc070e27842d..69ab184d480e98 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5513,21 +5513,11 @@ array-map@~0.0.0:
resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662"
integrity sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=
-array-parallel@~0.1.3:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/array-parallel/-/array-parallel-0.1.3.tgz#8f785308926ed5aa478c47e64d1b334b6c0c947d"
- integrity sha1-j3hTCJJu1apHjEfmTRszS2wMlH0=
-
array-reduce@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
integrity sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=
-array-series@~0.1.5:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/array-series/-/array-series-0.1.5.tgz#df5d37bfc5c2ef0755e2aa4f92feae7d4b5a972f"
- integrity sha1-3103v8XC7wdV4qpPkv6ufUtaly8=
-
array-slice@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
@@ -5803,11 +5793,6 @@ async@^2.6.3:
dependencies:
lodash "^4.17.14"
-async@~0.2.9:
- version "0.2.10"
- resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
- integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
-
async@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9"
@@ -7133,15 +7118,6 @@ buffer@^5.1.0, buffer@^5.2.0:
base64-js "^1.0.2"
ieee754 "^1.1.4"
-buffered-spawn@~1.1.1:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/buffered-spawn/-/buffered-spawn-1.1.2.tgz#21ad9735dfbf6576745be0d74a23ef257bf3c58d"
- integrity sha1-Ia2XNd+/ZXZ0W+DXSiPvJXvzxY0=
- dependencies:
- cross-spawn-async "^1.0.1"
- err-code "^0.1.0"
- q "^1.0.1"
-
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@@ -8378,13 +8354,6 @@ commander@~2.8.1:
dependencies:
graceful-readlink ">= 1.0.0"
-commander@~2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
- integrity sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=
- dependencies:
- graceful-readlink ">= 1.0.0"
-
common-tags@1.8.0, common-tags@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@@ -8978,14 +8947,6 @@ cross-fetch@2.2.2:
node-fetch "2.1.2"
whatwg-fetch "2.0.4"
-cross-spawn-async@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-1.0.1.tgz#bb525c1e420d9942552e04791a3eb2d9887a105f"
- integrity sha1-u1JcHkINmUJVLgR5Gj6y2Yh6EF8=
- dependencies:
- lru-cache "^2.6.5"
- which "^1.1.1"
-
cross-spawn-async@^2.1.1:
version "2.2.5"
resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc"
@@ -9722,7 +9683,7 @@ debug-fabulous@1.X:
memoizee "0.4.X"
object-assign "4.X"
-debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9, debug@~2.2.0:
+debug@2.6.9, debug@^2.0.0, debug@^2.1.0, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.8, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -11011,11 +10972,6 @@ enzyme@^3.10.0:
rst-selector-parser "^2.2.3"
string.prototype.trim "^1.1.2"
-err-code@^0.1.0:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/err-code/-/err-code-0.1.2.tgz#122a92b3342b9899da02b5ac994d30f95d4763ee"
- integrity sha1-EiqSszQrmJnaArWsmU0w+V1HY+4=
-
errlop@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/errlop/-/errlop-1.1.2.tgz#a99a48f37aa264d614e342ffdbbaa49eec9220e0"
@@ -13852,15 +13808,6 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
-gm@~1.21.1:
- version "1.21.1"
- resolved "https://registry.yarnpkg.com/gm/-/gm-1.21.1.tgz#7ed5ed05db36d30c1943f39c3bc1c839b8f2361d"
- integrity sha1-ftXtBds20wwZQ/OcO8HIObjyNh0=
- dependencies:
- array-parallel "~0.1.3"
- array-series "~0.1.5"
- debug "~2.2.0"
-
gonzales-pe-sl@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6"
@@ -15324,18 +15271,6 @@ ignore@^5.1.1:
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558"
integrity sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ==
-image-diff@1.6.3:
- version "1.6.3"
- resolved "https://registry.yarnpkg.com/image-diff/-/image-diff-1.6.3.tgz#818a0e656ae89480e802e7ef14db460826f730fc"
- integrity sha1-gYoOZWrolIDoAufvFNtGCCb3MPw=
- dependencies:
- async "~0.2.9"
- buffered-spawn "~1.1.1"
- commander "~2.9.0"
- gm "~1.21.1"
- mkdirp "~0.3.5"
- tmp "0.0.23"
-
image-size@~0.5.0:
version "0.5.5"
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
@@ -18749,11 +18684,6 @@ lru-cache@4.1.x, lru-cache@^4.0.0:
pseudomap "^1.0.2"
yallist "^2.1.2"
-lru-cache@^2.6.5:
- version "2.7.3"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
- integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=
-
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -19588,7 +19518,7 @@ mixin-object@^2.0.1:
for-in "^0.1.3"
is-extendable "^0.1.1"
-mkdirp@0.3.5, mkdirp@^0.3.5, mkdirp@~0.3.5:
+mkdirp@0.3.5, mkdirp@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7"
integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=
@@ -21820,13 +21750,20 @@ pirates@^4.0.0, pirates@^4.0.1:
dependencies:
node-modules-regexp "^1.0.0"
-pixelmatch@4.0.2, pixelmatch@^4.0.2:
+pixelmatch@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-4.0.2.tgz#8f47dcec5011b477b67db03c243bc1f3085e8854"
integrity sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=
dependencies:
pngjs "^3.0.0"
+pixelmatch@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.1.0.tgz#b640f0e5a03a09f235a4b818ef3b9b98d9d0b911"
+ integrity sha512-HqtgvuWN12tBzKJf7jYsc38Ha28Q2NYpmBL9WostEGgDHJqbTLkjydZXL1ZHM02ZnB+Dkwlxo87HBY38kMiD6A==
+ dependencies:
+ pngjs "^3.4.0"
+
pkg-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
@@ -22628,7 +22565,7 @@ puppeteer@^1.13.0:
rimraf "^2.6.1"
ws "^6.1.0"
-q@^1.0.1, q@^1.1.2:
+q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
@@ -27472,11 +27409,6 @@ titleize@^1.0.1:
resolved "https://registry.yarnpkg.com/titleize/-/titleize-1.0.1.tgz#21bc24fcca658eadc6d3bd3c38f2bd173769b4c5"
integrity sha512-rUwGDruKq1gX+FFHbTl5qjI7teVO7eOe+C8IcQ7QT+1BK3eEUXJqbZcBOeaRP4FwSC/C1A5jDoIVta0nIQ9yew==
-tmp@0.0.23:
- version "0.0.23"
- resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.23.tgz#de874aa5e974a85f0a32cdfdbd74663cb3bd9c74"
- integrity sha1-3odKpel0qF8KMs39vXRmPLO9nHQ=
-
tmp@0.0.30:
version "0.0.30"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.30.tgz#72419d4a8be7d6ce75148fd8b324e593a711c2ed"
From ffdd39222298d7d859e21665e856e8c6b20bb351 Mon Sep 17 00:00:00 2001
From: CJ Cenizal
Date: Wed, 4 Dec 2019 07:19:39 -0800
Subject: [PATCH 12/30] Silence Axe's complaints about missing labels for
Console's textarea elements, in order to allow our automated a11y tests to
pass. (#52150)
---
.../editor/legacy/console_editor/editor.tsx | 20 +++++++++++++------
.../legacy/console_editor/editor_output.tsx | 9 ++++++++-
.../console/public/quarantined/_app.scss | 1 +
3 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx
index 0fa0ec732c7705..b99249b2b0016b 100644
--- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx
+++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor.tsx
@@ -193,12 +193,20 @@ function EditorUI({ previousStateLocation = 'stored' }: EditorProps) {
/>
-
+
+ {/* Axe complains about Ace's textarea element missing a label, which interferes with our
+ automated a11y tests per #52136. This wrapper does nothing to address a11y but it does
+ satisfy Axe. */}
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
+
);
diff --git a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx
index c167155bd18a91..3690ea61d56842 100644
--- a/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx
+++ b/src/legacy/core_plugins/console/np_ready/public/application/containers/editor/legacy/console_editor/editor_output.tsx
@@ -87,7 +87,14 @@ function EditorOutputUI() {
return (
-
+ {/* Axe complains about Ace's textarea element missing a label, which interferes with our
+ automated a11y tests per #52136. This wrapper does nothing to address a11y but it does
+ satisfy Axe. */}
+
+ {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
+
);
}
diff --git a/src/legacy/core_plugins/console/public/quarantined/_app.scss b/src/legacy/core_plugins/console/public/quarantined/_app.scss
index b19fd438f8ee3e..159e9f9e8a1730 100644
--- a/src/legacy/core_plugins/console/public/quarantined/_app.scss
+++ b/src/legacy/core_plugins/console/public/quarantined/_app.scss
@@ -33,6 +33,7 @@
flex: 1 1 1px;
}
+.conApp__textAreaLabelHack,
.conApp__editorContent,
.conApp__outputContent {
height: 100%;
From 4a9d77fd100dda5143fc40cdfb65963722595234 Mon Sep 17 00:00:00 2001
From: Stacey Gammon
Date: Wed, 4 Dec 2019 10:29:02 -0500
Subject: [PATCH 13/30] Add a section on naming conventions for Start and Setup
(#51676)
---
src/core/CONVENTIONS.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md
index fbe2740b961088..507ef5ba4b57ea 100644
--- a/src/core/CONVENTIONS.md
+++ b/src/core/CONVENTIONS.md
@@ -247,3 +247,8 @@ export function registerMyPluginUsageCollector(usageCollection?: UsageCollection
usageCollection.registerCollector(myCollector);
}
```
+
+### Naming conventions
+
+Export start and setup contracts as `MyPluginStart` and `MyPluginSetup`.
+This avoids naming clashes, if everyone exported them simply as `Start` and `Setup`.
From 4ce703e4090f18907513160c168f8f9e5f517b43 Mon Sep 17 00:00:00 2001
From: Patrick Mueller
Date: Wed, 4 Dec 2019 10:30:42 -0500
Subject: [PATCH 14/30] Whitelist email server in built-in email server action
(#51489)
resolves https://github.com/elastic/kibana/issues/50721
Uses the same whitelist config value / utilities that the webhook
action already uses. Was already mentioned in the README doc
that email uses this whitelist config value :-)
Required a change to the functional tests to use a host already
whitelisted in config, made for the the webhook action tests.
Also realized some jest tests on email were bogus, so fixed those
(was passing `user` in config, which is invalid, and masking the
actual thing being tested).
---
.../server/builtin_action_types/email.test.ts | 155 +++++++++++++-----
.../server/builtin_action_types/email.ts | 87 +++++-----
.../server/builtin_action_types/index.ts | 4 +-
.../actions/builtin_action_types/email.ts | 74 +++++++++
.../tests/actions/execute.ts | 8 +-
5 files changed, 239 insertions(+), 89 deletions(-)
diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts
index 8e6b1f19b172c1..513f51f644534a 100644
--- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts
+++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts
@@ -8,15 +8,30 @@ jest.mock('./lib/send_email', () => ({
sendEmail: jest.fn(),
}));
+import { Logger } from '../../../../../../src/core/server';
+import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
+
import { ActionType, ActionTypeExecutorOptions } from '../types';
+import { ActionsConfigurationUtilities } from '../actions_config';
import { validateConfig, validateSecrets, validateParams } from '../lib';
-import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks';
import { createActionTypeRegistry } from './index.test';
import { sendEmail } from './lib/send_email';
-import { ActionParamsType, ActionTypeConfigType, ActionTypeSecretsType } from './email';
+import {
+ ActionParamsType,
+ ActionTypeConfigType,
+ ActionTypeSecretsType,
+ getActionType,
+} from './email';
const sendEmailMock = sendEmail as jest.Mock;
+const configUtilsMock: ActionsConfigurationUtilities = {
+ isWhitelistedHostname: _ => true,
+ isWhitelistedUri: _ => true,
+ ensureWhitelistedHostname: _ => {},
+ ensureWhitelistedUri: _ => {},
+};
+
const ACTION_TYPE_ID = '.email';
const NO_OP_FN = () => {};
@@ -27,6 +42,7 @@ const services = {
};
let actionType: ActionType;
+let mockedLogger: jest.Mocked;
beforeAll(() => {
const { actionTypeRegistry } = createActionTypeRegistry();
@@ -69,8 +85,6 @@ describe('config validation', () => {
test('config validation fails when config is not valid', () => {
const baseConfig: Record = {
- user: 'bob',
- password: 'supersecret',
from: 'bob@example.com',
};
@@ -85,21 +99,21 @@ describe('config validation', () => {
expect(() => {
validateConfig(actionType, baseConfig);
}).toThrowErrorMatchingInlineSnapshot(
- `"error validating action type config: [user]: definition for this key is missing"`
+ `"error validating action type config: either [service] or [host]/[port] is required"`
);
// host but no port
expect(() => {
validateConfig(actionType, { ...baseConfig, host: 'elastic.co' });
}).toThrowErrorMatchingInlineSnapshot(
- `"error validating action type config: [user]: definition for this key is missing"`
+ `"error validating action type config: [port] is required if [service] is not provided"`
);
// port but no host
expect(() => {
validateConfig(actionType, { ...baseConfig, port: 8080 });
}).toThrowErrorMatchingInlineSnapshot(
- `"error validating action type config: [user]: definition for this key is missing"`
+ `"error validating action type config: [host] is required if [service] is not provided"`
);
// invalid service
@@ -109,7 +123,64 @@ describe('config validation', () => {
service: 'bad-nodemailer-service',
});
}).toThrowErrorMatchingInlineSnapshot(
- `"error validating action type config: [user]: definition for this key is missing"`
+ `"error validating action type config: [service] value 'bad-nodemailer-service' is not valid"`
+ );
+ });
+
+ // nodemailer supports a service named 'AOL' that maps to the host below
+ const NODEMAILER_AOL_SERVICE = 'AOL';
+ const NODEMAILER_AOL_SERVICE_HOST = 'smtp.aol.com';
+
+ test('config validation handles email host whitelisting', () => {
+ actionType = getActionType({
+ logger: mockedLogger,
+ configurationUtilities: {
+ ...configUtilsMock,
+ isWhitelistedHostname: hostname => hostname === NODEMAILER_AOL_SERVICE_HOST,
+ },
+ });
+ const baseConfig = {
+ from: 'bob@example.com',
+ };
+ const whitelistedConfig1 = {
+ ...baseConfig,
+ service: NODEMAILER_AOL_SERVICE,
+ };
+ const whitelistedConfig2 = {
+ ...baseConfig,
+ host: NODEMAILER_AOL_SERVICE_HOST,
+ port: 42,
+ };
+ const notWhitelistedConfig1 = {
+ ...baseConfig,
+ service: 'gmail',
+ };
+
+ const notWhitelistedConfig2 = {
+ ...baseConfig,
+ host: 'smtp.gmail.com',
+ port: 42,
+ };
+
+ const validatedConfig1 = validateConfig(actionType, whitelistedConfig1);
+ expect(validatedConfig1.service).toEqual(whitelistedConfig1.service);
+ expect(validatedConfig1.from).toEqual(whitelistedConfig1.from);
+
+ const validatedConfig2 = validateConfig(actionType, whitelistedConfig2);
+ expect(validatedConfig2.host).toEqual(whitelistedConfig2.host);
+ expect(validatedConfig2.port).toEqual(whitelistedConfig2.port);
+ expect(validatedConfig2.from).toEqual(whitelistedConfig2.from);
+
+ expect(() => {
+ validateConfig(actionType, notWhitelistedConfig1);
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"error validating action type config: [service] value 'gmail' resolves to host 'smtp.gmail.com' which is not in the whitelistedHosts configuration"`
+ );
+
+ expect(() => {
+ validateConfig(actionType, notWhitelistedConfig2);
+ }).toThrowErrorMatchingInlineSnapshot(
+ `"error validating action type config: [host] value 'smtp.gmail.com' is not in the whitelistedHosts configuration"`
);
});
});
@@ -140,16 +211,16 @@ describe('params validation', () => {
message: 'this is the message',
};
expect(validateParams(actionType, params)).toMatchInlineSnapshot(`
-Object {
- "bcc": Array [],
- "cc": Array [],
- "message": "this is the message",
- "subject": "this is a test",
- "to": Array [
- "bob@example.com",
- ],
-}
-`);
+ Object {
+ "bcc": Array [],
+ "cc": Array [],
+ "message": "this is the message",
+ "subject": "this is a test",
+ "to": Array [
+ "bob@example.com",
+ ],
+ }
+ `);
});
test('params validation fails when params is not valid', () => {
@@ -194,29 +265,29 @@ describe('execute()', () => {
sendEmailMock.mockReset();
await actionType.executor(executorOptions);
expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(`
- Object {
- "content": Object {
- "message": "a message to you",
- "subject": "the subject",
- },
- "routing": Object {
- "bcc": Array [
- "jimmy@example.com",
- ],
- "cc": Array [
- "james@example.com",
- ],
- "from": "bob@example.com",
- "to": Array [
- "jim@example.com",
- ],
- },
- "transport": Object {
- "password": "supersecret",
- "service": "__json",
- "user": "bob",
- },
- }
-`);
+ Object {
+ "content": Object {
+ "message": "a message to you",
+ "subject": "the subject",
+ },
+ "routing": Object {
+ "bcc": Array [
+ "jimmy@example.com",
+ ],
+ "cc": Array [
+ "james@example.com",
+ ],
+ "from": "bob@example.com",
+ "to": Array [
+ "jim@example.com",
+ ],
+ },
+ "transport": Object {
+ "password": "supersecret",
+ "service": "__json",
+ "user": "bob",
+ },
+ }
+ `);
});
});
diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts
index c68d9acd8b174a..a378d8a4b9b558 100644
--- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts
+++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts
@@ -7,31 +7,32 @@
import { curry } from 'lodash';
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
-import nodemailerServices from 'nodemailer/lib/well-known/services.json';
+import nodemailerGetService from 'nodemailer/lib/well-known';
import { sendEmail, JSON_TRANSPORT_SERVICE } from './lib/send_email';
import { nullableType } from './lib/nullable';
import { portSchema } from './lib/schemas';
import { Logger } from '../../../../../../src/core/server';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
+import { ActionsConfigurationUtilities } from '../actions_config';
// config definition
export type ActionTypeConfigType = TypeOf;
-const ConfigSchema = schema.object(
- {
- service: nullableType(schema.string()),
- host: nullableType(schema.string()),
- port: nullableType(portSchema()),
- secure: nullableType(schema.boolean()),
- from: schema.string(),
- },
- {
- validate: validateConfig,
- }
-);
+const ConfigSchemaProps = {
+ service: nullableType(schema.string()),
+ host: nullableType(schema.string()),
+ port: nullableType(portSchema()),
+ secure: nullableType(schema.boolean()),
+ from: schema.string(),
+};
-function validateConfig(configObject: any): string | void {
+const ConfigSchema = schema.object(ConfigSchemaProps);
+
+function validateConfig(
+ configurationUtilities: ActionsConfigurationUtilities,
+ configObject: any
+): string | void {
// avoids circular reference ...
const config: ActionTypeConfigType = configObject;
@@ -40,7 +41,9 @@ function validateConfig(configObject: any): string | void {
// Note, not currently making these message translated, as will be
// emitted alongside messages from @kbn/config-schema, which does not
// translate messages.
- if (config.service == null) {
+ if (config.service === JSON_TRANSPORT_SERVICE) {
+ return;
+ } else if (config.service == null) {
if (config.host == null && config.port == null) {
return 'either [service] or [host]/[port] is required';
}
@@ -52,10 +55,17 @@ function validateConfig(configObject: any): string | void {
if (config.port == null) {
return '[port] is required if [service] is not provided';
}
+
+ if (!configurationUtilities.isWhitelistedHostname(config.host)) {
+ return `[host] value '${config.host}' is not in the whitelistedHosts configuration`;
+ }
} else {
- // service is not null
- if (!isValidService(config.service)) {
- return `[service] value "${config.service}" is not valid`;
+ const host = getServiceNameHost(config.service);
+ if (host == null) {
+ return `[service] value '${config.service}' is not valid`;
+ }
+ if (!configurationUtilities.isWhitelistedHostname(host)) {
+ return `[service] value '${config.service}' resolves to host '${host}' which is not in the whitelistedHosts configuration`;
}
}
}
@@ -98,13 +108,21 @@ function validateParams(paramsObject: any): string | void {
}
}
+interface GetActionTypeParams {
+ logger: Logger;
+ configurationUtilities: ActionsConfigurationUtilities;
+}
+
// action type definition
-export function getActionType({ logger }: { logger: Logger }): ActionType {
+export function getActionType(params: GetActionTypeParams): ActionType {
+ const { logger, configurationUtilities } = params;
return {
id: '.email',
name: 'email',
validate: {
- config: ConfigSchema,
+ config: schema.object(ConfigSchemaProps, {
+ validate: curry(validateConfig)(configurationUtilities),
+ }),
secrets: SecretsSchema,
params: ParamsSchema,
},
@@ -173,31 +191,14 @@ async function executor(
// utilities
-const ValidServiceNames = getValidServiceNames();
-
-function isValidService(service: string): boolean {
- return ValidServiceNames.has(service.toLowerCase());
-}
-
-function getValidServiceNames(): Set {
- const result = new Set();
-
- // add our special json service
- result.add(JSON_TRANSPORT_SERVICE);
+function getServiceNameHost(service: string): string | null {
+ const serviceEntry = nodemailerGetService(service);
+ if (serviceEntry === false) return null;
- const keys = Object.keys(nodemailerServices) as string[];
- for (const key of keys) {
- result.add(key.toLowerCase());
-
- const record = nodemailerServices[key];
- if (record.aliases == null) continue;
-
- for (const alias of record.aliases as string[]) {
- result.add(alias.toLowerCase());
- }
- }
+ // in theory this won't happen, but it's JS, so just to be safe ...
+ if (serviceEntry == null) return null;
- return result;
+ return serviceEntry.host || null;
}
// Returns the secure value - whether to use TLS or not.
diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts
index be743c84f5dfed..0fe572bb57e412 100644
--- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts
+++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.ts
@@ -26,7 +26,9 @@ export function registerBuiltInActionTypes({
}) {
actionTypeRegistry.register(getServerLogActionType({ logger }));
actionTypeRegistry.register(getSlackActionType());
- actionTypeRegistry.register(getEmailActionType({ logger }));
+ actionTypeRegistry.register(
+ getEmailActionType({ logger, configurationUtilities: actionsConfigUtils })
+ );
actionTypeRegistry.register(getIndexActionType({ logger }));
actionTypeRegistry.register(getPagerDutyActionType({ logger }));
actionTypeRegistry.register(
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts
index 2c1cb966c6b7e0..be204c4567ed95 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts
@@ -153,5 +153,79 @@ export default function emailTest({ getService }: FtrProviderContext) {
});
});
});
+
+ it('should respond with a 400 Bad Request when creating an email action with non-whitelisted server', async () => {
+ await supertest
+ .post('/api/action')
+ .set('kbn-xsrf', 'foo')
+ .send({
+ description: 'An email action',
+ actionTypeId: '.email',
+ config: {
+ service: 'gmail', // not whitelisted in the config for this test
+ from: 'bob@example.com',
+ },
+ secrets: {
+ user: 'bob',
+ password: 'changeme',
+ },
+ })
+ .expect(400)
+ .then((resp: any) => {
+ expect(resp.body).to.eql({
+ statusCode: 400,
+ error: 'Bad Request',
+ message:
+ "error validating action type config: [service] value 'gmail' resolves to host 'smtp.gmail.com' which is not in the whitelistedHosts configuration",
+ });
+ });
+
+ await supertest
+ .post('/api/action')
+ .set('kbn-xsrf', 'foo')
+ .send({
+ description: 'An email action',
+ actionTypeId: '.email',
+ config: {
+ host: 'stmp.gmail.com', // not whitelisted in the config for this test
+ port: 666,
+ from: 'bob@example.com',
+ },
+ secrets: {
+ user: 'bob',
+ password: 'changeme',
+ },
+ })
+ .expect(400)
+ .then((resp: any) => {
+ expect(resp.body).to.eql({
+ statusCode: 400,
+ error: 'Bad Request',
+ message:
+ "error validating action type config: [host] value 'stmp.gmail.com' is not in the whitelistedHosts configuration",
+ });
+ });
+ });
+
+ it('should handle creating an email action with a whitelisted server', async () => {
+ const { body: createdAction } = await supertest
+ .post('/api/action')
+ .set('kbn-xsrf', 'foo')
+ .send({
+ description: 'An email action',
+ actionTypeId: '.email',
+ config: {
+ host: 'some.non.existent.com', // whitelisted in the config for this test
+ port: 666,
+ from: 'bob@example.com',
+ },
+ secrets: {
+ user: 'bob',
+ password: 'changeme',
+ },
+ })
+ .expect(200);
+ expect(typeof createdAction.id).to.be('string');
+ });
});
}
diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts
index 46c3de18a11961..a8260d47714596 100644
--- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts
+++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/execute.ts
@@ -331,8 +331,10 @@ export default function({ getService }: FtrProviderContext) {
name: 'test email action',
actionTypeId: '.email',
config: {
- from: 'email-from@example.com',
- host: 'host-is-ignored-here.example.com',
+ from: 'email-from-1@example.com',
+ // this host is specifically whitelisted in:
+ // x-pack/test/alerting_api_integration/common/config.ts
+ host: 'some.non.existent.com',
port: 666,
},
secrets: {
@@ -349,7 +351,7 @@ export default function({ getService }: FtrProviderContext) {
.send({
name: 'a test email action 2',
config: {
- from: 'email-from@example.com',
+ from: 'email-from-2@example.com',
service: '__json',
},
secrets: {
From 21edc28ed3d49f4347761c94f284d534a3061c07 Mon Sep 17 00:00:00 2001
From: Robert Oskamp
Date: Wed, 4 Dec 2019 17:20:38 +0100
Subject: [PATCH 15/30] [ML] Add functional tests for analytics UI (#52054)
This PR adds functional UI tests for outlier detection and regression job creation.
---
.../analytics_list/action_start.tsx | 1 +
.../components/analytics_list/actions.tsx | 2 +
.../analytics_list/analytics_list.tsx | 40 +-
.../components/analytics_list/columns.tsx | 15 +-
.../create_analytics_button.tsx | 2 +-
.../create_analytics_flyout.tsx | 10 +-
.../create_analytics_form.tsx | 8 +
.../create_analytics_form/job_type.tsx | 1 +
.../refresh_analytics_list_button.tsx | 2 +-
.../data_frame_analytics/index.ts | 15 +
.../outlier_detection_creation.ts | 157 ++
.../regression_creation.ts | 161 ++
.../functional/apps/machine_learning/index.ts | 1 +
.../ml/egs_regression/data.json.gz | Bin 0 -> 279698 bytes
.../ml/egs_regression/mappings.json | 1452 ++++++++++++++++
.../es_archives/ml/ihp_outlier/data.json.gz | Bin 0 -> 70805 bytes
.../es_archives/ml/ihp_outlier/mappings.json | 1503 +++++++++++++++++
.../services/machine_learning/api.ts | 75 +-
.../machine_learning/data_frame_analytics.ts | 38 +-
.../data_frame_analytics_creation.ts | 307 ++++
.../data_frame_analytics_table.ts | 97 ++
.../services/machine_learning/index.ts | 2 +
.../machine_learning/job_wizard_advanced.ts | 5 +-
x-pack/test/functional/services/ml.ts | 8 +-
x-pack/test/typings/encode_uri_query.d.ts | 11 +
x-pack/test/typings/rison_node.d.ts | 26 +
26 files changed, 3904 insertions(+), 35 deletions(-)
create mode 100644 x-pack/test/functional/apps/machine_learning/data_frame_analytics/index.ts
create mode 100644 x-pack/test/functional/apps/machine_learning/data_frame_analytics/outlier_detection_creation.ts
create mode 100644 x-pack/test/functional/apps/machine_learning/data_frame_analytics/regression_creation.ts
create mode 100644 x-pack/test/functional/es_archives/ml/egs_regression/data.json.gz
create mode 100644 x-pack/test/functional/es_archives/ml/egs_regression/mappings.json
create mode 100644 x-pack/test/functional/es_archives/ml/ihp_outlier/data.json.gz
create mode 100644 x-pack/test/functional/es_archives/ml/ihp_outlier/mappings.json
create mode 100644 x-pack/test/functional/services/machine_learning/data_frame_analytics_creation.ts
create mode 100644 x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts
create mode 100644 x-pack/test/typings/encode_uri_query.d.ts
create mode 100644 x-pack/test/typings/rison_node.d.ts
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx
index c92a251974a942..40664a1413845f 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_start.tsx
@@ -54,6 +54,7 @@ export const StartAction: FC = ({ item }) => {
iconType="play"
onClick={openModal}
aria-label={buttonStartText}
+ data-test-sub="mlAnalyticsJobStartButton"
>
{buttonStartText}
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
index f3da4839d4b977..e189c961ccbc95 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx
@@ -44,6 +44,7 @@ export const AnalyticsViewAction = {
aria-label={i18n.translate('xpack.ml.dataframe.analyticsList.viewAriaLabel', {
defaultMessage: 'View',
})}
+ data-test-sub="mlAnalyticsJobViewButton"
>
{i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', {
defaultMessage: 'View',
@@ -76,6 +77,7 @@ export const getActions = () => {
iconType="stop"
onClick={() => stopAnalytics(item)}
aria-label={buttonStopText}
+ data-test-sub="mlAnalyticsJobStopButton"
>
{buttonStopText}
diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
index b8a914b5e96b58..412779513e533d 100644
--- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
+++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx
@@ -232,6 +232,7 @@ export const DataFrameAnalyticsList: FC = ({
{i18n.translate('xpack.ml.dataFrame.analyticsList.emptyPromptButtonText', {
defaultMessage: 'Create your first data frame analytics job',
@@ -352,23 +353,28 @@ export const DataFrameAnalyticsList: FC = ({
-
+